+/////////////////////////////////////////////////////////////////////////////
+// Name: wx/testing.h
+// Purpose: helpers for GUI testing
+// Author: Vaclav Slavik
+// Created: 2012-08-28
+// RCS-ID: $Id$
+// Copyright: (c) 2012 Vaclav Slavik
+// Licence: wxWindows Licence
+/////////////////////////////////////////////////////////////////////////////
+
+#ifndef _WX_TESTING_H_
+#define _WX_TESTING_H_
+
+#include "wx/debug.h"
+#include "wx/string.h"
+
+class WXDLLIMPEXP_FWD_CORE wxDialog;
+class WXDLLIMPEXP_FWD_CORE wxMessageDialogBase;
+class WXDLLIMPEXP_FWD_CORE wxFileDialogBase;
+
+// ----------------------------------------------------------------------------
+// implementation helpers
+// ----------------------------------------------------------------------------
+
+// Helper hook class used to redirect ShowModal() to testing code.
+// Instead of showing a dialog modally, hook code is called to simulate what
+// the user would do and return appropriate ID from ShowModal().
+class WXDLLIMPEXP_CORE wxModalDialogHook
+{
+public:
+ wxModalDialogHook() {}
+ virtual ~wxModalDialogHook() {}
+
+ /// Returns currently active hook object or NULL.
+ static wxModalDialogHook *Get() { return ms_instance; }
+
+ /// Set the hook and returns the previously set one.
+ static wxModalDialogHook *Set(wxModalDialogHook *hook)
+ {
+ wxModalDialogHook *old = ms_instance;
+ ms_instance = hook;
+ return old;
+ }
+
+ /// Entry point that is called from ShowModal().
+ virtual int Invoke(wxDialog *dlg) = 0;
+
+private:
+ static wxModalDialogHook *ms_instance;
+
+ wxDECLARE_NO_COPY_CLASS(wxModalDialogHook);
+};
+
+// This macro needs to be used at the top of every implementation of
+// ShowModal() in order for the above modal dialogs testing code to work.
+#define WX_TESTING_SHOW_MODAL_HOOK() \
+ if ( wxModalDialogHook::Get() ) \
+ { \
+ int rc = wxModalDialogHook::Get()->Invoke(this); \
+ if ( rc != wxID_NONE ) \
+ return rc; \
+ } \
+ struct wxDummyTestingStruct /* just to force a semicolon */
+
+
+// ----------------------------------------------------------------------------
+// testing API
+// ----------------------------------------------------------------------------
+
+// Don't include this code when building the library itself
+#ifndef WXBUILDING
+
+#include "wx/beforestd.h"
+#include <algorithm>
+#include <iterator>
+#include <queue>
+#include "wx/afterstd.h"
+#include "wx/cpp.h"
+
+class wxTestingModalHook;
+
+// Non-template base class for wxExpectModal<T> (via wxExpectModalBase).
+// Only used internally.
+class wxModalExpectation
+{
+public:
+ wxModalExpectation() : m_isOptional(false) {}
+ virtual ~wxModalExpectation() {}
+
+ bool IsOptional() const { return m_isOptional; }
+
+ virtual int Invoke(wxDialog *dlg) const = 0;
+
+ virtual wxString GetDescription() const = 0;
+
+protected:
+ // Is this dialog optional, i.e. not required to be shown?
+ bool m_isOptional;
+};
+
+
+// This must be specialized for each type. The specialization MUST be derived
+// from wxExpectModalBase<T>.
+template<class T> class wxExpectModal {};
+
+
+/**
+ Base class for wxExpectModal<T> specializations.
+
+ Every such specialization must be derived from wxExpectModalBase; there's
+ no other use for this class than to serve as wxExpectModal<T>'s base class.
+
+ T must be a class derived from wxDialog.
+ */
+template<class T>
+class wxExpectModalBase : public wxModalExpectation
+{
+public:
+ typedef T DialogType;
+ typedef wxExpectModal<DialogType> ExpectationType;
+
+ /**
+ Returns a copy of the expectation where the expected dialog is marked
+ as optional.
+
+ Optional dialogs aren't required to appear, it's not an error if they
+ don't.
+ */
+ ExpectationType Optional() const
+ {
+ ExpectationType e(*static_cast<const ExpectationType*>(this));
+ e.m_isOptional = true;
+ return e;
+ }
+
+protected:
+ virtual int Invoke(wxDialog *dlg) const
+ {
+ DialogType *t = dynamic_cast<DialogType*>(dlg);
+ if ( t )
+ return OnInvoked(t);
+ else
+ return wxID_NONE; // not handled
+ }
+
+ /// Returns description of the expected dialog (by default, its class).
+ virtual wxString GetDescription() const
+ {
+ return wxCLASSINFO(T)->GetClassName();
+ }
+
+ /**
+ This method is called when ShowModal() was invoked on a dialog of type T.
+
+ @return Return value is used as ShowModal()'s return value.
+ */
+ virtual int OnInvoked(DialogType *dlg) const = 0;
+};
+
+
+// wxExpectModal<T> specializations for common dialogs:
+
+template<>
+class wxExpectModal<wxMessageDialog> : public wxExpectModalBase<wxMessageDialog>
+{
+public:
+ wxExpectModal(int id)
+ {
+ switch ( id )
+ {
+ case wxYES:
+ m_id = wxID_YES;
+ break;
+ case wxNO:
+ m_id = wxID_NO;
+ break;
+ case wxCANCEL:
+ m_id = wxID_CANCEL;
+ break;
+ case wxOK:
+ m_id = wxID_OK;
+ break;
+ case wxHELP:
+ m_id = wxID_HELP;
+ break;
+ default:
+ m_id = id;
+ break;
+ }
+ }
+
+protected:
+ virtual int OnInvoked(wxMessageDialog *WXUNUSED(dlg)) const
+ {
+ return m_id;
+ }
+
+ int m_id;
+};
+
+
+template<>
+class wxExpectModal<wxFileDialog> : public wxExpectModalBase<wxFileDialog>
+{
+public:
+ wxExpectModal(const wxString& path, int id = wxID_OK)
+ : m_path(path), m_id(id)
+ {
+ }
+
+protected:
+ virtual int OnInvoked(wxFileDialog *dlg) const
+ {
+ dlg->SetPath(m_path);
+ return m_id;
+ }
+
+ wxString m_path;
+ int m_id;
+};
+
+
+// Implementation of wxModalDialogHook for use in testing, with
+// wxExpectModal<T> and the wxTEST_DIALOG() macro. It is not intended for
+// direct use, use the macro instead.
+class wxTestingModalHook : public wxModalDialogHook
+{
+public:
+ wxTestingModalHook()
+ {
+ m_prevHook = wxModalDialogHook::Set(this);
+ }
+
+ virtual ~wxTestingModalHook()
+ {
+ wxModalDialogHook::Set(m_prevHook);
+ }
+
+ virtual int Invoke(wxDialog *dlg)
+ {
+ while ( !m_expectations.empty() )
+ {
+ const wxModalExpectation *expect = m_expectations.front();
+ m_expectations.pop();
+
+ int ret = expect->Invoke(dlg);
+ if ( ret != wxID_NONE )
+ return ret; // dialog shown as expected
+
+ // not showing an optional dialog is OK, but showing an unexpected
+ // one definitely isn't:
+ if ( !expect->IsOptional() )
+ {
+ ReportFailure
+ (
+ wxString::Format
+ (
+ "A %s dialog was shown unexpectedly, expected %s.",
+ dlg->GetClassInfo()->GetClassName(),
+ expect->GetDescription()
+ )
+ );
+ return wxID_NONE;
+ }
+ // else: try the next expectation in the chain
+ }
+
+ ReportFailure
+ (
+ wxString::Format
+ (
+ "A dialog (%s) was shown unexpectedly.",
+ dlg->GetClassInfo()->GetClassName()
+ )
+ );
+ return wxID_NONE;
+ }
+
+ // Called to verify that all expectations were met. This cannot be done in
+ // the destructor, because ReportFailure() may throw (either because it's
+ // overriden or because wx's assertions handling is, globally). And
+ // throwing from the destructor would introduce all sort of problems,
+ // including messing up the order of errors in some cases.
+ void CheckUnmetExpectations()
+ {
+ while ( !m_expectations.empty() )
+ {
+ const wxModalExpectation *expect = m_expectations.front();
+ m_expectations.pop();
+ if ( expect->IsOptional() )
+ continue;
+
+ ReportFailure
+ (
+ wxString::Format
+ (
+ "Expected %s dialog was not shown.",
+ expect->GetDescription()
+ )
+ );
+ break;
+ }
+ }
+
+ void AddExpectation(const wxModalExpectation& e)
+ {
+ m_expectations.push(&e);
+ }
+
+protected:
+ virtual void ReportFailure(const wxString& msg)
+ {
+ wxFAIL_MSG( msg );
+ }
+
+private:
+ wxModalDialogHook *m_prevHook;
+ std::queue<const wxModalExpectation*> m_expectations;
+
+ wxDECLARE_NO_COPY_CLASS(wxTestingModalHook);
+};
+
+
+// Redefining this value makes it possible to customize the hook class,
+// including e.g. its error reporting.
+#define wxTEST_DIALOG_HOOK_CLASS wxTestingModalHook
+
+#define WX_TEST_IMPL_ADD_EXPECTATION(pos, expect) \
+ const wxModalExpectation& wx_exp##pos = expect; \
+ wx_hook.AddExpectation(wx_exp##pos);
+
+/**
+ Runs given code with all modal dialogs redirected to wxExpectModal<T>
+ hooks, instead of being shown to the user.
+
+ The first argument is any valid expression, typically a function call. The
+ remaining arguments are wxExpectModal<T> instances defining the dialogs
+ that are expected to be shown, in order of appearance.
+
+ Some typical examples:
+
+ @code
+ wxTEST_DIALOG
+ (
+ rc = dlg.ShowModal(),
+ wxExpectModal<wxFileDialog>(wxGetCwd() + "/test.txt")
+ );
+ @endcode
+
+ Sometimes, the code may show more than one dialog:
+
+ @code
+ wxTEST_DIALOG
+ (
+ RunSomeFunction(),
+ wxExpectModal<wxMessageDialog>(wxNO),
+ wxExpectModal<MyConfirmationDialog>(wxYES),
+ wxExpectModal<wxFileDialog>(wxGetCwd() + "/test.txt")
+ );
+ @endcode
+
+ Notice that wxExpectModal<T> has some convenience methods for further
+ tweaking the expectations. For example, it's possible to mark an expected
+ dialog as @em optional for situations when a dialog may be shown, but isn't
+ required to, by calling the Optional() method:
+
+ @code
+ wxTEST_DIALOG
+ (
+ RunSomeFunction(),
+ wxExpectModal<wxMessageDialog>(wxNO),
+ wxExpectModal<wxFileDialog>(wxGetCwd() + "/test.txt").Optional()
+ );
+ @endcode
+
+ @note By default, errors are reported with wxFAIL_MSG(). You may customize this by
+ implementing a class derived from wxTestingModalHook, overriding its
+ ReportFailure() method and redefining the wxTEST_DIALOG_HOOK_CLASS
+ macro to be the name of this class.
+
+ @note Custom dialogs are supported too. All you have to do is to specialize
+ wxExpectModal<> for your dialog type and implement its OnInvoked()
+ method.
+ */
+#define wxTEST_DIALOG(codeToRun, ...) \
+ { \
+ wxTEST_DIALOG_HOOK_CLASS wx_hook; \
+ wxCALL_FOR_EACH(WX_TEST_IMPL_ADD_EXPECTATION, __VA_ARGS__) \
+ codeToRun; \
+ wx_hook.CheckUnmetExpectations(); \
+ }
+
+
+#endif // !WXBUILDING
+
+#endif // _WX_TESTING_H_