lumiera_/tests/gui/test/mock-elm.hpp
Ichthyostega 26d0f50e47 state mark handling within the base element: unit test PASS
...and I made the decision *not* to consider any kind of
generic properties for now. YAGNI.

UI coding is notorious spaghetti code.
No point in fighting that, it is just the way it is,
because somewhere you're bound to get concrete, hands-on.
2016-02-13 22:55:58 +01:00

343 lines
10 KiB
C++

/*
MOCK-ELM.hpp - generic mock UI element for unit testing
Copyright (C) Lumiera.org
2015, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file mock-elm.hpp
** A generic interface element instrumented for unit testing.
** All relevant building blocks within the Lumiera GTK UI are based on
** gui::model::Tangible, meaning that any generic effect of interface interactions
** can be expressed in terms of this interface contract. As far as the UI participates
** in interactions with the lower layers, like e.g. command invocation, structure updates
** and state notifications, these processes can be modelled and verified with the help
** of a specially prepared Tangible instance. This gui::test::MockElm provides the
** necessary instrumentation to observe what has been invoked and received.
**
** Since the purpose of a mock interface element is to test interactions and responses
** targeted at a generic interface element, the MockElm incorporates an implementation
** independent from the real gui::model::Widget or gui::model::Controller. This
** mock implementation is basically NOP, while logging any invocation. Matters get
** a bit fuzzy, when it comes to the distinction between _widget_ and _controller_.
** Yet we should note that the purpose of this setup is to cover the connectivity
** and integration with the UI, not the tangible "mechanics" of the UI itself.
** It can be argued that covering the latter with unit tests is pretty much
** moot and will result just in a huge pile of code duplication and
** maintenance burden.
**
** People typically start to look into unit testing of user interfaces
** when faced with a largely dysfunctional architecture, where core functionality
** is littered and tangled into the presentation code. While in a system knowingly
** built with a distinct core, the UI should not contain anything not tangible enough
** as just to be verified by watching it in action. The push of a button should just
** invoke an action, and the action itself should be self contained enough to be
** tested in isolation. The UI-Bus and the [generic widget base](\ref gui::model::Tangible)
** was built to serve as a foundation to achieve that goal.
**
** @see abstract-tangible-test.cpp
**
*/
#ifndef GUI_TEST_MOCK_ELM_H
#define GUI_TEST_MOCK_ELM_H
#include "lib/error.hpp"
#include "lib/test/event-log.hpp"
#include "gui/model/tangible.hpp"
#include "lib/diff/record.hpp"
#include "lib/idi/genfunc.hpp"
#include "test/test-nexus.hpp"
#include "lib/format-cout.hpp"
#include "lib/symbol.hpp"
#include <string>
namespace gui {
namespace error = lumiera::error;
using error::LUMIERA_ERROR_ASSERTION;
using lib::test::EventLog;
using lib::test::EventMatch;
namespace test{
using lib::Symbol;
using std::string;
/**
* Mock UI element or controller.
* Within Lumiera, all interface components of relevance are based
* on the [Tangible] interface, which we mock here for unit testing.
* This special implementation is instrumented to [log](\ref lib::test::EventLog)
* any invocation and any messages sent or received through the UI Backbone,
* which is formed by the [UI-Bus](ui-bus.hpp).
*
* @todo some usage details
* @see abstract-tangible-test.cpp
*/
class MockElm
: public gui::model::Tangible
{
using _Par = gui::model::Tangible;
EventLog log_{this->identify()};
bool virgin_{true};
bool expanded_{false};
/* ==== Tangible interface ==== */
virtual void
doReset() override
{
log_.call(this->identify(), "reset");
expanded_ = false;
virgin_ = true;
log_.event("reset");
}
virtual bool
doExpand (bool yes) override
{
log_.call(this->identify(), "expand", yes);
virgin_ = false;
expanded_ = yes;
log_.event (expanded_? "expanded" : "collapsed");
return true;
}
virtual void
doReveal (ID child) override
{
UNIMPLEMENTED ("mock doReveal");
}
virtual void
doRevealYourself() override
{
UNIMPLEMENTED ("mock doRevealYourself");
}
virtual void
doMsg (string text) override
{
log_.call (this->identify(), "doMsg", text);
cout << this->identify() << " <-- Message(\""<<text<<"\")" <<endl;
log_.note ("type=mark", "ID=Message", text);
}
virtual void
doErr (string text) override
{
log_.call (this->identify(), "doErr", text);
cerr << this->identify() << " <-- Error(\""<<text<<"\")" <<endl;
log_.note ("type=mark", "ID=Error", text);
}
virtual void
doFlash() override
{
log_.call (this->identify(), "doFlash");
cout << this->identify() << " <-- Flash!" <<endl;
log_.note ("type=mark", "ID=Flash");
}
virtual void
doMark (GenNode const& mark) override
{
log_.call (this->identify(), "doMark", mark);
cout << this->identify() << " <-- state-mark = "<< mark <<endl;
log_.note ("type=mark", "ID="+mark.idi.getSym(), mark);
// forward to default handler
Tangible::doMark (mark);
}
protected:
string
identify() const
{
return getID().getSym() +"."+ lib::idi::instanceTypeID(this);
}
public:
explicit
MockElm(string id)
: MockElm(lib::idi::EntryID<MockElm>(id))
{ }
explicit
MockElm(ID identity, ctrl::BusTerm& nexus =Nexus::testUI())
: gui::model::Tangible(identity, nexus)
{
log_.call (this->identify(), "ctor", identity, string(nexus));
log_.create (getID().getSym());
}
/** document our death in the diagnostic log. */
~MockElm()
{
try {
log_.call (this->identify(), "dtor");
log_.destroy (getID().getSym());
}
catch(...)
{
const char* errID = lumiera_error();
if (errID)
cerr << "Error while logging shutdown of Mock-UI-Element: " << errID <<endl;
else
cerr << "Unknown Error while logging shutdown of Mock-UI-Element." <<endl;
}
}
/* ==== special operations API ==== */
/** commit suicide.
* @warning admittedly a wonky operation
* @remarks here the mock emulates the act of dying,
* by snuffing the UI-Bus connection sneakily.
* We leave the dead corpse hanging around,
* just for sake of further investigation,
* of course.
*/
void
kill()
{
log_.call (this->identify(), "kill");
log_.destroy (getID().getSym());
Nexus::zombificate (this->uiBus_);
log_.event (string(getID()) + " successfully connected to zombie bus");
}
/* ==== Query/Verification API ==== */
ID getID() const
{
return uiBus_.getID();
}
bool
isTouched() const
{
return not virgin_;
}
bool
isExpanded() const
{
return expanded_;
}
EventMatch
verify (string match) const
{
return getLog().verify(match);
}
EventMatch
verifyMatch (string regExp) const
{
return getLog().verifyMatch(regExp);
}
EventMatch
verifyEvent (string match) const
{
return getLog().verifyEvent(match);
}
EventMatch
verifyEvent (string classifier, string match) const
{
return getLog().verifyEvent (classifier,match);
}
EventMatch
verifyCall (string match) const
{
return getLog().verifyCall(match);
}
EventMatch
ensureNot (string match) const
{
return getLog().ensureNot(match);
}
/** special verification match on a "state mark" message to this element */
EventMatch
verifyMark (string id) const
{
return getLog().verify(id).type("mark").id(id);
}
/** verification match on a specific "state mark" message
* @param id the ID-symbol used, identifying the kind of notification message
* @param payloadMatch to be applied to the payload of the message solely
*/
EventMatch
verifyMark (string id, string payloadMatch) const
{
return getLog().verifyEvent("mark", payloadMatch).type("mark").id(id);
}
template<typename X>
EventMatch
verifyMark (string id, X const& something) const
{
return getLog().verifyEvent("mark", something).type("mark").id(id);
}
EventLog const&
getLog() const
{
return log_;
}
EventLog&
joinLog (MockElm& otherMock)
{
log_.joinInto (otherMock.log_);
return log_;
}
};
}} // namespace gui::test
#endif /*GUI_TEST_MOCK_ELM_H*/