Add wxTEST_DIALOG for testing of modal dialogs.
[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
80 class wxTestingModalHook;
81
82 // Non-template base class for wxExpectModal<T> (via wxExpectModalBase).
83 // Only used internally.
84 class wxModalExpectation
85 {
86 public:
87 wxModalExpectation() : m_isOptional(false) {}
88 virtual ~wxModalExpectation() {}
89
90 bool IsOptional() const { return m_isOptional; }
91
92 virtual int Invoke(wxDialog *dlg) const = 0;
93
94 virtual wxString GetDescription() const = 0;
95
96 protected:
97 // Is this dialog optional, i.e. not required to be shown?
98 bool m_isOptional;
99 };
100
101
102 // This must be specialized for each type. The specialization MUST be derived
103 // from wxExpectModalBase<T>.
104 template<class T> class wxExpectModal {};
105
106
107 /**
108 Base class for wxExpectModal<T> specializations.
109
110 Every such specialization must be derived from wxExpectModalBase; there's
111 no other use for this class than to serve as wxExpectModal<T>'s base class.
112
113 T must be a class derived from wxDialog.
114 */
115 template<class T>
116 class wxExpectModalBase : public wxModalExpectation
117 {
118 public:
119 typedef T DialogType;
120 typedef wxExpectModal<DialogType> ExpectationType;
121
122 /**
123 Returns a copy of the expectation where the expected dialog is marked
124 as optional.
125
126 Optional dialogs aren't required to appear, it's not an error if they
127 don't.
128 */
129 ExpectationType Optional() const
130 {
131 ExpectationType e(*static_cast<const ExpectationType*>(this));
132 e.m_isOptional = true;
133 return e;
134 }
135
136 protected:
137 virtual int Invoke(wxDialog *dlg) const
138 {
139 DialogType *t = dynamic_cast<DialogType*>(dlg);
140 if ( t )
141 return OnInvoked(t);
142 else
143 return wxID_NONE; // not handled
144 }
145
146 /// Returns description of the expected dialog (by default, its class).
147 virtual wxString GetDescription() const
148 {
149 return wxCLASSINFO(T)->GetClassName();
150 }
151
152 /**
153 This method is called when ShowModal() was invoked on a dialog of type T.
154
155 @return Return value is used as ShowModal()'s return value.
156 */
157 virtual int OnInvoked(DialogType *dlg) const = 0;
158 };
159
160
161 // wxExpectModal<T> specializations for common dialogs:
162
163 template<>
164 class wxExpectModal<wxMessageDialog> : public wxExpectModalBase<wxMessageDialog>
165 {
166 public:
167 wxExpectModal(int id)
168 {
169 switch ( id )
170 {
171 case wxYES:
172 m_id = wxID_YES;
173 break;
174 case wxNO:
175 m_id = wxID_NO;
176 break;
177 case wxCANCEL:
178 m_id = wxID_CANCEL;
179 break;
180 case wxOK:
181 m_id = wxID_OK;
182 break;
183 case wxHELP:
184 m_id = wxID_HELP;
185 break;
186 default:
187 m_id = id;
188 break;
189 }
190 }
191
192 protected:
193 virtual int OnInvoked(wxMessageDialog *WXUNUSED(dlg)) const
194 {
195 return m_id;
196 }
197
198 int m_id;
199 };
200
201
202 template<>
203 class wxExpectModal<wxFileDialog> : public wxExpectModalBase<wxFileDialog>
204 {
205 public:
206 wxExpectModal(const wxString& path, int id = wxID_OK)
207 : m_path(path), m_id(id)
208 {
209 }
210
211 protected:
212 virtual int OnInvoked(wxFileDialog *dlg) const
213 {
214 dlg->SetPath(m_path);
215 return m_id;
216 }
217
218 wxString m_path;
219 int m_id;
220 };
221
222
223 // Implementation of wxModalDialogHook for use in testing, with
224 // wxExpectModal<T> and the wxTEST_DIALOG() macro. It is not intended for
225 // direct use, use the macro instead.
226 class wxTestingModalHook : public wxModalDialogHook
227 {
228 public:
229 wxTestingModalHook()
230 {
231 m_prevHook = wxModalDialogHook::Set(this);
232 }
233
234 virtual ~wxTestingModalHook()
235 {
236 wxModalDialogHook::Set(m_prevHook);
237 }
238
239 virtual int Invoke(wxDialog *dlg)
240 {
241 while ( !m_expectations.empty() )
242 {
243 const wxModalExpectation *expect = m_expectations.front();
244 m_expectations.pop();
245
246 int ret = expect->Invoke(dlg);
247 if ( ret != wxID_NONE )
248 return ret; // dialog shown as expected
249
250 // not showing an optional dialog is OK, but showing an unexpected
251 // one definitely isn't:
252 if ( !expect->IsOptional() )
253 {
254 ReportFailure
255 (
256 wxString::Format
257 (
258 "A %s dialog was shown unexpectedly, expected %s.",
259 dlg->GetClassInfo()->GetClassName(),
260 expect->GetDescription()
261 )
262 );
263 return wxID_NONE;
264 }
265 // else: try the next expectation in the chain
266 }
267
268 ReportFailure
269 (
270 wxString::Format
271 (
272 "A dialog (%s) was shown unexpectedly.",
273 dlg->GetClassInfo()->GetClassName()
274 )
275 );
276 return wxID_NONE;
277 }
278
279 // Called to verify that all expectations were met. This cannot be done in
280 // the destructor, because ReportFailure() may throw (either because it's
281 // overriden or because wx's assertions handling is, globally). And
282 // throwing from the destructor would introduce all sort of problems,
283 // including messing up the order of errors in some cases.
284 void CheckUnmetExpectations()
285 {
286 while ( !m_expectations.empty() )
287 {
288 const wxModalExpectation *expect = m_expectations.front();
289 m_expectations.pop();
290 if ( expect->IsOptional() )
291 continue;
292
293 ReportFailure
294 (
295 wxString::Format
296 (
297 "Expected %s dialog was not shown.",
298 expect->GetDescription()
299 )
300 );
301 break;
302 }
303 }
304
305 void AddExpectation(const wxModalExpectation& e)
306 {
307 m_expectations.push(&e);
308 }
309
310 protected:
311 virtual void ReportFailure(const wxString& msg)
312 {
313 wxFAIL_MSG( msg );
314 }
315
316 private:
317 wxModalDialogHook *m_prevHook;
318 std::queue<const wxModalExpectation*> m_expectations;
319
320 wxDECLARE_NO_COPY_CLASS(wxTestingModalHook);
321 };
322
323
324 // Redefining this value makes it possible to customize the hook class,
325 // including e.g. its error reporting.
326 #define wxTEST_DIALOG_HOOK_CLASS wxTestingModalHook
327
328 #define WX_TEST_IMPL_ADD_EXPECTATION(pos, expect) \
329 const wxModalExpectation& wx_exp##pos = expect; \
330 wx_hook.AddExpectation(wx_exp##pos);
331
332 /**
333 Runs given code with all modal dialogs redirected to wxExpectModal<T>
334 hooks, instead of being shown to the user.
335
336 The first argument is any valid expression, typically a function call. The
337 remaining arguments are wxExpectModal<T> instances defining the dialogs
338 that are expected to be shown, in order of appearance.
339
340 Some typical examples:
341
342 @code
343 wxTEST_DIALOG
344 (
345 rc = dlg.ShowModal(),
346 wxExpectModal<wxFileDialog>(wxGetCwd() + "/test.txt")
347 );
348 @endcode
349
350 Sometimes, the code may show more than one dialog:
351
352 @code
353 wxTEST_DIALOG
354 (
355 RunSomeFunction(),
356 wxExpectModal<wxMessageDialog>(wxNO),
357 wxExpectModal<MyConfirmationDialog>(wxYES),
358 wxExpectModal<wxFileDialog>(wxGetCwd() + "/test.txt")
359 );
360 @endcode
361
362 Notice that wxExpectModal<T> has some convenience methods for further
363 tweaking the expectations. For example, it's possible to mark an expected
364 dialog as @em optional for situations when a dialog may be shown, but isn't
365 required to, by calling the Optional() method:
366
367 @code
368 wxTEST_DIALOG
369 (
370 RunSomeFunction(),
371 wxExpectModal<wxMessageDialog>(wxNO),
372 wxExpectModal<wxFileDialog>(wxGetCwd() + "/test.txt").Optional()
373 );
374 @endcode
375
376 @note By default, errors are reported with wxFAIL_MSG(). You may customize this by
377 implementing a class derived from wxTestingModalHook, overriding its
378 ReportFailure() method and redefining the wxTEST_DIALOG_HOOK_CLASS
379 macro to be the name of this class.
380
381 @note Custom dialogs are supported too. All you have to do is to specialize
382 wxExpectModal<> for your dialog type and implement its OnInvoked()
383 method.
384 */
385 #define wxTEST_DIALOG(codeToRun, ...) \
386 { \
387 wxTEST_DIALOG_HOOK_CLASS wx_hook; \
388 wxCALL_FOR_EACH(WX_TEST_IMPL_ADD_EXPECTATION, __VA_ARGS__) \
389 codeToRun; \
390 wx_hook.CheckUnmetExpectations(); \
391 }
392
393
394 #endif // !WXBUILDING
395
396 #endif // _WX_TESTING_H_