]> git.saurik.com Git - wxWidgets.git/blob - include/wx/testing.h
Mention using wxString::c_str() with vararg functions in the change log.
[wxWidgets.git] / include / wx / testing.h
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: wx/testing.h
3 // Purpose: helpers for GUI testing
4 // Author: Vaclav Slavik
5 // Created: 2012-08-28
6 // RCS-ID: $Id$
7 // Copyright: (c) 2012 Vaclav Slavik
8 // Licence: wxWindows Licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 #ifndef _WX_TESTING_H_
12 #define _WX_TESTING_H_
13
14 #include "wx/debug.h"
15 #include "wx/string.h"
16
17 class WXDLLIMPEXP_FWD_CORE wxDialog;
18 class WXDLLIMPEXP_FWD_CORE wxMessageDialogBase;
19 class WXDLLIMPEXP_FWD_CORE wxFileDialogBase;
20
21 // ----------------------------------------------------------------------------
22 // implementation helpers
23 // ----------------------------------------------------------------------------
24
25 // Helper hook class used to redirect ShowModal() to testing code.
26 // Instead of showing a dialog modally, hook code is called to simulate what
27 // the user would do and return appropriate ID from ShowModal().
28 class WXDLLIMPEXP_CORE wxModalDialogHook
29 {
30 public:
31 wxModalDialogHook() {}
32 virtual ~wxModalDialogHook() {}
33
34 /// Returns currently active hook object or NULL.
35 static wxModalDialogHook *Get() { return ms_instance; }
36
37 /// Set the hook and returns the previously set one.
38 static wxModalDialogHook *Set(wxModalDialogHook *hook)
39 {
40 wxModalDialogHook *old = ms_instance;
41 ms_instance = hook;
42 return old;
43 }
44
45 /// Entry point that is called from ShowModal().
46 virtual int Invoke(wxDialog *dlg) = 0;
47
48 private:
49 static wxModalDialogHook *ms_instance;
50
51 wxDECLARE_NO_COPY_CLASS(wxModalDialogHook);
52 };
53
54 // This macro needs to be used at the top of every implementation of
55 // ShowModal() in order for the above modal dialogs testing code to work.
56 #define WX_TESTING_SHOW_MODAL_HOOK() \
57 if ( wxModalDialogHook::Get() ) \
58 { \
59 int rc = wxModalDialogHook::Get()->Invoke(this); \
60 if ( rc != wxID_NONE ) \
61 return rc; \
62 } \
63 struct wxDummyTestingStruct /* just to force a semicolon */
64
65
66 // ----------------------------------------------------------------------------
67 // testing API
68 // ----------------------------------------------------------------------------
69
70 // Don't include this code when building the library itself
71 #ifndef WXBUILDING
72
73 #include "wx/beforestd.h"
74 #include <algorithm>
75 #include <iterator>
76 #include <queue>
77 #include "wx/afterstd.h"
78 #include "wx/cpp.h"
79 #include "wx/dialog.h"
80 #include "wx/msgdlg.h"
81 #include "wx/filedlg.h"
82
83 class wxTestingModalHook;
84
85 // Non-template base class for wxExpectModal<T> (via wxExpectModalBase).
86 // Only used internally.
87 class wxModalExpectation
88 {
89 public:
90 wxModalExpectation() : m_isOptional(false) {}
91 virtual ~wxModalExpectation() {}
92
93 bool IsOptional() const { return m_isOptional; }
94
95 virtual int Invoke(wxDialog *dlg) const = 0;
96
97 virtual wxString GetDescription() const = 0;
98
99 protected:
100 // Is this dialog optional, i.e. not required to be shown?
101 bool m_isOptional;
102 };
103
104
105 // This must be specialized for each type. The specialization MUST be derived
106 // from wxExpectModalBase<T>.
107 template<class T> class wxExpectModal {};
108
109
110 /**
111 Base class for wxExpectModal<T> specializations.
112
113 Every such specialization must be derived from wxExpectModalBase; there's
114 no other use for this class than to serve as wxExpectModal<T>'s base class.
115
116 T must be a class derived from wxDialog.
117 */
118 template<class T>
119 class wxExpectModalBase : public wxModalExpectation
120 {
121 public:
122 typedef T DialogType;
123 typedef wxExpectModal<DialogType> ExpectationType;
124
125 /**
126 Returns a copy of the expectation where the expected dialog is marked
127 as optional.
128
129 Optional dialogs aren't required to appear, it's not an error if they
130 don't.
131 */
132 ExpectationType Optional() const
133 {
134 ExpectationType e(*static_cast<const ExpectationType*>(this));
135 e.m_isOptional = true;
136 return e;
137 }
138
139 protected:
140 virtual int Invoke(wxDialog *dlg) const
141 {
142 DialogType *t = dynamic_cast<DialogType*>(dlg);
143 if ( t )
144 return OnInvoked(t);
145 else
146 return wxID_NONE; // not handled
147 }
148
149 /// Returns description of the expected dialog (by default, its class).
150 virtual wxString GetDescription() const
151 {
152 return wxCLASSINFO(T)->GetClassName();
153 }
154
155 /**
156 This method is called when ShowModal() was invoked on a dialog of type T.
157
158 @return Return value is used as ShowModal()'s return value.
159 */
160 virtual int OnInvoked(DialogType *dlg) const = 0;
161 };
162
163
164 // wxExpectModal<T> specializations for common dialogs:
165
166 template<>
167 class wxExpectModal<wxMessageDialog> : public wxExpectModalBase<wxMessageDialog>
168 {
169 public:
170 wxExpectModal(int id)
171 {
172 switch ( id )
173 {
174 case wxYES:
175 m_id = wxID_YES;
176 break;
177 case wxNO:
178 m_id = wxID_NO;
179 break;
180 case wxCANCEL:
181 m_id = wxID_CANCEL;
182 break;
183 case wxOK:
184 m_id = wxID_OK;
185 break;
186 case wxHELP:
187 m_id = wxID_HELP;
188 break;
189 default:
190 m_id = id;
191 break;
192 }
193 }
194
195 protected:
196 virtual int OnInvoked(wxMessageDialog *WXUNUSED(dlg)) const
197 {
198 return m_id;
199 }
200
201 int m_id;
202 };
203
204 #if wxUSE_FILEDLG
205
206 template<>
207 class wxExpectModal<wxFileDialog> : public wxExpectModalBase<wxFileDialog>
208 {
209 public:
210 wxExpectModal(const wxString& path, int id = wxID_OK)
211 : m_path(path), m_id(id)
212 {
213 }
214
215 protected:
216 virtual int OnInvoked(wxFileDialog *dlg) const
217 {
218 dlg->SetPath(m_path);
219 return m_id;
220 }
221
222 wxString m_path;
223 int m_id;
224 };
225
226 #endif
227
228 // Implementation of wxModalDialogHook for use in testing, with
229 // wxExpectModal<T> and the wxTEST_DIALOG() macro. It is not intended for
230 // direct use, use the macro instead.
231 class wxTestingModalHook : public wxModalDialogHook
232 {
233 public:
234 wxTestingModalHook()
235 {
236 m_prevHook = wxModalDialogHook::Set(this);
237 }
238
239 virtual ~wxTestingModalHook()
240 {
241 wxModalDialogHook::Set(m_prevHook);
242 }
243
244 virtual int Invoke(wxDialog *dlg)
245 {
246 while ( !m_expectations.empty() )
247 {
248 const wxModalExpectation *expect = m_expectations.front();
249 m_expectations.pop();
250
251 int ret = expect->Invoke(dlg);
252 if ( ret != wxID_NONE )
253 return ret; // dialog shown as expected
254
255 // not showing an optional dialog is OK, but showing an unexpected
256 // one definitely isn't:
257 if ( !expect->IsOptional() )
258 {
259 ReportFailure
260 (
261 wxString::Format
262 (
263 "A %s dialog was shown unexpectedly, expected %s.",
264 dlg->GetClassInfo()->GetClassName(),
265 expect->GetDescription()
266 )
267 );
268 return wxID_NONE;
269 }
270 // else: try the next expectation in the chain
271 }
272
273 ReportFailure
274 (
275 wxString::Format
276 (
277 "A dialog (%s) was shown unexpectedly.",
278 dlg->GetClassInfo()->GetClassName()
279 )
280 );
281 return wxID_NONE;
282 }
283
284 // Called to verify that all expectations were met. This cannot be done in
285 // the destructor, because ReportFailure() may throw (either because it's
286 // overriden or because wx's assertions handling is, globally). And
287 // throwing from the destructor would introduce all sort of problems,
288 // including messing up the order of errors in some cases.
289 void CheckUnmetExpectations()
290 {
291 while ( !m_expectations.empty() )
292 {
293 const wxModalExpectation *expect = m_expectations.front();
294 m_expectations.pop();
295 if ( expect->IsOptional() )
296 continue;
297
298 ReportFailure
299 (
300 wxString::Format
301 (
302 "Expected %s dialog was not shown.",
303 expect->GetDescription()
304 )
305 );
306 break;
307 }
308 }
309
310 void AddExpectation(const wxModalExpectation& e)
311 {
312 m_expectations.push(&e);
313 }
314
315 protected:
316 virtual void ReportFailure(const wxString& msg)
317 {
318 wxFAIL_MSG( msg );
319 }
320
321 private:
322 wxModalDialogHook *m_prevHook;
323 std::queue<const wxModalExpectation*> m_expectations;
324
325 wxDECLARE_NO_COPY_CLASS(wxTestingModalHook);
326 };
327
328
329 // Redefining this value makes it possible to customize the hook class,
330 // including e.g. its error reporting.
331 #define wxTEST_DIALOG_HOOK_CLASS wxTestingModalHook
332
333 #define WX_TEST_IMPL_ADD_EXPECTATION(pos, expect) \
334 const wxModalExpectation& wx_exp##pos = expect; \
335 wx_hook.AddExpectation(wx_exp##pos);
336
337 /**
338 Runs given code with all modal dialogs redirected to wxExpectModal<T>
339 hooks, instead of being shown to the user.
340
341 The first argument is any valid expression, typically a function call. The
342 remaining arguments are wxExpectModal<T> instances defining the dialogs
343 that are expected to be shown, in order of appearance.
344
345 Some typical examples:
346
347 @code
348 wxTEST_DIALOG
349 (
350 rc = dlg.ShowModal(),
351 wxExpectModal<wxFileDialog>(wxGetCwd() + "/test.txt")
352 );
353 @endcode
354
355 Sometimes, the code may show more than one dialog:
356
357 @code
358 wxTEST_DIALOG
359 (
360 RunSomeFunction(),
361 wxExpectModal<wxMessageDialog>(wxNO),
362 wxExpectModal<MyConfirmationDialog>(wxYES),
363 wxExpectModal<wxFileDialog>(wxGetCwd() + "/test.txt")
364 );
365 @endcode
366
367 Notice that wxExpectModal<T> has some convenience methods for further
368 tweaking the expectations. For example, it's possible to mark an expected
369 dialog as @em optional for situations when a dialog may be shown, but isn't
370 required to, by calling the Optional() method:
371
372 @code
373 wxTEST_DIALOG
374 (
375 RunSomeFunction(),
376 wxExpectModal<wxMessageDialog>(wxNO),
377 wxExpectModal<wxFileDialog>(wxGetCwd() + "/test.txt").Optional()
378 );
379 @endcode
380
381 @note By default, errors are reported with wxFAIL_MSG(). You may customize this by
382 implementing a class derived from wxTestingModalHook, overriding its
383 ReportFailure() method and redefining the wxTEST_DIALOG_HOOK_CLASS
384 macro to be the name of this class.
385
386 @note Custom dialogs are supported too. All you have to do is to specialize
387 wxExpectModal<> for your dialog type and implement its OnInvoked()
388 method.
389 */
390 #ifdef wxHAS_VARIADIC_MACROS
391 #define wxTEST_DIALOG(codeToRun, ...) \
392 { \
393 wxTEST_DIALOG_HOOK_CLASS wx_hook; \
394 wxCALL_FOR_EACH(WX_TEST_IMPL_ADD_EXPECTATION, __VA_ARGS__) \
395 codeToRun; \
396 wx_hook.CheckUnmetExpectations(); \
397 }
398 #endif /* wxHAS_VARIADIC_MACROS */
399
400 #endif // !WXBUILDING
401
402 #endif // _WX_TESTING_H_