2017-10-02 18:11:21 +02:00
/*
UICoordResolver ( Test ) - resolve UI coordinates against actual topology
Copyright ( C ) Lumiera . org
2017 , 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 0213 9 , USA .
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/** @file ui-coord-resolver-test.cpp
* * unit test \ ref UICoordResolver_test
*/
# include "lib/test/run.hpp"
# include "lib/test/test-helper.hpp"
# include "gui/interact/ui-coord.hpp"
# include "gui/interact/ui-coord-resolver.hpp"
2017-10-16 02:39:22 +02:00
# include "gui/interact/gen-node-location-query.hpp"
2017-10-16 01:28:49 +02:00
# include "lib/diff/gen-node.hpp"
2017-10-02 18:11:21 +02:00
# include "lib/format-util.hpp"
# include "lib/util.hpp"
# include <string>
using std : : string ;
2017-10-16 01:28:49 +02:00
using lib : : diff : : MakeRec ;
using lib : : diff : : Rec ;
2017-10-02 18:11:21 +02:00
using lib : : Symbol ;
using util : : isnil ;
using util : : join ;
namespace gui {
namespace interact {
namespace test {
2018-04-02 01:48:51 +02:00
using lumiera : : error : : LERR_ ( INVALID ) ;
using lumiera : : error : : LERR_ ( STATE ) ;
2017-10-02 18:11:21 +02:00
/******************************************************************************/ /**
* @ test verify query and mutation of UICoord in relation to actual UI topology .
2018-01-06 02:54:24 +01:00
* A UI - Coordinate resolver is a special builder , which is initialised by
* the given coordinate spec , and also attached to a " location query API " ,
* which allows to investigate the current actual UI structure . The builder
* then exposes query and mutation operations , to determine to what extent
* the coordinate spec is " covered " by the real UI , and to match and expand
* any wildcards in the coordinate spec ( pattern ) .
2017-10-02 18:11:21 +02:00
*
* @ see UICoordResolver
* @ see navigator . hpp
* @ see ViewLocator
* @ see UICoord_test
*/
class UICoordResolver_test : public Test
{
virtual void
run ( Arg )
{
2017-12-26 14:04:21 +01:00
verify_simpleUsage ( ) ;
2017-10-02 18:39:18 +02:00
verify_backingQuery ( ) ;
2017-10-02 18:11:21 +02:00
verify_queryAnchor ( ) ;
2018-01-04 04:52:09 +01:00
verify_mutateCoverage ( ) ;
2018-02-08 03:00:38 +01:00
verify_mutateCoverPartially ( ) ;
2017-10-02 18:11:21 +02:00
verify_mutateAnchor ( ) ;
verify_mutateExtend ( ) ;
}
2017-10-16 01:28:49 +02:00
/** @test introduction to UI coordinate resolution
* - use a backing " real " ( dummy ) data structure to resolve against
* - establish a suitable implementation of the LocationQuery interface
* - attach a resolver
* - have fun
*/
2017-10-02 18:11:21 +02:00
void
2017-10-02 18:39:18 +02:00
verify_simpleUsage ( )
2017-10-02 18:11:21 +02:00
{
2017-10-16 02:39:22 +02:00
// a Test dummy placeholder for the real UI structure
2017-10-28 00:06:44 +02:00
Rec dummyUiStructure = MakeRec ( )
. set ( " window-1 "
, MakeRec ( )
. type ( " perspective-A " )
)
. set ( " window-2 "
, MakeRec ( )
. type ( " perspective-B " )
. set ( " panelX " , MakeRec ( ) )
. set ( " panelXX " , MakeRec ( ) )
) ;
2017-10-16 01:28:49 +02:00
2017-10-16 02:39:22 +02:00
// helper to answer "location queries" backed by this structure
GenNodeLocationQuery locationQuery { dummyUiStructure } ;
2017-10-30 01:47:29 +01:00
UICoord uic { " window-2 " , " * " , " panelX " , " someView " } ;
2017-10-16 02:39:22 +02:00
UICoordResolver resolver { uic , locationQuery } ;
CHECK ( not resolver . isCovered ( ) ) ;
2017-10-21 01:53:13 +02:00
CHECK ( resolver . canCover ( ) ) ;
2017-10-16 02:39:22 +02:00
UICoord uic2 = resolver . cover ( )
. extend ( " otherView " ) ;
CHECK ( " UI:window-2[perspective-B]-panelX.otherView " = = string ( uic2 ) ) ;
2017-10-02 18:11:21 +02:00
}
2017-10-22 00:44:30 +02:00
/** @test verify the command-and-query interface backing the resolver.
* This test actually uses a dummy implementation of the interface , which ,
* instead of navigating an actual UI topology , just uses a ` Record < GenNode > `
* ( a " GenNode tree " ) to emulate the hierarchical structure of UI components .
* @ remarks note some twists how the GenNode tree is used here to represent
* an imaginary UI structure :
* - we use the special _type_ attribute to represent the _perspective_
* within each window ; deliberately , we ' ll use this twisted structure
* here to highlight the fact that the backing structure need not be
* homogeneous ; rather , it may require explicit branching
* - we use the _attributes_ within the GenNode " object " representation ,
* since these are named nested elements , and the whole notion of an
* UI coordinate path is based on named child components
* - we use the _object builder_ helper to define the whole structure
2017-10-28 00:06:44 +02:00
* as nested inline tree ; named nested elements ( " attributes " ) are
* added with the ` set ( key , val ) ` builder function , and for each
* nested scope , we start a new nested builder with ` MakeRec ( ) ` .
2017-10-22 00:44:30 +02:00
* - there is a special convention _for this test setup solely_ to
* set the ` currentWindow ` to be the last one in list - - in a real
2017-12-22 19:35:36 +01:00
* UI this would of course not be a configurable property of the
* LocationQuery , but rather just reflect the transient window
2017-10-22 00:44:30 +02:00
* state and return the currently activated window
2017-12-26 14:56:43 +01:00
* @ see IterTreeExplorer_test : : verify_IterSource ( ) regarding " child exploration " . . .
2017-10-22 00:44:30 +02:00
*/
2017-10-02 18:11:21 +02:00
void
2017-10-02 18:39:18 +02:00
verify_backingQuery ( )
2017-10-02 18:11:21 +02:00
{
2017-10-28 00:06:44 +02:00
GenNodeLocationQuery queryAPI { MakeRec ( )
. set ( " window-1 "
, MakeRec ( )
. type ( " perspective-A " )
. set ( " panelX "
, MakeRec ( )
. set ( " firstView " , MakeRec ( ) )
. set ( " secondView " , MakeRec ( ) )
)
)
. set ( " window-2 "
, MakeRec ( )
. type ( " perspective-B " )
. set ( " panelY " , MakeRec ( ) )
)
. set ( " window-3 "
, MakeRec ( )
. type ( " perspective-C " )
. set ( " panelZ "
, MakeRec ( )
. set ( " thirdView " , MakeRec ( ) )
)
. set ( " panelZZ " , MakeRec ( ) )
)
} ;
2017-10-22 00:44:30 +02:00
// the LocationQuery API works by matching a UICoord spec against the "real" structure
UICoord uic1 = UICoord : : window ( " window-2 " ) . persp ( " perspective-B " ) ;
UICoord uic2 = UICoord : : window ( " windows " ) ;
UICoord uic3 = UICoord : : firstWindow ( ) . persp ( " perspective-A " ) . panel ( " panelX " ) . view ( " secondView " ) ;
UICoord uic4 = UICoord : : currentWindow ( ) . persp ( " perspective-B " ) ;
UICoord uic5 = UICoord : : currentWindow ( ) . persp ( " perspective-C " ) . panel ( " panelZ " ) . view ( " someOtherView " ) ;
CHECK ( " window-2 " = = queryAPI . determineAnchor ( uic1 ) ) ;
CHECK ( Symbol : : BOTTOM = = queryAPI . determineAnchor ( uic2 ) ) ;
CHECK ( " window-1 " = = queryAPI . determineAnchor ( uic3 ) ) ;
CHECK ( " window-3 " = = queryAPI . determineAnchor ( uic4 ) ) ;
CHECK ( " window-3 " = = queryAPI . determineAnchor ( uic5 ) ) ;
CHECK ( 2 = = queryAPI . determineCoverage ( uic1 ) ) ;
CHECK ( 0 = = queryAPI . determineCoverage ( uic2 ) ) ;
CHECK ( 4 = = queryAPI . determineCoverage ( uic3 ) ) ;
CHECK ( 1 = = queryAPI . determineCoverage ( uic4 ) ) ;
CHECK ( 3 = = queryAPI . determineCoverage ( uic5 ) ) ;
LocationQuery : : ChildIter cii = queryAPI . getChildren ( uic3 , 3 ) ;
CHECK ( not isnil ( cii ) ) ;
CHECK ( " firstView " = = * cii ) ;
+ + cii ;
CHECK ( " secondView " = = * cii ) ;
CHECK ( not isnil ( cii ) ) ;
+ + cii ;
CHECK ( isnil ( cii ) ) ;
CHECK ( " window-1, window-2, window-3 " = = join ( queryAPI . getChildren ( uic3 , 0 ) ) ) ;
CHECK ( " perspective-A " = = join ( queryAPI . getChildren ( uic3 , 1 ) ) ) ;
CHECK ( " panelX " = = join ( queryAPI . getChildren ( uic3 , 2 ) ) ) ;
CHECK ( " firstView, secondView " = = join ( queryAPI . getChildren ( uic3 , 3 ) ) ) ;
CHECK ( isnil ( queryAPI . getChildren ( uic3 , 4 ) ) ) ; // "firstView" has no children
CHECK ( " window-1, window-2, window-3 " = = join ( queryAPI . getChildren ( uic2 , 0 ) ) ) ;
VERIFY_ERROR ( STATE , queryAPI . getChildren ( uic2 , 1 ) ) ; // "windows" at pos==0 is not covered by real UI
CHECK ( " window-1, window-2, window-3 " = = join ( queryAPI . getChildren ( uic5 , 0 ) ) ) ;
CHECK ( " perspective-C " = = join ( queryAPI . getChildren ( uic5 , 1 ) ) ) ;
CHECK ( " panelZ, panelZZ " = = join ( queryAPI . getChildren ( uic5 , 2 ) ) ) ;
CHECK ( " thirdView " = = join ( queryAPI . getChildren ( uic5 , 3 ) ) ) ;
VERIFY_ERROR ( STATE , queryAPI . getChildren ( uic5 , 4 ) ) ; // "someOtherView" at level 4 does not exist
2017-12-26 05:07:35 +01:00
2017-12-26 14:04:21 +01:00
// verify "child exploration" via iterator interface
cii = queryAPI . getChildren ( uic3 , 0 ) ; // enter at root level...
CHECK ( " window-1 " = = * cii ) ; // first child of root to appear is "window-1"
CHECK ( 0 = = cii . depth ( ) ) ; // (note depth just happens to coincide with absolute tree depth here)
cii . expandChildren ( ) ; // drill down into current element's children
2017-12-26 05:07:35 +01:00
CHECK ( 1 = = cii . depth ( ) ) ;
2017-12-26 14:04:21 +01:00
CHECK ( " perspective-A " = = * cii ) ; // which is just one, the perspective
cii . expandChildren ( ) ; // drill down into the (formal, logical) children of "perspective-A"
2017-12-26 05:07:35 +01:00
CHECK ( 2 = = cii . depth ( ) ) ;
2017-12-26 14:04:21 +01:00
CHECK ( " panelX " = = * cii ) ; // ..and find the "panelX" at level 2
cii . expandChildren ( ) ; // drill down one level further
2017-12-26 05:07:35 +01:00
CHECK ( 3 = = cii . depth ( ) ) ;
2017-12-26 14:04:21 +01:00
CHECK ( " firstView " = = * cii ) ; // and then just continue iteration, which first explores that scope...
2017-12-26 14:56:43 +01:00
CHECK ( " firstView, secondView, window-2, window-3 " = = join ( cii ) ) ; // ...followed by returning to the enclosing scopes, finally top level.
2017-10-02 18:11:21 +02:00
}
2017-10-18 03:40:26 +02:00
/** @test query anchorage of given UI coordinates.
* - an anchored UI coordinate spec explicitly rooted within a top level window .
2018-01-04 04:52:09 +01:00
* - an explicit UI coordinate spec impossible to anchor within current UI tree
* - a UI coordinate spec with dynamic reference to first / current window
* - an incomplete spec , which needs to be solved ( pattern matched ) to determine anchor .
2017-10-18 03:40:26 +02:00
*/
2017-10-02 18:11:21 +02:00
void
verify_queryAnchor ( )
{
2018-01-04 04:52:09 +01:00
GenNodeLocationQuery tree { MakeRec ( )
2017-10-28 00:06:44 +02:00
. set ( " window-1 "
, MakeRec ( )
. type ( " perspective-A " )
)
. set ( " window-2 "
, MakeRec ( )
. type ( " perspective-B " )
. set ( " panelX "
, MakeRec ( )
. set ( " someView " , MakeRec ( ) )
)
)
} ;
2017-10-18 03:40:26 +02:00
UICoord uic1 = UICoord : : window ( " window-1 " ) . persp ( " perspective-A " ) ;
UICoord uic2 = UICoord : : window ( " windows " ) ;
UICoord uic3 = UICoord : : firstWindow ( ) ;
UICoord uic4 = UICoord : : currentWindow ( ) . persp ( " perspective-B " ) ;
UICoord uic5 = UICoord : : currentWindow ( ) . panel ( " panelY " ) ;
2018-01-05 03:57:27 +01:00
UICoord uic6 = UICoord ( ) . view ( " someView " ) ;
2017-10-18 03:40:26 +02:00
2018-01-04 04:52:09 +01:00
UICoordResolver r1 { uic1 , tree } ;
UICoordResolver r2 { uic2 , tree } ;
UICoordResolver r3 { uic3 , tree } ;
UICoordResolver r4 { uic4 , tree } ;
UICoordResolver r5 { uic5 , tree } ;
UICoordResolver r6 { uic6 , tree } ;
2017-10-18 03:40:26 +02:00
CHECK ( r1 . isAnchored ( ) ) ;
CHECK ( not r2 . isAnchored ( ) ) ;
CHECK ( r3 . isAnchored ( ) ) ;
CHECK ( r4 . isAnchored ( ) ) ;
2018-01-03 02:46:12 +01:00
CHECK ( r5 . isAnchored ( ) ) ;
2017-10-18 03:40:26 +02:00
CHECK ( not r6 . isAnchored ( ) ) ;
CHECK ( r1 . canAnchor ( ) ) ;
CHECK ( not r2 . canAnchor ( ) ) ;
CHECK ( r3 . canAnchor ( ) ) ;
CHECK ( r4 . canAnchor ( ) ) ;
2018-01-03 02:46:12 +01:00
CHECK ( r5 . canAnchor ( ) ) ;
2017-10-18 03:40:26 +02:00
CHECK ( r6 . canAnchor ( ) ) ;
2017-10-02 18:11:21 +02:00
}
2018-01-04 04:52:09 +01:00
2018-02-08 03:00:38 +01:00
/** @test path matching algorithm to resolve UI coordinates with wildcards against the current UI structure tree.
2018-01-04 04:52:09 +01:00
* Since an UI coordinate path with gaps and wildcards could match anywhere , even several times , we need to perform
* an exhaustive search with backtracking over the whole tree . By convention , we use the first maximal solution ,
* which can be just a partial solution , leaving an additional uncovered trailing part of the UI coordinate spec .
* Whenever a coordinate spec is _not explicit , _ has wildcards or a leading gap , we need to perform the full
* matching algorithm , even to just answer the question if coverage _is possible_ . The result , i . e . the computed
* coverage , is cached internally , and can be used to _mutate_ the UI coordinate spec to match that coverage .
*
* This test verifies various corner cases ; especially there is a rule to prevent a partial match based
* on wildcards solely , rather we require at least one explicit match to qualify as partial solution .
2018-01-05 02:14:22 +01:00
* - ( 1 ) trivial cases not requiring a tree search
2018-01-04 04:52:09 +01:00
* * * total coverage
* * * partial coverage , leaving an uncovered suffix
2018-01-05 02:14:22 +01:00
* - ( 2 ) expand dynamic anchor specifiers
* * * with following content
* * * anchor spec alone
* - ( 3 ) wildcard interpolation
* * * interpolate a single gap
* * * interpolate several gaps
* * * interpolate anchor and consecutive wildcards
* * * discriminate by anchor and fill additional gap
* - ( 4 ) failure detection
2018-01-05 03:57:27 +01:00
* * * trailing wildcards are stripped and ignored
2018-01-05 02:14:22 +01:00
* * * reject gap beyond existing real UI tree
* * * reject gap ending at perimeter of real UI tree
* * * reject interpolated gap on immediately following mismatch
* * * reject mismatch immediately behind second gap
* * * mismatch of tree level
* * * contradiction to anchorage
2018-01-06 04:36:18 +01:00
* - ( 5 ) selection between several possible solutions
* * * the length of the covered trailing suffix decides
* * * when two solutions are equivalent , pick the fist one
* * * best solution will be picked , irrespective of discovery order
2018-01-04 04:52:09 +01:00
*/
2017-10-02 18:11:21 +02:00
void
2018-01-04 04:52:09 +01:00
verify_mutateCoverage ( )
2017-10-02 18:11:21 +02:00
{
2018-01-04 04:52:09 +01:00
GenNodeLocationQuery tree { MakeRec ( )
. set ( " window-1 "
, MakeRec ( )
2018-01-05 03:57:27 +01:00
. type ( " persp-A " )
2018-01-04 04:52:09 +01:00
. set ( " panelX "
, MakeRec ( )
. set ( " firstView " , MakeRec ( ) )
. set ( " secondView " , MakeRec ( ) )
)
2018-01-05 02:14:22 +01:00
. set ( " panelZ "
, MakeRec ( )
. set ( " thirdView "
, MakeRec ( )
. set ( " #1 " , MakeRec ( ) )
. set ( " #2 " , MakeRec ( ) )
2018-01-05 03:57:27 +01:00
. set ( " tab " , MakeRec ( ) )
2018-01-05 02:14:22 +01:00
)
)
2018-01-04 04:52:09 +01:00
)
. set ( " window-2 "
, MakeRec ( )
2018-01-05 03:57:27 +01:00
. type ( " persp-B " )
2018-01-04 04:52:09 +01:00
. set ( " panelY " , MakeRec ( ) )
)
. set ( " window-3 "
, MakeRec ( )
2018-01-05 03:57:27 +01:00
. type ( " persp-C " )
2018-01-04 04:52:09 +01:00
. set ( " panelZ "
, MakeRec ( )
2018-01-05 02:14:22 +01:00
. set ( " thirdView "
, MakeRec ( )
2018-01-05 03:57:27 +01:00
. set ( " tab "
2018-01-05 02:14:22 +01:00
, MakeRec ( )
. set ( " sub " , MakeRec ( ) )
)
2018-01-06 04:36:18 +01:00
. set ( " #1 " , MakeRec ( ) )
2018-01-05 02:14:22 +01:00
)
2018-01-04 04:52:09 +01:00
)
. set ( " panelZZ " , MakeRec ( ) )
)
} ;
/* === trivial cases === */
UICoordResolver r11 { UICoord : : window ( " window-1 " )
2018-01-05 03:57:27 +01:00
. persp ( " persp-A " )
2018-01-04 04:52:09 +01:00
. panel ( " panelX " ) , tree } ;
CHECK ( r11 . isCovered ( ) ) ;
CHECK ( 3 = = r11 . coverDepth ( ) ) ;
UICoordResolver r12 { UICoord : : window ( " window-1 " )
2018-01-05 03:57:27 +01:00
. persp ( " persp-A " )
2018-01-04 04:52:09 +01:00
. panel ( " panelX " )
. view ( " thirdView " ) , tree } ;
CHECK ( not r12 . isCovered ( ) ) ;
CHECK ( r12 . isCoveredPartially ( ) ) ;
CHECK ( 3 = = r12 . coverDepth ( ) ) ;
2018-01-05 03:57:27 +01:00
CHECK ( " UI:window-1[persp-A]-panelX.thirdView " = = string ( r12 ) ) ;
2018-01-04 04:52:09 +01:00
2018-01-06 02:54:24 +01:00
r12 . cover ( ) ;
CHECK ( r12 . isCovered ( ) ) ;
CHECK ( r12 . isCoveredPartially ( ) ) ;
CHECK ( 3 = = r12 . coverDepth ( ) ) ;
CHECK ( " UI:window-1[persp-A]-panelX " = = string ( r12 ) ) ;
2018-01-04 04:52:09 +01:00
2018-01-05 02:14:22 +01:00
/* === expand anchor === */
2018-01-05 03:57:27 +01:00
UICoordResolver r21 { UICoord : : firstWindow ( ) . persp ( " persp-A " ) , tree } ;
2018-01-06 02:54:24 +01:00
CHECK ( " UI:firstWindow[persp-A] " = = string ( r21 ) ) ;
r21 . cover ( ) ;
CHECK ( " UI:window-1[persp-A] " = = string ( r21 ) ) ;
2018-01-05 02:14:22 +01:00
/* === expand anchor alone === */
UICoordResolver r22 { UICoord : : currentWindow ( ) , tree } ;
2018-01-06 02:54:24 +01:00
CHECK ( " UI:window-3 " = = string ( r22 . cover ( ) ) ) ;
2018-01-05 02:14:22 +01:00
/* === interpolate a single gap === */
UICoordResolver r31 { UICoord : : window ( " window-1 " ) . view ( " secondView " ) , tree } ;
2018-01-06 02:54:24 +01:00
CHECK ( " UI:window-1[*]-*.secondView " = = string ( r31 ) ) ;
CHECK ( 0 = = r31 . coverDepth ( ) ) ;
CHECK ( not r31 . isCovered ( ) ) ;
CHECK ( r31 . canCover ( ) ) ;
r31 . cover ( ) ;
CHECK ( r31 . isCovered ( ) ) ;
CHECK ( 4 = = r31 . coverDepth ( ) ) ;
CHECK ( " UI:window-1[persp-A]-panelX.secondView " = = string ( r31 ) ) ;
2018-01-05 02:14:22 +01:00
/* === interpolate several gaps === */
UICoordResolver r32 { UICoord ( ) . view ( " thirdView " ) . path ( " sub " ) , tree } ;
2018-01-06 02:54:24 +01:00
CHECK ( " UI:window-3[persp-C]-panelZ.thirdView.tab/sub " = = string ( r32 . cover ( ) ) ) ;
2018-01-05 02:14:22 +01:00
/* === interpolate anchor and consecutive wildcards === */
UICoordResolver r33 { UICoord : : firstWindow ( ) . tab ( 2 ) , tree } ;
2018-01-06 02:54:24 +01:00
CHECK ( " UI:window-1[persp-A]-panelZ.thirdView.#2 " = = string ( r33 . cover ( ) ) ) ;
2018-01-05 02:14:22 +01:00
/* === discriminate by anchor and fill second gap === */
2018-01-05 03:57:27 +01:00
UICoordResolver r34 { UICoord : : currentWindow ( ) . panel ( " panelZ " ) . tab ( " tab " ) , tree } ;
2018-01-06 02:54:24 +01:00
CHECK ( " UI:currentWindow[*]-panelZ.*.tab " = = string ( r34 ) ) ;
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
2018-01-05 02:14:22 +01:00
2018-02-16 06:37:43 +01:00
UICoordResolver r35 { UICoord : : currentWindow ( ) . persp ( UIC_ELIDED ) . panel ( " panelZ " ) . tab ( " tab " ) , tree } ;
CHECK ( " UI:currentWindow[.]-panelZ.*.tab " = = string ( r35 ) ) ;
CHECK ( " UI:window-3[persp-C]-panelZ.thirdView.tab " = = string ( r35 . cover ( ) ) ) ; // elided (existentially quantified) element interpolated similar to a wildcard
UICoordResolver r36 { UICoord : : currentWindow ( ) . panel ( UIC_ELIDED ) . view ( " nonexisting " ) , tree } ;
CHECK ( " UI:currentWindow[*]-..nonexisting " = = string ( r36 ) ) ;
CHECK ( " UI:window-3[persp-C]-panelZ " = = string ( r36 . cover ( ) ) ) ; // ...but elided counts as existing element and matches arbitrarily (-> contrast this to r44)
2018-01-05 02:14:22 +01:00
2018-01-05 03:57:27 +01:00
/* === trailing wildcards stripped automatically === */
2018-01-05 02:14:22 +01:00
UICoordResolver r41 { UICoord : : window ( " window-2 " ) . append ( " */* " ) , tree } ;
2018-01-06 02:54:24 +01:00
CHECK ( " UI:window-2 " = = string ( r41 ) ) ; // Note: trailing wildcards are already discarded by PathArray / UICoord
2018-01-05 03:57:27 +01:00
2018-01-06 02:54:24 +01:00
r41 . extend ( " */* " ) ; // if we now attempt to "sneak in" trailing wildcards...
CHECK ( " UI:window-2[*]-* " = = string ( r41 ) ) ;
CHECK ( not r41 . canCover ( ) ) ; // ...then the algorithm rejects any solution
CHECK ( " UI:window-2 " = = string ( r41 . cover ( ) ) ) ; // Note: but cover() will act on the previous coverage and just strip the extraneous suffix
2018-01-05 02:14:22 +01:00
/* === reject gap beyond existing real UI tree === */
UICoordResolver r42 { UICoord : : window ( " window-2 " ) . append ( " */*/*/some/path " ) , tree } ;
2018-01-05 03:57:27 +01:00
CHECK ( not r42 . canCover ( ) ) ;
2018-01-05 02:14:22 +01:00
/* === reject gap ending at real UI tree boundary === */
2018-01-05 03:57:27 +01:00
UICoordResolver r43 { UICoord : : currentWindow ( ) . view ( " firstView " ) . tab ( " nonexisting " ) , tree } ;
2018-01-05 02:14:22 +01:00
CHECK ( not r43 . canCover ( ) ) ;
/* === reject interpolated gap on mismatch right behind === */
2018-01-05 03:57:27 +01:00
UICoordResolver r44 { UICoord ( ) . view ( " otherView " ) , tree } ; // Note: will be checked on all four existing views, but never matches
2018-01-05 02:14:22 +01:00
CHECK ( not r44 . canCover ( ) ) ;
/* === reject mismatch immediately behind second gap === */
2018-01-05 03:57:27 +01:00
UICoordResolver r45 { UICoord ( ) . panel ( " panelZ " ) . tab ( 3 ) , tree } ; // Note: we have two "panelZ", but none has a tab #3
CHECK ( not r45 . canCover ( ) ) ;
2018-01-05 02:14:22 +01:00
/* === mismatch of tree level === */
2018-01-05 03:57:27 +01:00
UICoordResolver r46 { UICoord : : currentWindow ( ) . append ( " */*/panelZ/thirdView " ) , tree } ; // Note: one '*' too much, thus 'panelZ' is matched on view level
2018-01-05 02:14:22 +01:00
CHECK ( not r46 . canCover ( ) ) ;
/* === impossible to anchor === */
UICoordResolver r47 { UICoord : : firstWindow ( ) . tab ( 3 ) , tree } ;
CHECK ( not r47 . canCover ( ) ) ;
2018-01-06 04:36:18 +01:00
/* === the solution with maximum covered depth wins === */
UICoordResolver r51 { UICoord ( ) . tab ( " tab " ) . path ( " sub " ) , tree } ;
CHECK ( " UI:window-3[persp-C]-panelZ.thirdView.tab/sub " = = string ( r51 . cover ( ) ) ) ; // the second solution found covers to maximum depth
/* === when two solutions are equivalent, pick the fist one === */
UICoordResolver r52 { UICoord ( ) . tab ( " tab " ) , tree } ;
CHECK ( " UI:window-1[persp-A]-panelZ.thirdView.tab " = = string ( r52 . cover ( ) ) ) ; // "UI:window-3[persp-C]-panelZ.thirdView.tab" would match too
/* === best solution will be picked, irrespective of discovery order === */
UICoordResolver r531 { UICoord ( ) . persp ( " persp-A " ) . tab ( 1 ) , tree } ;
CHECK ( " UI:window-1[persp-A]-panelZ.thirdView.#1 " = = string ( r531 . cover ( ) ) ) ; // best solution discovered as first one
UICoordResolver r532 { UICoord ( ) . view ( " thirdView " ) . tab ( " tab " ) , tree } ;
CHECK ( " UI:window-1[persp-A]-panelZ.thirdView.tab " = = string ( r532 . cover ( ) ) ) ; // best solution is 3rd of five possible ones
UICoordResolver r533 { UICoord ( ) . persp ( " persp-C " ) . tab ( 1 ) , tree } ;
CHECK ( " UI:window-3[persp-C]-panelZ.thirdView.#1 " = = string ( r533 . cover ( ) ) ) ; // best solution is found as last one
2017-10-02 18:11:21 +02:00
}
2018-02-08 03:00:38 +01:00
/** @test resolve by matching, but retain an extraneous, uncovered extension.
2018-02-09 03:30:45 +01:00
* This is a variation of the UICoordResolver : : cover ( ) operation , which likewise resolves
* any wildcards ; but here we tolerate _additional elements below_ the covered part , as long
* as those are explicit . The typical use case is when we ' re about to create a new UI element
* at a specific existing anchor location within the UI . The extraneous uncovered part then
* describes those extra element yet to be created .
2018-02-08 03:00:38 +01:00
*/
void
verify_mutateCoverPartially ( )
{
GenNodeLocationQuery tree { MakeRec ( )
2018-02-09 03:30:45 +01:00
. set ( " window-2 "
2018-02-08 03:00:38 +01:00
, MakeRec ( )
. type ( " persp-B " )
. set ( " panelY "
, MakeRec ( )
. set ( " someView "
, MakeRec ( )
. set ( " #1 " , MakeRec ( ) )
. set ( " #2 " , MakeRec ( ) )
)
)
)
} ;
/* === explicitly given spec partially covered === */
2018-02-09 03:30:45 +01:00
UICoordResolver r1 { UICoord { " window-2 " , " persp-B " , " panelY " , " otherView " , " tab " } , tree } ;
2018-02-08 03:00:38 +01:00
CHECK ( 3 = = r1 . coverDepth ( ) ) ;
r1 . coverPartially ( ) ;
CHECK ( not r1 . isCovered ( ) ) ;
CHECK ( 3 = = r1 . coverDepth ( ) ) ;
2018-02-09 03:30:45 +01:00
CHECK ( r1 . isCoveredPartially ( ) ) ; // is covered down to the "panelY"
2018-02-08 03:00:38 +01:00
CHECK ( " UI:window-2[persp-B]-panelY.otherView.tab " = = string ( r1 ) ) ;
2018-02-09 03:30:45 +01:00
r1 . cover ( ) ;
CHECK ( r1 . isCovered ( ) ) ; // cover() retains the covered part only
CHECK ( " UI:window-2[persp-B]-panelY " = = string ( r1 ) ) ;
2018-02-08 03:00:38 +01:00
/* === fill wildcard gap but retain uncovered extension === */
UICoordResolver r2 { UICoord : : currentWindow ( ) . view ( " someView " ) . tab ( 3 ) . path ( " sub " ) , tree } ;
CHECK ( 0 = = r2 . coverDepth ( ) ) ;
r2 . coverPartially ( ) ;
CHECK ( not r2 . isCovered ( ) ) ;
CHECK ( 4 = = r2 . coverDepth ( ) ) ;
CHECK ( r2 . isCoveredPartially ( ) ) ;
CHECK ( " UI:window-2[persp-B]-panelY.someView.#3/sub " = = string ( r2 ) ) ;
r2 . cover ( ) ;
CHECK ( " UI:window-2[persp-B]-panelY.someView " = = string ( r2 ) ) ;
/* === reject when gap can not be closed unambiguously === */
2018-02-09 03:30:45 +01:00
UICoordResolver r3 { UICoord : : currentWindow ( ) . view ( " someView " ) . path ( " sub " ) , tree } ;
CHECK ( not r3 . canCover ( ) ) ; // NOTE: second gap here, tab info missing
2018-02-08 03:00:38 +01:00
r3 . coverPartially ( ) ;
CHECK ( isnil ( r3 ) ) ;
/* === reject when some wildcards remain after partial coverage === */
UICoordResolver r4 { UICoord : : currentWindow ( ) . tab ( 3 ) . path ( " sub " ) , tree } ;
r4 . coverPartially ( ) ;
CHECK ( isnil ( r4 ) ) ;
2018-02-16 06:37:43 +01:00
/* === existentially quantified (elided) element constitutes partial coverage === */
UICoordResolver r5 { UICoord : : currentWindow ( ) . persp ( UIC_ELIDED ) . panel ( " fantasy " ) . view ( " fantomas " ) , tree } ;
CHECK ( " UI:currentWindow[.]-fantasy.fantomas " = = string ( r5 ) ) ;
CHECK ( 1 = = r5 . coverDepth ( ) ) ;
r5 . coverPartially ( ) ;
CHECK ( not r5 . isCovered ( ) ) ;
CHECK ( 2 = = r5 . coverDepth ( ) ) ; // Note side-effect of computing the coverage...
CHECK ( r5 . isCoveredPartially ( ) ) ; // it is known to be covered including "the" perspective
CHECK ( " UI:window-2[persp-B]-fantasy.fantomas " = = string ( r5 ) ) ;
r5 . cover ( ) ;
CHECK ( " UI:window-2[persp-B] " = = string ( r5 ) ) ;
CHECK ( 2 = = r5 . coverDepth ( ) ) ;
2018-02-08 03:00:38 +01:00
}
2018-02-09 03:30:45 +01:00
2018-01-07 02:24:33 +01:00
/** @test mutate given UI coordinates by anchoring them.
* This operation changes only the window part of the coordinate spec ;
* it might use the result of a preceding coverage solution search or even
* trigger such a search , but only to find out about the root window .
2018-01-07 05:45:21 +01:00
* @ note some fine points touched here : to anchor a path is something
* different than to cover it ; in fact there are cases where we
* can determine the possible anchor point , but are unable to
* cover the path spec beyond that . And , on the other hand ,
* there are cases where you _need to compute a coverage_
* in order to decide upon the anchor point .
2018-01-07 02:24:33 +01:00
*/
2017-10-02 18:11:21 +02:00
void
verify_mutateAnchor ( )
{
2018-01-07 02:24:33 +01:00
GenNodeLocationQuery tree { MakeRec ( )
. set ( " window-1 "
, MakeRec ( )
. type ( " persp-A " )
. set ( " panelX "
, MakeRec ( )
. set ( " firstView " , MakeRec ( ) )
. set ( " secondView " , MakeRec ( ) )
)
2018-01-07 03:17:15 +01:00
)
. set ( " window-2 "
, MakeRec ( )
. type ( " persp-B " )
. set ( " panelY "
2018-01-09 01:52:49 +01:00
, MakeRec ( )
2018-01-07 02:24:33 +01:00
. set ( " thirdView "
, MakeRec ( )
. set ( " #1 " , MakeRec ( ) )
. set ( " #2 " , MakeRec ( ) )
)
2018-01-09 01:52:49 +01:00
)
2018-01-07 02:24:33 +01:00
)
. set ( " window-3 "
, MakeRec ( )
. type ( " persp-C " )
. set ( " panelZ "
, MakeRec ( )
2018-01-07 03:17:15 +01:00
. set ( " thirdView " , MakeRec ( ) )
2018-01-07 02:24:33 +01:00
)
)
} ;
/* === explicitly given window spec remains unchanged === */
2018-01-07 03:17:15 +01:00
UICoordResolver r1 { UICoord { " window-2 " , " persp-B " , " panelY " } , tree } ;
CHECK ( 3 = = r1 . coverDepth ( ) ) ;
r1 . anchor ( ) ;
CHECK ( " UI:window-2[persp-B]-panelY " = = string ( r1 ) ) ;
2018-01-07 02:24:33 +01:00
/* === `firstWindow` meta spec is resolved === */
2018-01-07 03:17:15 +01:00
UICoordResolver r2 { UICoord : : firstWindow ( ) . view ( " blah " ) , tree } ;
CHECK ( 0 = = r2 . coverDepth ( ) ) ;
2018-01-08 23:49:24 +01:00
CHECK ( r2 . isAnchored ( ) ) ; // can obviously be anchored, since there is always a first window
CHECK ( not r2 . canCover ( ) ) ; // yet this path is impossible to cover in the current UI
2018-01-07 03:17:15 +01:00
CHECK ( " UI:firstWindow[*]-*.blah " = = string ( r2 ) ) ;
r2 . anchor ( ) ;
CHECK ( " UI:window-1[*]-*.blah " = = string ( r2 ) ) ;
CHECK ( 0 = = r2 . coverDepth ( ) ) ;
CHECK ( not r2 . canCover ( ) ) ;
2018-01-07 02:24:33 +01:00
/* === `currentWindow` meta spec is resolved === */
2018-01-07 03:17:15 +01:00
UICoordResolver r3 { UICoord : : currentWindow ( ) . view ( " thirdView " ) , tree } ;
CHECK ( 0 = = r3 . coverDepth ( ) ) ;
CHECK ( r3 . isAnchored ( ) ) ;
CHECK ( not r3 . isCovered ( ) ) ;
CHECK ( r3 . canCover ( ) ) ;
r3 . anchor ( ) ;
CHECK ( not r3 . isCovered ( ) ) ;
CHECK ( r3 . isCoveredPartially ( ) ) ;
2018-01-08 23:49:24 +01:00
CHECK ( 1 = = r3 . coverDepth ( ) ) ; // anchoring also picks the second of two possible solutions
CHECK ( " UI:window-3[*]-*.thirdView " = = string ( r3 ) ) ; // thereby covering the "thirdView"
2018-01-07 03:17:15 +01:00
2018-01-07 02:24:33 +01:00
/* === coverage solution is calculated on demand === */
2018-01-07 03:17:15 +01:00
UICoordResolver r4 { UICoord ( ) . view ( " thirdView " ) . append ( " #2/sub " ) , tree } ;
2018-01-08 23:49:24 +01:00
CHECK ( " UI:?.thirdView.#2/sub " = = string ( r4 ) ) ; // an incomplete path is not automatically resolved
2018-01-07 03:17:15 +01:00
CHECK ( not r4 . isAnchored ( ) ) ;
CHECK ( 0 = = r4 . coverDepth ( ) ) ;
2018-01-08 23:49:24 +01:00
r4 . anchor ( ) ; // but if we anchor, we force search for a coverage solution
2018-01-09 01:52:49 +01:00
CHECK ( 1 = = r4 . coverDepth ( ) ) ; // which is actually found starting from the second window,
2018-01-08 23:49:24 +01:00
CHECK ( r4 . isCoveredPartially ( ) ) ; // and kept in the internal cache for future use,
2018-01-09 01:52:49 +01:00
CHECK ( " UI:window-2[*]-*.thirdView.#2/sub " = = string ( r4 ) ) ; // but not made explicit, since we only requested anchorage
2018-01-07 03:17:15 +01:00
/* === already calculated coverage solution is used === */
UICoordResolver r5 { UICoord : : currentWindow ( ) . view ( " thirdView " ) , tree } ;
CHECK ( not r5 . isCovered ( ) ) ;
CHECK ( not r5 . isCoveredPartially ( ) ) ;
CHECK ( 0 = = r5 . coverDepth ( ) ) ;
2018-01-08 23:49:24 +01:00
CHECK ( r5 . canCover ( ) ) ; // this triggers search for a coverage solution
2018-01-07 03:17:15 +01:00
CHECK ( 1 = = r5 . coverDepth ( ) ) ;
CHECK ( not r5 . isCovered ( ) ) ;
CHECK ( r5 . isCoveredPartially ( ) ) ;
2018-01-07 05:45:21 +01:00
CHECK ( " UI:currentWindow[*]-*.thirdView " = = string ( r5 ) ) ;
2018-01-08 23:49:24 +01:00
r5 . anchor ( ) ; // and this (cached) solution is also used to make anchorage explicit
2018-01-07 05:45:21 +01:00
CHECK ( " UI:window-3[*]-*.thirdView " = = string ( r5 ) ) ;
2018-01-07 03:17:15 +01:00
CHECK ( 1 = = r5 . coverDepth ( ) ) ;
CHECK ( not r5 . isCovered ( ) ) ;
2018-01-08 23:49:24 +01:00
r5 . cover ( ) ; // ...now also the coverage solution was made explicit
2018-01-07 03:17:15 +01:00
CHECK ( r5 . isCovered ( ) ) ;
CHECK ( 4 = = r5 . coverDepth ( ) ) ;
2018-01-07 05:45:21 +01:00
CHECK ( " UI:window-3[persp-C]-panelZ.thirdView " = = string ( r5 ) ) ;
2018-01-07 03:17:15 +01:00
2018-01-07 02:24:33 +01:00
/* === impossible to cover and can not be anchored === */
2018-01-07 03:17:15 +01:00
UICoordResolver r6 { UICoord : : window ( " windows " ) . path ( " to/hell " ) , tree } ;
CHECK ( not r6 . isAnchored ( ) ) ;
CHECK ( not r6 . canCover ( ) ) ;
r6 . anchor ( ) ;
CHECK ( not r6 . isAnchored ( ) ) ;
CHECK ( 0 = = r6 . coverDepth ( ) ) ;
2018-01-07 05:45:21 +01:00
CHECK ( " UI:windows[*]-*.*.*/to/hell " = = string ( r6 ) ) ;
2017-10-02 18:11:21 +02:00
}
2018-01-08 23:49:24 +01:00
/** @test mutate given UI coordinates by uncovered extension.
* Contrary to just appending something to the path ( which is a basic path operation
* available on the generic path builder ) , a _path extension_ is always rooted at the
* end of the actually covered part of the UI coordinates . So extending a path implies
* search for a coverage solution , followed by truncating the path to the covered part .
2018-01-10 04:42:49 +01:00
* There are two flavours of extending a path :
* - extending with a literal specification , which is just appended behind the coverage
* - extending with an incomplete UI coordinate spec , which allows to place the extension
* at a specific depth ( e . g . as a view ) . This is typically what we want in practice .
2018-01-08 23:49:24 +01:00
*/
2017-10-02 18:11:21 +02:00
void
verify_mutateExtend ( )
{
2018-01-08 23:49:24 +01:00
GenNodeLocationQuery tree { MakeRec ( )
. set ( " window-2 "
, MakeRec ( )
. type ( " persp-B " )
. set ( " panelY "
2018-01-09 01:52:49 +01:00
, MakeRec ( )
2018-01-08 23:49:24 +01:00
. set ( " thirdView "
, MakeRec ( )
. set ( " #1 " , MakeRec ( ) )
. set ( " #2 " , MakeRec ( ) )
)
2018-01-09 01:52:49 +01:00
)
2018-01-08 23:49:24 +01:00
)
} ;
/* === extend fully covered explicit path === */
UICoordResolver r1 { UICoord { " window-2 " , " persp-B " , " panelY " } , tree } ;
2018-01-10 03:21:10 +01:00
CHECK ( " UI:window-2[persp-B]-panelY " = = string ( r1 ) ) ;
CHECK ( r1 . isCovered ( ) ) ;
r1 . extend ( UICoord ( ) . path ( " gappy " ) . tab ( 2 ) ) ; // can extend with partially defined UI coordinates
CHECK ( " UI:window-2[persp-B]-panelY.*.#2/gappy " = = string ( r1 ) ) ; // ...the resulting UI path is unresolved, yet can be partially covered
r1 . extend ( " seamless " ) ; // ...and this partial coverage is used as base for further extension
CHECK ( " UI:window-2[persp-B]-panelY.thirdView.#2/seamless " = = string ( r1 ) ) ;
2018-01-08 23:49:24 +01:00
/* === extend partially covered path === */
UICoordResolver r2 { UICoord ( ) . view ( " thirdView " ) . append ( " some/where " ) , tree } ;
2018-01-10 03:21:10 +01:00
CHECK ( " UI:?.thirdView.some/where " = = string ( r2 ) ) ; // "thirdView" is covered, "some/where" is not
2018-01-08 23:49:24 +01:00
r2 . extend ( " no/where " ) ;
2018-01-10 03:21:10 +01:00
CHECK ( " UI:window-2[persp-B]-panelY.thirdView.no/where " = = string ( r2 ) ) ; // ...and thus the extension is attached behind "thirdView"
CHECK ( r2 . isCoveredPartially ( ) ) ;
/* === impossible extensions rejected === */ // since r2 already specifies a perspective ("persp-B")....
VERIFY_ERROR ( INVALID , r2 . extend ( UICoord ( ) . persp ( " fisheye " ) ) ) ; // ...overwriting with another perspective is rejected as extension
CHECK ( " UI:window-2[persp-B]-panelY.thirdView.no/where " = = string ( r2 ) ) ; // ...and the existing state is unaffected from this error
VERIFY_ERROR ( INVALID , r2 . extend ( UICoord ( ) . view ( " alternative " ) ) ) ; // Likewise, extending with a conflicting view spec is rejected
r2 . extend ( UICoord ( ) . tab ( " nada " ) ) ; // But a tab is not yet covered and thus acceptable as extension
CHECK ( " UI:window-2[persp-B]-panelY.thirdView.nada " = = string ( r2 ) ) ;
r2 . extend ( UICoord ( ) ) ;
CHECK ( " UI:window-2[persp-B]-panelY.thirdView " = = string ( r2 ) ) ; // empty coordinates implicitly attached behind the covered part
2018-01-08 23:49:24 +01:00
/* === unsolvable: truncate, extend, recalculate coverage === */
UICoordResolver r3 { UICoord ( ) . persp ( " awesome " ) , tree } ;
2018-01-09 02:12:00 +01:00
CHECK ( not r3 . canCover ( ) ) ;
CHECK ( 0 = = r3 . coverDepth ( ) ) ;
2018-01-10 03:21:10 +01:00
r3 . extend ( UICoord : : currentWindow ( ) . tab ( 1 ) ) ; // Extension implies covering, which effectively truncates the path
CHECK ( 1 = = r3 . coverDepth ( ) ) ; // ...and "currentWindow" can even be covered, thus the coverage increases
CHECK ( " UI:currentWindow[*]-*.*.#1 " = = string ( r3 ) ) ; // note coverage calculated internally, not made explicit
2017-10-02 18:11:21 +02:00
}
} ;
/** Register this test class... */
LAUNCHER ( UICoordResolver_test , " unit gui " ) ;
} } } // namespace gui::interact::test