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)
5  2017, Hermann Vosseler <Ichthyostega@web.de>
6 
7   **Lumiera** is free software; you can redistribute it and/or modify it
8   under the terms of the GNU General Public License as published by the
9   Free Software Foundation; either version 2 of the License, or (at your
10   option) any later version. See the file COPYING for further details.
11 
12 * *****************************************************************/
13 
19 #include "lib/test/run.hpp"
20 #include "lib/test/test-helper.hpp"
24 #include "lib/diff/gen-node.hpp"
25 #include "lib/format-util.hpp"
26 #include "lib/util.hpp"
27 
28 #include <string>
29 
30 
31 using std::string;
32 using lib::diff::MakeRec;
33 using lib::diff::Rec;
34 using lib::Symbol;
35 using util::isnil;
36 using util::join;
37 
38 
39 
40 namespace stage {
41 namespace interact{
42 namespace test {
43 
44  using LERR_(INVALID);
45  using LERR_(STATE);
46 
47 
48 
49  /******************************************************************************/
63  class UICoordResolver_test : public Test
64  {
65 
66  virtual void
67  run (Arg)
68  {
76  }
77 
78 
85  void
87  {
88  // a Test dummy placeholder for the real UI structure
89  Rec dummyUiStructure = MakeRec()
90  .set("window-1"
91  , MakeRec()
92  .type("perspective-A")
93  )
94  .set("window-2"
95  , MakeRec()
96  .type("perspective-B")
97  .set("panelX", MakeRec())
98  .set("panelXX", MakeRec())
99  );
100 
101  // helper to answer "location queries" backed by this structure
102  GenNodeLocationQuery locationQuery{dummyUiStructure};
103 
104  UICoord uic{"window-2","*","panelX","someView"};
105  UICoordResolver resolver{uic, locationQuery};
106 
107  CHECK (not resolver.isCovered());
108  CHECK ( resolver.canCover());
109 
110  UICoord uic2 = resolver.cover()
111  .extend("otherView");
112 
113  CHECK ("UI:window-2[perspective-B]-panelX.otherView" == string(uic2));
114  }
115 
116 
141  void
143  {
144  GenNodeLocationQuery queryAPI{MakeRec()
145  .set("window-1"
146  , MakeRec()
147  .type("perspective-A")
148  .set("panelX"
149  , MakeRec()
150  .set("firstView", MakeRec())
151  .set("secondView", MakeRec())
152  )
153  )
154  .set("window-2"
155  , MakeRec()
156  .type("perspective-B")
157  .set("panelY", MakeRec())
158  )
159  .set("window-3"
160  , MakeRec()
161  .type("perspective-C")
162  .set("panelZ"
163  , MakeRec()
164  .set("thirdView", MakeRec())
165  )
166  .set("panelZZ", MakeRec())
167  )
168  };
169 
170  // the LocationQuery API works by matching a UICoord spec against the "real" structure
171  UICoord uic1 = UICoord::window("window-2").persp("perspective-B");
172  UICoord uic2 = UICoord::window("windows");
173  UICoord uic3 = UICoord::firstWindow().persp("perspective-A").panel("panelX").view("secondView");
174  UICoord uic4 = UICoord::currentWindow().persp("perspective-B");
175  UICoord uic5 = UICoord::currentWindow().persp("perspective-C").panel("panelZ").view("someOtherView");
176 
177  CHECK ("window-2" == queryAPI.determineAnchor(uic1));
178  CHECK (Symbol::BOTTOM == queryAPI.determineAnchor(uic2));
179  CHECK ("window-1" == queryAPI.determineAnchor(uic3));
180  CHECK ("window-3" == queryAPI.determineAnchor(uic4));
181  CHECK ("window-3" == queryAPI.determineAnchor(uic5));
182 
183  CHECK (2 == queryAPI.determineCoverage(uic1));
184  CHECK (0 == queryAPI.determineCoverage(uic2));
185  CHECK (4 == queryAPI.determineCoverage(uic3));
186  CHECK (1 == queryAPI.determineCoverage(uic4));
187  CHECK (3 == queryAPI.determineCoverage(uic5));
188 
189  LocationQuery::ChildIter cii = queryAPI.getChildren(uic3, 3);
190  CHECK (not isnil(cii));
191  CHECK ("firstView" == *cii);
192  ++cii;
193  CHECK ("secondView" == *cii);
194  CHECK (not isnil(cii));
195  ++cii;
196  CHECK (isnil(cii));
197 
198  CHECK ("window-1, window-2, window-3" == join (queryAPI.getChildren (uic3, 0)));
199  CHECK ("perspective-A" == join (queryAPI.getChildren (uic3, 1)));
200  CHECK ("panelX" == join (queryAPI.getChildren (uic3, 2)));
201  CHECK ("firstView, secondView" == join (queryAPI.getChildren (uic3, 3)));
202  CHECK (isnil ( queryAPI.getChildren (uic3, 4))); // "firstView" has no children
203 
204  CHECK ("window-1, window-2, window-3" == join (queryAPI.getChildren (uic2, 0)));
205  VERIFY_ERROR (STATE, queryAPI.getChildren (uic2, 1) ); // "windows" at pos==0 is not covered by real UI
206 
207  CHECK ("window-1, window-2, window-3" == join (queryAPI.getChildren (uic5, 0)));
208  CHECK ("perspective-C" == join (queryAPI.getChildren (uic5, 1)));
209  CHECK ("panelZ, panelZZ" == join (queryAPI.getChildren (uic5, 2)));
210  CHECK ("thirdView" == join (queryAPI.getChildren (uic5, 3)));
211  VERIFY_ERROR (STATE, queryAPI.getChildren (uic5, 4) ); // "someOtherView" at level 4 does not exist
212 
213 
214  // verify "child exploration" via iterator interface
215  cii = queryAPI.getChildren (uic3, 0); // enter at root level...
216  CHECK ("window-1" == *cii); // first child of root to appear is "window-1"
217  CHECK (0 == cii.depth()); // (note depth just happens to coincide with absolute tree depth here)
218  cii.expandChildren(); // drill down into current element's children
219  CHECK (1 == cii.depth());
220  CHECK ("perspective-A" == *cii); // which is just one, the perspective
221  cii.expandChildren(); // drill down into the (formal, logical) children of "perspective-A"
222  CHECK (2 == cii.depth());
223  CHECK ("panelX" == *cii); // ..and find the "panelX" at level 2
224  cii.expandChildren(); // drill down one level further
225  CHECK (3 == cii.depth());
226  CHECK ("firstView" == *cii); // and then just continue iteration, which first explores that scope...
227  CHECK ("firstView, secondView, window-2, window-3" == join (cii)); // ...followed by returning to the enclosing scopes, finally top level.
228  }
229 
230 
237  void
239  {
240  GenNodeLocationQuery tree{MakeRec()
241  .set("window-1"
242  , MakeRec()
243  .type("perspective-A")
244  )
245  .set("window-2"
246  , MakeRec()
247  .type("perspective-B")
248  .set("panelX"
249  , MakeRec()
250  .set("someView", MakeRec())
251  )
252  )
253  };
254  UICoord uic1 = UICoord::window("window-1").persp("perspective-A");
255  UICoord uic2 = UICoord::window("windows");
256  UICoord uic3 = UICoord::firstWindow();
257  UICoord uic4 = UICoord::currentWindow().persp("perspective-B");
258  UICoord uic5 = UICoord::currentWindow().panel("panelY");
259  UICoord uic6 = UICoord().view("someView");
260 
261  UICoordResolver r1{uic1, tree};
262  UICoordResolver r2{uic2, tree};
263  UICoordResolver r3{uic3, tree};
264  UICoordResolver r4{uic4, tree};
265  UICoordResolver r5{uic5, tree};
266  UICoordResolver r6{uic6, tree};
267 
268  CHECK ( r1.isAnchored());
269  CHECK (not r2.isAnchored());
270  CHECK ( r3.isAnchored());
271  CHECK ( r4.isAnchored());
272  CHECK ( r5.isAnchored());
273  CHECK (not r6.isAnchored());
274 
275  CHECK ( r1.canAnchor());
276  CHECK (not r2.canAnchor());
277  CHECK ( r3.canAnchor());
278  CHECK ( r4.canAnchor());
279  CHECK ( r5.canAnchor());
280  CHECK ( r6.canAnchor());
281  }
282 
283 
284 
319  void
321  {
322  GenNodeLocationQuery tree{MakeRec()
323  .set("window-1"
324  , MakeRec()
325  .type("persp-A")
326  .set("panelX"
327  , MakeRec()
328  .set("firstView", MakeRec())
329  .set("secondView", MakeRec())
330  )
331  .set("panelZ"
332  , MakeRec()
333  .set("thirdView"
334  , MakeRec()
335  .set("#1", MakeRec())
336  .set("#2", MakeRec())
337  .set("tab", MakeRec())
338  )
339  )
340  )
341  .set("window-2"
342  , MakeRec()
343  .type("persp-B")
344  .set("panelY", MakeRec())
345  )
346  .set("window-3"
347  , MakeRec()
348  .type("persp-C")
349  .set("panelZ"
350  , MakeRec()
351  .set("thirdView"
352  , MakeRec()
353  .set("tab"
354  , MakeRec()
355  .set("sub", MakeRec())
356  )
357  .set("#1", MakeRec())
358  )
359  )
360  .set("panelZZ", MakeRec())
361  )
362  };
363 
364  /* === trivial cases === */
365  UICoordResolver r11 {UICoord::window("window-1")
366  .persp("persp-A")
367  .panel("panelX"), tree};
368  CHECK (r11.isCovered());
369  CHECK (3 == r11.coverDepth());
370 
371 
372  UICoordResolver r12 {UICoord::window("window-1")
373  .persp("persp-A")
374  .panel("panelX")
375  .view("thirdView"), tree};
376  CHECK (not r12.isCovered());
377  CHECK ( r12.isCoveredPartially());
378  CHECK (3 ==r12.coverDepth());
379  CHECK ("UI:window-1[persp-A]-panelX.thirdView" == string(r12));
380 
381  r12.cover();
382  CHECK (r12.isCovered());
383  CHECK (r12.isCoveredPartially());
384  CHECK (3 ==r12.coverDepth());
385  CHECK ("UI:window-1[persp-A]-panelX" == string(r12));
386 
387 
388  /* === expand anchor === */
389  UICoordResolver r21 {UICoord::firstWindow().persp("persp-A"), tree};
390  CHECK ("UI:firstWindow[persp-A]" == string(r21));
391  r21.cover();
392  CHECK ("UI:window-1[persp-A]" == string(r21));
393 
394  /* === expand anchor alone === */
396  CHECK ("UI:window-3" == string(r22.cover()));
397 
398 
399  /* === interpolate a single gap === */
400  UICoordResolver r31 {UICoord::window("window-1").view("secondView"), tree};
401  CHECK ("UI:window-1[*]-*.secondView" == string(r31));
402  CHECK (0 ==r31.coverDepth());
403  CHECK (not r31.isCovered());
404  CHECK (r31.canCover());
405  r31.cover();
406  CHECK (r31.isCovered());
407  CHECK (4 == r31.coverDepth());
408  CHECK ("UI:window-1[persp-A]-panelX.secondView" == string(r31));
409 
410  /* === interpolate several gaps === */
411  UICoordResolver r32 {UICoord().view("thirdView").path("sub"), tree};
412  CHECK ("UI:window-3[persp-C]-panelZ.thirdView.tab/sub" == string(r32.cover()));
413 
414  /* === interpolate anchor and consecutive wildcards === */
415  UICoordResolver r33 {UICoord::firstWindow().tab(2), tree};
416  CHECK ("UI:window-1[persp-A]-panelZ.thirdView.#2" == string(r33.cover()));
417 
418  /* === discriminate by anchor and fill second gap === */
419  UICoordResolver r34 {UICoord::currentWindow().panel("panelZ").tab("tab"), tree};
420  CHECK ("UI:currentWindow[*]-panelZ.*.tab" == string(r34));
421  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
422 
423  UICoordResolver r35 {UICoord::currentWindow().persp(UIC_ELIDED).panel("panelZ").tab("tab"), tree};
424  CHECK ("UI:currentWindow[.]-panelZ.*.tab" == string(r35));
425  CHECK ("UI:window-3[persp-C]-panelZ.thirdView.tab" == string(r35.cover())); // elided (existentially quantified) element interpolated similar to a wildcard
426 
427  UICoordResolver r36 {UICoord::currentWindow().panel(UIC_ELIDED).view("nonexisting"), tree};
428  CHECK ("UI:currentWindow[*]-..nonexisting" == string(r36));
429  CHECK ("UI:window-3[persp-C]-panelZ" == string(r36.cover())); // ...but elided counts as existing element and matches arbitrarily (-> contrast this to r44)
430 
431 
432  /* === trailing wildcards stripped automatically === */
433  UICoordResolver r41 {UICoord::window("window-2").append("*/*"), tree};
434  CHECK ("UI:window-2" == string(r41)); // Note: trailing wildcards are already discarded by PathArray / UICoord
435 
436  r41.extend("*/*"); // if we now attempt to "sneak in" trailing wildcards...
437  CHECK ("UI:window-2[*]-*" == string(r41));
438  CHECK (not r41.canCover()); // ...then the algorithm rejects any solution
439  CHECK ("UI:window-2" == string(r41.cover())); // Note: but cover() will act on the previous coverage and just strip the extraneous suffix
440 
441  /* === reject gap beyond existing real UI tree === */
442  UICoordResolver r42 {UICoord::window("window-2").append("*/*/*/some/path"), tree};
443  CHECK (not r42.canCover());
444 
445  /* === reject gap ending at real UI tree boundary === */
446  UICoordResolver r43 {UICoord::currentWindow().view("firstView").tab("nonexisting"), tree};
447  CHECK (not r43.canCover());
448 
449  /* === reject interpolated gap on mismatch right behind === */
450  UICoordResolver r44 {UICoord().view("otherView"), tree}; // Note: will be checked on all four existing views, but never matches
451  CHECK (not r44.canCover());
452 
453  /* === reject mismatch immediately behind second gap === */
454  UICoordResolver r45 {UICoord().panel("panelZ").tab(3), tree}; // Note: we have two "panelZ", but none has a tab #3
455  CHECK (not r45.canCover());
456 
457  /* === mismatch of tree level === */
458  UICoordResolver r46 {UICoord::currentWindow().append("*/*/panelZ/thirdView"), tree}; // Note: one '*' too much, thus 'panelZ' is matched on view level
459  CHECK (not r46.canCover());
460 
461  /* === impossible to anchor === */
462  UICoordResolver r47 {UICoord::firstWindow().tab(3), tree};
463  CHECK (not r47.canCover());
464 
465 
466  /* === the solution with maximum covered depth wins === */
467  UICoordResolver r51 {UICoord().tab("tab").path("sub"), tree};
468  CHECK ("UI:window-3[persp-C]-panelZ.thirdView.tab/sub" == string(r51.cover())); // the second solution found covers to maximum depth
469 
470  /* === when two solutions are equivalent, pick the fist one === */
471  UICoordResolver r52 {UICoord().tab("tab"), tree};
472  CHECK ("UI:window-1[persp-A]-panelZ.thirdView.tab" == string(r52.cover())); // "UI:window-3[persp-C]-panelZ.thirdView.tab" would match too
473 
474  /* === best solution will be picked, irrespective of discovery order === */
475  UICoordResolver r531 {UICoord().persp("persp-A").tab(1), tree};
476  CHECK ("UI:window-1[persp-A]-panelZ.thirdView.#1" == string(r531.cover())); // best solution discovered as first one
477 
478  UICoordResolver r532 {UICoord().view("thirdView").tab("tab"), tree};
479  CHECK ("UI:window-1[persp-A]-panelZ.thirdView.tab" == string(r532.cover())); // best solution is 3rd of five possible ones
480 
481  UICoordResolver r533 {UICoord().persp("persp-C").tab(1), tree};
482  CHECK ("UI:window-3[persp-C]-panelZ.thirdView.#1" == string(r533.cover())); // best solution is found as last one
483  }
484 
485 
486 
494  void
496  {
497  GenNodeLocationQuery tree{MakeRec()
498  .set("window-2"
499  , MakeRec()
500  .type("persp-B")
501  .set("panelY"
502  , MakeRec()
503  .set("someView"
504  , MakeRec()
505  .set("#1", MakeRec())
506  .set("#2", MakeRec())
507  )
508  )
509  )
510  };
511 
512  /* === explicitly given spec partially covered === */
513  UICoordResolver r1 {UICoord{"window-2","persp-B","panelY","otherView","tab"}, tree};
514  CHECK (3 == r1.coverDepth());
515  r1.coverPartially();
516  CHECK (not r1.isCovered());
517  CHECK (3 == r1.coverDepth());
518  CHECK (r1.isCoveredPartially()); // is covered down to the "panelY"
519  CHECK ("UI:window-2[persp-B]-panelY.otherView.tab" == string(r1));
520  r1.cover();
521  CHECK (r1.isCovered()); // cover() retains the covered part only
522  CHECK ("UI:window-2[persp-B]-panelY" == string(r1));
523 
524  /* === fill wildcard gap but retain uncovered extension === */
525  UICoordResolver r2 {UICoord::currentWindow().view("someView").tab(3).path("sub"), tree};
526  CHECK (0 == r2.coverDepth());
527  r2.coverPartially();
528  CHECK (not r2.isCovered());
529  CHECK (4 == r2.coverDepth());
530  CHECK (r2.isCoveredPartially());
531  CHECK ("UI:window-2[persp-B]-panelY.someView.#3/sub" == string(r2));
532  r2.cover();
533  CHECK ("UI:window-2[persp-B]-panelY.someView" == string(r2));
534 
535  /* === reject when gap can not be closed unambiguously === */
536  UICoordResolver r3 {UICoord::currentWindow().view("someView").path("sub"), tree};
537  CHECK (not r3.canCover()); // NOTE: second gap here, tab info missing
538  r3.coverPartially();
539  CHECK (isnil (r3));
540 
541  /* === reject when some wildcards remain after partial coverage === */
542  UICoordResolver r4 {UICoord::currentWindow().tab(3).path("sub"), tree};
543  r4.coverPartially();
544  CHECK (isnil (r4));
545 
546  /* === existentially quantified (elided) element constitutes partial coverage === */
547  UICoordResolver r5 {UICoord::currentWindow().persp(UIC_ELIDED).panel("fantasy").view("fantomas"), tree};
548  CHECK ("UI:currentWindow[.]-fantasy.fantomas" == string(r5));
549  CHECK (1 == r5.coverDepth());
550  r5.coverPartially();
551  CHECK (not r5.isCovered());
552  CHECK (2 == r5.coverDepth()); // Note side-effect of computing the coverage...
553  CHECK (r5.isCoveredPartially()); // it is known to be covered including "the" perspective
554  CHECK ("UI:window-2[persp-B]-fantasy.fantomas" == string(r5));
555  r5.cover();
556  CHECK ("UI:window-2[persp-B]" == string(r5));
557  CHECK (2 == r5.coverDepth());
558  }
559 
560 
561 
562 
574  void
576  {
577  GenNodeLocationQuery tree{MakeRec()
578  .set("window-1"
579  , MakeRec()
580  .type("persp-A")
581  .set("panelX"
582  , MakeRec()
583  .set("firstView", MakeRec())
584  .set("secondView", MakeRec())
585  )
586  )
587  .set("window-2"
588  , MakeRec()
589  .type("persp-B")
590  .set("panelY"
591  , MakeRec()
592  .set("thirdView"
593  , MakeRec()
594  .set("#1", MakeRec())
595  .set("#2", MakeRec())
596  )
597  )
598  )
599  .set("window-3"
600  , MakeRec()
601  .type("persp-C")
602  .set("panelZ"
603  , MakeRec()
604  .set("thirdView", MakeRec())
605  )
606  )
607  };
608 
609  /* === explicitly given window spec remains unchanged === */
610  UICoordResolver r1 {UICoord{"window-2","persp-B","panelY"}, tree};
611  CHECK (3 == r1.coverDepth());
612  r1.anchor();
613  CHECK ("UI:window-2[persp-B]-panelY" == string(r1));
614 
615  /* === `firstWindow` meta spec is resolved === */
616  UICoordResolver r2 {UICoord::firstWindow().view("blah"), tree};
617  CHECK (0 == r2.coverDepth());
618  CHECK (r2.isAnchored()); // can obviously be anchored, since there is always a first window
619  CHECK (not r2.canCover()); // yet this path is impossible to cover in the current UI
620  CHECK ("UI:firstWindow[*]-*.blah" == string(r2));
621  r2.anchor();
622  CHECK ("UI:window-1[*]-*.blah" == string(r2));
623  CHECK (0 == r2.coverDepth());
624  CHECK (not r2.canCover());
625 
626  /* === `currentWindow` meta spec is resolved === */
627  UICoordResolver r3 {UICoord::currentWindow().view("thirdView"), tree};
628  CHECK (0 == r3.coverDepth());
629  CHECK (r3.isAnchored());
630  CHECK (not r3.isCovered());
631  CHECK (r3.canCover());
632  r3.anchor();
633  CHECK (not r3.isCovered());
634  CHECK (r3.isCoveredPartially());
635  CHECK (1 == r3.coverDepth()); // anchoring also picks the second of two possible solutions
636  CHECK ("UI:window-3[*]-*.thirdView" == string(r3)); // thereby covering the "thirdView"
637 
638  /* === coverage solution is calculated on demand === */
639  UICoordResolver r4 {UICoord().view("thirdView").append("#2/sub"), tree};
640  CHECK ("UI:?.thirdView.#2/sub" == string(r4)); // an incomplete path is not automatically resolved
641  CHECK (not r4.isAnchored());
642  CHECK (0 == r4.coverDepth());
643  r4.anchor(); // but if we anchor, we force search for a coverage solution
644  CHECK (1 == r4.coverDepth()); // which is actually found starting from the second window,
645  CHECK (r4.isCoveredPartially()); // and kept in the internal cache for future use,
646  CHECK ("UI:window-2[*]-*.thirdView.#2/sub" == string(r4)); // but not made explicit, since we only requested anchorage
647 
648  /* === already calculated coverage solution is used === */
649  UICoordResolver r5 {UICoord::currentWindow().view("thirdView"), tree};
650  CHECK (not r5.isCovered());
651  CHECK (not r5.isCoveredPartially());
652  CHECK (0 == r5.coverDepth());
653  CHECK (r5.canCover()); // this triggers search for a coverage solution
654  CHECK (1 == r5.coverDepth());
655  CHECK (not r5.isCovered());
656  CHECK (r5.isCoveredPartially());
657  CHECK ("UI:currentWindow[*]-*.thirdView" == string(r5));
658  r5.anchor(); // and this (cached) solution is also used to make anchorage explicit
659  CHECK ("UI:window-3[*]-*.thirdView" == string(r5));
660  CHECK (1 == r5.coverDepth());
661  CHECK (not r5.isCovered());
662  r5.cover(); // ...now also the coverage solution was made explicit
663  CHECK (r5.isCovered());
664  CHECK (4 == r5.coverDepth());
665  CHECK ("UI:window-3[persp-C]-panelZ.thirdView" == string(r5));
666 
667  /* === impossible to cover and can not be anchored === */
668  UICoordResolver r6 {UICoord::window("windows").path("to/hell"), tree};
669  CHECK (not r6.isAnchored());
670  CHECK (not r6.canCover());
671  r6.anchor();
672  CHECK (not r6.isAnchored());
673  CHECK (0 == r6.coverDepth());
674  CHECK ("UI:windows[*]-*.*.*/to/hell" == string(r6));
675  }
676 
677 
688  void
690  {
691  GenNodeLocationQuery tree{MakeRec()
692  .set("window-2"
693  , MakeRec()
694  .type("persp-B")
695  .set("panelY"
696  , MakeRec()
697  .set("thirdView"
698  , MakeRec()
699  .set("#1", MakeRec())
700  .set("#2", MakeRec())
701  )
702  )
703  )
704  };
705 
706  /* === extend fully covered explicit path === */
707  UICoordResolver r1 {UICoord{"window-2","persp-B","panelY"}, tree};
708  CHECK ("UI:window-2[persp-B]-panelY" == string(r1));
709  CHECK (r1.isCovered());
710  r1.extend (UICoord().path("gappy").tab(2)); // can extend with partially defined UI coordinates
711  CHECK ("UI:window-2[persp-B]-panelY.*.#2/gappy" == string(r1)); // ...the resulting UI path is unresolved, yet can be partially covered
712  r1.extend ("seamless"); // ...and this partial coverage is used as base for further extension
713  CHECK ("UI:window-2[persp-B]-panelY.thirdView.#2/seamless" ==string(r1));
714 
715  /* === extend partially covered path === */
716  UICoordResolver r2 {UICoord().view("thirdView").append("some/where"), tree};
717  CHECK ("UI:?.thirdView.some/where" ==string(r2)); // "thirdView" is covered, "some/where" is not
718  r2.extend ("no/where");
719  CHECK ("UI:window-2[persp-B]-panelY.thirdView.no/where" ==string(r2)); // ...and thus the extension is attached behind "thirdView"
720  CHECK (r2.isCoveredPartially());
721 
722  /* === impossible extensions rejected === */ // since r2 already specifies a perspective ("persp-B")....
723  VERIFY_ERROR (INVALID, r2.extend(UICoord().persp("fisheye"))); // ...overwriting with another perspective is rejected as extension
724  CHECK ("UI:window-2[persp-B]-panelY.thirdView.no/where" ==string(r2)); // ...and the existing state is unaffected from this error
725  VERIFY_ERROR (INVALID, r2.extend(UICoord().view("alternative"))); // Likewise, extending with a conflicting view spec is rejected
726  r2.extend(UICoord().tab("nada")); // But a tab is not yet covered and thus acceptable as extension
727  CHECK ("UI:window-2[persp-B]-panelY.thirdView.nada" ==string(r2));
728  r2.extend(UICoord());
729  CHECK ("UI:window-2[persp-B]-panelY.thirdView" ==string(r2)); // empty coordinates implicitly attached behind the covered part
730 
731  /* === unsolvable: truncate, extend, recalculate coverage === */
732  UICoordResolver r3 {UICoord().persp("awesome"), tree};
733  CHECK (not r3.canCover());
734  CHECK (0 == r3.coverDepth());
735  r3.extend (UICoord::currentWindow().tab(1)); // Extension implies covering, which effectively truncates the path
736  CHECK (1 == r3.coverDepth()); // ...and "currentWindow" can even be covered, thus the coverage increases
737  CHECK ("UI:currentWindow[*]-*.*.#1" ==string(r3)); // note coverage calculated internally, not made explicit
738  }
739  };
740 
741 
743  LAUNCHER (UICoordResolver_test, "unit stage");
744 
745 
746 }}} // 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:129
Builder && path(Literal pathDef)
augment UI coordinates to define a complete local path
Definition: ui-coord.hpp:616
Builder persp(Literal perspectiveID) const
Definition: ui-coord.hpp:695
Implementation of the stage::interact::LocationQuery interface to work on a GenNode tree...
Definition: run.hpp:40
Builder && persp(Literal perspectiveID)
augment UI coordinates to mandate a specific perspective to be active within the window ...
Definition: ui-coord.hpp:538
#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:591
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:562
Token or Atom with distinct identity.
Definition: symbol.hpp:117
Builder && panel(Literal panelID)
augment UI coordinates to indicate a specific view to be used
Definition: ui-coord.hpp:546
Simplistic test class runner.
Lumiera GTK UI implementation root.
Definition: guifacade.cpp:37
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:554
static Builder window(Literal windowID)
Builder: start definition of UI-Coordinates rooted in given window.
Definition: ui-coord.hpp:682
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:141
static Builder firstWindow()
Builder: start definition of UI-Coordinates rooted in the firstWindow
Definition: ui-coord.hpp:675
static Builder currentWindow()
Builder: start definition of UI-Coordinates rooted in the currentWindow
Definition: ui-coord.hpp:669