@react-native-harness/jest 1.4.0-rc.1 → 1.4.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/__tests__/bridge.test.js +6 -2
- package/dist/__tests__/crash-monitor.test.js +64 -0
- package/dist/__tests__/environment-reset.test.d.ts +2 -0
- package/dist/__tests__/environment-reset.test.d.ts.map +1 -0
- package/dist/__tests__/environment-reset.test.js +238 -0
- package/dist/__tests__/execute-run.test.js +250 -4
- package/dist/__tests__/format-harness-error.test.d.ts +2 -0
- package/dist/__tests__/format-harness-error.test.d.ts.map +1 -0
- package/dist/__tests__/format-harness-error.test.js +27 -0
- package/dist/__tests__/harness-cache.test.js +62 -41
- package/dist/__tests__/harness-session-signal.test.d.ts +2 -0
- package/dist/__tests__/harness-session-signal.test.d.ts.map +1 -0
- package/dist/__tests__/harness-session-signal.test.js +298 -0
- package/dist/__tests__/harness-session.test.js +161 -2
- package/dist/__tests__/index.test.js +3 -1
- package/dist/__tests__/metro-port.test.js +2 -0
- package/dist/__tests__/run.test.d.ts +2 -0
- package/dist/__tests__/run.test.d.ts.map +1 -0
- package/dist/__tests__/run.test.js +48 -0
- package/dist/crash-monitor.d.ts +7 -0
- package/dist/crash-monitor.d.ts.map +1 -1
- package/dist/crash-monitor.js +19 -0
- package/dist/diagnostics/__tests__/derivers.test.d.ts +2 -0
- package/dist/diagnostics/__tests__/derivers.test.d.ts.map +1 -0
- package/dist/diagnostics/__tests__/derivers.test.js +248 -0
- package/dist/diagnostics/__tests__/report.test.d.ts +2 -0
- package/dist/diagnostics/__tests__/report.test.d.ts.map +1 -0
- package/dist/diagnostics/__tests__/report.test.js +97 -0
- package/dist/diagnostics/derivers.d.ts +24 -0
- package/dist/diagnostics/derivers.d.ts.map +1 -0
- package/dist/diagnostics/derivers.js +146 -0
- package/dist/diagnostics/index.d.ts +3 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +2 -0
- package/dist/diagnostics/report.d.ts +7 -0
- package/dist/diagnostics/report.d.ts.map +1 -0
- package/dist/diagnostics/report.js +84 -0
- package/dist/environment-reset.d.ts +51 -0
- package/dist/environment-reset.d.ts.map +1 -0
- package/dist/environment-reset.js +83 -0
- package/dist/execute-run.d.ts.map +1 -1
- package/dist/execute-run.js +121 -69
- package/dist/format-harness-error.d.ts +5 -0
- package/dist/format-harness-error.d.ts.map +1 -0
- package/dist/format-harness-error.js +43 -0
- package/dist/harness-session.d.ts +26 -1
- package/dist/harness-session.d.ts.map +1 -1
- package/dist/harness-session.js +260 -87
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +21 -4
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/src/__tests__/bridge.test.ts +8 -3
- package/src/__tests__/crash-monitor.test.ts +84 -0
- package/src/__tests__/environment-reset.test.ts +308 -0
- package/src/__tests__/execute-run.test.ts +327 -4
- package/src/__tests__/format-harness-error.test.ts +32 -0
- package/src/__tests__/harness-cache.test.ts +91 -0
- package/src/__tests__/harness-session.test.ts +211 -1
- package/src/__tests__/index.test.ts +3 -1
- package/src/__tests__/metro-port.test.ts +2 -0
- package/src/__tests__/run.test.ts +74 -0
- package/src/crash-monitor.ts +28 -0
- package/src/diagnostics/__tests__/derivers.test.ts +300 -0
- package/src/diagnostics/__tests__/report.test.ts +128 -0
- package/src/diagnostics/derivers.ts +170 -0
- package/src/diagnostics/index.ts +2 -0
- package/src/diagnostics/report.ts +124 -0
- package/src/environment-reset.ts +138 -0
- package/src/execute-run.ts +136 -77
- package/src/format-harness-error.ts +73 -0
- package/src/harness-session.ts +369 -131
- package/src/run.ts +23 -4
- package/tsconfig.json +3 -0
- package/tsconfig.lib.json +3 -0
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
AppBridgeDisconnectedError,
|
|
12
12
|
DeviceNotRespondingError,
|
|
13
13
|
} from '@react-native-harness/bridge/server';
|
|
14
|
+
import { createDiagnostics } from '@react-native-harness/tools';
|
|
14
15
|
import type { HarnessSession } from '../harness-session.js';
|
|
15
16
|
import { executeRun } from '../execute-run.js';
|
|
16
17
|
|
|
@@ -29,6 +30,16 @@ vi.mock('../run.js', () => ({
|
|
|
29
30
|
runHarnessTestFile: mockRunHarnessTestFile,
|
|
30
31
|
}));
|
|
31
32
|
|
|
33
|
+
// Mock the reporting module so diagnostics tests can inspect the entries that
|
|
34
|
+
// would have been reported without touching the real filesystem, and so
|
|
35
|
+
// asserting on them doesn't race with executeRun's own diagnostics.flush().
|
|
36
|
+
const mockPrintSummary = vi.hoisted(() => vi.fn());
|
|
37
|
+
const mockWriteTraceFile = vi.hoisted(() => vi.fn(async () => '/fake/trace.json'));
|
|
38
|
+
vi.mock('../diagnostics/index.js', () => ({
|
|
39
|
+
printSummary: mockPrintSummary,
|
|
40
|
+
writeTraceFile: mockWriteTraceFile,
|
|
41
|
+
}));
|
|
42
|
+
|
|
32
43
|
// ---------------------------------------------------------------------------
|
|
33
44
|
// Helpers
|
|
34
45
|
// ---------------------------------------------------------------------------
|
|
@@ -112,9 +123,11 @@ const makeSession = (overrides: Partial<HarnessSession> = {}): HarnessSession =>
|
|
|
112
123
|
config: {},
|
|
113
124
|
},
|
|
114
125
|
} as HarnessSession['context'],
|
|
126
|
+
diagnostics: createDiagnostics({ enabled: false }),
|
|
115
127
|
ensureAppReady: vi.fn(resolveUndefined),
|
|
116
128
|
runTestFile: vi.fn(async () => makeHarnessResult()),
|
|
117
129
|
restartApp: vi.fn(resolveUndefined),
|
|
130
|
+
resetEnvironment: vi.fn(resolveUndefined),
|
|
118
131
|
resetCrashState: vi.fn(),
|
|
119
132
|
flushClientLogs: vi.fn(() => []),
|
|
120
133
|
callHook: vi.fn(resolveUndefined),
|
|
@@ -186,13 +199,68 @@ describe('executeRun', () => {
|
|
|
186
199
|
});
|
|
187
200
|
});
|
|
188
201
|
|
|
202
|
+
describe('diagnostics', () => {
|
|
203
|
+
beforeEach(() => {
|
|
204
|
+
mockPrintSummary.mockClear();
|
|
205
|
+
mockWriteTraceFile.mockClear();
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it('records run.total and run.file spans when diagnostics is enabled', async () => {
|
|
209
|
+
const diagnostics = createDiagnostics({ enabled: true });
|
|
210
|
+
const session = makeSession({ diagnostics });
|
|
211
|
+
|
|
212
|
+
await executeRun(session, [makeTest('/a.ts')], makeWatcher(), makeEmitEvent().emitEvent, makeGlobalConfig());
|
|
213
|
+
|
|
214
|
+
// executeRun flushes and hands entries to the (mocked) reporter in its
|
|
215
|
+
// finally block, so we inspect what was reported rather than flushing
|
|
216
|
+
// the diagnostics instance ourselves.
|
|
217
|
+
expect(mockPrintSummary).toHaveBeenCalledTimes(1);
|
|
218
|
+
const entries = mockPrintSummary.mock.calls[0]?.[0] as Array<{ label: string; status: string; attrs?: Record<string, unknown> }>;
|
|
219
|
+
const runEntry = entries.find((e) => e.label === 'run.total');
|
|
220
|
+
const fileEntry = entries.find((e) => e.label === 'run.file');
|
|
221
|
+
|
|
222
|
+
expect(runEntry).toMatchObject({ status: 'ok', attrs: { status: 'passed' } });
|
|
223
|
+
expect(runEntry?.attrs?.runId).toBeTypeOf('string');
|
|
224
|
+
expect(fileEntry).toMatchObject({ status: 'ok', attrs: { file: '../a.ts', status: 'passed' } });
|
|
225
|
+
expect(fileEntry?.attrs?.runId).toBeTypeOf('string');
|
|
226
|
+
expect(mockWriteTraceFile).toHaveBeenCalledWith(entries, expect.objectContaining({ runId: expect.any(String) }));
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('still records run.total when a test throws', async () => {
|
|
230
|
+
const diagnostics = createDiagnostics({ enabled: true });
|
|
231
|
+
const session = makeSession({
|
|
232
|
+
diagnostics,
|
|
233
|
+
ensureAppReady: vi.fn(async () => { throw new Error('unexpected'); }),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
await executeRun(session, [makeTest()], makeWatcher(), makeEmitEvent().emitEvent, makeGlobalConfig());
|
|
237
|
+
|
|
238
|
+
const entries = mockPrintSummary.mock.calls[0]?.[0] as Array<{ label: string; attrs?: Record<string, unknown> }>;
|
|
239
|
+
expect(entries.find((e) => e.label === 'run.total')).toBeDefined();
|
|
240
|
+
expect(entries.find((e) => e.label === 'run.file')).toMatchObject({
|
|
241
|
+
attrs: { status: 'failed' },
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('is a no-op and does not generate a report when diagnostics is disabled', async () => {
|
|
246
|
+
const diagnostics = createDiagnostics({ enabled: false });
|
|
247
|
+
const session = makeSession({ diagnostics });
|
|
248
|
+
|
|
249
|
+
await executeRun(session, [makeTest()], makeWatcher(), makeEmitEvent().emitEvent, makeGlobalConfig());
|
|
250
|
+
|
|
251
|
+
expect(diagnostics.flush()).toEqual([]);
|
|
252
|
+
expect(mockPrintSummary).not.toHaveBeenCalled();
|
|
253
|
+
expect(mockWriteTraceFile).not.toHaveBeenCalled();
|
|
254
|
+
});
|
|
255
|
+
});
|
|
256
|
+
|
|
189
257
|
describe('happy path', () => {
|
|
190
258
|
it('emits file start, ensureAppReady, runTestFile, file success in order', async () => {
|
|
191
259
|
const order: string[] = [];
|
|
192
260
|
const session = makeSession({
|
|
193
261
|
ensureAppReady: vi.fn(async () => { order.push('ensureAppReady'); }),
|
|
194
262
|
});
|
|
195
|
-
const emitEvent: EmitEvent = (async (eventName
|
|
263
|
+
const emitEvent: EmitEvent = (async (eventName) => {
|
|
196
264
|
if (eventName === 'test-file-start') {
|
|
197
265
|
order.push('test-file-start');
|
|
198
266
|
}
|
|
@@ -236,6 +304,25 @@ describe('executeRun', () => {
|
|
|
236
304
|
expect(payload.status).toBe('passed');
|
|
237
305
|
});
|
|
238
306
|
|
|
307
|
+
it('marks the final run state as completed', async () => {
|
|
308
|
+
const session = makeSession();
|
|
309
|
+
|
|
310
|
+
await executeRun(
|
|
311
|
+
session,
|
|
312
|
+
[makeTest()],
|
|
313
|
+
makeWatcher(),
|
|
314
|
+
makeEmitEvent().emitEvent,
|
|
315
|
+
makeGlobalConfig(),
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
expect(session.setRunState).toHaveBeenLastCalledWith(
|
|
319
|
+
expect.objectContaining({
|
|
320
|
+
completed: true,
|
|
321
|
+
status: 'passed',
|
|
322
|
+
}),
|
|
323
|
+
);
|
|
324
|
+
});
|
|
325
|
+
|
|
239
326
|
it('attaches buffered client logs to the Jest result', async () => {
|
|
240
327
|
const clientLogs = [{ message: 'Loaded screen', origin: '', type: 'warn' }] satisfies NonNullable<JestTestResult['console']>;
|
|
241
328
|
const session = makeSession({
|
|
@@ -330,6 +417,65 @@ describe('executeRun', () => {
|
|
|
330
417
|
['test-file-success', expect.anything(), expect.anything()],
|
|
331
418
|
]);
|
|
332
419
|
});
|
|
420
|
+
|
|
421
|
+
it('includes pending promise diagnostics in live test-case failures', async () => {
|
|
422
|
+
let testRunnerListener:
|
|
423
|
+
| ((event: TestRunnerTestStartedEvent | TestRunnerTestFinishedEvent) => void)
|
|
424
|
+
| undefined;
|
|
425
|
+
const { emitEvent, calls: emittedEvents } = makeEmitEvent();
|
|
426
|
+
const session = makeSession({
|
|
427
|
+
onTestRunnerEvent: vi.fn((listener) => {
|
|
428
|
+
testRunnerListener = listener as typeof testRunnerListener;
|
|
429
|
+
return () => undefined;
|
|
430
|
+
}),
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
mockRunHarnessTestFile.mockImplementation(async () => {
|
|
434
|
+
testRunnerListener?.({
|
|
435
|
+
type: 'test-finished',
|
|
436
|
+
file: 'example.ts',
|
|
437
|
+
suite: 'suite',
|
|
438
|
+
name: 'hangs',
|
|
439
|
+
ancestorTitles: ['suite'],
|
|
440
|
+
fullName: 'suite hangs',
|
|
441
|
+
startedAt: 100,
|
|
442
|
+
duration: 50,
|
|
443
|
+
status: 'failed',
|
|
444
|
+
error: {
|
|
445
|
+
name: 'TestCaseTimeoutError',
|
|
446
|
+
message: 'Test timed out after 50ms: suite hangs',
|
|
447
|
+
diagnostics: {
|
|
448
|
+
pendingPromises: {
|
|
449
|
+
total: 1,
|
|
450
|
+
items: [
|
|
451
|
+
{
|
|
452
|
+
id: 7,
|
|
453
|
+
createdAt: 110,
|
|
454
|
+
stack: 'Error: Promise created\n at hangs (example.ts:10:5)',
|
|
455
|
+
},
|
|
456
|
+
],
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
},
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
return makeFileRunResult();
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
await executeRun(session, [makeTest()], makeWatcher(), emitEvent, makeGlobalConfig());
|
|
466
|
+
|
|
467
|
+
expect(emittedEvents).toContainEqual([
|
|
468
|
+
'test-case-result',
|
|
469
|
+
'example.ts',
|
|
470
|
+
expect.objectContaining({
|
|
471
|
+
failureMessages: [
|
|
472
|
+
expect.stringContaining(
|
|
473
|
+
'Pending promises at timeout: 1\n\nPromise #7, created 10ms after test start:',
|
|
474
|
+
),
|
|
475
|
+
],
|
|
476
|
+
}),
|
|
477
|
+
]);
|
|
478
|
+
});
|
|
333
479
|
});
|
|
334
480
|
|
|
335
481
|
describe('runtime failures', () => {
|
|
@@ -437,7 +583,7 @@ describe('executeRun', () => {
|
|
|
437
583
|
});
|
|
438
584
|
|
|
439
585
|
describe('resetEnvironmentBetweenTestFiles', () => {
|
|
440
|
-
it('calls
|
|
586
|
+
it('calls resetEnvironment before the second test but not the first', async () => {
|
|
441
587
|
const session = makeSession({
|
|
442
588
|
config: {
|
|
443
589
|
metroPort: 8081,
|
|
@@ -458,8 +604,184 @@ describe('executeRun', () => {
|
|
|
458
604
|
makeGlobalConfig(),
|
|
459
605
|
);
|
|
460
606
|
|
|
461
|
-
//
|
|
462
|
-
expect(session.
|
|
607
|
+
// resetEnvironment should be called for tests 2 and 3, not test 1.
|
|
608
|
+
expect(session.resetEnvironment).toHaveBeenCalledTimes(2);
|
|
609
|
+
expect(session.restartApp).not.toHaveBeenCalled();
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
it('calls resetEnvironment with the "runtime" strategy configured', async () => {
|
|
613
|
+
const session = makeSession({
|
|
614
|
+
config: {
|
|
615
|
+
metroPort: 8081,
|
|
616
|
+
resetEnvironmentBetweenTestFiles: 'runtime',
|
|
617
|
+
detectNativeCrashes: false,
|
|
618
|
+
runners: [
|
|
619
|
+
{ platformId: 'android', name: 'android' },
|
|
620
|
+
{ platformId: 'ios', name: 'ios' },
|
|
621
|
+
],
|
|
622
|
+
} as HarnessSession['config'],
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
await executeRun(
|
|
626
|
+
session,
|
|
627
|
+
[makeTest('/a.ts'), makeTest('/b.ts')],
|
|
628
|
+
makeWatcher(),
|
|
629
|
+
makeEmitEvent().emitEvent,
|
|
630
|
+
makeGlobalConfig(),
|
|
631
|
+
);
|
|
632
|
+
|
|
633
|
+
expect(session.resetEnvironment).toHaveBeenCalledTimes(1);
|
|
634
|
+
expect(session.resetEnvironment).toHaveBeenCalledWith('/b.ts');
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it('does not call resetEnvironment when resetEnvironmentBetweenTestFiles is false', async () => {
|
|
638
|
+
const session = makeSession({
|
|
639
|
+
config: {
|
|
640
|
+
metroPort: 8081,
|
|
641
|
+
resetEnvironmentBetweenTestFiles: false,
|
|
642
|
+
detectNativeCrashes: false,
|
|
643
|
+
runners: [
|
|
644
|
+
{ platformId: 'android', name: 'android' },
|
|
645
|
+
{ platformId: 'ios', name: 'ios' },
|
|
646
|
+
],
|
|
647
|
+
} as HarnessSession['config'],
|
|
648
|
+
});
|
|
649
|
+
|
|
650
|
+
await executeRun(
|
|
651
|
+
session,
|
|
652
|
+
[makeTest('/a.ts'), makeTest('/b.ts')],
|
|
653
|
+
makeWatcher(),
|
|
654
|
+
makeEmitEvent().emitEvent,
|
|
655
|
+
makeGlobalConfig(),
|
|
656
|
+
);
|
|
657
|
+
|
|
658
|
+
expect(session.resetEnvironment).not.toHaveBeenCalled();
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
it('restarts after a test case timeout before the next runnable file', async () => {
|
|
662
|
+
const timedOutResult = makeHarnessResult('failed');
|
|
663
|
+
timedOutResult.tests = [
|
|
664
|
+
{
|
|
665
|
+
name: 'hangs',
|
|
666
|
+
status: 'failed',
|
|
667
|
+
duration: 10,
|
|
668
|
+
error: {
|
|
669
|
+
name: 'TestCaseTimeoutError',
|
|
670
|
+
message: 'Test timed out after 10ms: hangs',
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
];
|
|
674
|
+
mockRunHarnessTestFile
|
|
675
|
+
.mockResolvedValueOnce(makeFileRunResult({
|
|
676
|
+
harnessResult: timedOutResult,
|
|
677
|
+
jestResult: makeJestResult({
|
|
678
|
+
numFailingTests: 1,
|
|
679
|
+
numPassingTests: 0,
|
|
680
|
+
}),
|
|
681
|
+
}))
|
|
682
|
+
.mockResolvedValueOnce(makeFileRunResult());
|
|
683
|
+
const session = makeSession({
|
|
684
|
+
config: {
|
|
685
|
+
metroPort: 8081,
|
|
686
|
+
resetEnvironmentBetweenTestFiles: false,
|
|
687
|
+
detectNativeCrashes: false,
|
|
688
|
+
runners: [
|
|
689
|
+
{ platformId: 'android', name: 'android' },
|
|
690
|
+
{ platformId: 'ios', name: 'ios' },
|
|
691
|
+
],
|
|
692
|
+
} as HarnessSession['config'],
|
|
693
|
+
});
|
|
694
|
+
|
|
695
|
+
await executeRun(
|
|
696
|
+
session,
|
|
697
|
+
[makeTest('/a.ts'), makeTest('/b.ts')],
|
|
698
|
+
makeWatcher(),
|
|
699
|
+
makeEmitEvent().emitEvent,
|
|
700
|
+
makeGlobalConfig(),
|
|
701
|
+
);
|
|
702
|
+
|
|
703
|
+
expect(session.restartApp).toHaveBeenCalledTimes(1);
|
|
704
|
+
expect(session.restartApp).toHaveBeenCalledWith('/a.ts');
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
it('restarts after a timeout in the last runnable file', async () => {
|
|
708
|
+
const timedOutResult = makeHarnessResult('failed');
|
|
709
|
+
timedOutResult.tests = [
|
|
710
|
+
{
|
|
711
|
+
name: 'hangs',
|
|
712
|
+
status: 'failed',
|
|
713
|
+
duration: 10,
|
|
714
|
+
error: {
|
|
715
|
+
name: 'TestCaseTimeoutError',
|
|
716
|
+
message: 'Test timed out after 10ms: hangs',
|
|
717
|
+
},
|
|
718
|
+
},
|
|
719
|
+
];
|
|
720
|
+
mockRunHarnessTestFile.mockResolvedValueOnce(makeFileRunResult({
|
|
721
|
+
harnessResult: timedOutResult,
|
|
722
|
+
jestResult: makeJestResult({
|
|
723
|
+
numFailingTests: 1,
|
|
724
|
+
numPassingTests: 0,
|
|
725
|
+
}),
|
|
726
|
+
}));
|
|
727
|
+
const session = makeSession({
|
|
728
|
+
config: {
|
|
729
|
+
metroPort: 8081,
|
|
730
|
+
resetEnvironmentBetweenTestFiles: false,
|
|
731
|
+
detectNativeCrashes: false,
|
|
732
|
+
runners: [
|
|
733
|
+
{ platformId: 'android', name: 'android' },
|
|
734
|
+
{ platformId: 'ios', name: 'ios' },
|
|
735
|
+
],
|
|
736
|
+
} as HarnessSession['config'],
|
|
737
|
+
});
|
|
738
|
+
|
|
739
|
+
await executeRun(
|
|
740
|
+
session,
|
|
741
|
+
[makeTest('/a.ts')],
|
|
742
|
+
makeWatcher(),
|
|
743
|
+
makeEmitEvent().emitEvent,
|
|
744
|
+
makeGlobalConfig({ watch: true }),
|
|
745
|
+
);
|
|
746
|
+
|
|
747
|
+
expect(session.restartApp).toHaveBeenCalledTimes(1);
|
|
748
|
+
expect(session.restartApp).toHaveBeenCalledWith('/a.ts');
|
|
749
|
+
});
|
|
750
|
+
|
|
751
|
+
it('restarts after a suite hook timeout', async () => {
|
|
752
|
+
const timedOutResult = makeHarnessResult('failed');
|
|
753
|
+
timedOutResult.suites = [
|
|
754
|
+
{
|
|
755
|
+
name: 'suite',
|
|
756
|
+
tests: [],
|
|
757
|
+
suites: [],
|
|
758
|
+
status: 'failed',
|
|
759
|
+
duration: 10,
|
|
760
|
+
error: {
|
|
761
|
+
name: 'SuiteHookTimeoutError',
|
|
762
|
+
message: 'beforeAll hook timed out after 10ms in suite: suite',
|
|
763
|
+
},
|
|
764
|
+
},
|
|
765
|
+
];
|
|
766
|
+
mockRunHarnessTestFile.mockResolvedValueOnce(makeFileRunResult({
|
|
767
|
+
harnessResult: timedOutResult,
|
|
768
|
+
jestResult: makeJestResult({
|
|
769
|
+
numFailingTests: 1,
|
|
770
|
+
numPassingTests: 0,
|
|
771
|
+
}),
|
|
772
|
+
}));
|
|
773
|
+
const session = makeSession();
|
|
774
|
+
|
|
775
|
+
await executeRun(
|
|
776
|
+
session,
|
|
777
|
+
[makeTest('/a.ts')],
|
|
778
|
+
makeWatcher(),
|
|
779
|
+
makeEmitEvent().emitEvent,
|
|
780
|
+
makeGlobalConfig(),
|
|
781
|
+
);
|
|
782
|
+
|
|
783
|
+
expect(session.restartApp).toHaveBeenCalledTimes(1);
|
|
784
|
+
expect(session.restartApp).toHaveBeenCalledWith('/a.ts');
|
|
463
785
|
});
|
|
464
786
|
});
|
|
465
787
|
|
|
@@ -520,6 +842,7 @@ describe('executeRun', () => {
|
|
|
520
842
|
);
|
|
521
843
|
|
|
522
844
|
expect(session.restartApp).not.toHaveBeenCalled();
|
|
845
|
+
expect(session.resetEnvironment).not.toHaveBeenCalled();
|
|
523
846
|
expect(mockRunHarnessTestFile).toHaveBeenCalledTimes(1);
|
|
524
847
|
});
|
|
525
848
|
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { formatHarnessErrorMessage } from '../format-harness-error.js';
|
|
3
|
+
|
|
4
|
+
describe('formatHarnessErrorMessage', () => {
|
|
5
|
+
it('formats pending promise diagnostics for timeout errors', () => {
|
|
6
|
+
const message = formatHarnessErrorMessage(
|
|
7
|
+
{
|
|
8
|
+
name: 'TestCaseTimeoutError',
|
|
9
|
+
message: 'Test timed out after 50ms: suite hangs',
|
|
10
|
+
diagnostics: {
|
|
11
|
+
pendingPromises: {
|
|
12
|
+
total: 2,
|
|
13
|
+
items: [
|
|
14
|
+
{
|
|
15
|
+
id: 7,
|
|
16
|
+
createdAt: 110,
|
|
17
|
+
stack: 'Error: Promise created\n at hangs (example.ts:10:5)',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
{ testStartedAt: 100 },
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
expect(message).toContain('Test timed out after 50ms: suite hangs');
|
|
27
|
+
expect(message).toContain('Pending promises at timeout: 2');
|
|
28
|
+
expect(message).toContain('Showing 1 of 2 pending promises.');
|
|
29
|
+
expect(message).toContain('Promise #7, created 10ms after test start:');
|
|
30
|
+
expect(message).toContain(' Error: Promise created');
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { HarnessPlatform } from '@react-native-harness/platforms';
|
|
2
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
import { maybeLogMetroCacheReused } from '../harness-session.js';
|
|
4
|
+
|
|
5
|
+
const { createHarnessCache, isWarm, logMetroCacheReused } = vi.hoisted(() => {
|
|
6
|
+
const isWarm = vi.fn();
|
|
7
|
+
|
|
8
|
+
return {
|
|
9
|
+
isWarm,
|
|
10
|
+
createHarnessCache: vi.fn((options: { projectRoot: string }) => ({
|
|
11
|
+
paths: {
|
|
12
|
+
root: `${options.projectRoot}/.harness/cache`,
|
|
13
|
+
metroTransform: `${options.projectRoot}/.harness/cache/metro`,
|
|
14
|
+
metroFileMap: `${options.projectRoot}/.harness/cache/metro-file-map`,
|
|
15
|
+
},
|
|
16
|
+
isWarm,
|
|
17
|
+
ensureDomainDirectories: vi.fn(),
|
|
18
|
+
})),
|
|
19
|
+
logMetroCacheReused: vi.fn(),
|
|
20
|
+
};
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
vi.mock('@react-native-harness/cache', () => ({
|
|
24
|
+
createHarnessCache,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
vi.mock('../logs.js', async (importOriginal) => ({
|
|
28
|
+
...(await importOriginal<typeof import('../logs.js')>()),
|
|
29
|
+
logMetroCacheReused,
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
const platform = { name: 'test-runner' } as HarnessPlatform;
|
|
33
|
+
|
|
34
|
+
describe('maybeLogMetroCacheReused', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
vi.clearAllMocks();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('logs the reuse message when caching is enabled and the cache is warm', () => {
|
|
40
|
+
isWarm.mockReturnValue(true);
|
|
41
|
+
|
|
42
|
+
maybeLogMetroCacheReused({
|
|
43
|
+
config: { cache: { metro: true } },
|
|
44
|
+
projectRoot: '/tmp/app',
|
|
45
|
+
platform,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
expect(createHarnessCache).toHaveBeenCalledWith({
|
|
49
|
+
projectRoot: '/tmp/app',
|
|
50
|
+
});
|
|
51
|
+
expect(isWarm).toHaveBeenCalledWith('metro');
|
|
52
|
+
expect(logMetroCacheReused).toHaveBeenCalledWith(platform);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('logs the reuse message with a warm cache when caching is on by default', () => {
|
|
56
|
+
isWarm.mockReturnValue(true);
|
|
57
|
+
|
|
58
|
+
maybeLogMetroCacheReused({
|
|
59
|
+
config: {},
|
|
60
|
+
projectRoot: '/tmp/app',
|
|
61
|
+
platform,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
expect(logMetroCacheReused).toHaveBeenCalledWith(platform);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('does not log when caching is enabled but the cache is cold', () => {
|
|
68
|
+
isWarm.mockReturnValue(false);
|
|
69
|
+
|
|
70
|
+
maybeLogMetroCacheReused({
|
|
71
|
+
config: { cache: { metro: true } },
|
|
72
|
+
projectRoot: '/tmp/app',
|
|
73
|
+
platform,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
expect(logMetroCacheReused).not.toHaveBeenCalled();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('does not log when caching is disabled, regardless of cache state', () => {
|
|
80
|
+
isWarm.mockReturnValue(true);
|
|
81
|
+
|
|
82
|
+
maybeLogMetroCacheReused({
|
|
83
|
+
config: { cache: { metro: false } },
|
|
84
|
+
projectRoot: '/tmp/app',
|
|
85
|
+
platform,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
expect(createHarnessCache).not.toHaveBeenCalled();
|
|
89
|
+
expect(logMetroCacheReused).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
});
|