@@ -27,97 +27,152 @@ export const getTestStatus = (
2727 : 'pass' ;
2828} ;
2929
30- function hasOnlyTest ( test : Test [ ] ) : boolean {
31- return test . some ( ( t ) => {
32- return t . runMode === 'only' || ( t . type === 'suite' && hasOnlyTest ( t . tests ) ) ;
33- } ) ;
34- }
30+ type TestModeContext = {
31+ shouldSkipByName ?: ( test : TestCase ) => boolean ;
32+ suiteHasOnlyDescendants : WeakMap < TestSuite , boolean > ;
33+ } ;
34+
35+ const collectOnlyTests = (
36+ tests : Test [ ] ,
37+ suiteHasOnlyDescendants : WeakMap < TestSuite , boolean > ,
38+ ) : boolean => {
39+ let hasOnly = false ;
40+
41+ for ( const test of tests ) {
42+ const childrenHaveOnly =
43+ test . type === 'suite'
44+ ? collectOnlyTests ( test . tests , suiteHasOnlyDescendants )
45+ : false ;
46+
47+ if ( test . type === 'suite' ) {
48+ suiteHasOnlyDescendants . set ( test , childrenHaveOnly ) ;
49+ }
50+
51+ if ( test . runMode === 'only' || childrenHaveOnly ) {
52+ hasOnly = true ;
53+ }
54+ }
55+
56+ return hasOnly ;
57+ } ;
58+
59+ const createShouldSkipByName = (
60+ testNamePattern ?: RegExp | string ,
61+ ) : ( ( test : TestCase ) => boolean ) | undefined => {
62+ if ( ! testNamePattern ) {
63+ return undefined ;
64+ }
65+
66+ const regex =
67+ typeof testNamePattern === 'string'
68+ ? new RegExp ( testNamePattern )
69+ : testNamePattern ;
70+ const delimiter = regex . toString ( ) . includes ( TEST_DELIMITER )
71+ ? TEST_DELIMITER
72+ : '' ;
73+
74+ return ( test : TestCase ) => {
75+ if ( regex . global || regex . sticky ) {
76+ regex . lastIndex = 0 ;
77+ }
78+
79+ return ! regex . test ( getTaskNameWithPrefix ( test , delimiter ) ) ;
80+ } ;
81+ } ;
3582
3683const shouldTestSkip = (
3784 test : TestCase ,
3885 runOnly : boolean ,
39- testNamePattern ?: RegExp | string ,
86+ shouldSkipByName ?: ( test : TestCase ) => boolean ,
4087) => {
4188 if ( runOnly && test . runMode !== 'only' ) {
4289 return true ;
4390 }
4491
45- const delimiter = testNamePattern ?. toString ( ) . includes ( TEST_DELIMITER )
46- ? TEST_DELIMITER
47- : '' ;
48-
49- if (
50- testNamePattern &&
51- ! getTaskNameWithPrefix ( test , delimiter ) . match ( testNamePattern )
52- ) {
92+ if ( shouldSkipByName ?.( test ) ) {
5393 return true ;
5494 }
5595
5696 return false ;
5797} ;
5898
59- export const traverseUpdateTestRunMode = (
99+ const traverseUpdateTestRunModeWithContext = (
60100 testSuite : TestSuite ,
61101 parentRunMode : TestRunMode ,
62102 runOnly : boolean ,
63- testNamePattern ?: RegExp | string ,
103+ context : TestModeContext ,
64104) : void => {
65105 if ( testSuite . tests . length === 0 ) {
66106 return ;
67107 }
68108
69- if (
70- runOnly &&
71- testSuite . runMode !== 'only' &&
72- ! hasOnlyTest ( testSuite . tests )
73- ) {
109+ const childrenHaveOnly =
110+ context . suiteHasOnlyDescendants . get ( testSuite ) ?? false ;
111+
112+ if ( runOnly && testSuite . runMode !== 'only' && ! childrenHaveOnly ) {
74113 testSuite . runMode = 'skip' ;
75114 } else if ( [ 'skip' , 'todo' ] . includes ( parentRunMode ) ) {
76115 testSuite . runMode = parentRunMode ;
77116 }
78117
79- const tests = testSuite . tests . map ( ( test ) => {
80- const runSubOnly =
81- runOnly && testSuite . runMode !== 'only'
82- ? runOnly
83- : hasOnlyTest ( testSuite . tests ) ;
118+ const runSubOnly =
119+ runOnly && testSuite . runMode !== 'only' ? runOnly : childrenHaveOnly ;
120+ let hasRunTest = false ;
121+ let allTodoTest = true ;
84122
123+ for ( const test of testSuite . tests ) {
85124 if ( test . type === 'case' ) {
86125 if ( [ 'skip' , 'todo' ] . includes ( testSuite . runMode ) ) {
87126 test . runMode = testSuite . runMode ;
88127 }
89- if ( shouldTestSkip ( test , runSubOnly , testNamePattern ) ) {
128+ if ( shouldTestSkip ( test , runSubOnly , context . shouldSkipByName ) ) {
90129 test . runMode = 'skip' ;
91130 }
92- return test ;
131+ } else {
132+ traverseUpdateTestRunModeWithContext (
133+ test ,
134+ testSuite . runMode ,
135+ runSubOnly ,
136+ context ,
137+ ) ;
93138 }
94- traverseUpdateTestRunMode (
95- test ,
96- testSuite . runMode ,
97- runSubOnly ,
98- testNamePattern ,
99- ) ;
100- return test ;
101- } ) ;
139+
140+ if ( test . runMode === 'run' || test . runMode === 'only' ) {
141+ hasRunTest = true ;
142+ }
143+
144+ if ( test . runMode !== 'todo' ) {
145+ allTodoTest = false ;
146+ }
147+ }
102148
103149 if ( testSuite . runMode !== 'run' ) {
104150 return ;
105151 }
106152
107- const hasRunTest = tests . some (
108- ( test ) => test . runMode === 'run' || test . runMode === 'only' ,
109- ) ;
110-
111153 if ( hasRunTest ) {
112154 testSuite . runMode = 'run' ;
113155 return ;
114156 }
115157
116- const allTodoTest = tests . every ( ( test ) => test . runMode === 'todo' ) ;
117-
118158 testSuite . runMode = allTodoTest ? 'todo' : 'skip' ;
119159} ;
120160
161+ export const traverseUpdateTestRunMode = (
162+ testSuite : TestSuite ,
163+ parentRunMode : TestRunMode ,
164+ runOnly : boolean ,
165+ testNamePattern ?: RegExp | string ,
166+ ) : void => {
167+ const suiteHasOnlyDescendants = new WeakMap < TestSuite , boolean > ( ) ;
168+ collectOnlyTests ( [ testSuite ] , suiteHasOnlyDescendants ) ;
169+
170+ traverseUpdateTestRunModeWithContext ( testSuite , parentRunMode , runOnly , {
171+ shouldSkipByName : createShouldSkipByName ( testNamePattern ) ,
172+ suiteHasOnlyDescendants,
173+ } ) ;
174+ } ;
175+
121176/**
122177 * sets the runMode of the test based on the runMode of its parent suite
123178 * - if the parent suite is 'todo', set the test to 'todo'
@@ -136,12 +191,17 @@ export const updateTestModes = (
136191 tests : Test [ ] ,
137192 testNamePattern ?: RegExp | string ,
138193) : void => {
139- const hasOnly = hasOnlyTest ( tests ) ;
194+ const suiteHasOnlyDescendants = new WeakMap < TestSuite , boolean > ( ) ;
195+ const hasOnly = collectOnlyTests ( tests , suiteHasOnlyDescendants ) ;
196+ const shouldSkipByName = createShouldSkipByName ( testNamePattern ) ;
140197
141198 for ( const test of tests ) {
142199 if ( test . type === 'suite' ) {
143- traverseUpdateTestRunMode ( test , 'run' , hasOnly , testNamePattern ) ;
144- } else if ( shouldTestSkip ( test , hasOnly , testNamePattern ) ) {
200+ traverseUpdateTestRunModeWithContext ( test , 'run' , hasOnly , {
201+ shouldSkipByName,
202+ suiteHasOnlyDescendants,
203+ } ) ;
204+ } else if ( shouldTestSkip ( test , hasOnly , shouldSkipByName ) ) {
145205 test . runMode = 'skip' ;
146206 }
147207 }
0 commit comments