Skip to content

Commit 5e9b83a

Browse files
andrewdacenkofacebook-github-bot
authored andcommitted
Add support for (before|after)(Each|All) methods (facebook#48820)
Summary: Changelog: [Internal] Add capability to setup / teardown tests Differential Revision: D68454176
1 parent 9afad52 commit 5e9b83a

File tree

3 files changed

+261
-46
lines changed

3 files changed

+261
-46
lines changed

packages/react-native-fantom/runtime/setup.js

+160-46
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import nullthrows from 'nullthrows';
1818
import NativeFantom from 'react-native/src/private/specs/modules/NativeFantom';
1919

2020
export type TestCaseResult = {
21-
ancestorTitles: Array<string>,
2221
title: string,
2322
fullName: string,
2423
status: 'passed' | 'failed' | 'pending',
@@ -40,36 +39,91 @@ export type TestSuiteResult =
4039
},
4140
};
4241

43-
const tests: Array<{
42+
type Hook = () => void;
43+
44+
type Spec = {
4445
title: string,
45-
ancestorTitles: Array<string>,
46-
implementation: () => mixed,
4746
isFocused: boolean,
4847
isSkipped: boolean,
48+
implementation: () => mixed,
4949
result?: TestCaseResult,
50-
}> = [];
50+
};
5151

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+
};
5380

5481
const globalModifiers: Array<'focused' | 'skipped'> = [];
5582

5683
const globalDescribe = (global.describe = (
5784
title: string,
5885
implementation: () => mixed,
5986
) => {
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;
61100
implementation();
62-
ancestorTitles.pop();
101+
currentContext = parentContext;
63102
});
64103

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+
65120
const globalIt =
66121
(global.it =
67122
global.test =
68123
(title: string, implementation: () => mixed) =>
69-
tests.push({
124+
currentContext.specs.push({
70125
title,
71126
implementation,
72-
ancestorTitles: ancestorTitles.slice(),
73127
isFocused:
74128
globalModifiers.length > 0 &&
75129
globalModifiers[globalModifiers.length - 1] === 'focused',
@@ -142,52 +196,112 @@ function runWithGuard(fn: () => void) {
142196
}
143197
}
144198

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+
145218
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);
159220

160-
test.result = result;
161-
snapshotContext.setTargetTest(result.fullName);
221+
const results: Array<TestCaseResult> = [];
222+
executeSpecs(currentContext);
223+
reportTestSuiteResult({
224+
testResults: results,
225+
});
162226

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+
}
166234

167-
const start = Date.now();
235+
if (hookType === 'beforeEach') {
236+
contextStack.reverse();
237+
}
168238

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();
175242
}
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');
176273

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();
183275

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+
}
185295
}
186-
}
187296

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+
}
191305
}
192306

193307
function reportTestSuiteResult(testSuiteResult: TestSuiteResult): void {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
* @oncall react_native
10+
*/
11+
12+
const logs: string[] = [];
13+
14+
beforeAll(() => {
15+
logs.push('root - beforeAll');
16+
});
17+
18+
afterAll(() => {
19+
logs.push('root - afterAll');
20+
});
21+
22+
beforeEach(() => {
23+
logs.push('root - beforeEach');
24+
});
25+
26+
afterEach(() => {
27+
logs.push('root - afterEach');
28+
});
29+
30+
test('root hooks', () => {
31+
logs.push('root - test');
32+
expect(logs).toMatchSnapshot();
33+
});
34+
35+
describe('FantomSetupHooks', () => {
36+
beforeAll(() => {
37+
logs.push('FantomSetupHooks - beforeAll');
38+
});
39+
40+
afterAll(() => {
41+
logs.push('FantomSetupHooks - afterAll');
42+
});
43+
44+
beforeEach(() => {
45+
logs.push('FantomSetupHooks - beforeEach');
46+
});
47+
48+
afterEach(() => {
49+
logs.push('FantomSetupHooks - afterEach');
50+
});
51+
52+
test('first', () => {
53+
logs.push('FantomSetupHooks - first');
54+
expect(logs).toMatchSnapshot();
55+
});
56+
57+
test('second', () => {
58+
logs.push('FantomSetupHooks - second');
59+
expect(logs).toMatchSnapshot();
60+
});
61+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`FantomSetupHooks first 1`] = `
4+
Array [
5+
"root - beforeAll",
6+
"root - beforeEach",
7+
"root - test",
8+
"root - afterEach",
9+
"FantomSetupHooks - beforeAll",
10+
"root - beforeEach",
11+
"FantomSetupHooks - beforeEach",
12+
"FantomSetupHooks - first",
13+
]
14+
`;
15+
16+
exports[`FantomSetupHooks second 1`] = `
17+
Array [
18+
"root - beforeAll",
19+
"root - beforeEach",
20+
"root - test",
21+
"root - afterEach",
22+
"FantomSetupHooks - beforeAll",
23+
"root - beforeEach",
24+
"FantomSetupHooks - beforeEach",
25+
"FantomSetupHooks - first",
26+
"FantomSetupHooks - afterEach",
27+
"root - afterEach",
28+
"root - beforeEach",
29+
"FantomSetupHooks - beforeEach",
30+
"FantomSetupHooks - second",
31+
]
32+
`;
33+
34+
exports[`root hooks 1`] = `
35+
Array [
36+
"root - beforeAll",
37+
"root - beforeEach",
38+
"root - test",
39+
]
40+
`;

0 commit comments

Comments
 (0)