X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/a4d982a7cfcc3663c03e05fc5b62ba1b4965fae1..643e9cf9f61fdbe517b15477f9247ca8ac0b3578:/include/wx/testing.h diff --git a/include/wx/testing.h b/include/wx/testing.h new file mode 100644 index 0000000000..905184e493 --- /dev/null +++ b/include/wx/testing.h @@ -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 +#include +#include +#include "wx/afterstd.h" +#include "wx/cpp.h" + +class wxTestingModalHook; + +// Non-template base class for wxExpectModal (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. +template class wxExpectModal {}; + + +/** + Base class for wxExpectModal specializations. + + Every such specialization must be derived from wxExpectModalBase; there's + no other use for this class than to serve as wxExpectModal's base class. + + T must be a class derived from wxDialog. + */ +template +class wxExpectModalBase : public wxModalExpectation +{ +public: + typedef T DialogType; + typedef wxExpectModal 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(this)); + e.m_isOptional = true; + return e; + } + +protected: + virtual int Invoke(wxDialog *dlg) const + { + DialogType *t = dynamic_cast(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 specializations for common dialogs: + +template<> +class wxExpectModal : public wxExpectModalBase +{ +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 : public wxExpectModalBase +{ +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 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 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 + hooks, instead of being shown to the user. + + The first argument is any valid expression, typically a function call. The + remaining arguments are wxExpectModal instances defining the dialogs + that are expected to be shown, in order of appearance. + + Some typical examples: + + @code + wxTEST_DIALOG + ( + rc = dlg.ShowModal(), + wxExpectModal(wxGetCwd() + "/test.txt") + ); + @endcode + + Sometimes, the code may show more than one dialog: + + @code + wxTEST_DIALOG + ( + RunSomeFunction(), + wxExpectModal(wxNO), + wxExpectModal(wxYES), + wxExpectModal(wxGetCwd() + "/test.txt") + ); + @endcode + + Notice that wxExpectModal 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(wxNO), + wxExpectModal(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_