@react-native-harness/runtime 1.2.0 → 1.3.0
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/dist/collector/functions.d.ts +3 -3
- package/dist/collector/functions.d.ts.map +1 -1
- package/dist/collector/types.d.ts +3 -2
- package/dist/collector/types.d.ts.map +1 -1
- package/dist/collector/validation.d.ts +2 -2
- package/dist/collector/validation.d.ts.map +1 -1
- package/dist/device/index.d.ts +12 -0
- package/dist/device/index.d.ts.map +1 -0
- package/dist/device/index.js +62 -0
- package/dist/hmr.d.ts +2 -0
- package/dist/hmr.d.ts.map +1 -0
- package/dist/hmr.js +5 -0
- package/dist/logbox.d.ts +4 -0
- package/dist/logbox.d.ts.map +1 -0
- package/dist/logbox.js +18 -0
- package/dist/runner/hooks.d.ts +2 -1
- package/dist/runner/hooks.d.ts.map +1 -1
- package/dist/runner/hooks.js +27 -17
- package/dist/runner/runSuite.d.ts.map +1 -1
- package/dist/runner/runSuite.js +56 -6
- package/dist/runner/test-context.d.ts +16 -0
- package/dist/runner/test-context.d.ts.map +1 -0
- package/dist/runner/test-context.js +57 -0
- package/dist/runner/types.d.ts +2 -1
- package/dist/runner/types.d.ts.map +1 -1
- package/dist/test-utils/react-native-url-polyfill.d.ts +9 -0
- package/dist/test-utils/react-native-url-polyfill.d.ts.map +1 -0
- package/dist/test-utils/react-native-url-polyfill.js +1 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/out-tsc/vitest/src/__tests__/device.test.d.ts +2 -0
- package/out-tsc/vitest/src/__tests__/device.test.d.ts.map +1 -0
- package/out-tsc/vitest/src/__tests__/logbox.test.d.ts +2 -0
- package/out-tsc/vitest/src/__tests__/logbox.test.d.ts.map +1 -0
- package/out-tsc/vitest/src/__tests__/runner-context.test.d.ts +2 -0
- package/out-tsc/vitest/src/__tests__/runner-context.test.d.ts.map +1 -0
- package/out-tsc/vitest/src/collector/functions.d.ts +3 -3
- package/out-tsc/vitest/src/collector/functions.d.ts.map +1 -1
- package/out-tsc/vitest/src/collector/types.d.ts +3 -2
- package/out-tsc/vitest/src/collector/types.d.ts.map +1 -1
- package/out-tsc/vitest/src/collector/validation.d.ts +2 -2
- package/out-tsc/vitest/src/collector/validation.d.ts.map +1 -1
- package/out-tsc/vitest/src/device/index.d.ts +12 -0
- package/out-tsc/vitest/src/device/index.d.ts.map +1 -0
- package/out-tsc/vitest/src/hmr.d.ts +2 -0
- package/out-tsc/vitest/src/hmr.d.ts.map +1 -0
- package/out-tsc/vitest/src/logbox.d.ts +4 -0
- package/out-tsc/vitest/src/logbox.d.ts.map +1 -0
- package/out-tsc/vitest/src/runner/hooks.d.ts +2 -1
- package/out-tsc/vitest/src/runner/hooks.d.ts.map +1 -1
- package/out-tsc/vitest/src/runner/runSuite.d.ts.map +1 -1
- package/out-tsc/vitest/src/runner/test-context.d.ts +16 -0
- package/out-tsc/vitest/src/runner/test-context.d.ts.map +1 -0
- package/out-tsc/vitest/src/runner/types.d.ts +2 -1
- package/out-tsc/vitest/src/runner/types.d.ts.map +1 -1
- package/out-tsc/vitest/src/test-utils/react-native-url-polyfill.d.ts +9 -0
- package/out-tsc/vitest/src/test-utils/react-native-url-polyfill.d.ts.map +1 -0
- package/out-tsc/vitest/tsconfig.spec.tsbuildinfo +1 -1
- package/out-tsc/vitest/vite.config.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/__tests__/runner-context.test.ts +483 -0
- package/src/collector/functions.ts +5 -4
- package/src/collector/types.ts +4 -1
- package/src/collector/validation.ts +2 -2
- package/src/runner/hooks.ts +43 -19
- package/src/runner/runSuite.ts +75 -9
- package/src/runner/test-context.ts +84 -0
- package/src/runner/types.ts +3 -0
- package/src/test-utils/react-native-url-polyfill.ts +1 -0
- package/vite.config.ts +4 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vite.config.d.ts","sourceRoot":"","sources":["../../vite.config.ts"],"names":[],"mappings":";AAIA,
|
|
1
|
+
{"version":3,"file":"vite.config.d.ts","sourceRoot":"","sources":["../../vite.config.ts"],"names":[],"mappings":";AAIA,wBA0BI"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-harness/runtime",
|
|
3
3
|
"description": "The core test runtime that executes on React Native devices, providing Jest-compatible APIs (describe, it, expect) and managing test collection, execution, and result reporting in native environments.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"react-native-url-polyfill": "^3.0.0",
|
|
48
48
|
"use-sync-external-store": "^1.6.0",
|
|
49
49
|
"zustand": "^5.0.5",
|
|
50
|
-
"@react-native-harness/bridge": "1.
|
|
50
|
+
"@react-native-harness/bridge": "1.3.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/chai": "^5.2.2"
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterEach,
|
|
3
|
+
beforeEach,
|
|
4
|
+
describe as harnessDescribe,
|
|
5
|
+
getTestCollector,
|
|
6
|
+
it as harnessIt,
|
|
7
|
+
} from '../collector/index.js';
|
|
8
|
+
import type { HarnessTestContext } from '@react-native-harness/bridge';
|
|
9
|
+
import { getTestRunner } from '../runner/index.js';
|
|
10
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
11
|
+
|
|
12
|
+
vi.mock('../symbolicate.js', async () => {
|
|
13
|
+
return {
|
|
14
|
+
getCodeFrame: vi.fn(async () => null),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const getTask = (context: HarnessTestContext) => {
|
|
19
|
+
return context.task;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const getTaskContext = (context: HarnessTestContext) => {
|
|
23
|
+
return context;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe('runner task context', () => {
|
|
27
|
+
it('passes minimal task metadata to tests and per-test hooks', async () => {
|
|
28
|
+
const observedTasks: Array<{
|
|
29
|
+
source: 'beforeEach' | 'test' | 'afterEach';
|
|
30
|
+
task: {
|
|
31
|
+
name: string;
|
|
32
|
+
type: 'test';
|
|
33
|
+
mode: 'run' | 'skip' | 'todo';
|
|
34
|
+
file: { name: string };
|
|
35
|
+
suite: { name: string };
|
|
36
|
+
};
|
|
37
|
+
}> = [];
|
|
38
|
+
const collector = getTestCollector();
|
|
39
|
+
const runner = getTestRunner();
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
const collection = await collector.collect(() => {
|
|
43
|
+
harnessDescribe('Task Context Suite', () => {
|
|
44
|
+
beforeEach((context: HarnessTestContext) => {
|
|
45
|
+
observedTasks.push({ source: 'beforeEach', task: getTask(context) });
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
afterEach((context: HarnessTestContext) => {
|
|
49
|
+
observedTasks.push({ source: 'afterEach', task: getTask(context) });
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
harnessIt('exposes task metadata', (context: HarnessTestContext) => {
|
|
53
|
+
observedTasks.push({ source: 'test', task: getTask(context) });
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
}, 'runtime/context.test.ts');
|
|
57
|
+
|
|
58
|
+
const result = await runner.run({
|
|
59
|
+
testSuite: collection.testSuite,
|
|
60
|
+
testFilePath: 'runtime/context.test.ts',
|
|
61
|
+
runner: 'ios',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(result.status).toBe('passed');
|
|
65
|
+
expect(result.suites[0].tests[0]).toMatchObject({
|
|
66
|
+
name: 'exposes task metadata',
|
|
67
|
+
status: 'passed',
|
|
68
|
+
});
|
|
69
|
+
expect(observedTasks).toEqual([
|
|
70
|
+
{
|
|
71
|
+
source: 'beforeEach',
|
|
72
|
+
task: {
|
|
73
|
+
name: 'exposes task metadata',
|
|
74
|
+
type: 'test',
|
|
75
|
+
mode: 'run',
|
|
76
|
+
file: { name: 'runtime/context.test.ts' },
|
|
77
|
+
suite: { name: 'Task Context Suite' },
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
source: 'test',
|
|
82
|
+
task: {
|
|
83
|
+
name: 'exposes task metadata',
|
|
84
|
+
type: 'test',
|
|
85
|
+
mode: 'run',
|
|
86
|
+
file: { name: 'runtime/context.test.ts' },
|
|
87
|
+
suite: { name: 'Task Context Suite' },
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
source: 'afterEach',
|
|
92
|
+
task: {
|
|
93
|
+
name: 'exposes task metadata',
|
|
94
|
+
type: 'test',
|
|
95
|
+
mode: 'run',
|
|
96
|
+
file: { name: 'runtime/context.test.ts' },
|
|
97
|
+
suite: { name: 'Task Context Suite' },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
]);
|
|
101
|
+
} finally {
|
|
102
|
+
collector.dispose();
|
|
103
|
+
runner.dispose();
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('keeps zero-argument tests and hooks working', async () => {
|
|
108
|
+
const calls: string[] = [];
|
|
109
|
+
const collector = getTestCollector();
|
|
110
|
+
const runner = getTestRunner();
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const collection = await collector.collect(() => {
|
|
114
|
+
harnessDescribe('Compatibility Suite', () => {
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
calls.push('beforeEach');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
afterEach(() => {
|
|
120
|
+
calls.push('afterEach');
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
harnessIt('still runs', () => {
|
|
124
|
+
calls.push('test');
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
}, 'runtime/compatibility.test.ts');
|
|
128
|
+
|
|
129
|
+
const result = await runner.run({
|
|
130
|
+
testSuite: collection.testSuite,
|
|
131
|
+
testFilePath: 'runtime/compatibility.test.ts',
|
|
132
|
+
runner: 'android',
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
expect(result.suites[0].tests[0]).toMatchObject({ status: 'passed' });
|
|
136
|
+
expect(calls).toEqual(['beforeEach', 'test', 'afterEach']);
|
|
137
|
+
} finally {
|
|
138
|
+
collector.dispose();
|
|
139
|
+
runner.dispose();
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('marks dynamically skipped tests as skipped and still runs afterEach', async () => {
|
|
144
|
+
const calls: string[] = [];
|
|
145
|
+
const collector = getTestCollector();
|
|
146
|
+
const runner = getTestRunner();
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const collection = await collector.collect(() => {
|
|
150
|
+
harnessDescribe('Skip Suite', () => {
|
|
151
|
+
afterEach(() => {
|
|
152
|
+
calls.push('afterEach');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
harnessIt('skips from context', (context: HarnessTestContext) => {
|
|
156
|
+
const { skip } = getTaskContext(context);
|
|
157
|
+
|
|
158
|
+
calls.push('before-skip');
|
|
159
|
+
skip('skip this test');
|
|
160
|
+
calls.push('after-skip');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
harnessIt('still runs sibling test', () => {
|
|
164
|
+
calls.push('sibling');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}, 'runtime/skip.test.ts');
|
|
168
|
+
|
|
169
|
+
const result = await runner.run({
|
|
170
|
+
testSuite: collection.testSuite,
|
|
171
|
+
testFilePath: 'runtime/skip.test.ts',
|
|
172
|
+
runner: 'ios',
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
expect(result.suites[0].tests).toMatchObject([
|
|
176
|
+
{ name: 'skips from context', status: 'skipped' },
|
|
177
|
+
{ name: 'still runs sibling test', status: 'passed' },
|
|
178
|
+
]);
|
|
179
|
+
expect(calls).toEqual([
|
|
180
|
+
'before-skip',
|
|
181
|
+
'afterEach',
|
|
182
|
+
'sibling',
|
|
183
|
+
'afterEach',
|
|
184
|
+
]);
|
|
185
|
+
} finally {
|
|
186
|
+
collector.dispose();
|
|
187
|
+
runner.dispose();
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('supports conditional skipping without changing false conditions', async () => {
|
|
192
|
+
const calls: string[] = [];
|
|
193
|
+
const collector = getTestCollector();
|
|
194
|
+
const runner = getTestRunner();
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const collection = await collector.collect(() => {
|
|
198
|
+
harnessDescribe('Conditional Skip Suite', () => {
|
|
199
|
+
harnessIt('continues when condition is false', (context: HarnessTestContext) => {
|
|
200
|
+
const { skip } = getTaskContext(context);
|
|
201
|
+
|
|
202
|
+
calls.push('before');
|
|
203
|
+
skip(false, 'do not skip');
|
|
204
|
+
calls.push('after');
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}, 'runtime/conditional-skip.test.ts');
|
|
208
|
+
|
|
209
|
+
const result = await runner.run({
|
|
210
|
+
testSuite: collection.testSuite,
|
|
211
|
+
testFilePath: 'runtime/conditional-skip.test.ts',
|
|
212
|
+
runner: 'android',
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
expect(result.suites[0].tests[0]).toMatchObject({ status: 'passed' });
|
|
216
|
+
expect(calls).toEqual(['before', 'after']);
|
|
217
|
+
} finally {
|
|
218
|
+
collector.dispose();
|
|
219
|
+
runner.dispose();
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it('runs onTestFinished after afterEach for passing tests', async () => {
|
|
224
|
+
const calls: string[] = [];
|
|
225
|
+
const collector = getTestCollector();
|
|
226
|
+
const runner = getTestRunner();
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const collection = await collector.collect(() => {
|
|
230
|
+
harnessDescribe('Finished Suite', () => {
|
|
231
|
+
afterEach(() => {
|
|
232
|
+
calls.push('afterEach');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
harnessIt('runs finished callbacks', (context: HarnessTestContext) => {
|
|
236
|
+
const { onTestFinished } = getTaskContext(context);
|
|
237
|
+
|
|
238
|
+
onTestFinished(() => {
|
|
239
|
+
calls.push('onTestFinished:first');
|
|
240
|
+
});
|
|
241
|
+
onTestFinished(() => {
|
|
242
|
+
calls.push('onTestFinished:second');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
calls.push('test');
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
}, 'runtime/on-test-finished-pass.test.ts');
|
|
249
|
+
|
|
250
|
+
const result = await runner.run({
|
|
251
|
+
testSuite: collection.testSuite,
|
|
252
|
+
testFilePath: 'runtime/on-test-finished-pass.test.ts',
|
|
253
|
+
runner: 'ios',
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
expect(result.suites[0].tests[0]).toMatchObject({ status: 'passed' });
|
|
257
|
+
expect(calls).toEqual([
|
|
258
|
+
'test',
|
|
259
|
+
'afterEach',
|
|
260
|
+
'onTestFinished:second',
|
|
261
|
+
'onTestFinished:first',
|
|
262
|
+
]);
|
|
263
|
+
} finally {
|
|
264
|
+
collector.dispose();
|
|
265
|
+
runner.dispose();
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('runs onTestFinished for dynamically skipped tests', async () => {
|
|
270
|
+
const calls: string[] = [];
|
|
271
|
+
const collector = getTestCollector();
|
|
272
|
+
const runner = getTestRunner();
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
const collection = await collector.collect(() => {
|
|
276
|
+
harnessDescribe('Finished Skip Suite', () => {
|
|
277
|
+
afterEach(() => {
|
|
278
|
+
calls.push('afterEach');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
harnessIt(
|
|
282
|
+
'runs finished callback after skip',
|
|
283
|
+
(context: HarnessTestContext) => {
|
|
284
|
+
const { onTestFinished, skip } = getTaskContext(context);
|
|
285
|
+
|
|
286
|
+
onTestFinished(() => {
|
|
287
|
+
calls.push('onTestFinished');
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
calls.push('before-skip');
|
|
291
|
+
skip();
|
|
292
|
+
},
|
|
293
|
+
);
|
|
294
|
+
});
|
|
295
|
+
}, 'runtime/on-test-finished-skip.test.ts');
|
|
296
|
+
|
|
297
|
+
const result = await runner.run({
|
|
298
|
+
testSuite: collection.testSuite,
|
|
299
|
+
testFilePath: 'runtime/on-test-finished-skip.test.ts',
|
|
300
|
+
runner: 'android',
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
expect(result.suites[0].tests[0]).toMatchObject({ status: 'skipped' });
|
|
304
|
+
expect(calls).toEqual(['before-skip', 'afterEach', 'onTestFinished']);
|
|
305
|
+
} finally {
|
|
306
|
+
collector.dispose();
|
|
307
|
+
runner.dispose();
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('runs onTestFinished for failed tests', async () => {
|
|
312
|
+
const calls: string[] = [];
|
|
313
|
+
const collector = getTestCollector();
|
|
314
|
+
const runner = getTestRunner();
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const collection = await collector.collect(() => {
|
|
318
|
+
harnessDescribe('Finished Failure Suite', () => {
|
|
319
|
+
afterEach(() => {
|
|
320
|
+
calls.push('afterEach');
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
harnessIt(
|
|
324
|
+
'runs finished callback after failure',
|
|
325
|
+
(context: HarnessTestContext) => {
|
|
326
|
+
const { onTestFinished } = getTaskContext(context);
|
|
327
|
+
|
|
328
|
+
onTestFinished(() => {
|
|
329
|
+
calls.push('onTestFinished');
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
calls.push('test');
|
|
333
|
+
throw new Error('expected failure');
|
|
334
|
+
},
|
|
335
|
+
);
|
|
336
|
+
});
|
|
337
|
+
}, 'runtime/on-test-finished-failure.test.ts');
|
|
338
|
+
|
|
339
|
+
const result = await runner.run({
|
|
340
|
+
testSuite: collection.testSuite,
|
|
341
|
+
testFilePath: 'runtime/on-test-finished-failure.test.ts',
|
|
342
|
+
runner: 'ios',
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
expect(result.suites[0].tests[0]).toMatchObject({ status: 'failed' });
|
|
346
|
+
expect(calls).toEqual(['test', 'afterEach', 'onTestFinished']);
|
|
347
|
+
} finally {
|
|
348
|
+
collector.dispose();
|
|
349
|
+
runner.dispose();
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it('runs onTestFailed after afterEach for failed tests', async () => {
|
|
354
|
+
const calls: string[] = [];
|
|
355
|
+
const collector = getTestCollector();
|
|
356
|
+
const runner = getTestRunner();
|
|
357
|
+
|
|
358
|
+
try {
|
|
359
|
+
const collection = await collector.collect(() => {
|
|
360
|
+
harnessDescribe('Failed Hook Suite', () => {
|
|
361
|
+
afterEach(() => {
|
|
362
|
+
calls.push('afterEach');
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
harnessIt('runs failed callbacks', (context: HarnessTestContext) => {
|
|
366
|
+
const { onTestFailed } = getTaskContext(context);
|
|
367
|
+
|
|
368
|
+
onTestFailed(() => {
|
|
369
|
+
calls.push('onTestFailed:first');
|
|
370
|
+
});
|
|
371
|
+
onTestFailed(() => {
|
|
372
|
+
calls.push('onTestFailed:second');
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
calls.push('test');
|
|
376
|
+
throw new Error('expected failure');
|
|
377
|
+
});
|
|
378
|
+
});
|
|
379
|
+
}, 'runtime/on-test-failed-failure.test.ts');
|
|
380
|
+
|
|
381
|
+
const result = await runner.run({
|
|
382
|
+
testSuite: collection.testSuite,
|
|
383
|
+
testFilePath: 'runtime/on-test-failed-failure.test.ts',
|
|
384
|
+
runner: 'ios',
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
expect(result.suites[0].tests[0]).toMatchObject({ status: 'failed' });
|
|
388
|
+
expect(calls).toEqual([
|
|
389
|
+
'test',
|
|
390
|
+
'afterEach',
|
|
391
|
+
'onTestFailed:second',
|
|
392
|
+
'onTestFailed:first',
|
|
393
|
+
]);
|
|
394
|
+
} finally {
|
|
395
|
+
collector.dispose();
|
|
396
|
+
runner.dispose();
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
it('does not run onTestFailed for dynamically skipped tests', async () => {
|
|
401
|
+
const calls: string[] = [];
|
|
402
|
+
const collector = getTestCollector();
|
|
403
|
+
const runner = getTestRunner();
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
const collection = await collector.collect(() => {
|
|
407
|
+
harnessDescribe('Failed Skip Suite', () => {
|
|
408
|
+
afterEach(() => {
|
|
409
|
+
calls.push('afterEach');
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
harnessIt(
|
|
413
|
+
'does not run failed callbacks on skip',
|
|
414
|
+
(context: HarnessTestContext) => {
|
|
415
|
+
const { onTestFailed, skip } = getTaskContext(context);
|
|
416
|
+
|
|
417
|
+
onTestFailed(() => {
|
|
418
|
+
calls.push('onTestFailed');
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
calls.push('before-skip');
|
|
422
|
+
skip();
|
|
423
|
+
},
|
|
424
|
+
);
|
|
425
|
+
});
|
|
426
|
+
}, 'runtime/on-test-failed-skip.test.ts');
|
|
427
|
+
|
|
428
|
+
const result = await runner.run({
|
|
429
|
+
testSuite: collection.testSuite,
|
|
430
|
+
testFilePath: 'runtime/on-test-failed-skip.test.ts',
|
|
431
|
+
runner: 'android',
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
expect(result.suites[0].tests[0]).toMatchObject({ status: 'skipped' });
|
|
435
|
+
expect(calls).toEqual(['before-skip', 'afterEach']);
|
|
436
|
+
} finally {
|
|
437
|
+
collector.dispose();
|
|
438
|
+
runner.dispose();
|
|
439
|
+
}
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('runs onTestFailed when afterEach fails', async () => {
|
|
443
|
+
const calls: string[] = [];
|
|
444
|
+
const collector = getTestCollector();
|
|
445
|
+
const runner = getTestRunner();
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
const collection = await collector.collect(() => {
|
|
449
|
+
harnessDescribe('Failed AfterEach Suite', () => {
|
|
450
|
+
afterEach(() => {
|
|
451
|
+
calls.push('afterEach');
|
|
452
|
+
throw new Error('afterEach failure');
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
harnessIt(
|
|
456
|
+
'runs failed callback after afterEach failure',
|
|
457
|
+
(context: HarnessTestContext) => {
|
|
458
|
+
const { onTestFailed } = getTaskContext(context);
|
|
459
|
+
|
|
460
|
+
onTestFailed(() => {
|
|
461
|
+
calls.push('onTestFailed');
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
calls.push('test');
|
|
465
|
+
},
|
|
466
|
+
);
|
|
467
|
+
});
|
|
468
|
+
}, 'runtime/on-test-failed-after-each.test.ts');
|
|
469
|
+
|
|
470
|
+
const result = await runner.run({
|
|
471
|
+
testSuite: collection.testSuite,
|
|
472
|
+
testFilePath: 'runtime/on-test-failed-after-each.test.ts',
|
|
473
|
+
runner: 'ios',
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
expect(result.suites[0].tests[0]).toMatchObject({ status: 'failed' });
|
|
477
|
+
expect(calls).toEqual(['test', 'afterEach', 'onTestFailed']);
|
|
478
|
+
} finally {
|
|
479
|
+
collector.dispose();
|
|
480
|
+
runner.dispose();
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
});
|
|
@@ -2,6 +2,7 @@ import type {
|
|
|
2
2
|
TestCase,
|
|
3
3
|
TestSuite,
|
|
4
4
|
CollectionResult,
|
|
5
|
+
SuiteHookFn,
|
|
5
6
|
} from '@react-native-harness/bridge';
|
|
6
7
|
import type { TestFn } from './types.js';
|
|
7
8
|
import { TestError } from './errors.js';
|
|
@@ -24,8 +25,8 @@ type RawTestSuite = {
|
|
|
24
25
|
tests: RawTestCase[];
|
|
25
26
|
suites: RawTestSuite[];
|
|
26
27
|
hooks: {
|
|
27
|
-
beforeAll:
|
|
28
|
-
afterAll:
|
|
28
|
+
beforeAll: SuiteHookFn[];
|
|
29
|
+
afterAll: SuiteHookFn[];
|
|
29
30
|
beforeEach: TestFn[];
|
|
30
31
|
afterEach: TestFn[];
|
|
31
32
|
};
|
|
@@ -316,7 +317,7 @@ export const test = Object.assign(
|
|
|
316
317
|
|
|
317
318
|
export const it = test;
|
|
318
319
|
|
|
319
|
-
export function beforeAll(fn:
|
|
320
|
+
export function beforeAll(fn: SuiteHookFn) {
|
|
320
321
|
validateTestFunction(fn, 'beforeAll');
|
|
321
322
|
|
|
322
323
|
const currentSuite = getCurrentSuite();
|
|
@@ -326,7 +327,7 @@ export function beforeAll(fn: TestFn) {
|
|
|
326
327
|
currentSuite.hooks.beforeAll.push(fn);
|
|
327
328
|
}
|
|
328
329
|
|
|
329
|
-
export function afterAll(fn:
|
|
330
|
+
export function afterAll(fn: SuiteHookFn) {
|
|
330
331
|
validateTestFunction(fn, 'afterAll');
|
|
331
332
|
|
|
332
333
|
const currentSuite = getCurrentSuite();
|
package/src/collector/types.ts
CHANGED
|
@@ -2,9 +2,12 @@ import { EventEmitter } from '../utils/emitter.js';
|
|
|
2
2
|
import {
|
|
3
3
|
TestCollectorEvents,
|
|
4
4
|
CollectionResult,
|
|
5
|
+
type HarnessTestContext,
|
|
5
6
|
} from '@react-native-harness/bridge';
|
|
6
7
|
|
|
7
|
-
export type TestFn = () => void | Promise<void>;
|
|
8
|
+
export type TestFn = (context: HarnessTestContext) => void | Promise<void>;
|
|
9
|
+
|
|
10
|
+
export type SuiteHookFn = () => void | Promise<void>;
|
|
8
11
|
|
|
9
12
|
export type TestCollectorEventsEmitter = EventEmitter<TestCollectorEvents>;
|
|
10
13
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { TestError } from './errors.js';
|
|
2
|
-
import { TestFn } from './types.js';
|
|
2
|
+
import { TestFn, SuiteHookFn } from './types.js';
|
|
3
3
|
|
|
4
4
|
export const validateTestName = (name: string, functionName: string): void => {
|
|
5
5
|
if (!name || typeof name !== 'string' || name.trim() === '') {
|
|
@@ -10,7 +10,7 @@ export const validateTestName = (name: string, functionName: string): void => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export const validateTestFunction = (
|
|
13
|
-
fn: TestFn,
|
|
13
|
+
fn: TestFn | SuiteHookFn,
|
|
14
14
|
functionName: string
|
|
15
15
|
): void => {
|
|
16
16
|
if (typeof fn !== 'function') {
|
package/src/runner/hooks.ts
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
|
-
import type { TestSuite } from '@react-native-harness/bridge';
|
|
1
|
+
import type { SuiteHookFn, TestFn, TestSuite } from '@react-native-harness/bridge';
|
|
2
|
+
import type { ActiveTestContext } from './types.js';
|
|
2
3
|
|
|
3
4
|
export type HookType = 'beforeEach' | 'afterEach' | 'beforeAll' | 'afterAll';
|
|
4
5
|
|
|
5
6
|
const collectInheritedHooks = (
|
|
6
7
|
suite: TestSuite,
|
|
7
|
-
hookType:
|
|
8
|
-
):
|
|
9
|
-
const hooks:
|
|
8
|
+
hookType: 'beforeEach' | 'afterEach'
|
|
9
|
+
): TestFn[] => {
|
|
10
|
+
const hooks: TestFn[] = [];
|
|
11
|
+
const suiteChain: TestSuite[] = [];
|
|
12
|
+
|
|
13
|
+
let current: TestSuite | undefined = suite;
|
|
14
|
+
while (current) {
|
|
15
|
+
suiteChain.unshift(current);
|
|
16
|
+
current = current.parent;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
for (const currentSuite of suiteChain) {
|
|
20
|
+
hooks.push(...currentSuite[hookType]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return hooks;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const collectSuiteHooks = (
|
|
27
|
+
suite: TestSuite,
|
|
28
|
+
hookType: 'beforeAll' | 'afterAll'
|
|
29
|
+
): SuiteHookFn[] => {
|
|
30
|
+
const hooks: SuiteHookFn[] = [];
|
|
10
31
|
const suiteChain: TestSuite[] = [];
|
|
11
32
|
|
|
12
33
|
// Collect all suites from current to root
|
|
@@ -16,23 +37,15 @@ const collectInheritedHooks = (
|
|
|
16
37
|
currentSuite = currentSuite.parent;
|
|
17
38
|
}
|
|
18
39
|
|
|
19
|
-
if (hookType === '
|
|
20
|
-
//
|
|
40
|
+
if (hookType === 'beforeAll') {
|
|
41
|
+
// Run parent suite hooks before child suite hooks.
|
|
21
42
|
for (let i = suiteChain.length - 1; i >= 0; i--) {
|
|
22
|
-
|
|
23
|
-
hooks.push(...suiteChain[i].beforeEach);
|
|
24
|
-
} else {
|
|
25
|
-
hooks.push(...suiteChain[i].beforeAll);
|
|
26
|
-
}
|
|
43
|
+
hooks.push(...suiteChain[i].beforeAll);
|
|
27
44
|
}
|
|
28
45
|
} else {
|
|
29
|
-
//
|
|
46
|
+
// Run child suite hooks before parent suite hooks.
|
|
30
47
|
for (const suiteInChain of suiteChain) {
|
|
31
|
-
|
|
32
|
-
hooks.push(...suiteInChain.afterEach);
|
|
33
|
-
} else {
|
|
34
|
-
hooks.push(...suiteInChain.afterAll);
|
|
35
|
-
}
|
|
48
|
+
hooks.push(...suiteInChain.afterAll);
|
|
36
49
|
}
|
|
37
50
|
}
|
|
38
51
|
|
|
@@ -41,11 +54,22 @@ const collectInheritedHooks = (
|
|
|
41
54
|
|
|
42
55
|
export const runHooks = async (
|
|
43
56
|
suite: TestSuite,
|
|
44
|
-
hookType: HookType
|
|
57
|
+
hookType: HookType,
|
|
58
|
+
context?: ActiveTestContext,
|
|
45
59
|
): Promise<void> => {
|
|
60
|
+
if (hookType === 'beforeAll' || hookType === 'afterAll') {
|
|
61
|
+
const hooks = collectSuiteHooks(suite, hookType);
|
|
62
|
+
|
|
63
|
+
for (const hook of hooks) {
|
|
64
|
+
await hook();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
46
70
|
const hooks = collectInheritedHooks(suite, hookType);
|
|
47
71
|
|
|
48
72
|
for (const hook of hooks) {
|
|
49
|
-
await hook();
|
|
73
|
+
await hook(context as ActiveTestContext);
|
|
50
74
|
}
|
|
51
75
|
};
|