@react-native-harness/jest 1.1.0 → 1.2.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.
@@ -549,6 +549,41 @@ describe('plugins', () => {
549
549
  'beforeDispose:1:normal',
550
550
  ]);
551
551
  });
552
+ it('disposes lifecycle hooks and platform resources only once', async () => {
553
+ const { serverBridge } = createBridgeServer();
554
+ const platformInstance = createPlatformRunner();
555
+ const observedHooks = [];
556
+ mocks.getBridgeServer.mockResolvedValue(serverBridge);
557
+ mocks.getMetroInstance.mockResolvedValue(createMetroInstance());
558
+ globalThis.__HARNESS_PLATFORM_RUNNER__ = vi.fn(async () => platformInstance);
559
+ const plugin = definePlugin({
560
+ name: 'dispose-plugin',
561
+ hooks: {
562
+ harness: {
563
+ afterRun: () => {
564
+ observedHooks.push('afterRun');
565
+ },
566
+ beforeDispose: () => {
567
+ observedHooks.push('beforeDispose');
568
+ },
569
+ },
570
+ },
571
+ });
572
+ const platform = {
573
+ config: {},
574
+ name: 'ios',
575
+ platformId: 'ios',
576
+ runner: `data:text/javascript,${encodeURIComponent('export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);')}`,
577
+ getResourceLockKey: () => 'ios:simulator:iPhone 17 Pro:26.2',
578
+ };
579
+ const harness = await getHarness(createHarnessConfig({
580
+ plugins: [plugin],
581
+ }), platform, '/tmp/project');
582
+ await harness.dispose();
583
+ await harness.dispose();
584
+ expect(observedHooks).toEqual(['afterRun', 'beforeDispose']);
585
+ expect(platformInstance.dispose).toHaveBeenCalledTimes(1);
586
+ });
552
587
  it('waits in queue before starting Metro and releases the lock on dispose', async () => {
553
588
  const resourceKey = 'ios:simulator:iPhone 17 Pro:26.2';
554
589
  const firstPlatformRunner = createPlatformRunner();
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli-args.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-args.test.d.ts","sourceRoot":"","sources":["../src/cli-args.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import { HarnessPlatformRunner } from '@react-native-harness/platforms';
2
+ import { BridgeServer } from '@react-native-harness/bridge/server';
3
+ export type CrashMonitor = {
4
+ startMonitoring(testFilePath: string): Promise<never>;
5
+ stopMonitoring(): void;
6
+ markIntentionalRestart(): void;
7
+ clearIntentionalRestart(): void;
8
+ dispose(): void;
9
+ };
10
+ export type CrashMonitorOptions = {
11
+ interval: number;
12
+ platformRunner: HarnessPlatformRunner;
13
+ bridgeServer: BridgeServer;
14
+ };
15
+ export declare const createCrashMonitor: ({ interval, platformRunner, bridgeServer, }: CrashMonitorOptions) => CrashMonitor;
16
+ //# sourceMappingURL=crash-monitor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crash-monitor.d.ts","sourceRoot":"","sources":["../src/crash-monitor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAInE,MAAM,MAAM,YAAY,GAAG;IACzB,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC;IACtD,cAAc,IAAI,IAAI,CAAC;IACvB,sBAAsB,IAAI,IAAI,CAAC;IAC/B,uBAAuB,IAAI,IAAI,CAAC;IAChC,OAAO,IAAI,IAAI,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,qBAAqB,CAAC;IACtC,YAAY,EAAE,YAAY,CAAC;CAC5B,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAAI,6CAIhC,mBAAmB,KAAG,YAqFxB,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { NativeCrashError } from './errors.js';
2
+ import { logger } from '@react-native-harness/tools';
3
+ export const createCrashMonitor = ({ interval, platformRunner, bridgeServer, }) => {
4
+ let pollingInterval = null;
5
+ let isIntentionalRestart = false;
6
+ let currentTestFilePath = null;
7
+ let rejectFn = null;
8
+ const handleDisconnect = () => {
9
+ // Verify if it's actually a crash by checking if app is still running
10
+ if (!isIntentionalRestart && currentTestFilePath) {
11
+ // Capture the value to avoid it being null when setTimeout callback runs
12
+ const testFilePath = currentTestFilePath;
13
+ logger.debug('Bridge disconnected, checking if app crashed');
14
+ // Use a slight delay to allow the OS to clean up the process
15
+ setTimeout(async () => {
16
+ const isRunning = await platformRunner.isAppRunning();
17
+ if (!isRunning && !isIntentionalRestart && rejectFn) {
18
+ logger.debug(`Native crash detected during: ${testFilePath}`);
19
+ rejectFn(new NativeCrashError(testFilePath));
20
+ }
21
+ }, 100);
22
+ }
23
+ };
24
+ const startMonitoring = (testFilePath) => {
25
+ currentTestFilePath = testFilePath;
26
+ return new Promise((_, reject) => {
27
+ rejectFn = reject;
28
+ // Listen for bridge disconnect as early indicator
29
+ bridgeServer.on('disconnect', handleDisconnect);
30
+ // Poll for app running status
31
+ pollingInterval = setInterval(async () => {
32
+ // Skip check during intentional restarts
33
+ if (isIntentionalRestart) {
34
+ return;
35
+ }
36
+ try {
37
+ const isRunning = await platformRunner.isAppRunning();
38
+ if (!isRunning && currentTestFilePath) {
39
+ logger.debug(`Native crash detected during: ${currentTestFilePath}`);
40
+ stopMonitoring();
41
+ reject(new NativeCrashError(currentTestFilePath));
42
+ }
43
+ }
44
+ catch (error) {
45
+ logger.error('Error checking app status:', error);
46
+ }
47
+ }, interval);
48
+ });
49
+ };
50
+ const stopMonitoring = () => {
51
+ if (pollingInterval) {
52
+ clearInterval(pollingInterval);
53
+ pollingInterval = null;
54
+ }
55
+ bridgeServer.off('disconnect', handleDisconnect);
56
+ currentTestFilePath = null;
57
+ rejectFn = null;
58
+ };
59
+ const markIntentionalRestart = () => {
60
+ isIntentionalRestart = true;
61
+ };
62
+ const clearIntentionalRestart = () => {
63
+ isIntentionalRestart = false;
64
+ };
65
+ const dispose = () => {
66
+ stopMonitoring();
67
+ };
68
+ return {
69
+ startMonitoring,
70
+ stopMonitoring,
71
+ markIntentionalRestart,
72
+ clearIntentionalRestart,
73
+ dispose,
74
+ };
75
+ };
@@ -0,0 +1,16 @@
1
+ import type { TestEvents } from '@jest/test-result';
2
+ import type { Test } from 'jest-runner';
3
+ import type { BridgeEvents } from '@react-native-harness/bridge';
4
+ import type { TestCaseResult } from '@jest/test-result';
5
+ export type EventMapperState = {
6
+ currentTest?: Test;
7
+ suiteHierarchy: string[];
8
+ testResults: Map<string, TestCaseResult>;
9
+ fileStartTime: number;
10
+ };
11
+ export type HarnessEventMapper = {
12
+ handleBridgeEvent: (event: BridgeEvents, test: Test) => void;
13
+ reset: () => void;
14
+ };
15
+ export declare const getHarnessEventMapper: (emit: <T extends keyof TestEvents>(eventName: T, eventData: TestEvents[T]) => void) => HarnessEventMapper;
16
+ //# sourceMappingURL=event-mapper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-mapper.d.ts","sourceRoot":"","sources":["../src/event-mapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,EASV,YAAY,EACb,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGxD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,CAAC,EAAE,IAAI,CAAC;IACnB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IACzC,aAAa,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,iBAAiB,EAAE,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC;IAC7D,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB,CAAC;AAEF,eAAO,MAAM,qBAAqB,GAChC,MAAM,CAAC,CAAC,SAAS,MAAM,UAAU,EAC/B,SAAS,EAAE,CAAC,EACZ,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,KACrB,IAAI,KACR,kBA8JF,CAAC"}
@@ -0,0 +1,120 @@
1
+ export const getHarnessEventMapper = (emit) => {
2
+ const state = {
3
+ suiteHierarchy: [],
4
+ testResults: new Map(),
5
+ fileStartTime: 0,
6
+ };
7
+ const handleBridgeEvent = (event, test) => {
8
+ // Filter to only process test runner events
9
+ if (event.type.startsWith('test-') ||
10
+ event.type.startsWith('suite-') ||
11
+ event.type.startsWith('file-')) {
12
+ switch (event.type) {
13
+ case 'file-started':
14
+ handleFileStarted(event, test);
15
+ break;
16
+ case 'file-finished':
17
+ handleFileFinished(event, test);
18
+ break;
19
+ case 'suite-started':
20
+ handleSuiteStarted(event);
21
+ break;
22
+ case 'suite-finished':
23
+ handleSuiteFinished(event);
24
+ break;
25
+ case 'test-started':
26
+ handleTestStarted(event, test);
27
+ break;
28
+ case 'test-finished':
29
+ handleTestFinished(event, test);
30
+ break;
31
+ }
32
+ }
33
+ };
34
+ const handleFileStarted = (event, test) => {
35
+ state.currentTest = test;
36
+ state.fileStartTime = Date.now();
37
+ state.suiteHierarchy = [];
38
+ state.testResults.clear();
39
+ // Don't emit test-file-start here - that should be handled by the Jest runner
40
+ // when it actually starts running the test file
41
+ };
42
+ const handleFileFinished = (_event, _test) => {
43
+ // File finished - all test results should already be emitted
44
+ // The final TestResult will be handled by the runHarnessTestFile function
45
+ };
46
+ const handleSuiteStarted = (event) => {
47
+ state.suiteHierarchy.push(event.name);
48
+ };
49
+ const handleSuiteFinished = (event) => {
50
+ // Remove the suite from hierarchy when it finishes
51
+ const index = state.suiteHierarchy.lastIndexOf(event.name);
52
+ if (index !== -1) {
53
+ state.suiteHierarchy.splice(index, 1);
54
+ }
55
+ };
56
+ const handleTestStarted = (event, test) => {
57
+ const testCaseStartInfo = {
58
+ ancestorTitles: [...state.suiteHierarchy],
59
+ fullName: `${state.suiteHierarchy.join(' ')} ${event.name}`.trim(),
60
+ title: event.name,
61
+ mode: 'skip', // Default mode, will be updated based on test status
62
+ };
63
+ emit('test-case-start', [test.path, testCaseStartInfo]);
64
+ };
65
+ const handleTestFinished = (event, test) => {
66
+ const testCaseResult = {
67
+ ancestorTitles: [...state.suiteHierarchy],
68
+ duration: event.duration,
69
+ failureDetails: [],
70
+ failureMessages: event.error ? [formatError(event.error)] : [],
71
+ fullName: `${state.suiteHierarchy.join(' ')} ${event.name}`.trim(),
72
+ numPassingAsserts: event.status === 'passed' ? 1 : 0,
73
+ status: mapStatus(event.status),
74
+ title: event.name,
75
+ startedAt: Date.now() - event.duration,
76
+ };
77
+ state.testResults.set(event.name, testCaseResult);
78
+ emit('test-case-result', [test.path, testCaseResult]);
79
+ };
80
+ const mapStatus = (status) => {
81
+ return status;
82
+ };
83
+ const formatError = (error) => {
84
+ let errorMessage = `${error.name}: ${error.message}`;
85
+ if (error.codeFrame) {
86
+ errorMessage += '\n\n' + formatCodeFrame(error.codeFrame);
87
+ }
88
+ return errorMessage;
89
+ };
90
+ const formatCodeFrame = (codeFrame) => {
91
+ const lines = codeFrame.content.split('\n');
92
+ const lineNumber = codeFrame.location?.row || 1;
93
+ const column = codeFrame.location?.column || 1;
94
+ let formatted = `\n${codeFrame.fileName}:${lineNumber}:${column}\n`;
95
+ // Add context lines around the error
96
+ const startLine = Math.max(0, lineNumber - 3);
97
+ const endLine = Math.min(lines.length, lineNumber + 2);
98
+ for (let i = startLine; i < endLine; i++) {
99
+ const lineNum = i + 1;
100
+ const line = lines[i];
101
+ const isErrorLine = lineNum === lineNumber;
102
+ formatted += `${lineNum.toString().padStart(3)} | ${line}\n`;
103
+ if (isErrorLine && column > 0) {
104
+ const pointer = ' '.repeat(column + 5) + '^';
105
+ formatted += pointer + '\n';
106
+ }
107
+ }
108
+ return formatted;
109
+ };
110
+ const reset = () => {
111
+ state.suiteHierarchy = [];
112
+ state.testResults.clear();
113
+ state.fileStartTime = 0;
114
+ state.currentTest = undefined;
115
+ };
116
+ return {
117
+ handleBridgeEvent,
118
+ reset,
119
+ };
120
+ };
@@ -0,0 +1,3 @@
1
+ import type { Config as JestConfig } from 'jest-runner';
2
+ export default function (globalConfig: JestConfig.GlobalConfig): Promise<void>;
3
+ //# sourceMappingURL=global-setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-setup.d.ts","sourceRoot":"","sources":["../src/global-setup.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,aAAa,CAAC;AA4CxD,yBAA+B,YAAY,EAAE,UAAU,CAAC,YAAY,iBA4CnE"}
@@ -0,0 +1,60 @@
1
+ import { getConfig, } from '@react-native-harness/config';
2
+ import { getHarness as getHarnessExternal, NoRunnerSpecifiedError, RunnerNotFoundError, } from '@react-native-harness/cli/external';
3
+ import { preRunMessage } from 'jest-util';
4
+ import { getAdditionalCliArgs } from './cli-args.js';
5
+ import { logTestEnvironmentReady, logTestRunHeader } from './logs.js';
6
+ const getHarnessConfig = async (globalConfig) => {
7
+ const projectRoot = globalConfig.rootDir;
8
+ const { config: harnessConfig } = await getConfig(projectRoot);
9
+ return harnessConfig;
10
+ };
11
+ const getHarnessRunner = (config, cliArgs) => {
12
+ const selectedRunnerName = cliArgs.harnessRunner ?? config.defaultRunner;
13
+ if (!selectedRunnerName) {
14
+ throw new NoRunnerSpecifiedError(config.runners);
15
+ }
16
+ const runner = config.runners.find((runner) => runner.name === selectedRunnerName);
17
+ if (!runner) {
18
+ throw new RunnerNotFoundError(selectedRunnerName, config.runners);
19
+ }
20
+ return runner;
21
+ };
22
+ const getHarness = async (runner) => {
23
+ return await getHarnessExternal(runner);
24
+ };
25
+ export default async function (globalConfig) {
26
+ preRunMessage.remove(process.stderr);
27
+ const harnessConfig = global.HARNESS_CONFIG ?? (await getHarnessConfig(globalConfig));
28
+ const isWatchMode = globalConfig.watch || globalConfig.watchAll;
29
+ if (global.HARNESS) {
30
+ // Do not setup again if HARNESS is already initialized
31
+ // This is useful when running tests in watch mode
32
+ if (harnessConfig.resetEnvironmentBetweenTestFiles) {
33
+ // In watch mode, we want to restart the environment before each test run
34
+ await new Promise((resolve) => {
35
+ global.HARNESS.bridge.once('ready', resolve);
36
+ global.HARNESS.environment.restart();
37
+ });
38
+ }
39
+ return;
40
+ }
41
+ if (isWatchMode) {
42
+ // In watch mode, we want to dispose the Harness when the process exits.
43
+ process.on('exit', async () => {
44
+ await global.HARNESS.bridge.dispose();
45
+ await global.HARNESS.environment.dispose();
46
+ });
47
+ }
48
+ const cliArgs = getAdditionalCliArgs();
49
+ const selectedRunner = getHarnessRunner(harnessConfig, cliArgs);
50
+ if (globalConfig.collectCoverage) {
51
+ // This is going to be used by @react-native-harness/babel-preset
52
+ // to enable instrumentation of test files.
53
+ process.env.RN_HARNESS_COLLECT_COVERAGE = 'true';
54
+ }
55
+ logTestRunHeader(selectedRunner);
56
+ const harness = await getHarness(selectedRunner);
57
+ logTestEnvironmentReady(selectedRunner);
58
+ global.HARNESS_CONFIG = harnessConfig;
59
+ global.HARNESS = harness;
60
+ }
@@ -0,0 +1,3 @@
1
+ import type { Config as JestConfig } from 'jest-runner';
2
+ export default function (globalConfig: JestConfig.GlobalConfig): Promise<void>;
3
+ //# sourceMappingURL=global-teardown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"global-teardown.d.ts","sourceRoot":"","sources":["../src/global-teardown.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,aAAa,CAAC;AAExD,yBAA+B,YAAY,EAAE,UAAU,CAAC,YAAY,iBAYnE"}
@@ -0,0 +1,11 @@
1
+ export default async function (globalConfig) {
2
+ const isWatchMode = globalConfig.watch || globalConfig.watchAll;
3
+ if (isWatchMode) {
4
+ // In watch mode, we don't want to dispose the Harness.
5
+ return;
6
+ }
7
+ if (global.HARNESS) {
8
+ await global.HARNESS.bridge.dispose();
9
+ await global.HARNESS.environment.dispose();
10
+ }
11
+ }
@@ -0,0 +1,8 @@
1
+ import { BaseReporter } from '@jest/reporters';
2
+ import { Config } from '@jest/types';
3
+ export default class HarnessReporter extends BaseReporter {
4
+ #private;
5
+ constructor(globalConfig: Config.GlobalConfig);
6
+ onRunStart(): void;
7
+ }
8
+ //# sourceMappingURL=harness-reporter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness-reporter.d.ts","sourceRoot":"","sources":["../src/harness-reporter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,MAAM,CAAC,OAAO,OAAO,eAAgB,SAAQ,YAAY;;gBAE3C,YAAY,EAAE,MAAM,CAAC,YAAY;IAKpC,UAAU,IAAI,IAAI;CAM5B"}
@@ -0,0 +1,12 @@
1
+ import { BaseReporter } from '@jest/reporters';
2
+ export default class HarnessReporter extends BaseReporter {
3
+ #globalConfig;
4
+ constructor(globalConfig) {
5
+ super();
6
+ this.#globalConfig = globalConfig;
7
+ }
8
+ onRunStart() {
9
+ super.onRunStart();
10
+ this.log(`Selected runner: ${this.#globalConfig.selectedRunner.name}`);
11
+ }
12
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,YAAY,EACb,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAEL,cAAc,EAGd,oBAAoB,EACpB,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAEL,KAAK,gBAAgB,EACrB,eAAe,EAEf,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAIL,KAAK,aAAa,EAGnB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AAQvC,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAgB/B,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;AAE/E,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,KAC5B,OAAO,CAAC,eAAe,CAAC,CAAC;IAC9B,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,OAAO,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,eAAe,EAAE,eAAe,CAAC;IACjC,QAAQ,EAAE,oBAAoB,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC,UAAU,CAAC,CAAC;IAC3E,WAAW,EAAE,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,CAAC;IACxD,WAAW,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CAC3C,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,QAAQ,aAAa,EACrB,UAAU,eAAe,EACzB,aAAa,MAAM,KAClB,IAIF,CAAC;AAgDF,eAAO,MAAM,eAAe,GAAU,SAAS;IAC7C,aAAa,EAAE,aAAa,CAAC;IAC7B,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,eAAe,CAAC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC,KAAG,OAAO,CAAC,IAAI,CA2Ef,CAAC;AAomBF,eAAO,MAAM,UAAU,GACrB,QAAQ,aAAa,EACrB,UAAU,eAAe,EACzB,aAAa,MAAM,KAClB,OAAO,CAAC,OAAO,CAYjB,CAAC"}
1
+ {"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../src/harness.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,YAAY,EACb,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAEL,cAAc,EAGd,oBAAoB,EACpB,eAAe,EAChB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAEL,KAAK,gBAAgB,EACrB,eAAe,EAEf,qBAAqB,EACtB,MAAM,iCAAiC,CAAC;AACzC,OAAO,EAIL,KAAK,aAAa,EAGnB,MAAM,qCAAqC,CAAC;AAC7C,OAAO,EAIL,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EACvB,MAAM,+BAA+B,CAAC;AAQvC,OAAO,EAAE,MAAM,IAAI,aAAa,EAAE,MAAM,8BAA8B,CAAC;AACvE,OAAO,EAEL,KAAK,eAAe,EACrB,MAAM,uBAAuB,CAAC;AAgB/B,MAAM,MAAM,sBAAsB,GAAG,OAAO,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;AAE/E,MAAM,MAAM,eAAe,GAAG;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,CAAC,EAAE,iBAAiB,CAAC;IAC5B,MAAM,CAAC,EAAE,gBAAgB,CAAC;IAC1B,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,OAAO,GAAG;IACpB,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,CACR,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,sBAAsB,KAC5B,OAAO,CAAC,eAAe,CAAC,CAAC;IAC9B,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,OAAO,EAAE,CAAC,YAAY,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAClD,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,eAAe,EAAE,eAAe,CAAC;IACjC,QAAQ,EAAE,oBAAoB,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC,UAAU,CAAC,CAAC;IAC3E,WAAW,EAAE,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,CAAC;IACxD,WAAW,EAAE,MAAM,eAAe,GAAG,IAAI,CAAC;CAC3C,CAAC;AAEF,eAAO,MAAM,uBAAuB,GAClC,QAAQ,aAAa,EACrB,UAAU,eAAe,EACzB,aAAa,MAAM,KAClB,IAIF,CAAC;AAgDF,eAAO,MAAM,eAAe,GAAU,SAAS;IAC7C,aAAa,EAAE,aAAa,CAAC;IAC7B,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,qBAAqB,CAAC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,eAAe,CAAC;IACjC,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,gBAAgB,CAAC,EAAE,gBAAgB,CAAC;IACpC,SAAS,CAAC,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACjC,KAAG,OAAO,CAAC,IAAI,CA2Ef,CAAC;AA0mBF,eAAO,MAAM,UAAU,GACrB,QAAQ,aAAa,EACrB,UAAU,eAAe,EACzB,aAAa,MAAM,KAClB,OAAO,CAAC,OAAO,CAYjB,CAAC"}
package/dist/harness.js CHANGED
@@ -437,7 +437,8 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
437
437
  metroInstance.events.addListener(clientLogListener);
438
438
  harnessLogger.debug('client log forwarding enabled');
439
439
  }
440
- const dispose = async (reason = 'normal') => {
440
+ let disposePromise = null;
441
+ const disposeOnce = async (reason = 'normal') => {
441
442
  harnessLogger.debug('disposing Harness (reason=%s)', reason);
442
443
  let hookError;
443
444
  try {
@@ -495,6 +496,10 @@ const getHarnessInternal = async (config, platform, projectRoot, signal) => {
495
496
  throw cleanupError;
496
497
  }
497
498
  };
499
+ const dispose = (reason = 'normal') => {
500
+ disposePromise ??= disposeOnce(reason);
501
+ return disposePromise;
502
+ };
498
503
  if (signal.aborted) {
499
504
  await dispose('abort');
500
505
  throw new DOMException('The operation was aborted', 'AbortError');
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=jest-options.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jest-options.d.ts","sourceRoot":"","sources":["../src/jest-options.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ export {};