Lumiera  0.pre.03
»edit your freedom«
ui-coord-resolver-test.cpp
Go to the documentation of this file.
1 /*
2  UICoordResolver(Test) - resolve UI coordinates against actual topology
3 
4  Copyright (C) Lumiera.org
5  2017, 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 
28 #include "lib/test/run.hpp"
29 #include "lib/test/test-helper.hpp"
33 #include "lib/diff/gen-node.hpp"
34 #include "lib/format-util.hpp"
35 #include "lib/util.hpp"
36 
37 #include <string>
38 
39 
40 using std::string;
41 using lib::diff::MakeRec;
42 using lib::diff::Rec;
43 using lib::Symbol;
44 using util::isnil;
45 using util::join;
46 
47 
48 
49 namespace stage {
50 namespace interact{
51 namespace test {
52 
53  using LERR_(INVALID);
54  using LERR_(STATE);
55 
56 
57 
58  /******************************************************************************/
72  class UICoordResolver_test : public Test
73  {
74 
75  virtual void
76  run (Arg)
77  {
85  }
86 
87 
94  void
96  {
97  // a Test dummy placeholder for the real UI structure
98  Rec dummyUiStructure = MakeRec()
99  .set("window-1"
100  , MakeRec()
101  .type("perspective-A")
102  )
103  .set("window-2"
104  , MakeRec()
105  .type("perspective-B")
106  .set("panelX", MakeRec())
107  .set("panelXX", MakeRec())
108  );
109 
110  // helper to answer "location queries" backed by this structure
111  GenNodeLocationQuery locationQuery{dummyUiStructure};
112 
113  UICoord uic{"window-2","*","panelX","someView"};
114  UICoordResolver resolver{uic, locationQuery};
115 
116  CHECK (not resolver.isCovered());
117  CHECK ( resolver.canCover());
118 
119  UICoord uic2 = resolver.cover()
120  .extend("otherView");
121 
122  CHECK ("UI:window-2[perspective-B]-panelX.otherView" == string(uic2));
123  }
124 
125 
150  void
152  {
153  GenNodeLocationQuery queryAPI{MakeRec()
154  .set("window-1"
155  , MakeRec()
156  .type("perspective-A")
157  .set("panelX"
158  , MakeRec()
159  .set("firstView", MakeRec())
160  .set("secondView", MakeRec())
161  )
162  )
163  .set("window-2"
164  , MakeRec()
165  .type("perspective-B")
166  .set("panelY", MakeRec())
167  )
168  .set("window-3"
169  , MakeRec()
170  .type("perspective-C")
171  .set("panelZ"
172  , MakeRec()
173  .set("thirdView", MakeRec())
174  )
175  .set("panelZZ", MakeRec())
176  )
177  };
178 
179  // the LocationQuery API works by matching a UICoord spec against the "real" structure
180  UICoord uic1 = UICoord::window("window-2").persp("perspective-B");
181  UICoord uic2 = UICoord::window("windows");
182  UICoord uic3 = UICoord::firstWindow().persp("perspective-A").panel("panelX").view("secondView");
183  UICoord uic4 = UICoord::currentWindow().persp("perspective-B");
184  UICoord uic5 = UICoord::currentWindow().persp("perspective-C").panel("panelZ").view("someOtherView");
185 
186  CHECK ("window-2" == queryAPI.determineAnchor(uic1));
187  CHECK (Symbol::BOTTOM == queryAPI.determineAnchor(uic2));
188  CHECK ("window-1" == queryAPI.determineAnchor(uic3));
189  CHECK ("window-3" == queryAPI.determineAnchor(uic4));
190  CHECK ("window-3" == queryAPI.determineAnchor(uic5));
191 
192  CHECK (2 == queryAPI.determineCoverage(uic1));
193  CHECK (0 == queryAPI.determineCoverage(uic2));
194  CHECK (4 == queryAPI.determineCoverage(uic3));
195  CHECK (1 == queryAPI.determineCoverage(uic4));
196  CHECK (3 == queryAPI.determineCoverage(uic5));
197 
198  LocationQuery::ChildIter cii = queryAPI.getChildren(uic3, 3);
199  CHECK (not isnil(cii));
200  CHECK ("firstView" == *cii);
201  ++cii;
202  CHECK ("secondView" == *cii);
203  CHECK (not isnil(cii));
204  ++cii;
205  CHECK (isnil(cii));
206 
207  CHECK ("window-1, window-2, window-3" == join (queryAPI.getChildren (uic3, 0)));
208  CHECK ("perspective-A" == join (queryAPI.getChildren (uic3, 1)));
209  CHECK ("panelX" == join (queryAPI.getChildren (uic3, 2)));
210  CHECK ("firstView, secondView" == join (queryAPI.getChildren (uic3, 3)));
211  CHECK (isnil ( queryAPI.getChildren (uic3, 4))); // "firstView" has no children
212 
213  CHECK ("window-1, window-2, window-3" == join (queryAPI.getChildren (uic2, 0)));
214  VERIFY_ERROR (STATE, queryAPI.getChildren (uic2, 1) ); // "windows" at pos==0 is not covered by real UI
215 
216  CHECK ("window-1, window-2, window-3" == join (queryAPI.getChildren (uic5, 0)));
217  CHECK ("perspective-C" == join (queryAPI.getChildren (uic5, 1)));
218  CHECK ("panelZ, panelZZ" == join (queryAPI.getChildren (uic5, 2)));
219  CHECK ("thirdView" == join (queryAPI.getChildren (uic5, 3)));
220  VERIFY_ERROR (STATE, queryAPI.getChildren (uic5, 4) ); // "someOtherView" at level 4 does not exist
221 
222 
223  // verify "child exploration" via iterator interface
224  cii = queryAPI.getChildren (uic3, 0); // enter at root level...
225  CHECK ("window-1" == *cii); // first child of root to appear is "window-1"
226  CHECK (0 == cii.depth()); // (note depth just happens to coincide with absolute tree depth here)
227  cii.expandChildren(); // drill down into current element's children
228  CHECK (1 == cii.depth());
229  CHECK ("perspective-A" == *cii); // which is just one, the perspective
230  cii.expandChildren(); // drill down into the (formal, logical) children of "perspective-A"
231  CHECK (2 == cii.depth());
232  CHECK ("panelX" == *cii); // ..and find the "panelX" at level 2
233  cii.expandChildren(); // drill down one level further
234  CHECK (3 == cii.depth());
235  CHECK ("firstView" == *cii); // and then just continue iteration, which first explores that scope...
236  CHECK ("firstView, secondView, window-2, window-3" == join (cii)); // ...followed by returning to the enclosing scopes, finally top level.
237  }
238 
239 
246  void
248  {
249  GenNodeLocationQuery tree{MakeRec()
250  .set("window-1"
251  , MakeRec()
252  .type("perspective-A")
253  )
254  .set("window-2"
255  , MakeRec()
256  .type("perspective-B")
257  .set("panelX"
258  , MakeRec()
259  .set("someView", MakeRec())
260  )
261  )
262  };
263  UICoord uic1 = UICoord::window("window-1").persp("perspective-A");
264  UICoord uic2 = UICoord::window("windows");
265  UICoord uic3 = UICoord::firstWindow();
266  UICoord uic4 = UICoord::currentWindow().persp("perspective-B");
267  UICoord uic5 = UICoord::currentWindow().panel("panelY");
268  UICoord uic6 = UICoord().view("someView");
269 
270  UICoordResolver r1{uic1, tree};
271  UICoordResolver r2{uic2, tree};
272  UICoordResolver r3{uic3, tree};
273  UICoordResolver r4{uic4, tree};
274  UICoordResolver r5{uic5, tree};
275  UICoordResolver r6{uic6, tree};
276 
277  CHECK ( r1.isAnchored());
278  CHECK (not r2.isAnchored());
279  CHECK ( r3.isAnchored());
280  CHECK ( r4.isAnchored());
281  CHECK ( r5.isAnchored());
282  CHECK (not r6.isAnchored());
283 
284  CHECK ( r1.canAnchor());
285  CHECK (not r2.canAnchor());
286  CHECK ( r3.canAnchor());
287  CHECK ( r4.canAnchor());
288  CHECK ( r5.canAnchor());
289  CHECK ( r6.canAnchor());
290  }
291 
292 
293 
328  void
330  {
331  GenNodeLocationQuery tree{MakeRec()
332  .set("window-1"
333  , MakeRec()
334  .type("persp-A")
335  .set("panelX"
336  , MakeRec()
337  .set("firstView", MakeRec())
338  .set("secondView", MakeRec())
339  )
340  .set("panelZ"
341  , MakeRec()
342  .set("thirdView"
343  , MakeRec()
344  .set("#1", MakeRec())
345  .set("#2", MakeRec())
346  .set("tab", MakeRec())
347  )
348  )
349  )
350  .set("window-2"
351  , MakeRec()
352  .type("persp-B")
353  .set("panelY", MakeRec())
354  )
355  .set("window-3"
356  , MakeRec()
357  .type("persp-C")
358  .set("panelZ"
359  , MakeRec()
360  .set("thirdView"
361  , MakeRec()
362  .set("tab"
363  , MakeRec()
364  .set("sub", MakeRec())
365  )
366  .set("#1", MakeRec())
367  )
368  )
369  .set("panelZZ", MakeRec())
370  )
371  };
372 
373  /* === trivial cases === */
374  UICoordResolver r11 {UICoord::window("window-1")
375  .persp("persp-A")
376  .panel("panelX"), tree};
377  CHECK (r11.isCovered());
378  CHECK (3 == r11.coverDepth());
379 
380 
381  UICoordResolver r12 {UICoord::window("window-1")
382  .persp("persp-A")
383  .panel("panelX")
384  .view("thirdView"), tree};
385  CHECK (not r12.isCovered());
386  CHECK ( r12.isCoveredPartially());
387  CHECK (3 ==r12.coverDepth());
388  CHECK ("UI:window-1[persp-A]-panelX.thirdView" == string(r12));
389 
390  r12.cover();
391  CHECK (r12.isCovered());
392  CHECK (r12.isCoveredPartially());
393  CHECK (3 ==r12.coverDepth());
394  CHECK ("UI:window-1[persp-A]-panelX" == string(r12));
395 
396 
397  /* === expand anchor === */
398  UICoordResolver r21 {UICoord::firstWindow().persp("persp-A"), tree};
399  CHECK ("UI:firstWindow[persp-A]" == string(r21));
400  r21.cover();
401  CHECK ("UI:window-1[persp-A]" == string(r21));
402 
403  /* === expand anchor alone === */
405  CHECK ("UI:window-3" == string(r22.cover()));
406 
407 
408  /* === interpolate a single gap === */
409  UICoordResolver r31 {UICoord::window("window-1").view("secondView"), tree};
410  CHECK ("UI:window-1[*]-*.secondView" == string(r31));
411  CHECK (0 ==r31.coverDepth());
412  CHECK (not r31.isCovered());
413  CHECK (r31.canCover());
414  r31.cover();
415  CHECK (r31.isCovered());
416  CHECK (4 == r31.coverDepth());
417  CHECK ("UI:window-1[persp-A]-panelX.secondView" == string(r31));
418 
419  /* === interpolate several gaps === */
420  UICoordResolver r32 {UICoord().view("thirdView").path("sub"), tree};
421  CHECK ("UI:window-3[persp-C]-panelZ.thirdView.tab/sub" == string(r32.cover()));
422 
423  /* === interpolate anchor and consecutive wildcards === */
424  UICoordResolver r33 {UICoord::firstWindow().tab(2), tree};
425  CHECK ("UI:window-1[persp-A]-panelZ.thirdView.#2" == string(r33.cover()));
426 
427  /* === discriminate by anchor and fill second gap === */
428  UICoordResolver r34 {UICoord::currentWindow().panel("panelZ").tab("tab"), tree};
429  CHECK ("UI:currentWindow[*]-panelZ.*.tab" == string(r34));
430  CHECK ("UI:window-3[persp-C]-panelZ.thirdView.tab" == string(r34.cover())); // Note: rest of the path would also match on window-1, but currentWindow == window-3
431 
432  UICoordResolver r35 {UICoord::currentWindow().persp(UIC_ELIDED).panel("panelZ").tab("tab"), tree};
433  CHECK ("UI:currentWindow[.]-panelZ.*.tab" == string(r35));
434  CHECK ("UI:window-3[persp-C]-panelZ.thirdView.tab" == string(r35.cover())); // elided (existentially quantified) element interpolated similar to a wildcard
435 
436  UICoordResolver r36 {UICoord::currentWindow().panel(UIC_ELIDED).view("nonexisting"), tree};
437  CHECK ("UI:currentWindow[*]-..nonexisting" == string(r36));
438  CHECK ("UI:window-3[persp-C]-panelZ" == string(r36.cover())); // ...but elided counts as existing element and matches arbitrarily (-> contrast this to r44)
439 
440 
441  /* === trailing wildcards stripped automatically === */
442  UICoordResolver r41 {UICoord::window("window-2").append("*/*"), tree};
443  CHECK ("UI:window-2" == string(r41)); // Note: trailing wildcards are already discarded by PathArray / UICoord
444 
445  r41.extend("*/*"); // if we now attempt to "sneak in" trailing wildcards...
446  CHECK ("UI:window-2[*]-*" == string(r41));
447  CHECK (not r41.canCover()); // ...then the algorithm rejects any solution
448  CHECK ("UI:window-2" == string(r41.cover())); // Note: but cover() will act on the previous coverage and just strip the extraneous suffix
449 
450  /* === reject gap beyond existing real UI tree === */
451  UICoordResolver r42 {UICoord::window("window-2").append("*/*/*/some/path"), tree};
452  CHECK (not r42.canCover());
453 
454  /* === reject gap ending at real UI tree boundary === */
455  UICoordResolver r43 {UICoord::currentWindow().view("firstView").tab("nonexisting"), tree};
456  CHECK (not r43.canCover());
457 
458  /* === reject interpolated gap on mismatch right behind === */
459  UICoordResolver r44 {UICoord().view("otherView"), tree}; // Note: will be checked on all four existing views, but never matches
460  CHECK (not r44.canCover());
461 
462  /* === reject mismatch immediately behind second gap === */
463  UICoordResolver r45 {UICoord().panel("panelZ").tab(3), tree}; // Note: we have two "panelZ", but none has a tab #3
464  CHECK (not r45.canCover());
465 
466  /* === mismatch of tree level === */
467  UICoordResolver r46 {UICoord::currentWindow().append("*/*/panelZ/thirdView"), tree}; // Note: one '*' too much, thus 'panelZ' is matched on view level
468  CHECK (not r46.canCover());
469 
470  /* === impossible to anchor === */
471  UICoordResolver r47 {UICoord::firstWindow().tab(3), tree};
472  CHECK (not r47.canCover());
473 
474 
475  /* === the solution with maximum covered depth wins === */
476  UICoordResolver r51 {UICoord().tab("tab").path("sub"), tree};
477  CHECK ("UI:window-3[persp-C]-panelZ.thirdView.tab/sub" == string(r51.cover())); // the second solution found covers to maximum depth
478 
479  /* === when two solutions are equivalent, pick the fist one === */
480  UICoordResolver r52 {UICoord().tab("tab"), tree};
481  CHECK ("UI:window-1[persp-A]-panelZ.thirdView.tab" == string(r52.cover())); // "UI:window-3[persp-C]-panelZ.thirdView.tab" would match too
482 
483  /* === best solution will be picked, irrespective of discovery order === */
484  UICoordResolver r531 {UICoord().persp("persp-A").tab(1), tree};
485  CHECK ("UI:window-1[persp-A]-panelZ.thirdView.#1" == string(r531.cover())); // best solution discovered as first one
486 
487  UICoordResolver r532 {UICoord().view("thirdView").tab("tab"), tree};
488  CHECK ("UI:window-1[persp-A]-panelZ.thirdView.tab" == string(r532.cover())); // best solution is 3rd of five possible ones
489 
490  UICoordResolver r533 {UICoord().persp("persp-C").tab(1), tree};
491  CHECK ("UI:window-3[persp-C]-panelZ.thirdView.#1" == string(r533.cover())); // best solution is found as last one
492  }
493 
494 
495 
503  void
505  {
506  GenNodeLocationQuery tree{MakeRec()
507  .set("window-2"
508  , MakeRec()
509  .type("persp-B")
510  .set("panelY"
511  , MakeRec()
512  .set("someView"
513  , MakeRec()
514  .set("#1", MakeRec())
515  .set("#2", MakeRec())
516  )
517  )
518  )
519  };
520 
521  /* === explicitly given spec partially covered === */
522  UICoordResolver r1 {UICoord{"window-2","persp-B","panelY","otherView","tab"}, tree};
523  CHECK (3 == r1.coverDepth());
524  r1.coverPartially();
525  CHECK (not r1.isCovered());
526  CHECK (3 == r1.coverDepth());
527  CHECK (r1.isCoveredPartially()); // is covered down to the "panelY"
528  CHECK ("UI:window-2[persp-B]-panelY.otherView.tab" == string(r1));
529  r1.cover();
530  CHECK (r1.isCovered()); // cover() retains the covered part only
531  CHECK ("UI:window-2[persp-B]-panelY" == string(r1));
532 
533  /* === fill wildcard gap but retain uncovered extension === */
534  UICoordResolver r2 {UICoord::currentWindow().view("someView").tab(3).path("sub"), tree};
535  CHECK (0 == r2.coverDepth());
536  r2.coverPartially();
537  CHECK (not r2.isCovered());
538  CHECK (4 == r2.coverDepth());
539  CHECK (r2.isCoveredPartially());
540  CHECK ("UI:window-2[persp-B]-panelY.someView.#3/sub" == string(r2));
541  r2.cover();
542  CHECK ("UI:window-2[persp-B]-panelY.someView" == string(r2));
543 
544  /* === reject when gap can not be closed unambiguously === */
545  UICoordResolver r3 {UICoord::currentWindow().view("someView").path("sub"), tree};
546  CHECK (not r3.canCover()); // NOTE: second gap here, tab info missing
547  r3.coverPartially();
548  CHECK (isnil (r3));
549 
550  /* === reject when some wildcards remain after partial coverage === */
551  UICoordResolver r4 {UICoord::currentWindow().tab(3).path("sub"), tree};
552  r4.coverPartially();
553  CHECK (isnil (r4));
554 
555  /* === existentially quantified (elided) element constitutes partial coverage === */
556  UICoordResolver r5 {UICoord::currentWindow().persp(UIC_ELIDED).panel("fantasy").view("fantomas"), tree};
557  CHECK ("UI:currentWindow[.]-fantasy.fantomas" == string(r5));
558  CHECK (1 == r5.coverDepth());
559  r5.coverPartially();
560  CHECK (not r5.isCovered());
561  CHECK (2 == r5.coverDepth()); // Note side-effect of computing the coverage...
562  CHECK (r5.isCoveredPartially()); // it is known to be covered including "the" perspective
563  CHECK ("UI:window-2[persp-B]-fantasy.fantomas" == string(r5));
564  r5.cover();
565  CHECK ("UI:window-2[persp-B]" == string(r5));
566  CHECK (2 == r5.coverDepth());
567  }
568 
569 
570 
571 
583  void
585  {
586  GenNodeLocationQuery tree{MakeRec()
587  .set("window-1"
588  , MakeRec()
589  .type("persp-A")
590  .set("panelX"
591  , MakeRec()
592  .set("firstView", MakeRec())
593  .set("secondView", MakeRec())
594  )
595  )
596  .set("window-2"
597  , MakeRec()
598  .type("persp-B")
599  .set("panelY"
600  , MakeRec()
601  .set("thirdView"
602  , MakeRec()
603  .set("#1", MakeRec())
604  .set("#2", MakeRec())
605  )
606  )
607  )
608  .set("window-3"
609  , MakeRec()
610  .type("persp-C")
611  .set("panelZ"
612  , MakeRec()
613  .set("thirdView", MakeRec())
614  )
615  )
616  };
617 
618  /* === explicitly given window spec remains unchanged === */
619  UICoordResolver r1 {UICoord{"window-2","persp-B","panelY"}, tree};
620  CHECK (3 == r1.coverDepth());
621  r1.anchor();
622  CHECK ("UI:window-2[persp-B]-panelY" == string(r1));
623 
624  /* === `firstWindow` meta spec is resolved === */
625  UICoordResolver r2 {UICoord::firstWindow().view("blah"), tree};
626  CHECK (0 == r2.coverDepth());
627  CHECK (r2.isAnchored()); // can obviously be anchored, since there is always a first window
628  CHECK (not r2.canCover()); // yet this path is impossible to cover in the current UI
629  CHECK ("UI:firstWindow[*]-*.blah" == string(r2));
630  r2.anchor();
631  CHECK ("UI:window-1[*]-*.blah" == string(r2));
632  CHECK (0 == r2.coverDepth());
633  CHECK (not r2.canCover());
634 
635  /* === `currentWindow` meta spec is resolved === */
636  UICoordResolver r3 {UICoord::currentWindow().view("thirdView"), tree};
637  CHECK (0 == r3.coverDepth());
638  CHECK (r3.isAnchored());
639  CHECK (not r3.isCovered());
640  CHECK (r3.canCover());
641  r3.anchor();
642  CHECK (not r3.isCovered());
643  CHECK (r3.isCoveredPartially());
644  CHECK (1 == r3.coverDepth()); // anchoring also picks the second of two possible solutions
645  CHECK ("UI:window-3[*]-*.thirdView" == string(r3)); // thereby covering the "thirdView"
646 
647  /* === coverage solution is calculated on demand === */
648  UICoordResolver r4 {UICoord().view("thirdView").append("#2/sub"), tree};
649  CHECK ("UI:?.thirdView.#2/sub" == string(r4)); // an incomplete path is not automatically resolved
650  CHECK (not r4.isAnchored());
651  CHECK (0 == r4.coverDepth());
652  r4.anchor(); // but if we anchor, we force search for a coverage solution
653  CHECK (1 == r4.coverDepth()); // which is actually found starting from the second window,
654  CHECK (r4.isCoveredPartially()); // and kept in the internal cache for future use,
655  CHECK ("UI:window-2[*]-*.thirdView.#2/sub" == string(r4)); // but not made explicit, since we only requested anchorage
656 
657  /* === already calculated coverage solution is used === */
658  UICoordResolver r5 {UICoord::currentWindow().view("thirdView"), tree};
659  CHECK (not r5.isCovered());
660  CHECK (not r5.isCoveredPartially());
661  CHECK (0 == r5.coverDepth());
662  CHECK (r5.canCover()); // this triggers search for a coverage solution
663  CHECK (1 == r5.coverDepth());
664  CHECK (not r5.isCovered());
665  CHECK (r5.isCoveredPartially());
666  CHECK ("UI:currentWindow[*]-*.thirdView" == string(r5));
667  r5.anchor(); // and this (cached) solution is also used to make anchorage explicit
668  CHECK ("UI:window-3[*]-*.thirdView" == string(r5));
669  CHECK (1 == r5.coverDepth());
670  CHECK (not r5.isCovered());
671  r5.cover(); // ...now also the coverage solution was made explicit
672  CHECK (r5.isCovered());
673  CHECK (4 == r5.coverDepth());
674  CHECK ("UI:window-3[persp-C]-panelZ.thirdView" == string(r5));
675 
676  /* === impossible to cover and can not be anchored === */
677  UICoordResolver r6 {UICoord::window("windows").path("to/hell"), tree};
678  CHECK (not r6.isAnchored());
679  CHECK (not r6.canCover());
680  r6.anchor();
681  CHECK (not r6.isAnchored());
682  CHECK (0 == r6.coverDepth());
683  CHECK ("UI:windows[*]-*.*.*/to/hell" == string(r6));
684  }
685 
686 
697  void
699  {
700  GenNodeLocationQuery tree{MakeRec()
701  .set("window-2"
702  , MakeRec()
703  .type("persp-B")
704  .set("panelY"
705  , MakeRec()
706  .set("thirdView"
707  , MakeRec()
708  .set("#1", MakeRec())
709  .set("#2", MakeRec())
710  )
711  )
712  )
713  };
714 
715  /* === extend fully covered explicit path === */
716  UICoordResolver r1 {UICoord{"window-2","persp-B","panelY"}, tree};
717  CHECK ("UI:window-2[persp-B]-panelY" == string(r1));
718  CHECK (r1.isCovered());
719  r1.extend (UICoord().path("gappy").tab(2)); // can extend with partially defined UI coordinates
720  CHECK ("UI:window-2[persp-B]-panelY.*.#2/gappy" == string(r1)); // ...the resulting UI path is unresolved, yet can be partially covered
721  r1.extend ("seamless"); // ...and this partial coverage is used as base for further extension
722  CHECK ("UI:window-2[persp-B]-panelY.thirdView.#2/seamless" ==string(r1));
723 
724  /* === extend partially covered path === */
725  UICoordResolver r2 {UICoord().view("thirdView").append("some/where"), tree};
726  CHECK ("UI:?.thirdView.some/where" ==string(r2)); // "thirdView" is covered, "some/where" is not
727  r2.extend ("no/where");
728  CHECK ("UI:window-2[persp-B]-panelY.thirdView.no/where" ==string(r2)); // ...and thus the extension is attached behind "thirdView"
729  CHECK (r2.isCoveredPartially());
730 
731  /* === impossible extensions rejected === */ // since r2 already specifies a perspective ("persp-B")....
732  VERIFY_ERROR (INVALID, r2.extend(UICoord().persp("fisheye"))); // ...overwriting with another perspective is rejected as extension
733  CHECK ("UI:window-2[persp-B]-panelY.thirdView.no/where" ==string(r2)); // ...and the existing state is unaffected from this error
734  VERIFY_ERROR (INVALID, r2.extend(UICoord().view("alternative"))); // Likewise, extending with a conflicting view spec is rejected
735  r2.extend(UICoord().tab("nada")); // But a tab is not yet covered and thus acceptable as extension
736  CHECK ("UI:window-2[persp-B]-panelY.thirdView.nada" ==string(r2));
737  r2.extend(UICoord());
738  CHECK ("UI:window-2[persp-B]-panelY.thirdView" ==string(r2)); // empty coordinates implicitly attached behind the covered part
739 
740  /* === unsolvable: truncate, extend, recalculate coverage === */
741  UICoordResolver r3 {UICoord().persp("awesome"), tree};
742  CHECK (not r3.canCover());
743  CHECK (0 == r3.coverDepth());
744  r3.extend (UICoord::currentWindow().tab(1)); // Extension implies covering, which effectively truncates the path
745  CHECK (1 == r3.coverDepth()); // ...and "currentWindow" can even be covered, thus the coverage increases
746  CHECK ("UI:currentWindow[*]-*.*.#1" ==string(r3)); // note coverage calculated internally, not made explicit
747  }
748  };
749 
750 
752  LAUNCHER (UICoordResolver_test, "unit stage");
753 
754 
755 }}} // namespace stage::interact::test
Test/Diagnostics: implementation of the LocationQuery-API based on a abstract topological structure g...
Describe a location within the UI through structural/topological coordinates.
Definition: ui-coord.hpp:138
Builder && path(Literal pathDef)
augment UI coordinates to define a complete local path
Definition: ui-coord.hpp:625
Builder persp(Literal perspectiveID) const
Definition: ui-coord.hpp:704
Implementation of the stage::interact::LocationQuery interface to work on a GenNode tree...
Definition: run.hpp:49
Builder && persp(Literal perspectiveID)
augment UI coordinates to mandate a specific perspective to be active within the window ...
Definition: ui-coord.hpp:547
#define VERIFY_ERROR(ERROR_ID, ERRONEOUS_STATEMENT)
Macro to verify that a statement indeed raises an exception.
Builder && append(Literal elm)
augment UI coordinates by appending a further component at the end.
Definition: ui-coord.hpp:600
const Symbol UIC_ELIDED
indicate that a component is elided or irrelevant here
Builder && tab(Literal tabID)
augment UI coordinates to indicate a specific tab within the view"
Definition: ui-coord.hpp:571
Token or Atom with distinct identity.
Definition: symbol.hpp:126
Builder && panel(Literal panelID)
augment UI coordinates to indicate a specific view to be used
Definition: ui-coord.hpp:555
Simple test class runner.
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...
A topological addressing scheme to designate structural locations within the UI.
Query and mutate UICoord specifications in relation to actual UI topology.
Generic building block for tree shaped (meta)data structures.
A collection of frequently used helper functions to support unit testing.
Builder && view(Literal viewID)
augment UI coordinates to indicate a specific view to be used
Definition: ui-coord.hpp:563
static Builder window(Literal windowID)
Builder: start definition of UI-Coordinates rooted in given window.
Definition: ui-coord.hpp:691
Collection of small helpers and convenience shortcuts for diagnostics & formatting.
Evaluation of UI coordinates against a concrete window topology.
object-like record of data.
Definition: record.hpp:150
static Builder firstWindow()
Builder: start definition of UI-Coordinates rooted in the firstWindow
Definition: ui-coord.hpp:684
static Builder currentWindow()
Builder: start definition of UI-Coordinates rooted in the currentWindow
Definition: ui-coord.hpp:678