Lumiera  0.pre.03
»edit your freedom«
mock-elm.hpp
Go to the documentation of this file.
1 /*
2  MOCK-ELM.hpp - generic mock UI element for unit testing
3 
4  Copyright (C) Lumiera.org
5  2015, Hermann Vosseler <Ichthyostega@web.de>
6 
7  This program is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License as
9  published by the Free Software Foundation; either version 2 of
10  the License, or (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21 */
22 
23 
59 #ifndef STAGE_TEST_MOCK_ELM_H
60 #define STAGE_TEST_MOCK_ELM_H
61 
62 
63 #include "lib/error.hpp"
64 #include "include/ui-protocol.hpp"
65 #include "lib/test/event-log.hpp"
66 #include "stage/model/tangible.hpp"
67 #include "lib/diff/record.hpp"
68 #include "lib/idi/genfunc.hpp"
69 #include "test/test-nexus.hpp"
71 #include "lib/format-cout.hpp"
72 #include "lib/symbol.hpp"
73 #include "lib/util.hpp"
74 
75 #include <string>
76 #include <memory>
77 #include <vector>
78 #include <map>
79 
80 
81 namespace stage {
82  using lib::test::EventLog;
84 
85 namespace test{
86 
87 
89  using util::isnil;
90  using lib::Symbol;
91  using std::string;
92 
93  class MockElm;
94  using PMockElm = std::unique_ptr<MockElm>;
95 
96 
108  class MockElm
109  : public stage::model::Tangible
110  {
112 
113  EventLog log_{this->identify()};
114 
115  bool virgin_{true};
116  bool expanded_{false};
117 
118  string message_;
119  string error_;
120 
121 
122 
123  /* ==== Tangible interface ==== */
124 
125  virtual bool
126  doReset() override
127  {
128  log_.call(this->identify(), string{MARK_reset});
129  if (virgin_)
130  return false; // there was nothing to reset
131 
132  error_ = "";
133  message_ = "";
134  expanded_ = false;
135  virgin_ = true;
136  log_.event(string{MARK_reset});
137  return true; // we did indeed reset something
138  } // and thus a state mark should be captured
139 
140  virtual bool
141  doExpand (bool yes) override
142  {
143  log_.call(this->identify(), string{MARK_expand}, yes);
144  return Tangible::doExpand (yes);
145  }
146 
147  virtual void
148  doReveal() override
149  {
150  log_.call(this->identify(), string{MARK_reveal});
151  Tangible::doReveal(); // NOTE: without specific configuration this is NOP
152  }
153 
154  virtual bool
155  doMsg (string text) override
156  {
157  log_.call (this->identify(), "doMsg", text);
158  cout << this->identify() << " <-- Message(\""<<text<<"\")" <<endl;
159  message_ = text;
160  virgin_ = false;
161  log_.note ("type=mark", "ID=Message", text);
162 
163  return false; // messages not sticky for this mock implementation
164  }
165 
166  virtual bool
167  doClearMsg () override
168  {
169  log_.call (this->identify(), "doClearMsg");
170  if (isnil (message_))
171  return false;
172 
173  message_ = "";
174  log_.note ("type=mark", "ID=Message", "Message notification cleared");
175  return true;
176  }
177 
178  virtual bool
179  doErr (string text) override
180  {
181  log_.call (this->identify(), "doErr", text);
182  cerr << this->identify() << " <-- Error(\""<<text<<"\")" <<endl;
183  error_ = text;
184  virgin_ = false;
185  log_.note ("type=mark", "ID=Error", text);
186 
187  return true; // error states are sticky for this mock implementation
188  }
189 
190  virtual bool
191  doClearErr () override
192  {
193  log_.call (this->identify(), "doClearErr");
194  if (not isError())
195  return false;
196 
197  error_ = "";
198  log_.note ("type=mark", "ID=Error", "Error state cleared");
199  return true;
200  }
201 
202  virtual void
203  doFlash() override
204  {
205  log_.call (this->identify(), "doFlash");
206  cout << this->identify() << " <-- Flash!" <<endl;
207  log_.note ("type=mark", "ID=Flash");
208  }
209 
210  virtual void
211  doMark (GenNode const& mark) override
212  {
213  log_.call (this->identify(), "doMark", mark);
214  cout << this->identify() << " <-- state-mark = "<< mark <<endl;
215  log_.note ("type=mark", "ID="+mark.idi.getSym(), mark);
216 
217  this->virgin_ = false; // assume state change....
218 
219  // forward to default handler
220  Tangible::doMark (mark);
221  }
222 
223  virtual void
225  {
226  using Attrib = std::pair<const string,string>;
227  using lib::diff::collection;
228  using lib::diff::render;
229 
230  log_.call (this->identify(), "buildMutator");
231  cout << this->identify() << " <-- DIFF" <<endl;
232 
233  buffer.emplace(
235  .attach (collection(scope)
236  .isApplicableIf ([&](GenNode const& spec) -> bool
237  {
238  return spec.data.isNested(); // »Selector« : require object-like sub scope
239  })
240  .matchElement ([&](GenNode const& spec, PMockElm const& elm) -> bool
241  {
242  return spec.idi == elm->getID();
243  })
244  .constructFrom ([&](GenNode const& spec) -> PMockElm
245  {
246  log_.event("diff", "create child \""+spec.idi.getSym()+"\"");
247  PMockElm child = std::make_unique<MockElm>(spec.idi, this->uiBus_);
248  child->joinLog(*this); // create a child element wired via this Element's BusTerm
249  return child;
250  })
251  .buildChildMutator ([&](PMockElm& target, GenNode::ID const& subID, TreeMutator::Handle buff) -> bool
252  {
253  if (target->getID() != subID) return false; //require match on already existing child object
254  target->buildMutator (buff); // delegate to child to build nested TreeMutator
255  log_.event("diff", ">>Scope>> "+subID.getSym());
256  return true;
257  }))
258  .attach (collection(attrib)
259  .isApplicableIf ([&](GenNode const& spec) -> bool
260  {
261  return spec.isNamed() // »Selector« : accept attribute-like values
262  and not spec.data.isNested(); // but no nested objects
263  })
264  .matchElement ([&](GenNode const& spec, Attrib const& elm) -> bool
265  {
266  return elm.first == spec.idi.getSym();
267  })
268  .constructFrom ([&](GenNode const& spec) -> Attrib
269  {
270  string key{spec.idi.getSym()},
271  val{render(spec.data)};
272  log_.event("diff", "++Attrib++ "+key+" = "+val);
273  return {key, val};
274  })
275  .assignElement ([&](Attrib& target, GenNode const& spec) -> bool
276  {
277  string key{spec.idi.getSym()},
278  newVal{render (spec.data)};
279  log_.event("diff", "set Attrib "+key+" <-"+newVal);
280  target.second = newVal;
281  return true;
282  })));
283 
284  log_.event ("diff", getID().getSym() +" accepts mutation...");
285  }
286 
287 
288  protected:
289  string
290  identify() const
291  {
292  return getID().getSym() +"."+ lib::idi::instanceTypeID(this);
293  }
294 
295 
296  public:
297  explicit
298  MockElm(string id)
300  { }
301 
302  explicit
303  MockElm(ID identity, ctrl::BusTerm& nexus =Nexus::testUI())
304  : stage::model::Tangible(identity, nexus)
305  {
306  log_.call (this->identify(), "ctor", identity, string(nexus));
307  log_.create (getID().getSym());
308  installExpander ([&](){ return this->expanded_; }
309  ,[&](bool yes)
310  {
311  virgin_ = false;
312  expanded_ = yes;
313  log_.event (expanded_? "expanded" : "collapsed");
314  });
315  }
316 
317 
320  {
321  try {
322  log_.call (this->identify(), "dtor");
323  log_.destroy (getID().getSym());
324  }
325  catch(...)
326  {
327  const char* errID = lumiera_error();
328  if (errID)
329  cerr << "Error while logging shutdown of Mock-UI-Element: " << errID <<endl;
330  else
331  cerr << "Unknown Error while logging shutdown of Mock-UI-Element." <<endl;
332  }
333  }
334 
335 
336 
337 
338  /* ==== special operations API ==== */
339 
348  void
350  {
351  log_.call (this->identify(), "kill");
352  log_.destroy (getID().getSym());
353 
354  Nexus::zombificate (this->uiBus_);
355  log_.event (string(getID()) + " successfully connected to zombie bus");
356  }
357 
358 
359 
360 
361  /* ==== Attributes and mock children ==== */
362 
363  std::map<string, string> attrib;
364  std::vector<PMockElm> scope;
365 
366 
367  /* ==== Query/Verification API ==== */
368 
369  ID getID() const
370  {
371  return uiBus_.getID();
372  }
373 
374  bool
375  isTouched() const
376  {
377  return not virgin_;
378  }
379 
380  bool
381  isExpanded() const
382  {
383  return expanded_;
384  }
385 
386  bool
387  isError() const
388  {
389  return not isnil(error_);
390  }
391 
392  string
393  getMessage() const
394  {
395  return message_;
396  }
397 
398  string
399  getError() const
400  {
401  return error_;
402  }
403 
404 
405 
406  EventMatch
407  verify (string match) const
408  {
409  return getLog().verify(match);
410  }
411 
412  EventMatch
413  verifyMatch (string regExp) const
414  {
415  return getLog().verifyMatch(regExp);
416  }
417 
418  EventMatch
419  verifyEvent (string match) const
420  {
421  return getLog().verifyEvent(match);
422  }
423 
424  EventMatch
425  verifyEvent (string classifier, string match) const
426  {
427  return getLog().verifyEvent (classifier,match);
428  }
429 
430  EventMatch
431  verifyCall (string match) const
432  {
433  return getLog().verifyCall(match);
434  }
435 
436  EventMatch
437  ensureNot (string match) const
438  {
439  return getLog().ensureNot(match);
440  }
441 
443  EventMatch
444  verifyMark (string id) const
445  {
446  return getLog().verify(id).type("mark").id(id);
447  }
448 
453  EventMatch
454  verifyMark (string id, string payloadMatch) const
455  {
456  return getLog().verifyEvent("mark", payloadMatch).type("mark").id(id);
457  }
458 
459  template<typename X>
460  EventMatch
461  verifyMark (string id, X const& something) const
462  {
463  return getLog().verifyEvent("mark", something).type("mark").id(id);
464  }
465 
466 
467  EventLog const&
468  getLog() const
469  {
470  return log_;
471  }
472 
473  EventLog&
474  joinLog (MockElm& otherMock)
475  {
476  log_.joinInto (otherMock.log_);
477  return log_;
478  }
479 
480  EventLog&
481  joinLog (EventLog& otherLog)
482  {
483  log_.joinInto (otherLog);
484  return log_;
485  }
486  };
487 
488 
489 
490 }} // namespace stage::test
491 #endif /*STAGE_TEST_MOCK_ELM_H*/
EventMatch & type(string typeID)
refine filter to additionally require a matching log entry type
Definition: event-log.cpp:548
EventLog & event(string text)
log some text as event
Definition: event-log.cpp:685
virtual void doReveal() override
generic default implementation of the "reveal" functionality.
Definition: mock-elm.hpp:148
Automatically use custom string conversion in C++ stream output.
Abstraction: a tangible element of the User Interface.
Hard wired key constants and basic definitions for communication with the GUI.
connection point at the UI-Bus.
Definition: bus-term.hpp:105
bool isNested() const
determine if payload constitutes a nested scope ("object")
Definition: gen-node.hpp:777
Support for verifying the occurrence of events from unit tests.
Definition: run.hpp:49
A fake UI backbone for investigations and unit testing.
EventMatch verifyMark(string id) const
special verification match on a "state mark" message to this element
Definition: mock-elm.hpp:444
Helper to log and verify the occurrence of events.
Definition: event-log.hpp:284
typed symbolic and hash ID for asset-like position accounting.
Definition: entry-id.hpp:135
EventLog & create(string text)
Log the creation of an object.
Definition: event-log.cpp:742
void mark(GenNode const &)
generic handler for all incoming "state mark" messages
Definition: tangible.cpp:260
EventMatch verifyEvent(string match) const
start a query to match for some event.
Definition: event-log.cpp:779
Generic functions to build identification schemes.
static Builder< TreeMutator > build()
DSL: start building a custom adapted tree mutator, where the operations are tied by closures or wrapp...
SUB & emplace(SUB &&implementation)
move-construct an instance of a subclass into the opaque buffer
Token or Atom with distinct identity.
Definition: symbol.hpp:126
EventMatch verifyCall(string match) const
start a query to match especially a function call
Definition: event-log.cpp:797
A handle to allow for safe »remote implantation« of an unknown subclass into a given opaque InPlaceBu...
Definition: record.hpp:113
Special collection to represent object-like data.
EventMatch ensureNot(string match) const
start a query to ensure the given expression does not match.
Definition: event-log.cpp:814
Marker types to indicate a literal string and a Symbol.
~MockElm()
document our death in the diagnostic log.
Definition: mock-elm.hpp:319
Lumiera GTK UI implementation root.
Definition: guifacade.cpp:46
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
EventLog & call(string target, string function)
Log occurrence of a function call with no arguments.
Definition: event-log.cpp:699
lumiera_err lumiera_error(void)
Get and clear current error state.
Definition: error-state.c:124
EventLog & joinInto(EventLog &otherLog)
Merge this log into another log, forming a combined log.
Definition: event-log.cpp:634
virtual bool doExpand(bool yes) override
generic default implementation of the expand/collapse functionality.
Definition: mock-elm.hpp:141
EventMatch verifyMark(string id, string payloadMatch) const
verification match on a specific "state mark" message
Definition: mock-elm.hpp:454
void kill()
commit suicide.
Definition: mock-elm.hpp:349
EventMatch & id(string classifier)
refine filter to additionally match on the ID attribute
Definition: event-log.cpp:581
virtual void doMark(GenNode const &mark) override
default implementation and catch-all handler for receiving »state mark« messages. ...
Definition: mock-elm.hpp:211
static void zombificate(ctrl::BusTerm &)
kill the given [BusTerm] and implant a dead terminal in place
Definition: test-nexus.cpp:593
void installExpander(Expander::ProbeFun, Expander::ChangeFun)
Configure the (optional) functionality to expand or collapse the UI-Element.
Definition: tangible.hpp:282
Lumiera error handling (C++ interface).
string instanceTypeID(const TY *const obj)
designation of an distinct object instance
Definition: genfunc.hpp:125
static ctrl::BusTerm & testUI()
get a connection point to a UI backbone faked for test
Definition: test-nexus.cpp:375
Mock UI element or controller.
Definition: mock-elm.hpp:108
EventMatch verify(string match) const
start a query to match for some substring.
Definition: event-log.cpp:761
EventLog & destroy(string text)
Log the destruction of an object.
Definition: event-log.cpp:749
EventMatch verifyMatch(string regExp) const
start a query to match with a regular expression
Definition: event-log.cpp:770
Interface common to all UI elements of relevance for the Lumiera application.
Definition: tangible.hpp:165
auto collection(COLL &coll)
Entry point to a nested DSL for setup and configuration of a collection binding.
virtual void buildMutator(TreeMutator::Handle buffer) override
build a custom implementation of the TreeMutator interface, suitably wired to cause appropriate chang...
Definition: mock-elm.hpp:224
Diagnostic helper for unit tests regarding mutation of custom data.
generic data element node within a tree
Definition: gen-node.hpp:231
Customisable intermediary to abstract mutating operations on arbitrary, hierarchical object-like data...