]> git.saurik.com Git - wxWidgets.git/blob - include/wx/testing.h
a67585a13040fa9862d4e8af4fd97a54475008fa
[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
205 template<>
206 class wxExpectModal<wxFileDialog> : public wxExpectModalBase<wxFileDialog>
207 {
208 public:
209 wxExpectModal(const wxString& path, int id = wxID_OK)
210 : m_path(path), m_id(id)
211 {
212 }
213
214 protected:
215 virtual int OnInvoked(wxFileDialog *dlg) const
216 {
217 dlg->SetPath(m_path);
218 return m_id;
219 }
220
221 wxString m_path;
222 int m_id;
223 };
224
225
226 // Implementation of wxModalDialogHook for use in testing, with
227 // wxExpectModal<T> and the wxTEST_DIALOG() macro. It is not intended for
228 // direct use, use the macro instead.
229 class wxTestingModalHook : public wxModalDialogHook
230 {
231 public:
232 wxTestingModalHook()
233 {
234 m_prevHook = wxModalDialogHook::Set(this);
235 }
236
237 virtual ~wxTestingModalHook()
238 {
239 wxModalDialogHook::Set(m_prevHook);
240 }
241
242 virtual int Invoke(wxDialog *dlg)
243 {
244 while ( !m_expectations.empty() )
245 {
246 const wxModalExpectation *expect = m_expectations.front();
247 m_expectations.pop();
248
249 int ret = expect->Invoke(dlg);
250 if ( ret != wxID_NONE )
251 return ret; // dialog shown as expected
252
253 // not showing an optional dialog is OK, but showing an unexpected
254 // one definitely isn't:
255 if ( !expect->IsOptional() )
256 {
257 ReportFailure
258 (
259 wxString::Format
260 (
261 "A %s dialog was shown unexpectedly, expected %s.",
262 dlg->GetClassInfo()->GetClassName(),
263 expect->GetDescription()
264 )
265 );
266 return wxID_NONE;
267 }
268 // else: try the next expectation in the chain
269 }
270
271 ReportFailure
272 (
273 wxString::Format
274 (
275 "A dialog (%s) was shown unexpectedly.",
276 dlg->GetClassInfo()->GetClassName()
277 )
278 );
279 return wxID_NONE;
280 }
281
282 // Called to verify that all expectations were met. This cannot be done in
283 // the destructor, because ReportFailure() may throw (either because it's
284 // overriden or because wx's assertions handling is, globally). And
285 // throwing from the destructor would introduce all sort of problems,
286 // including messing up the order of errors in some cases.
287 void CheckUnmetExpectations()
288 {
289 while ( !m_expectations.empty() )
290 {
291 const wxModalExpectation *expect = m_expectations.front();
292 m_expectations.pop();
293 if ( expect->IsOptional() )
294 continue;
295
296 ReportFailure
297 (
298 wxString::Format
299 (
300 "Expected %s dialog was not shown.",
301 expect->GetDescription()
302 )
303 );
304 break;
305 }
306 }
307
308 void AddExpectation(const wxModalExpectation& e)
309 {
310 m_expectations.push(&e);
311 }
312
313 protected:
314 virtual void ReportFailure(const wxString& msg)
315 {
316 wxFAIL_MSG( msg );
317 }
318
319 private:
320 wxModalDialogHook *m_prevHook;
321 std::queue<const wxModalExpectation*> m_expectations;
322
323 wxDECLARE_NO_COPY_CLASS(wxTestingModalHook);
324 };
325
326
327 // Redefining this value makes it possible to customize the hook class,
328 // including e.g. its error reporting.
329 #define wxTEST_DIALOG_HOOK_CLASS wxTestingModalHook
330
331 #define WX_TEST_IMPL_ADD_EXPECTATION(pos, expect) \
332 const wxModalExpectation& wx_exp##pos = expect; \
333 wx_hook.AddExpectation(wx_exp##pos);
334
335 /**
336 Runs given code with all modal dialogs redirected to wxExpectModal<T>
337 hooks, instead of being shown to the user.
338
339 The first argument is any valid expression, typically a function call. The
340 remaining arguments are wxExpectModal<T> instances defining the dialogs
341 that are expected to be shown, in order of appearance.
342
343 Some typical examples:
344
345 @code
346 wxTEST_DIALOG
347 (
348 rc = dlg.ShowModal(),
349 wxExpectModal<wxFileDialog>(wxGetCwd() + "/test.txt")
350 );
351 @endcode
352
353 Sometimes, the code may show more than one dialog:
354
355 @code
356 wxTEST_DIALOG
357 (
358 RunSomeFunction(),
359 wxExpectModal<wxMessageDialog>(wxNO),
360 wxExpectModal<MyConfirmationDialog>(wxYES),
361 wxExpectModal<wxFileDialog>(wxGetCwd() + "/test.txt")
362 );
363 @endcode
364
365 Notice that wxExpectModal<T> has some convenience methods for further
366 tweaking the expectations. For example, it's possible to mark an expected
367 dialog as @em optional for situations when a dialog may be shown, but isn't
368 required to, by calling the Optional() method:
369
370 @code
371 wxTEST_DIALOG
372 (
373 RunSomeFunction(),
374 wxExpectModal<wxMessageDialog>(wxNO),
375 wxExpectModal<wxFileDialog>(wxGetCwd() + "/test.txt").Optional()
376 );
377 @endcode
378
379 @note By default, errors are reported with wxFAIL_MSG(). You may customize this by
380 implementing a class derived from wxTestingModalHook, overriding its
381 ReportFailure() method and redefining the wxTEST_DIALOG_HOOK_CLASS
382 macro to be the name of this class.
383
384 @note Custom dialogs are supported too. All you have to do is to specialize
385 wxExpectModal<> for your dialog type and implement its OnInvoked()
386 method.
387 */
388 #define wxTEST_DIALOG(codeToRun, ...) \
389 { \
390 wxTEST_DIALOG_HOOK_CLASS wx_hook; \
391 wxCALL_FOR_EACH(WX_TEST_IMPL_ADD_EXPECTATION, __VA_ARGS__) \
392 codeToRun; \
393 wx_hook.CheckUnmetExpectations(); \
394 }
395
396
397 #endif // !WXBUILDING
398
399 #endif // _WX_TESTING_H_