]> git.saurik.com Git - wxWidgets.git/blame - include/wx/testing.h
Fix menu event handlers calling order.
[wxWidgets.git] / include / wx / testing.h
CommitLineData
643e9cf9
VS
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
17class WXDLLIMPEXP_FWD_CORE wxDialog;
18class WXDLLIMPEXP_FWD_CORE wxMessageDialogBase;
19class 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().
28class WXDLLIMPEXP_CORE wxModalDialogHook
29{
30public:
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
48private:
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"
9e0959cd
VS
79#include "wx/dialog.h"
80#include "wx/msgdlg.h"
81#include "wx/filedlg.h"
643e9cf9
VS
82
83class wxTestingModalHook;
84
85// Non-template base class for wxExpectModal<T> (via wxExpectModalBase).
86// Only used internally.
87class wxModalExpectation
88{
89public:
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
99protected:
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>.
107template<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 */
118template<class T>
119class wxExpectModalBase : public wxModalExpectation
120{
121public:
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
139protected:
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
166template<>
167class wxExpectModal<wxMessageDialog> : public wxExpectModalBase<wxMessageDialog>
168{
169public:
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
195protected:
196 virtual int OnInvoked(wxMessageDialog *WXUNUSED(dlg)) const
197 {
198 return m_id;
199 }
200
201 int m_id;
202};
203
0b7a7b8c 204#if wxUSE_FILEDLG
643e9cf9
VS
205
206template<>
207class wxExpectModal<wxFileDialog> : public wxExpectModalBase<wxFileDialog>
208{
209public:
210 wxExpectModal(const wxString& path, int id = wxID_OK)
211 : m_path(path), m_id(id)
212 {
213 }
214
215protected:
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
0b7a7b8c 226#endif
643e9cf9
VS
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.
231class wxTestingModalHook : public wxModalDialogHook
232{
233public:
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
315protected:
316 virtual void ReportFailure(const wxString& msg)
317 {
318 wxFAIL_MSG( msg );
319 }
320
321private:
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 */
e4f3b467 390#ifdef wxHAS_VARIADIC_MACROS
643e9cf9
VS
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 }
e4f3b467 398#endif /* wxHAS_VARIADIC_MACROS */
643e9cf9
VS
399
400#endif // !WXBUILDING
401
402#endif // _WX_TESTING_H_