@@ -18,7 +18,6 @@ import nullthrows from 'nullthrows';
18
18
import NativeFantom from 'react-native/src/private/specs/modules/NativeFantom' ;
19
19
20
20
export type TestCaseResult = {
21
- ancestorTitles : Array < string > ,
22
21
title : string ,
23
22
fullName : string ,
24
23
status : 'passed' | 'failed' | 'pending' ,
@@ -40,36 +39,91 @@ export type TestSuiteResult =
40
39
} ,
41
40
} ;
42
41
43
- const tests : Array < {
42
+ type Hook = ( ) => void ;
43
+
44
+ type Spec = {
44
45
title : string ,
45
- ancestorTitles : Array < string > ,
46
- implementation : ( ) => mixed ,
47
46
isFocused : boolean ,
48
47
isSkipped : boolean ,
48
+ implementation : ( ) => mixed ,
49
49
result ?: TestCaseResult ,
50
- } > = [ ] ;
50
+ } ;
51
51
52
- const ancestorTitles : Array < string > = [];
52
+ type Context =
53
+ | /* child context */ {
54
+ title : string ,
55
+ parent : Context ,
56
+ afterAll : Hook [ ] ,
57
+ afterEach : Hook [ ] ,
58
+ beforeAll : Hook [ ] ,
59
+ beforeEach : Hook [ ] ,
60
+ specs : Spec [ ] ,
61
+ children : Context [ ] ,
62
+ }
63
+ | /* root context */ {
64
+ afterAll : Hook [ ] ,
65
+ afterEach : Hook [ ] ,
66
+ beforeAll : Hook [ ] ,
67
+ beforeEach : Hook [ ] ,
68
+ specs : Spec [ ] ,
69
+ children : Context [ ] ,
70
+ } ;
71
+
72
+ let currentContext : Context = {
73
+ beforeAll : [ ] ,
74
+ beforeEach : [ ] ,
75
+ afterAll : [ ] ,
76
+ afterEach : [ ] ,
77
+ specs : [ ] ,
78
+ children : [ ] ,
79
+ } ;
53
80
54
81
const globalModifiers : Array < 'focused' | 'skipped' > = [ ] ;
55
82
56
83
const globalDescribe = ( global . describe = (
57
84
title : string ,
58
85
implementation : ( ) => mixed ,
59
86
) => {
60
- ancestorTitles. push ( title ) ;
87
+ const parentContext = currentContext ;
88
+ const childContext : Context = {
89
+ title,
90
+ parent : parentContext ,
91
+ afterAll : [ ] ,
92
+ afterEach : [ ] ,
93
+ beforeAll : [ ] ,
94
+ beforeEach : [ ] ,
95
+ specs : [ ] ,
96
+ children : [ ] ,
97
+ } ;
98
+ currentContext . children . push ( childContext ) ;
99
+ currentContext = childContext ;
61
100
implementation ( ) ;
62
- ancestorTitles . pop ( ) ;
101
+ currentContext = parentContext ;
63
102
} ) ;
64
103
104
+ global . afterAll = ( implementation : ( ) = > void ) => {
105
+ currentContext . afterAll . push ( implementation ) ;
106
+ } ;
107
+
108
+ global . afterEach = ( implementation : ( ) => void ) => {
109
+ currentContext . afterEach . push ( implementation ) ;
110
+ } ;
111
+
112
+ global . beforeAll = ( implementation : ( ) = > void ) => {
113
+ currentContext . beforeAll . push ( implementation ) ;
114
+ } ;
115
+
116
+ global . beforeEach = ( implementation : ( ) = > void ) => {
117
+ currentContext . beforeEach . push ( implementation ) ;
118
+ } ;
119
+
65
120
const globalIt =
66
121
( global . it =
67
122
global . test =
68
123
( title : string , implementation : ( ) => mixed ) =>
69
- tests . push ( {
124
+ currentContext . specs . push ( {
70
125
title,
71
126
implementation,
72
- ancestorTitles : ancestorTitles . slice ( ) ,
73
127
isFocused :
74
128
globalModifiers . length > 0 &&
75
129
globalModifiers [ globalModifiers . length - 1 ] === 'focused' ,
@@ -142,52 +196,112 @@ function runWithGuard(fn: () => void) {
142
196
}
143
197
}
144
198
199
+ function isFocusedRun ( context : Context ) : boolean {
200
+ return (
201
+ context . specs . some ( spec => spec . isFocused ) ||
202
+ context . children . some ( isFocusedRun )
203
+ ) ;
204
+ }
205
+
206
+ function getContextTitle ( context : Context ) : string [ ] {
207
+ if ( context . parent == null ) {
208
+ return [ ] ;
209
+ }
210
+
211
+ const titles = [ context . title ] ;
212
+ if ( context . parent ) {
213
+ titles . push ( ...getContextTitle ( context . parent ) ) ;
214
+ }
215
+ return titles . reverse ( ) ;
216
+ }
217
+
145
218
function executeTests ( ) {
146
- const hasFocusedTests = tests . some ( test => test . isFocused ) ;
147
-
148
- for ( const test of tests ) {
149
- const result : TestCaseResult = {
150
- title : test . title ,
151
- fullName : [ ...test . ancestorTitles , test . title ] . join ( ' ' ) ,
152
- ancestorTitles : test . ancestorTitles ,
153
- status : 'pending' ,
154
- duration : 0 ,
155
- failureMessages : [ ] ,
156
- numPassingAsserts : 0 ,
157
- snapshotResults : { } ,
158
- } ;
219
+ const hasFocusedTests = isFocusedRun ( currentContext ) ;
159
220
160
- test . result = result ;
161
- snapshotContext . setTargetTest ( result . fullName ) ;
221
+ const results : Array < TestCaseResult > = [];
222
+ executeSpecs(currentContext);
223
+ reportTestSuiteResult({
224
+ testResults : results ,
225
+ } );
162
226
163
- if ( ! test . isSkipped && ( ! hasFocusedTests || test . isFocused ) ) {
164
- let status ;
165
- let error ;
227
+ function invokeHooks(context: Context, hookType: 'beforeEach' | 'afterEach') {
228
+ const contextStack = [ ] ;
229
+ let current : ?Context = context ;
230
+ while ( current != null ) {
231
+ contextStack . push ( current ) ;
232
+ current = current . parent ;
233
+ }
166
234
167
- const start = Date . now ( ) ;
235
+ if (hookType === 'beforeEach') {
236
+ contextStack . reverse ( ) ;
237
+ }
168
238
169
- try {
170
- test . implementation ( ) ;
171
- status = 'passed' ;
172
- } catch ( e ) {
173
- error = e ;
174
- status = 'failed' ;
239
+ for (const c of contextStack) {
240
+ for ( const hook of c [ hookType ] ) {
241
+ hook ( ) ;
175
242
}
243
+ }
244
+ }
245
+
246
+ function executeSpecs ( context : Context ) {
247
+ for ( const setup of context . beforeAll ) {
248
+ setup ( ) ;
249
+ }
250
+
251
+ for (const test of context.specs) {
252
+ const result : TestCaseResult = {
253
+ title : test . title ,
254
+ fullName : [ ...getContextTitle ( context ) , test . title ] . join ( ' ' ) ,
255
+ status : 'pending' ,
256
+ duration : 0 ,
257
+ failureMessages : [ ] ,
258
+ numPassingAsserts : 0 ,
259
+ snapshotResults : { } ,
260
+ } ;
261
+
262
+ test . result = result ;
263
+ snapshotContext . setTargetTest ( result . fullName ) ;
264
+
265
+ if ( ! test . isSkipped && ( ! hasFocusedTests || test . isFocused ) ) {
266
+ let status ;
267
+ let error ;
268
+
269
+ const start = Date . now ( ) ;
270
+
271
+ try {
272
+ invokeHooks ( context , 'beforeEach' ) ;
176
273
177
- result . status = status ;
178
- result . duration = Date . now ( ) - start ;
179
- result . failureMessages =
180
- status === 'failed' && error
181
- ? [ error . stack ?? error . message ?? String ( error ) ]
182
- : [ ] ;
274
+ test . implementation ( ) ;
183
275
184
- result . snapshotResults = snapshotContext . getSnapshotResults ( ) ;
276
+ invokeHooks ( context , 'afterEach' ) ;
277
+
278
+ status = 'passed' ;
279
+ } catch ( e ) {
280
+ error = e ;
281
+ status = 'failed' ;
282
+ }
283
+
284
+ result . status = status ;
285
+ result . duration = Date . now ( ) - start ;
286
+ result . failureMessages =
287
+ status === 'failed' && error
288
+ ? [ error . stack ?? error . message ?? String ( error ) ]
289
+ : [ ] ;
290
+
291
+ result . snapshotResults = snapshotContext . getSnapshotResults ( ) ;
292
+
293
+ results . push ( result ) ;
294
+ }
185
295
}
186
- }
187
296
188
- reportTestSuiteResult ( {
189
- testResults : tests . map ( test => nullthrows ( test . result ) ) ,
190
- } ) ;
297
+ for ( const childContext of context . children ) {
298
+ executeSpecs ( childContext ) ;
299
+ }
300
+
301
+ for (const teardown of context.afterAll) {
302
+ teardown ( ) ;
303
+ }
304
+ }
191
305
}
192
306
193
307
function reportTestSuiteResult ( testSuiteResult : TestSuiteResult ) : void {
0 commit comments