@react-native-harness/runtime 1.2.0-rc.1 → 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/client/factory.d.ts +2 -1
- package/dist/client/factory.d.ts.map +1 -1
- package/dist/client/factory.js +53 -59
- package/dist/client/store.d.ts +3 -3
- package/dist/client/store.d.ts.map +1 -1
- package/dist/client/store.js +7 -7
- 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/expect/matchers/toMatchImageSnapshot.d.ts +1 -1
- package/dist/expect/matchers/toMatchImageSnapshot.d.ts.map +1 -1
- package/dist/expect/matchers/toMatchImageSnapshot.js +4 -12
- package/dist/hmr.d.ts +2 -0
- package/dist/hmr.d.ts.map +1 -0
- package/dist/hmr.js +5 -0
- package/dist/initialize.js +14 -5
- package/dist/jsx/jsx-dev-runtime.d.ts +2 -1
- package/dist/jsx/jsx-dev-runtime.d.ts.map +1 -1
- package/dist/jsx/jsx-dev-runtime.js +16 -7
- 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/dist/waitFor.d.ts.map +1 -1
- package/dist/waitFor.js +5 -3
- 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/client/factory.d.ts +2 -1
- package/out-tsc/vitest/src/client/factory.d.ts.map +1 -1
- package/out-tsc/vitest/src/client/store.d.ts +3 -3
- package/out-tsc/vitest/src/client/store.d.ts.map +1 -1
- 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/expect/matchers/toMatchImageSnapshot.d.ts +1 -1
- package/out-tsc/vitest/src/expect/matchers/toMatchImageSnapshot.d.ts.map +1 -1
- 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/jsx/jsx-dev-runtime.d.ts +2 -1
- package/out-tsc/vitest/src/jsx/jsx-dev-runtime.d.ts.map +1 -1
- 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/src/waitFor.d.ts.map +1 -1
- 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/client/factory.ts +63 -74
- package/src/client/store.ts +8 -8
- package/src/collector/functions.ts +5 -4
- package/src/collector/types.ts +4 -1
- package/src/collector/validation.ts +2 -2
- package/src/expect/matchers/toMatchImageSnapshot.ts +9 -23
- package/src/initialize.ts +14 -5
- package/src/jsx/jsx-dev-runtime.ts +34 -15
- 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/src/waitFor.ts +8 -6
- 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
|
+
});
|
package/src/client/factory.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type {
|
|
|
4
4
|
BundlerEvents,
|
|
5
5
|
TestExecutionOptions,
|
|
6
6
|
} from '@react-native-harness/bridge';
|
|
7
|
-
import {
|
|
7
|
+
import { connectToHarness, type HarnessHandle } from '@react-native-harness/bridge/client';
|
|
8
8
|
import { store } from '../ui/state.js';
|
|
9
9
|
import { getTestRunner, TestRunner } from '../runner/index.js';
|
|
10
10
|
import { getTestCollector, TestCollector } from '../collector/index.js';
|
|
@@ -14,92 +14,81 @@ import { getBundler, evaluateModule, Bundler } from '../bundler/index.js';
|
|
|
14
14
|
import { markTestsAsSkippedByName } from '../filtering/index.js';
|
|
15
15
|
import { setup } from '../render/setup.js';
|
|
16
16
|
import { runSetupFiles } from './setup-files.js';
|
|
17
|
-
import {
|
|
17
|
+
import { setHandle } from './store.js';
|
|
18
18
|
|
|
19
|
-
export const getClient = async () => {
|
|
20
|
-
const
|
|
21
|
-
runTests: async () => {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
setClient(client);
|
|
27
|
-
|
|
28
|
-
client.rpc.$functions.runTests = async (
|
|
29
|
-
path: string,
|
|
30
|
-
options: TestExecutionOptions
|
|
31
|
-
) => {
|
|
32
|
-
if (store.getState().status === 'running') {
|
|
33
|
-
throw new Error('Already running tests');
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
store.getState().setStatus('running');
|
|
19
|
+
export const getClient = async (): Promise<HarnessHandle> => {
|
|
20
|
+
const handle = await connectToHarness(getWSServer(), {
|
|
21
|
+
runTests: async (path: string, options: TestExecutionOptions) => {
|
|
22
|
+
if (store.getState().status === 'running') {
|
|
23
|
+
throw new Error('Already running tests');
|
|
24
|
+
}
|
|
37
25
|
|
|
38
|
-
|
|
39
|
-
let runner: TestRunner | null = null;
|
|
40
|
-
let events: EventEmitter<
|
|
41
|
-
TestRunnerEvents | TestCollectorEvents | BundlerEvents
|
|
42
|
-
> | null = null;
|
|
43
|
-
let bundler: Bundler | null = null;
|
|
26
|
+
store.getState().setStatus('running');
|
|
44
27
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
runner.events,
|
|
52
|
-
bundler.events
|
|
53
|
-
);
|
|
28
|
+
let collector: TestCollector | null = null;
|
|
29
|
+
let runner: TestRunner | null = null;
|
|
30
|
+
let events: EventEmitter<
|
|
31
|
+
TestRunnerEvents | TestCollectorEvents | BundlerEvents
|
|
32
|
+
> | null = null;
|
|
33
|
+
let bundler: Bundler | null = null;
|
|
54
34
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
35
|
+
try {
|
|
36
|
+
collector = getTestCollector();
|
|
37
|
+
runner = getTestRunner();
|
|
38
|
+
bundler = getBundler();
|
|
39
|
+
events = combineEventEmitters(
|
|
40
|
+
collector.events,
|
|
41
|
+
runner.events,
|
|
42
|
+
bundler.events,
|
|
43
|
+
);
|
|
58
44
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
events: events as EventEmitter<BundlerEvents>,
|
|
63
|
-
bundler: bundler as Bundler,
|
|
64
|
-
evaluateModule,
|
|
65
|
-
});
|
|
45
|
+
events.addListener((event) => {
|
|
46
|
+
handle.emitEvent(event);
|
|
47
|
+
});
|
|
66
48
|
|
|
67
|
-
const moduleJs = await bundler.getModule(path);
|
|
68
|
-
const collectionResult = await collector.collect(async () => {
|
|
69
49
|
await runSetupFiles({
|
|
70
|
-
setupFiles: [],
|
|
71
|
-
setupFilesAfterEnv:
|
|
50
|
+
setupFiles: options.setupFiles ?? [],
|
|
51
|
+
setupFilesAfterEnv: [],
|
|
72
52
|
events: events as EventEmitter<BundlerEvents>,
|
|
73
53
|
bundler: bundler as Bundler,
|
|
74
54
|
evaluateModule,
|
|
75
55
|
});
|
|
76
56
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
57
|
+
const moduleJs = await bundler.getModule(path);
|
|
58
|
+
const collectionResult = await collector.collect(async () => {
|
|
59
|
+
await runSetupFiles({
|
|
60
|
+
setupFiles: [],
|
|
61
|
+
setupFilesAfterEnv: options.setupFilesAfterEnv ?? [],
|
|
62
|
+
events: events as EventEmitter<BundlerEvents>,
|
|
63
|
+
bundler: bundler as Bundler,
|
|
64
|
+
evaluateModule,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
setup();
|
|
68
|
+
evaluateModule(moduleJs, path);
|
|
69
|
+
}, path);
|
|
81
70
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
: collectionResult.testSuite;
|
|
71
|
+
const processedTestSuite = options.testNamePattern
|
|
72
|
+
? markTestsAsSkippedByName(
|
|
73
|
+
collectionResult.testSuite,
|
|
74
|
+
options.testNamePattern,
|
|
75
|
+
)
|
|
76
|
+
: collectionResult.testSuite;
|
|
89
77
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
};
|
|
78
|
+
return await runner.run({
|
|
79
|
+
testSuite: processedTestSuite,
|
|
80
|
+
testFilePath: path,
|
|
81
|
+
runner: options.runner,
|
|
82
|
+
});
|
|
83
|
+
} finally {
|
|
84
|
+
collector?.dispose();
|
|
85
|
+
runner?.dispose();
|
|
86
|
+
events?.clearAllListeners();
|
|
87
|
+
store.getState().setStatus('idle');
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
});
|
|
103
91
|
|
|
104
|
-
|
|
92
|
+
setHandle(handle);
|
|
93
|
+
return handle;
|
|
105
94
|
};
|
package/src/client/store.ts
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { HarnessHandle } from '@react-native-harness/bridge/client';
|
|
2
2
|
|
|
3
|
-
let
|
|
3
|
+
let handle: HarnessHandle | null = null;
|
|
4
4
|
|
|
5
|
-
export const
|
|
6
|
-
|
|
5
|
+
export const setHandle = (h: HarnessHandle): void => {
|
|
6
|
+
handle = h;
|
|
7
7
|
};
|
|
8
8
|
|
|
9
|
-
export const
|
|
10
|
-
if (!
|
|
9
|
+
export const getHandle = (): HarnessHandle => {
|
|
10
|
+
if (!handle) {
|
|
11
11
|
throw new Error(
|
|
12
|
-
'
|
|
12
|
+
'Harness not connected. This should not happen in normal operation.'
|
|
13
13
|
);
|
|
14
14
|
}
|
|
15
|
-
return
|
|
15
|
+
return handle;
|
|
16
16
|
};
|