@peter.naydenov/cuts 2.1.3 → 2.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Changelog.md +12 -0
- package/coverage/lcov-report/index.html +29 -29
- package/coverage/lcov-report/src/findInstructions.js.html +29 -29
- package/coverage/lcov-report/src/index.html +23 -23
- package/coverage/lcov-report/src/main.js.html +64 -43
- package/coverage/lcov-report/src/methods/hide.js.html +106 -67
- package/coverage/lcov-report/src/methods/index.html +31 -31
- package/coverage/lcov-report/src/methods/jump.js.html +26 -11
- package/coverage/lcov-report/src/methods/jumpBack.js.html +5 -5
- package/coverage/lcov-report/src/methods/jumpsReset.js.html +2 -2
- package/coverage/lcov-report/src/methods/listShortcuts.js.html +5 -5
- package/coverage/lcov-report/src/methods/setScenes.js.html +16 -16
- package/coverage/lcov-report/src/methods/show.js.html +124 -67
- package/coverage/lcov-report/src/setInstruction.js.html +7 -7
- package/coverage/lcov.info +327 -306
- package/package.json +1 -1
- package/src/methods/hide.js +37 -37
- package/src/methods/jump.js +7 -2
- package/src/methods/setScenes.js +1 -1
- package/src/methods/show.js +12 -2
- package/test/02-cuts.test.js +270 -1
- package/vitest.config.js +1 -1
package/package.json
CHANGED
package/src/methods/hide.js
CHANGED
|
@@ -12,50 +12,51 @@ function hide ( dependencies, state ) {
|
|
|
12
12
|
return function hide ( endSteps=1 ) {
|
|
13
13
|
const
|
|
14
14
|
{ askForPromise, shortcutMngr, setInstruction } = dependencies
|
|
15
|
-
, { currentScene,
|
|
15
|
+
, { currentScene, scenes } = state
|
|
16
16
|
, hideTask = askForPromise ()
|
|
17
|
-
,
|
|
18
|
-
,
|
|
17
|
+
, unloadTask = askForPromise ()
|
|
18
|
+
, hasBeforeHide = typeof scenes[currentScene].beforeHide === 'function'
|
|
19
19
|
;
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
if ( hasBeforeHide ) { // Execute 'beforeHide' function if exists - same guard 'show' honors when navigating away
|
|
22
|
+
const closingFn = scenes[currentScene].beforeHide;
|
|
23
|
+
closingFn ({ done:unloadTask.done, dependencies: shortcutMngr.getDependencies() })
|
|
24
|
+
}
|
|
25
|
+
else unloadTask.done ( true )
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
unloadTask.onComplete ( continueHiding => {
|
|
28
|
+
if ( !continueHiding ) {
|
|
29
|
+
// 'beforeHide' returned false - cancel, nothing was torn down or changed
|
|
30
|
+
hideTask.done ()
|
|
31
|
+
return hideTask.promise
|
|
32
|
+
}
|
|
25
33
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
34
|
+
const
|
|
35
|
+
instructions = []
|
|
36
|
+
// Work on a copy of the recorded ancestor chain - it's the source of truth for what to
|
|
37
|
+
// climb back to, including for wildcard-overlay scenes ('*' itself is never stored here,
|
|
38
|
+
// only the real scene name that was current when the overlay was shown).
|
|
39
|
+
, remaining = [ ...state.currentParents ]
|
|
40
|
+
;
|
|
33
41
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
shortcutMngr.pause () // Stop all shortcuts
|
|
43
|
+
instructions.push ( setInstruction ( scenes[currentScene].hide )) // Hiding the current scene is always at least 1 step
|
|
44
|
+
|
|
45
|
+
const ancestorsToHide = ( endSteps === '*' ) ? remaining.length : Math.min ( endSteps - 1, remaining.length )
|
|
46
|
+
for ( let i = 0; i < ancestorsToHide; i++ ) {
|
|
47
|
+
const name = remaining.pop ()
|
|
48
|
+
instructions.push ( setInstruction ( scenes[name].hide ))
|
|
49
|
+
}
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// Climb to the parent of the scene just hidden - same technique 'show' uses for its 'hide' steps.
|
|
45
|
-
// A wildcard overlay isn't climbing its own declared parent ('*' isn't a real scene) - and
|
|
46
|
-
// there's no reliable way to recover what it originally covered once it's this deep in the
|
|
47
|
-
// chain, so fall back to treating it as the end of the chain rather than crashing.
|
|
48
|
-
state.currentScene = ( scenes[name].parents[0] === '*' )
|
|
49
|
-
? null
|
|
50
|
-
: ( scenes[name].parents.at ( -1 ) ?? null )
|
|
51
|
-
state.currentParents = state.currentScene ? scenes[state.currentScene].parents : []
|
|
52
|
-
})
|
|
51
|
+
state.currentScene = remaining.length > 0 ? remaining.pop () : null
|
|
52
|
+
state.currentParents = remaining
|
|
53
53
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
54
|
+
const goingTask = askForPromise.sequence ( instructions );
|
|
55
|
+
goingTask.onComplete ( () => {
|
|
56
|
+
shortcutMngr.changeContext ( state.currentScene ) // If state.currentScene is null, then all shortcuts are switched off and currentContext is null
|
|
57
|
+
hideTask.done ()
|
|
58
|
+
})
|
|
59
|
+
}) // unloadTask onComplete
|
|
59
60
|
return hideTask.promise
|
|
60
61
|
}} // hide func.
|
|
61
62
|
|
|
@@ -63,4 +64,3 @@ return function hide ( endSteps=1 ) {
|
|
|
63
64
|
|
|
64
65
|
export default hide
|
|
65
66
|
|
|
66
|
-
|
package/src/methods/jump.js
CHANGED
|
@@ -8,8 +8,13 @@ function jump ( state, show ) {
|
|
|
8
8
|
* @returns {Promise} - promise that resolves when the jump is completed
|
|
9
9
|
*/
|
|
10
10
|
function jump ( { scene }, ...args ) {
|
|
11
|
-
|
|
12
|
-
return show ( { scene }, ...args )
|
|
11
|
+
const previousScene = state.currentScene
|
|
12
|
+
return show ( { scene }, ...args ).then ( () => {
|
|
13
|
+
// Only record the jump if it actually landed - 'show' silently no-ops on a
|
|
14
|
+
// blocked (beforeHide) or invalid navigation, and pushing anyway would leave
|
|
15
|
+
// a phantom entry that desyncs jumpBack() from what actually happened.
|
|
16
|
+
if ( state.currentScene === scene ) state.jumpStack.push ( previousScene )
|
|
17
|
+
})
|
|
13
18
|
}
|
|
14
19
|
return jump
|
|
15
20
|
} // jump func.
|
package/src/methods/setScenes.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
/**
|
|
9
9
|
* @typedef {Object} SceneObject
|
|
10
10
|
* @property {function({task: AskObject, dependencies: *}): void} show - load interface and prerequisites for the scene;
|
|
11
|
-
* @property {function():
|
|
11
|
+
* @property {function({dependencies: *, done: function}):void} [afterShow] - run after the scene is shown. Optional. Fire-and-forget - its return value is not used, and it cannot cancel or affect the already-completed 'show';
|
|
12
12
|
* @property {function({task: AskObject, dependencies: *}): void} hide - Reverse the settings from 'show';
|
|
13
13
|
* @property {function():void} [beforeHide] - Run before scene is hidden. Optional;
|
|
14
14
|
* @property {Array.<string>} [parents] - list of parent scene names.
|
package/src/methods/show.js
CHANGED
|
@@ -53,6 +53,9 @@ function show ( dependencies, state ) {
|
|
|
53
53
|
// Executes only once when the scene manager is started
|
|
54
54
|
state.opened = true
|
|
55
55
|
state.currentScene = requestedScene
|
|
56
|
+
// The server-rendered scene's ancestors are assumed already rendered too -
|
|
57
|
+
// record them, or navigating away later won't know to hide them.
|
|
58
|
+
state.currentParents = scenes[requestedScene].parents ? [ ...scenes[requestedScene].parents ] : []
|
|
56
59
|
shortcutMngr.changeContext ( requestedScene )
|
|
57
60
|
showTask.done ()
|
|
58
61
|
return showTask.promise
|
|
@@ -65,7 +68,11 @@ function show ( dependencies, state ) {
|
|
|
65
68
|
if ( parents[0] === '*' ) {
|
|
66
69
|
const overlayTask = setInstruction(show, ...args)()
|
|
67
70
|
overlayTask.then ( () => showTask.done () )
|
|
68
|
-
|
|
71
|
+
// Only remember a real underlying scene - pushing 'null' as a placeholder
|
|
72
|
+
// makes later navigation treat it as a scene name to hide, and crash.
|
|
73
|
+
// Re-showing the SAME overlay that's already current must not push it onto
|
|
74
|
+
// its own ancestor chain either, or hide() later climbs back onto itself.
|
|
75
|
+
if ( state.currentScene && state.currentScene !== requestedScene ) state.currentParents.push ( state.currentScene )
|
|
69
76
|
state.currentScene = requestedScene
|
|
70
77
|
shortcutMngr.changeContext ( requestedScene ) // Activate the overlay scene's own shortcuts
|
|
71
78
|
return showTask.promise
|
|
@@ -101,7 +108,10 @@ function show ( dependencies, state ) {
|
|
|
101
108
|
} // getStep func.
|
|
102
109
|
|
|
103
110
|
const
|
|
104
|
-
|
|
111
|
+
// Snapshot - findInstructions is a lazy generator, and getStep (in the loop below)
|
|
112
|
+
// mutates state.currentParents in place; without a copy, that mutation corrupts
|
|
113
|
+
// the generator's still-pending reads of the very array it was given.
|
|
114
|
+
g = findInstructions ( state.currentScene, [ ...state.currentParents ], requestedScene, parents )
|
|
105
115
|
, instructions = []
|
|
106
116
|
;
|
|
107
117
|
|
package/test/02-cuts.test.js
CHANGED
|
@@ -455,7 +455,7 @@ describe ( 'Cuts integration', () => {
|
|
|
455
455
|
await script.show ({ scene: 'overlay' });
|
|
456
456
|
expect ( calls ).toEqual (['show overlay']);
|
|
457
457
|
expect ( script.getState().scene ).toEqual ( 'overlay' );
|
|
458
|
-
expect ( script.getState().parents ).toEqual ( [
|
|
458
|
+
expect ( script.getState().parents ).toEqual ( [] ); // No underlying scene to remember
|
|
459
459
|
}) // it Show scene with wildcard parent as the very first scene
|
|
460
460
|
|
|
461
461
|
|
|
@@ -658,4 +658,273 @@ describe ( 'Cuts integration', () => {
|
|
|
658
658
|
|
|
659
659
|
|
|
660
660
|
|
|
661
|
+
it ( 'hide() with the default single step climbs to the parent and restores its shortcuts context', async () => {
|
|
662
|
+
// Regression test: hide() used to leave currentScene/currentParents pointing at
|
|
663
|
+
// the just-hidden scene instead of climbing to its (still visible) parent, so
|
|
664
|
+
// NEITHER scene's shortcuts context was active afterward.
|
|
665
|
+
const seen = [];
|
|
666
|
+
const script = cuts();
|
|
667
|
+
const scenes = [
|
|
668
|
+
{
|
|
669
|
+
name: 'A'
|
|
670
|
+
, scene: {
|
|
671
|
+
show : ({task}) => task.done(),
|
|
672
|
+
hide : ({task}) => task.done(),
|
|
673
|
+
'@ping' : () => seen.push ( 'A' )
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
name: 'B'
|
|
678
|
+
, scene: {
|
|
679
|
+
show : ({task}) => task.done(),
|
|
680
|
+
hide : ({task}) => task.done(),
|
|
681
|
+
parents: ['A'],
|
|
682
|
+
'@ping' : () => seen.push ( 'B' )
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
];
|
|
686
|
+
|
|
687
|
+
script.setScenes ( scenes );
|
|
688
|
+
await script.show ({ scene: 'A' });
|
|
689
|
+
await script.show ({ scene: 'B' });
|
|
690
|
+
|
|
691
|
+
await script.hide (); // default endSteps = 1
|
|
692
|
+
expect ( script.getState() ).toEqual ({ scene: 'A', parents: [], opened: true });
|
|
693
|
+
|
|
694
|
+
script.emit ( '@ping' );
|
|
695
|
+
expect ( seen ).toEqual ([ 'A' ]);
|
|
696
|
+
}) // it hide() with the default single step climbs to the parent and restores its shortcuts context
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
|
|
700
|
+
it ( 'hide(n) climbs exactly n levels', async () => {
|
|
701
|
+
const calls = [];
|
|
702
|
+
const script = cuts();
|
|
703
|
+
const scenes = [
|
|
704
|
+
{ name: 'top' , scene: { show: ({task}) => { calls.push('show top'); task.done() }, hide: ({task}) => { calls.push('hide top'); task.done() } } },
|
|
705
|
+
{ name: 'mid' , scene: { show: ({task}) => { calls.push('show mid'); task.done() }, hide: ({task}) => { calls.push('hide mid'); task.done() }, parents: ['top'] } },
|
|
706
|
+
{ name: 'deep', scene: { show: ({task}) => { calls.push('show deep'); task.done() }, hide: ({task}) => { calls.push('hide deep'); task.done() }, parents: ['top', 'mid'] } }
|
|
707
|
+
];
|
|
708
|
+
|
|
709
|
+
script.setScenes ( scenes );
|
|
710
|
+
await script.show ({ scene: 'deep' });
|
|
711
|
+
calls.length = 0;
|
|
712
|
+
|
|
713
|
+
await script.hide ( 2 );
|
|
714
|
+
expect ( calls ).toEqual ([ 'hide deep', 'hide mid' ]);
|
|
715
|
+
expect ( script.getState() ).toEqual ({ scene: 'top', parents: [], opened: true });
|
|
716
|
+
}) // it hide(n) climbs exactly n levels
|
|
717
|
+
|
|
718
|
+
|
|
719
|
+
|
|
720
|
+
it ( 'SSR first load records the scene\'s parents, so leaving it hides them too', async () => {
|
|
721
|
+
// Regression test: the SSR branch of show() set currentScene but never
|
|
722
|
+
// currentParents, so navigating away from a deep SSR-loaded scene skipped
|
|
723
|
+
// hiding its ancestors entirely, leaving them stale in the DOM.
|
|
724
|
+
const calls = [];
|
|
725
|
+
const script = cuts();
|
|
726
|
+
const scenes = [
|
|
727
|
+
{ name: 'top' , scene: { show: ({task}) => { calls.push('show top'); task.done() }, hide: ({task}) => { calls.push('hide top'); task.done() } } },
|
|
728
|
+
{ name: 'mid' , scene: { show: ({task}) => { calls.push('show mid'); task.done() }, hide: ({task}) => { calls.push('hide mid'); task.done() }, parents: ['top'] } },
|
|
729
|
+
{ name: 'deep', scene: { show: ({task}) => { calls.push('show deep'); task.done() }, hide: ({task}) => { calls.push('hide deep'); task.done() }, parents: ['top', 'mid'] } },
|
|
730
|
+
{ name: 'other', scene: { show: ({task}) => { calls.push('show other'); task.done() }, hide: ({task}) => { calls.push('hide other'); task.done() } } }
|
|
731
|
+
];
|
|
732
|
+
|
|
733
|
+
script.setScenes ( scenes );
|
|
734
|
+
await script.show ({ scene: 'deep', options: { ssr: true } });
|
|
735
|
+
expect ( script.getState() ).toEqual ({ scene: 'deep', parents: [ 'top', 'mid' ], opened: true });
|
|
736
|
+
|
|
737
|
+
calls.length = 0;
|
|
738
|
+
await script.show ({ scene: 'other' });
|
|
739
|
+
expect ( calls ).toEqual ([ 'hide deep', 'hide mid', 'hide top', 'show other' ]);
|
|
740
|
+
}) // it SSR first load records the scene's parents, so leaving it hides them too
|
|
741
|
+
|
|
742
|
+
|
|
743
|
+
|
|
744
|
+
it ( 'afterShow return value has no effect on the completed show()', async () => {
|
|
745
|
+
// Regression test: setScenes.js's typedef documented afterShow as
|
|
746
|
+
// 'returns false to cancel', but show() never read its return value, so
|
|
747
|
+
// the scene was always shown regardless. The typedef is now corrected to
|
|
748
|
+
// describe the actual (README-documented) fire-and-forget behavior.
|
|
749
|
+
const calls = [];
|
|
750
|
+
const script = cuts();
|
|
751
|
+
const scenes = [
|
|
752
|
+
{
|
|
753
|
+
name: 'top'
|
|
754
|
+
, scene: {
|
|
755
|
+
show : ({task}) => { calls.push('show'); task.done() },
|
|
756
|
+
hide : ({task}) => task.done(),
|
|
757
|
+
afterShow : () => { calls.push('afterShow'); return false }
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
];
|
|
761
|
+
|
|
762
|
+
script.setScenes ( scenes );
|
|
763
|
+
await script.show ({ scene: 'top' });
|
|
764
|
+
|
|
765
|
+
expect ( calls ).toEqual ([ 'show', 'afterShow' ]);
|
|
766
|
+
expect ( script.getState() ).toEqual ({ scene: 'top', parents: [], opened: true });
|
|
767
|
+
}) // it afterShow return value has no effect on the completed show()
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
it ( 'hide() honors beforeHide, same as show() does', async () => {
|
|
772
|
+
// Regression test: hide() used to ignore beforeHide entirely, so it could
|
|
773
|
+
// never be blocked - even though show() already respected it when navigating
|
|
774
|
+
// away to a different scene.
|
|
775
|
+
const calls = [];
|
|
776
|
+
const script = cuts();
|
|
777
|
+
const scenes = [
|
|
778
|
+
{
|
|
779
|
+
name: 'top'
|
|
780
|
+
, scene: {
|
|
781
|
+
show : ({task}) => { calls.push('show top'); task.done() },
|
|
782
|
+
hide : ({task}) => { calls.push('hide top'); task.done() },
|
|
783
|
+
beforeHide : ({ done }) => { calls.push('beforeHide'); done ( false ) }
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
];
|
|
787
|
+
|
|
788
|
+
script.setScenes ( scenes );
|
|
789
|
+
await script.show ({ scene: 'top' });
|
|
790
|
+
calls.length = 0;
|
|
791
|
+
|
|
792
|
+
await script.hide ();
|
|
793
|
+
expect ( calls ).toEqual ([ 'beforeHide' ]); // hide() never actually ran
|
|
794
|
+
expect ( script.getState() ).toEqual ({ scene: 'top', parents: [], opened: true });
|
|
795
|
+
}) // it hide() honors beforeHide, same as show() does
|
|
796
|
+
|
|
797
|
+
|
|
798
|
+
|
|
799
|
+
it ( 'jump() does not record a jump-stack entry when the navigation is blocked', async () => {
|
|
800
|
+
// Regression test: jump() pushed onto the jump stack unconditionally, even when
|
|
801
|
+
// the underlying show() was blocked (eg. by beforeHide) and nothing actually
|
|
802
|
+
// changed - leaving a phantom entry that desynced jumpBack() from real history.
|
|
803
|
+
const calls = [];
|
|
804
|
+
const script = cuts();
|
|
805
|
+
let block = true;
|
|
806
|
+
const scenes = [
|
|
807
|
+
{
|
|
808
|
+
name: 'home'
|
|
809
|
+
, scene: {
|
|
810
|
+
show : ({task}) => { calls.push('show home'); task.done() },
|
|
811
|
+
hide : ({task}) => { calls.push('hide home'); task.done() },
|
|
812
|
+
beforeHide : ({ done }) => done ( !block )
|
|
813
|
+
}
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
name: 'settings'
|
|
817
|
+
, scene: {
|
|
818
|
+
show : ({task}) => { calls.push('show settings'); task.done() },
|
|
819
|
+
hide : ({task}) => { calls.push('hide settings'); task.done() }
|
|
820
|
+
}
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
name: 'other'
|
|
824
|
+
, scene: {
|
|
825
|
+
show : ({task}) => { calls.push('show other'); task.done() },
|
|
826
|
+
hide : ({task}) => { calls.push('hide other'); task.done() }
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
];
|
|
830
|
+
|
|
831
|
+
script.setScenes ( scenes );
|
|
832
|
+
await script.show ({ scene: 'home' });
|
|
833
|
+
|
|
834
|
+
await script.jump ({ scene: 'settings' }); // blocked by beforeHide - no stack entry
|
|
835
|
+
expect ( script.getState().scene ).toEqual ( 'home' );
|
|
836
|
+
|
|
837
|
+
block = false;
|
|
838
|
+
await script.jump ({ scene: 'other' }); // succeeds - exactly one real stack entry
|
|
839
|
+
expect ( script.getState().scene ).toEqual ( 'other' );
|
|
840
|
+
|
|
841
|
+
calls.length = 0;
|
|
842
|
+
await script.jumpBack ();
|
|
843
|
+
expect ( calls ).toEqual ([ 'hide other', 'show home' ]);
|
|
844
|
+
expect ( script.getState().scene ).toEqual ( 'home' );
|
|
845
|
+
|
|
846
|
+
calls.length = 0;
|
|
847
|
+
await script.jumpBack (); // stack should already be empty - true no-op
|
|
848
|
+
expect ( calls ).toEqual ([]);
|
|
849
|
+
expect ( script.getState().scene ).toEqual ( 'home' );
|
|
850
|
+
}) // it jump() does not record a jump-stack entry when the navigation is blocked
|
|
851
|
+
|
|
852
|
+
|
|
853
|
+
|
|
854
|
+
it ( 'Navigating from a wildcard overlay to an unrelated scene hides the scene it was covering', async () => {
|
|
855
|
+
// Regression test: findInstructions is a lazy generator, and show() passed it
|
|
856
|
+
// state.currentParents by reference. getStep's wildcard climb (state.currentParents.pop())
|
|
857
|
+
// mutated that same array mid-iteration, corrupting the generator's still-pending reads
|
|
858
|
+
// and silently dropping the 'hide' instruction for the scene the overlay was covering -
|
|
859
|
+
// leaking it as permanently visible.
|
|
860
|
+
const calls = [];
|
|
861
|
+
const script = cuts();
|
|
862
|
+
const scenes = [
|
|
863
|
+
{ name: 'G', scene: { show: ({task}) => { calls.push('show G'); task.done() }, hide: ({task}) => { calls.push('hide G'); task.done() } } },
|
|
864
|
+
{ name: 'OV', scene: { show: ({task}) => { calls.push('show OV'); task.done() }, hide: ({task}) => { calls.push('hide OV'); task.done() }, parents: ['*'] } },
|
|
865
|
+
{ name: 'D', scene: { show: ({task}) => { calls.push('show D'); task.done() }, hide: ({task}) => { calls.push('hide D'); task.done() } } }
|
|
866
|
+
];
|
|
867
|
+
|
|
868
|
+
script.setScenes ( scenes );
|
|
869
|
+
await script.show ({ scene: 'G' });
|
|
870
|
+
await script.show ({ scene: 'OV' });
|
|
871
|
+
calls.length = 0;
|
|
872
|
+
|
|
873
|
+
await script.show ({ scene: 'D' });
|
|
874
|
+
expect ( calls ).toEqual ([ 'hide OV', 'hide G', 'show D' ]);
|
|
875
|
+
expect ( script.getState() ).toEqual ({ scene: 'D', parents: [], opened: true });
|
|
876
|
+
}) // it Navigating from a wildcard overlay to an unrelated scene hides the scene it was covering
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
|
|
880
|
+
it ( 'Showing a wildcard overlay with no underlying scene does not crash later navigation', async () => {
|
|
881
|
+
// Regression test: showing an overlay when state.currentScene was null recorded a
|
|
882
|
+
// literal 'null' placeholder in currentParents. Navigating away from it later crashed,
|
|
883
|
+
// because findInstructions/getStep treated that placeholder as a real scene name to hide.
|
|
884
|
+
const script = cuts();
|
|
885
|
+
const scenes = [
|
|
886
|
+
{ name: 'F', scene: { show: ({task}) => task.done(), hide: ({task}) => task.done() } },
|
|
887
|
+
{ name: 'OV', scene: { show: ({task}) => task.done(), hide: ({task}) => task.done(), parents: ['*'] } },
|
|
888
|
+
{ name: 'A', scene: { show: ({task}) => task.done(), hide: ({task}) => task.done() } }
|
|
889
|
+
];
|
|
890
|
+
|
|
891
|
+
script.setScenes ( scenes );
|
|
892
|
+
await script.show ({ scene: 'F' });
|
|
893
|
+
await script.hide ( 1 ); // back to nothing - currentScene becomes null
|
|
894
|
+
await script.show ({ scene: 'OV' }); // overlay with no underlying scene
|
|
895
|
+
|
|
896
|
+
expect ( script.getState() ).toEqual ({ scene: 'OV', parents: [], opened: true });
|
|
897
|
+
|
|
898
|
+
await script.show ({ scene: 'A' }); // used to throw
|
|
899
|
+
expect ( script.getState() ).toEqual ({ scene: 'A', parents: [], opened: true });
|
|
900
|
+
}) // it Showing a wildcard overlay with no underlying scene does not crash later navigation
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
|
|
904
|
+
it ( 'Re-showing the same wildcard overlay does not push it onto its own ancestor chain', async () => {
|
|
905
|
+
// Regression test: showing an overlay while it was ALREADY the current scene pushed
|
|
906
|
+
// it onto its own currentParents (self-reference). A later hide() would then pop that
|
|
907
|
+
// self-reference and report the overlay as still current, even though it had just been
|
|
908
|
+
// hidden - a state/reality desync.
|
|
909
|
+
const script = cuts();
|
|
910
|
+
const visible = new Set();
|
|
911
|
+
const scenes = [
|
|
912
|
+
{ name: 'C', scene: { show: ({task}) => { visible.add('C'); task.done() }, hide: ({task}) => { visible.delete('C'); task.done() } } },
|
|
913
|
+
{ name: 'OV', scene: { show: ({task}) => { visible.add('OV'); task.done() }, hide: ({task}) => { visible.delete('OV'); task.done() }, parents: ['*'] } }
|
|
914
|
+
];
|
|
915
|
+
|
|
916
|
+
script.setScenes ( scenes );
|
|
917
|
+
await script.show ({ scene: 'C' });
|
|
918
|
+
await script.show ({ scene: 'OV' });
|
|
919
|
+
await script.show ({ scene: 'OV' }); // show the same overlay again while already current
|
|
920
|
+
|
|
921
|
+
expect ( script.getState() ).toEqual ({ scene: 'OV', parents: [ 'C' ], opened: true });
|
|
922
|
+
|
|
923
|
+
await script.hide ( 1 );
|
|
924
|
+
expect ( script.getState() ).toEqual ({ scene: 'C', parents: [], opened: true });
|
|
925
|
+
expect ( [...visible] ).toEqual ([ 'C' ]); // OV must actually be gone, matching reported state
|
|
926
|
+
}) // it Re-showing the same wildcard overlay does not push it onto its own ancestor chain
|
|
927
|
+
|
|
928
|
+
|
|
929
|
+
|
|
661
930
|
}) // describe
|
package/vitest.config.js
CHANGED
|
@@ -3,7 +3,7 @@ import { defineConfig } from 'vitest/config'
|
|
|
3
3
|
export default defineConfig({
|
|
4
4
|
test: {
|
|
5
5
|
environment: 'node',
|
|
6
|
-
exclude: ['test-e2e/**', 'node_modules/**'],
|
|
6
|
+
exclude: ['test-e2e/**', '**/node_modules/**'],
|
|
7
7
|
coverage: {
|
|
8
8
|
provider: 'v8',
|
|
9
9
|
reporter: ['lcov', 'text-summary'],
|