]> git.saurik.com Git - wxWidgets.git/blobdiff - include/wx/testing.h
Add wxTEST_DIALOG for testing of modal dialogs.
[wxWidgets.git] / include / wx / testing.h
diff --git a/include/wx/testing.h b/include/wx/testing.h
new file mode 100644 (file)
index 0000000..905184e
--- /dev/null
@@ -0,0 +1,396 @@
+/////////////////////////////////////////////////////////////////////////////
+// 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_