@react-native-harness/jest 1.3.0 → 1.4.0-rc.1
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/__tests__/bridge.test.js +23 -2
- package/dist/__tests__/crash-monitor.test.js +146 -192
- package/dist/__tests__/errors.test.js +39 -0
- package/dist/__tests__/execute-run.test.js +190 -26
- package/dist/__tests__/harness-session.test.js +63 -239
- package/dist/__tests__/index.test.js +59 -10
- package/dist/__tests__/test-file-platform-filter.test.d.ts +2 -0
- package/dist/__tests__/test-file-platform-filter.test.d.ts.map +1 -0
- package/dist/__tests__/test-file-platform-filter.test.js +47 -0
- package/dist/crash-monitor.d.ts +5 -4
- package/dist/crash-monitor.d.ts.map +1 -1
- package/dist/crash-monitor.js +116 -102
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +41 -2
- package/dist/execute-run.d.ts +4 -2
- package/dist/execute-run.d.ts.map +1 -1
- package/dist/execute-run.js +85 -7
- package/dist/harness-session.d.ts +21 -1
- package/dist/harness-session.d.ts.map +1 -1
- package/dist/harness-session.js +134 -39
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +1 -0
- package/dist/test-file-platform-filter.d.ts +5 -0
- package/dist/test-file-platform-filter.d.ts.map +1 -0
- package/dist/test-file-platform-filter.js +40 -0
- package/dist/toTestResult.d.ts +1 -0
- package/dist/toTestResult.d.ts.map +1 -1
- package/dist/toTestResult.js +2 -2
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/__tests__/bridge.test.ts +37 -2
- package/src/__tests__/crash-monitor.test.ts +182 -231
- package/src/__tests__/errors.test.ts +75 -0
- package/src/__tests__/execute-run.test.ts +235 -34
- package/src/__tests__/harness-session.test.ts +93 -0
- package/src/__tests__/index.test.ts +89 -11
- package/src/__tests__/test-file-platform-filter.test.ts +83 -0
- package/src/crash-monitor.ts +192 -131
- package/src/errors.ts +62 -1
- package/src/execute-run.ts +125 -11
- package/src/harness-session.ts +177 -42
- package/src/index.ts +40 -11
- package/src/run.ts +1 -0
- package/src/test-file-platform-filter.ts +57 -0
- package/src/toTestResult.ts +3 -2
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
2
2
|
import type { Config, Test, TestWatcher } from 'jest-runner';
|
|
3
3
|
import type { TestResult as JestTestResult } from '@jest/test-result';
|
|
4
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
TestRunnerTestFinishedEvent,
|
|
6
|
+
TestRunnerTestStartedEvent,
|
|
7
|
+
TestSuiteResult,
|
|
8
|
+
} from '@react-native-harness/bridge';
|
|
5
9
|
import { NativeCrashError, StartupStallError } from '../errors.js';
|
|
6
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
AppBridgeDisconnectedError,
|
|
12
|
+
DeviceNotRespondingError,
|
|
13
|
+
} from '@react-native-harness/bridge/server';
|
|
7
14
|
import type { HarnessSession } from '../harness-session.js';
|
|
8
15
|
import { executeRun } from '../execute-run.js';
|
|
9
16
|
|
|
17
|
+
type EmitEvent = Parameters<typeof executeRun>[3];
|
|
18
|
+
type RecordedEmitEvent = {
|
|
19
|
+
emitEvent: EmitEvent;
|
|
20
|
+
calls: Array<unknown[]>;
|
|
21
|
+
};
|
|
22
|
+
|
|
10
23
|
const resolveUndefined = async () => undefined;
|
|
11
24
|
|
|
12
25
|
// Mock the file-runner so we control what jestResult/harnessResult each test returns
|
|
@@ -85,19 +98,41 @@ const makeSession = (overrides: Partial<HarnessSession> = {}): HarnessSession =>
|
|
|
85
98
|
host: undefined,
|
|
86
99
|
resetEnvironmentBetweenTestFiles: false,
|
|
87
100
|
detectNativeCrashes: true,
|
|
101
|
+
runners: [
|
|
102
|
+
{ platformId: 'android', name: 'android' },
|
|
103
|
+
{ platformId: 'ios', name: 'ios' },
|
|
104
|
+
],
|
|
88
105
|
} as HarnessSession['config'],
|
|
89
|
-
context: {
|
|
106
|
+
context: {
|
|
107
|
+
platform: {
|
|
108
|
+
platformId: 'android',
|
|
109
|
+
name: 'android',
|
|
110
|
+
runner: '/virtual/android-runner.js',
|
|
111
|
+
cli: '/virtual/android-cli.js',
|
|
112
|
+
config: {},
|
|
113
|
+
},
|
|
114
|
+
} as HarnessSession['context'],
|
|
90
115
|
ensureAppReady: vi.fn(resolveUndefined),
|
|
91
116
|
runTestFile: vi.fn(async () => makeHarnessResult()),
|
|
92
117
|
restartApp: vi.fn(resolveUndefined),
|
|
93
118
|
resetCrashState: vi.fn(),
|
|
94
119
|
flushClientLogs: vi.fn(() => []),
|
|
95
120
|
callHook: vi.fn(resolveUndefined),
|
|
121
|
+
onTestRunnerEvent: vi.fn(() => () => undefined),
|
|
96
122
|
setRunState: vi.fn(),
|
|
97
123
|
dispose: vi.fn(resolveUndefined),
|
|
98
124
|
...overrides,
|
|
99
125
|
});
|
|
100
126
|
|
|
127
|
+
const makeEmitEvent = (): RecordedEmitEvent => {
|
|
128
|
+
const calls: Array<unknown[]> = [];
|
|
129
|
+
const emitEvent: EmitEvent = (async (...eventData) => {
|
|
130
|
+
calls.push(eventData);
|
|
131
|
+
}) as EmitEvent;
|
|
132
|
+
|
|
133
|
+
return { emitEvent, calls };
|
|
134
|
+
};
|
|
135
|
+
|
|
101
136
|
// ---------------------------------------------------------------------------
|
|
102
137
|
// Tests
|
|
103
138
|
// ---------------------------------------------------------------------------
|
|
@@ -115,7 +150,7 @@ describe('executeRun', () => {
|
|
|
115
150
|
callHook: vi.fn(async (name) => { hookNames.push(name as string); }),
|
|
116
151
|
});
|
|
117
152
|
|
|
118
|
-
await executeRun(session, [makeTest()], makeWatcher(),
|
|
153
|
+
await executeRun(session, [makeTest()], makeWatcher(), makeEmitEvent().emitEvent, makeGlobalConfig());
|
|
119
154
|
|
|
120
155
|
expect(hookNames[0]).toBe('run:started');
|
|
121
156
|
expect(hookNames[hookNames.length - 1]).toBe('run:finished');
|
|
@@ -127,7 +162,7 @@ describe('executeRun', () => {
|
|
|
127
162
|
callHook: vi.fn(async (name) => { hookNames.push(name as string); }),
|
|
128
163
|
});
|
|
129
164
|
|
|
130
|
-
await executeRun(session, [makeTest('/a.ts'), makeTest('/b.ts')], makeWatcher(),
|
|
165
|
+
await executeRun(session, [makeTest('/a.ts'), makeTest('/b.ts')], makeWatcher(), makeEmitEvent().emitEvent, makeGlobalConfig());
|
|
131
166
|
|
|
132
167
|
const fileHooks = hookNames.filter((n) =>
|
|
133
168
|
n === 'test-file:started' || n === 'test-file:finished',
|
|
@@ -145,29 +180,41 @@ describe('executeRun', () => {
|
|
|
145
180
|
ensureAppReady: vi.fn(async () => { throw new Error('unexpected'); }),
|
|
146
181
|
});
|
|
147
182
|
|
|
148
|
-
await executeRun(session, [makeTest()], makeWatcher(),
|
|
183
|
+
await executeRun(session, [makeTest()], makeWatcher(), makeEmitEvent().emitEvent, makeGlobalConfig());
|
|
149
184
|
|
|
150
185
|
expect(hookNames).toContain('run:finished');
|
|
151
186
|
});
|
|
152
187
|
});
|
|
153
188
|
|
|
154
189
|
describe('happy path', () => {
|
|
155
|
-
it('
|
|
190
|
+
it('emits file start, ensureAppReady, runTestFile, file success in order', async () => {
|
|
156
191
|
const order: string[] = [];
|
|
157
192
|
const session = makeSession({
|
|
158
193
|
ensureAppReady: vi.fn(async () => { order.push('ensureAppReady'); }),
|
|
159
194
|
});
|
|
160
|
-
const
|
|
161
|
-
|
|
195
|
+
const emitEvent: EmitEvent = (async (eventName, ..._eventData) => {
|
|
196
|
+
if (eventName === 'test-file-start') {
|
|
197
|
+
order.push('test-file-start');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (eventName === 'test-file-success') {
|
|
201
|
+
order.push('test-file-success');
|
|
202
|
+
}
|
|
203
|
+
}) as EmitEvent;
|
|
162
204
|
|
|
163
205
|
mockRunHarnessTestFile.mockImplementation(async () => {
|
|
164
206
|
order.push('runTestFile');
|
|
165
207
|
return makeFileRunResult();
|
|
166
208
|
});
|
|
167
209
|
|
|
168
|
-
await executeRun(session, [makeTest()], makeWatcher(),
|
|
210
|
+
await executeRun(session, [makeTest()], makeWatcher(), emitEvent, makeGlobalConfig());
|
|
169
211
|
|
|
170
|
-
expect(order).toEqual([
|
|
212
|
+
expect(order).toEqual([
|
|
213
|
+
'test-file-start',
|
|
214
|
+
'ensureAppReady',
|
|
215
|
+
'runTestFile',
|
|
216
|
+
'test-file-success',
|
|
217
|
+
]);
|
|
171
218
|
});
|
|
172
219
|
|
|
173
220
|
it('accumulates passing test counts in run:finished summary', async () => {
|
|
@@ -182,7 +229,7 @@ describe('executeRun', () => {
|
|
|
182
229
|
makeFileRunResult({ jestResult: makeJestResult({ numPassingTests: 3 }) }),
|
|
183
230
|
);
|
|
184
231
|
|
|
185
|
-
await executeRun(session, [makeTest(), makeTest('/b.ts')], makeWatcher(),
|
|
232
|
+
await executeRun(session, [makeTest(), makeTest('/b.ts')], makeWatcher(), makeEmitEvent().emitEvent, makeGlobalConfig());
|
|
186
233
|
|
|
187
234
|
const payload = finishedPayload as { summary: { passed: number }; status: string };
|
|
188
235
|
expect(payload.summary.passed).toBe(6);
|
|
@@ -196,55 +243,146 @@ describe('executeRun', () => {
|
|
|
196
243
|
.mockReturnValueOnce([])
|
|
197
244
|
.mockReturnValueOnce(clientLogs),
|
|
198
245
|
});
|
|
199
|
-
const
|
|
246
|
+
const { emitEvent, calls } = makeEmitEvent();
|
|
200
247
|
|
|
201
248
|
await executeRun(
|
|
202
249
|
session,
|
|
203
250
|
[makeTest()],
|
|
204
251
|
makeWatcher(),
|
|
205
|
-
|
|
206
|
-
onResult,
|
|
207
|
-
vi.fn(),
|
|
252
|
+
emitEvent,
|
|
208
253
|
makeGlobalConfig(),
|
|
209
254
|
);
|
|
210
255
|
|
|
211
256
|
expect(session.flushClientLogs).toHaveBeenCalledTimes(2);
|
|
212
|
-
expect(
|
|
257
|
+
expect(calls).toContainEqual([
|
|
258
|
+
'test-file-success',
|
|
213
259
|
expect.anything(),
|
|
214
260
|
expect.objectContaining({ console: clientLogs }),
|
|
215
|
-
);
|
|
261
|
+
]);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('emits test-case events before file success', async () => {
|
|
265
|
+
let testRunnerListener:
|
|
266
|
+
| ((event: TestRunnerTestStartedEvent | TestRunnerTestFinishedEvent) => void)
|
|
267
|
+
| undefined;
|
|
268
|
+
const { emitEvent, calls: emittedEvents } = makeEmitEvent();
|
|
269
|
+
const session = makeSession({
|
|
270
|
+
onTestRunnerEvent: vi.fn((listener) => {
|
|
271
|
+
testRunnerListener = listener as typeof testRunnerListener;
|
|
272
|
+
return () => undefined;
|
|
273
|
+
}),
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
mockRunHarnessTestFile.mockImplementation(async () => {
|
|
277
|
+
testRunnerListener?.({
|
|
278
|
+
type: 'test-started',
|
|
279
|
+
file: 'example.ts',
|
|
280
|
+
suite: 'suite',
|
|
281
|
+
name: 'works',
|
|
282
|
+
ancestorTitles: ['suite'],
|
|
283
|
+
fullName: 'suite works',
|
|
284
|
+
startedAt: 10,
|
|
285
|
+
declarationMode: 'only',
|
|
286
|
+
});
|
|
287
|
+
testRunnerListener?.({
|
|
288
|
+
type: 'test-finished',
|
|
289
|
+
file: 'example.ts',
|
|
290
|
+
suite: 'suite',
|
|
291
|
+
name: 'works',
|
|
292
|
+
ancestorTitles: ['suite'],
|
|
293
|
+
fullName: 'suite works',
|
|
294
|
+
startedAt: 10,
|
|
295
|
+
declarationMode: 'only',
|
|
296
|
+
duration: 5,
|
|
297
|
+
status: 'passed',
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
return makeFileRunResult();
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
await executeRun(session, [makeTest()], makeWatcher(), emitEvent, makeGlobalConfig());
|
|
304
|
+
|
|
305
|
+
expect(emittedEvents).toEqual([
|
|
306
|
+
['test-file-start', expect.anything()],
|
|
307
|
+
[
|
|
308
|
+
'test-case-start',
|
|
309
|
+
'example.ts',
|
|
310
|
+
expect.objectContaining({
|
|
311
|
+
ancestorTitles: ['suite'],
|
|
312
|
+
fullName: 'suite works',
|
|
313
|
+
mode: 'only',
|
|
314
|
+
title: 'works',
|
|
315
|
+
startedAt: 10,
|
|
316
|
+
}),
|
|
317
|
+
],
|
|
318
|
+
[
|
|
319
|
+
'test-case-result',
|
|
320
|
+
'example.ts',
|
|
321
|
+
expect.objectContaining({
|
|
322
|
+
ancestorTitles: ['suite'],
|
|
323
|
+
fullName: 'suite works',
|
|
324
|
+
numPassingAsserts: 1,
|
|
325
|
+
startedAt: 10,
|
|
326
|
+
status: 'passed',
|
|
327
|
+
title: 'works',
|
|
328
|
+
}),
|
|
329
|
+
],
|
|
330
|
+
['test-file-success', expect.anything(), expect.anything()],
|
|
331
|
+
]);
|
|
216
332
|
});
|
|
217
333
|
});
|
|
218
334
|
|
|
219
335
|
describe('runtime failures', () => {
|
|
220
336
|
it('passes StartupStallError to onFailure with an empty stack', async () => {
|
|
221
|
-
const
|
|
337
|
+
const { emitEvent, calls } = makeEmitEvent();
|
|
222
338
|
const session = makeSession({
|
|
223
339
|
ensureAppReady: vi.fn().mockRejectedValue(new StartupStallError(1500, 3)),
|
|
224
340
|
});
|
|
225
341
|
|
|
226
|
-
await executeRun(session, [makeTest()], makeWatcher(),
|
|
342
|
+
await executeRun(session, [makeTest()], makeWatcher(), emitEvent, makeGlobalConfig());
|
|
227
343
|
|
|
228
|
-
expect(
|
|
344
|
+
expect(calls).toContainEqual([
|
|
345
|
+
'test-file-failure',
|
|
229
346
|
expect.objectContaining({ path: '/test/example.ts' }),
|
|
230
347
|
expect.objectContaining({ message: expect.stringContaining('1500'), stack: '' }),
|
|
231
|
-
);
|
|
348
|
+
]);
|
|
232
349
|
});
|
|
233
350
|
|
|
234
351
|
it('passes DeviceNotRespondingError to onFailure with an empty stack', async () => {
|
|
235
|
-
const
|
|
352
|
+
const { emitEvent, calls } = makeEmitEvent();
|
|
236
353
|
const session = makeSession({
|
|
237
354
|
ensureAppReady: vi.fn().mockRejectedValue(
|
|
238
355
|
new DeviceNotRespondingError('runTests', []),
|
|
239
356
|
),
|
|
240
357
|
});
|
|
241
358
|
|
|
242
|
-
await executeRun(session, [makeTest()], makeWatcher(),
|
|
359
|
+
await executeRun(session, [makeTest()], makeWatcher(), emitEvent, makeGlobalConfig());
|
|
243
360
|
|
|
244
|
-
expect(
|
|
361
|
+
expect(calls).toContainEqual([
|
|
362
|
+
'test-file-failure',
|
|
245
363
|
expect.anything(),
|
|
246
364
|
expect.objectContaining({ stack: '' }),
|
|
247
|
-
);
|
|
365
|
+
]);
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
it('passes AppBridgeDisconnectedError to onFailure with an empty stack', async () => {
|
|
369
|
+
const { emitEvent, calls } = makeEmitEvent();
|
|
370
|
+
const session = makeSession({
|
|
371
|
+
ensureAppReady: vi.fn().mockRejectedValue(
|
|
372
|
+
new AppBridgeDisconnectedError('app-disconnected'),
|
|
373
|
+
),
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
await executeRun(session, [makeTest()], makeWatcher(), emitEvent, makeGlobalConfig());
|
|
377
|
+
|
|
378
|
+
expect(calls).toContainEqual([
|
|
379
|
+
'test-file-failure',
|
|
380
|
+
expect.anything(),
|
|
381
|
+
expect.objectContaining({
|
|
382
|
+
message: expect.stringContaining('The app bridge disconnected during test execution.'),
|
|
383
|
+
stack: '',
|
|
384
|
+
}),
|
|
385
|
+
]);
|
|
248
386
|
});
|
|
249
387
|
|
|
250
388
|
it('calls resetCrashState after a NativeCrashError', async () => {
|
|
@@ -254,7 +392,7 @@ describe('executeRun', () => {
|
|
|
254
392
|
),
|
|
255
393
|
});
|
|
256
394
|
|
|
257
|
-
await executeRun(session, [makeTest()], makeWatcher(),
|
|
395
|
+
await executeRun(session, [makeTest()], makeWatcher(), makeEmitEvent().emitEvent, makeGlobalConfig());
|
|
258
396
|
|
|
259
397
|
expect(session.resetCrashState).toHaveBeenCalled();
|
|
260
398
|
});
|
|
@@ -268,7 +406,7 @@ describe('executeRun', () => {
|
|
|
268
406
|
ensureAppReady: vi.fn().mockRejectedValue(new StartupStallError(1000, 2)),
|
|
269
407
|
});
|
|
270
408
|
|
|
271
|
-
await executeRun(session, [makeTest()], makeWatcher(),
|
|
409
|
+
await executeRun(session, [makeTest()], makeWatcher(), makeEmitEvent().emitEvent, makeGlobalConfig());
|
|
272
410
|
|
|
273
411
|
const payload = finishedPayload as { summary: { failed: number }; status: string };
|
|
274
412
|
expect(payload.summary.failed).toBe(1);
|
|
@@ -282,20 +420,18 @@ describe('executeRun', () => {
|
|
|
282
420
|
const session = makeSession({
|
|
283
421
|
callHook: vi.fn(async (name) => { hookNames.push(name as string); }),
|
|
284
422
|
});
|
|
285
|
-
const
|
|
423
|
+
const { emitEvent, calls } = makeEmitEvent();
|
|
286
424
|
|
|
287
425
|
await executeRun(
|
|
288
426
|
session,
|
|
289
427
|
[makeTest('/a.ts'), makeTest('/b.ts')],
|
|
290
428
|
makeWatcher(true /* interrupted */),
|
|
291
|
-
|
|
292
|
-
vi.fn(),
|
|
293
|
-
vi.fn(),
|
|
429
|
+
emitEvent,
|
|
294
430
|
makeGlobalConfig(),
|
|
295
431
|
);
|
|
296
432
|
|
|
297
433
|
// No test was started; watcher was already interrupted.
|
|
298
|
-
expect(
|
|
434
|
+
expect(calls).not.toContainEqual(['test-file-start', expect.anything()]);
|
|
299
435
|
expect(hookNames).toContain('run:finished');
|
|
300
436
|
});
|
|
301
437
|
});
|
|
@@ -307,6 +443,10 @@ describe('executeRun', () => {
|
|
|
307
443
|
metroPort: 8081,
|
|
308
444
|
resetEnvironmentBetweenTestFiles: true,
|
|
309
445
|
detectNativeCrashes: false,
|
|
446
|
+
runners: [
|
|
447
|
+
{ platformId: 'android', name: 'android' },
|
|
448
|
+
{ platformId: 'ios', name: 'ios' },
|
|
449
|
+
],
|
|
310
450
|
} as HarnessSession['config'],
|
|
311
451
|
});
|
|
312
452
|
|
|
@@ -314,7 +454,7 @@ describe('executeRun', () => {
|
|
|
314
454
|
session,
|
|
315
455
|
[makeTest('/a.ts'), makeTest('/b.ts'), makeTest('/c.ts')],
|
|
316
456
|
makeWatcher(),
|
|
317
|
-
|
|
457
|
+
makeEmitEvent().emitEvent,
|
|
318
458
|
makeGlobalConfig(),
|
|
319
459
|
);
|
|
320
460
|
|
|
@@ -322,4 +462,65 @@ describe('executeRun', () => {
|
|
|
322
462
|
expect(session.restartApp).toHaveBeenCalledTimes(2);
|
|
323
463
|
});
|
|
324
464
|
});
|
|
465
|
+
|
|
466
|
+
describe('platform-specific test files', () => {
|
|
467
|
+
it('skips files for other platforms without running them on the device', async () => {
|
|
468
|
+
const session = makeSession();
|
|
469
|
+
const { emitEvent, calls } = makeEmitEvent();
|
|
470
|
+
|
|
471
|
+
await executeRun(
|
|
472
|
+
session,
|
|
473
|
+
[
|
|
474
|
+
makeTest('/project/smoke.harness.ts'),
|
|
475
|
+
makeTest('/project/kotlin.android.harness.ts'),
|
|
476
|
+
makeTest('/project/swift.ios.harness.ts'),
|
|
477
|
+
],
|
|
478
|
+
makeWatcher(),
|
|
479
|
+
emitEvent,
|
|
480
|
+
makeGlobalConfig(),
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
expect(mockRunHarnessTestFile).toHaveBeenCalledTimes(2);
|
|
484
|
+
expect(mockRunHarnessTestFile).toHaveBeenCalledWith(
|
|
485
|
+
expect.objectContaining({ testPath: '/project/smoke.harness.ts' }),
|
|
486
|
+
);
|
|
487
|
+
expect(mockRunHarnessTestFile).toHaveBeenCalledWith(
|
|
488
|
+
expect.objectContaining({ testPath: '/project/kotlin.android.harness.ts' }),
|
|
489
|
+
);
|
|
490
|
+
expect(session.ensureAppReady).toHaveBeenCalledTimes(2);
|
|
491
|
+
expect(calls).toContainEqual([
|
|
492
|
+
'test-file-success',
|
|
493
|
+
expect.objectContaining({ path: '/project/swift.ios.harness.ts' }),
|
|
494
|
+
expect.objectContaining({ skipped: true }),
|
|
495
|
+
]);
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it('does not restart the app for skipped platform-specific files', async () => {
|
|
499
|
+
const session = makeSession({
|
|
500
|
+
config: {
|
|
501
|
+
metroPort: 8081,
|
|
502
|
+
resetEnvironmentBetweenTestFiles: true,
|
|
503
|
+
detectNativeCrashes: false,
|
|
504
|
+
runners: [
|
|
505
|
+
{ platformId: 'android', name: 'android' },
|
|
506
|
+
{ platformId: 'ios', name: 'ios' },
|
|
507
|
+
],
|
|
508
|
+
} as HarnessSession['config'],
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
await executeRun(
|
|
512
|
+
session,
|
|
513
|
+
[
|
|
514
|
+
makeTest('/project/swift.ios.harness.ts'),
|
|
515
|
+
makeTest('/project/kotlin.android.harness.ts'),
|
|
516
|
+
],
|
|
517
|
+
makeWatcher(),
|
|
518
|
+
vi.fn(),
|
|
519
|
+
makeGlobalConfig(),
|
|
520
|
+
);
|
|
521
|
+
|
|
522
|
+
expect(session.restartApp).not.toHaveBeenCalled();
|
|
523
|
+
expect(mockRunHarnessTestFile).toHaveBeenCalledTimes(1);
|
|
524
|
+
});
|
|
525
|
+
});
|
|
325
526
|
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import type { AppConnection } from '@react-native-harness/bridge/server';
|
|
3
|
+
import {
|
|
4
|
+
waitForBridgeDisconnectOrTimeout,
|
|
5
|
+
waitForStartupCrash,
|
|
6
|
+
} from '../harness-session.js';
|
|
7
|
+
import type { CrashMonitor } from '../crash-monitor.js';
|
|
8
|
+
|
|
9
|
+
const createConnection = (): AppConnection => ({
|
|
10
|
+
device: {
|
|
11
|
+
platform: 'ios',
|
|
12
|
+
manufacturer: 'Apple',
|
|
13
|
+
model: 'iPhone',
|
|
14
|
+
osVersion: '18.0',
|
|
15
|
+
},
|
|
16
|
+
runTests: vi.fn(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const createBridge = (connection: AppConnection | null) => {
|
|
20
|
+
let currentConnection = connection;
|
|
21
|
+
let disconnectedListener: (() => void) | null = null;
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
get connection() {
|
|
25
|
+
return currentConnection;
|
|
26
|
+
},
|
|
27
|
+
on: vi.fn((event: 'disconnected', listener: () => void) => {
|
|
28
|
+
if (event === 'disconnected') {
|
|
29
|
+
disconnectedListener = listener;
|
|
30
|
+
}
|
|
31
|
+
}),
|
|
32
|
+
off: vi.fn((event: 'disconnected', listener: () => void) => {
|
|
33
|
+
if (event === 'disconnected' && disconnectedListener === listener) {
|
|
34
|
+
disconnectedListener = null;
|
|
35
|
+
}
|
|
36
|
+
}),
|
|
37
|
+
disconnect: () => {
|
|
38
|
+
currentConnection = null;
|
|
39
|
+
disconnectedListener?.();
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
describe('waitForBridgeDisconnectOrTimeout', () => {
|
|
45
|
+
it('returns true when the bridge disconnects before the timeout', async () => {
|
|
46
|
+
const connection = createConnection();
|
|
47
|
+
const bridge = createBridge(connection);
|
|
48
|
+
|
|
49
|
+
const waitPromise = waitForBridgeDisconnectOrTimeout({
|
|
50
|
+
bridge,
|
|
51
|
+
connection,
|
|
52
|
+
timeoutMs: 50,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
bridge.disconnect();
|
|
56
|
+
|
|
57
|
+
await expect(waitPromise).resolves.toBe(true);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('returns false when the bridge stays connected through the timeout', async () => {
|
|
61
|
+
const connection = createConnection();
|
|
62
|
+
const bridge = createBridge(connection);
|
|
63
|
+
|
|
64
|
+
await expect(
|
|
65
|
+
waitForBridgeDisconnectOrTimeout({
|
|
66
|
+
bridge,
|
|
67
|
+
connection,
|
|
68
|
+
timeoutMs: 10,
|
|
69
|
+
}),
|
|
70
|
+
).resolves.toBe(false);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('waitForStartupCrash', () => {
|
|
75
|
+
it('does not install a startup crash watch when native crash detection is disabled', async () => {
|
|
76
|
+
const watch = vi.fn();
|
|
77
|
+
const crashMonitor = {
|
|
78
|
+
watch,
|
|
79
|
+
} as unknown as CrashMonitor;
|
|
80
|
+
const controller = new AbortController();
|
|
81
|
+
const waitPromise = waitForStartupCrash({
|
|
82
|
+
crashMonitor,
|
|
83
|
+
detectNativeCrashes: false,
|
|
84
|
+
testFilePath: '/test.harness.ts',
|
|
85
|
+
signal: controller.signal,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
controller.abort(new DOMException('Aborted', 'AbortError'));
|
|
89
|
+
|
|
90
|
+
await expect(waitPromise).rejects.toThrow('Aborted');
|
|
91
|
+
expect(watch).not.toHaveBeenCalled();
|
|
92
|
+
});
|
|
93
|
+
});
|