@react-native-harness/jest 1.0.0-alpha.20 → 1.0.0-alpha.21
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/crash-monitor.d.ts +16 -0
- package/dist/crash-monitor.d.ts.map +1 -0
- package/dist/crash-monitor.js +75 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +17 -0
- package/dist/harness.d.ts +13 -1
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +84 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +38 -7
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +6 -7
- package/src/crash-monitor.ts +109 -0
- package/src/errors.ts +20 -0
- package/src/harness.ts +125 -25
- package/src/index.ts +45 -9
- package/dist/cli-args.test.d.ts +0 -2
- package/dist/cli-args.test.d.ts.map +0 -1
- package/dist/cli-args.test.js +0 -1
- package/dist/event-mapper.d.ts +0 -16
- package/dist/event-mapper.d.ts.map +0 -1
- package/dist/event-mapper.js +0 -120
- package/dist/global-setup.d.ts +0 -3
- package/dist/global-setup.d.ts.map +0 -1
- package/dist/global-setup.js +0 -60
- package/dist/global-teardown.d.ts +0 -3
- package/dist/global-teardown.d.ts.map +0 -1
- package/dist/global-teardown.js +0 -11
- package/dist/harness-reporter.d.ts +0 -8
- package/dist/harness-reporter.d.ts.map +0 -1
- package/dist/harness-reporter.js +0 -12
- package/dist/jest-options.d.ts +0 -2
- package/dist/jest-options.d.ts.map +0 -1
- package/dist/jest-options.js +0 -1
- package/dist/types.d.ts +0 -17
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-harness/jest",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.21",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.js",
|
|
@@ -38,14 +38,13 @@
|
|
|
38
38
|
"jest-message-util": "^30.2.0",
|
|
39
39
|
"jest-util": "^30.2.0",
|
|
40
40
|
"p-limit": "^7.1.1",
|
|
41
|
-
"p-retry": "^7.1.0",
|
|
42
41
|
"tslib": "^2.3.0",
|
|
43
42
|
"yargs": "^17.7.2",
|
|
44
|
-
"@react-native-harness/
|
|
45
|
-
"@react-native-harness/
|
|
46
|
-
"@react-native-harness/
|
|
47
|
-
"@react-native-harness/
|
|
48
|
-
"@react-native-harness/
|
|
43
|
+
"@react-native-harness/bridge": "1.0.0-alpha.21",
|
|
44
|
+
"@react-native-harness/bundler-metro": "1.0.0-alpha.21",
|
|
45
|
+
"@react-native-harness/config": "1.0.0-alpha.21",
|
|
46
|
+
"@react-native-harness/platforms": "1.0.0-alpha.21",
|
|
47
|
+
"@react-native-harness/tools": "1.0.0-alpha.21"
|
|
49
48
|
},
|
|
50
49
|
"devDependencies": {
|
|
51
50
|
"@types/yargs": "^17.0.32",
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { HarnessPlatformRunner } from '@react-native-harness/platforms';
|
|
2
|
+
import { BridgeServer } from '@react-native-harness/bridge/server';
|
|
3
|
+
import { NativeCrashError } from './errors.js';
|
|
4
|
+
import { logger } from '@react-native-harness/tools';
|
|
5
|
+
|
|
6
|
+
export type CrashMonitor = {
|
|
7
|
+
startMonitoring(testFilePath: string): Promise<never>;
|
|
8
|
+
stopMonitoring(): void;
|
|
9
|
+
markIntentionalRestart(): void;
|
|
10
|
+
clearIntentionalRestart(): void;
|
|
11
|
+
dispose(): void;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type CrashMonitorOptions = {
|
|
15
|
+
interval: number;
|
|
16
|
+
platformRunner: HarnessPlatformRunner;
|
|
17
|
+
bridgeServer: BridgeServer;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const createCrashMonitor = ({
|
|
21
|
+
interval,
|
|
22
|
+
platformRunner,
|
|
23
|
+
bridgeServer,
|
|
24
|
+
}: CrashMonitorOptions): CrashMonitor => {
|
|
25
|
+
let pollingInterval: NodeJS.Timeout | null = null;
|
|
26
|
+
let isIntentionalRestart = false;
|
|
27
|
+
let currentTestFilePath: string | null = null;
|
|
28
|
+
let rejectFn: ((error: NativeCrashError) => void) | null = null;
|
|
29
|
+
|
|
30
|
+
const handleDisconnect = () => {
|
|
31
|
+
// Verify if it's actually a crash by checking if app is still running
|
|
32
|
+
if (!isIntentionalRestart && currentTestFilePath) {
|
|
33
|
+
// Capture the value to avoid it being null when setTimeout callback runs
|
|
34
|
+
const testFilePath = currentTestFilePath;
|
|
35
|
+
logger.debug('Bridge disconnected, checking if app crashed');
|
|
36
|
+
// Use a slight delay to allow the OS to clean up the process
|
|
37
|
+
setTimeout(async () => {
|
|
38
|
+
const isRunning = await platformRunner.isAppRunning();
|
|
39
|
+
if (!isRunning && !isIntentionalRestart && rejectFn) {
|
|
40
|
+
logger.debug(`Native crash detected during: ${testFilePath}`);
|
|
41
|
+
rejectFn(new NativeCrashError(testFilePath));
|
|
42
|
+
}
|
|
43
|
+
}, 100);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const startMonitoring = (testFilePath: string): Promise<never> => {
|
|
48
|
+
currentTestFilePath = testFilePath;
|
|
49
|
+
|
|
50
|
+
return new Promise<never>((_, reject) => {
|
|
51
|
+
rejectFn = reject;
|
|
52
|
+
|
|
53
|
+
// Listen for bridge disconnect as early indicator
|
|
54
|
+
bridgeServer.on('disconnect', handleDisconnect);
|
|
55
|
+
|
|
56
|
+
// Poll for app running status
|
|
57
|
+
pollingInterval = setInterval(async () => {
|
|
58
|
+
// Skip check during intentional restarts
|
|
59
|
+
if (isIntentionalRestart) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const isRunning = await platformRunner.isAppRunning();
|
|
65
|
+
|
|
66
|
+
if (!isRunning && currentTestFilePath) {
|
|
67
|
+
logger.debug(
|
|
68
|
+
`Native crash detected during: ${currentTestFilePath}`
|
|
69
|
+
);
|
|
70
|
+
stopMonitoring();
|
|
71
|
+
reject(new NativeCrashError(currentTestFilePath));
|
|
72
|
+
}
|
|
73
|
+
} catch (error) {
|
|
74
|
+
logger.error('Error checking app status:', error);
|
|
75
|
+
}
|
|
76
|
+
}, interval);
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const stopMonitoring = () => {
|
|
81
|
+
if (pollingInterval) {
|
|
82
|
+
clearInterval(pollingInterval);
|
|
83
|
+
pollingInterval = null;
|
|
84
|
+
}
|
|
85
|
+
bridgeServer.off('disconnect', handleDisconnect);
|
|
86
|
+
currentTestFilePath = null;
|
|
87
|
+
rejectFn = null;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const markIntentionalRestart = () => {
|
|
91
|
+
isIntentionalRestart = true;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const clearIntentionalRestart = () => {
|
|
95
|
+
isIntentionalRestart = false;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const dispose = () => {
|
|
99
|
+
stopMonitoring();
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
startMonitoring,
|
|
104
|
+
stopMonitoring,
|
|
105
|
+
markIntentionalRestart,
|
|
106
|
+
clearIntentionalRestart,
|
|
107
|
+
dispose,
|
|
108
|
+
};
|
|
109
|
+
};
|
package/src/errors.ts
CHANGED
|
@@ -20,3 +20,23 @@ export class InitializationTimeoutError extends HarnessError {
|
|
|
20
20
|
this.name = 'InitializationTimeoutError';
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
|
|
24
|
+
export class MaxAppRestartsError extends HarnessError {
|
|
25
|
+
constructor(attempts: number) {
|
|
26
|
+
super(
|
|
27
|
+
`App failed to start after ${attempts} attempts. ` +
|
|
28
|
+
`No bundling activity detected within timeout period.`
|
|
29
|
+
);
|
|
30
|
+
this.name = 'MaxAppRestartsError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class NativeCrashError extends HarnessError {
|
|
35
|
+
constructor(
|
|
36
|
+
public readonly testFilePath: string,
|
|
37
|
+
public readonly lastKnownTest?: string
|
|
38
|
+
) {
|
|
39
|
+
super('The native app crashed during test execution.');
|
|
40
|
+
this.name = 'NativeCrashError';
|
|
41
|
+
}
|
|
42
|
+
}
|
package/src/harness.ts
CHANGED
|
@@ -1,17 +1,115 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
getBridgeServer,
|
|
3
|
+
BridgeServer,
|
|
4
|
+
} from '@react-native-harness/bridge/server';
|
|
2
5
|
import { BridgeClientFunctions } from '@react-native-harness/bridge';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
import {
|
|
7
|
+
HarnessPlatform,
|
|
8
|
+
HarnessPlatformRunner,
|
|
9
|
+
} from '@react-native-harness/platforms';
|
|
10
|
+
import {
|
|
11
|
+
getMetroInstance,
|
|
12
|
+
Reporter,
|
|
13
|
+
ReportableEvent,
|
|
14
|
+
} from '@react-native-harness/bundler-metro';
|
|
15
|
+
import { InitializationTimeoutError, MaxAppRestartsError } from './errors.js';
|
|
6
16
|
import { Config as HarnessConfig } from '@react-native-harness/config';
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
const BRIDGE_READY_TIMEOUT = 10000;
|
|
17
|
+
import { createCrashMonitor, CrashMonitor } from './crash-monitor.js';
|
|
10
18
|
|
|
11
19
|
export type Harness = {
|
|
12
20
|
runTests: BridgeClientFunctions['runTests'];
|
|
13
21
|
restart: () => Promise<void>;
|
|
14
22
|
dispose: () => Promise<void>;
|
|
23
|
+
crashMonitor: CrashMonitor;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const waitForAppReady = async (options: {
|
|
27
|
+
metroEvents: Reporter;
|
|
28
|
+
serverBridge: BridgeServer;
|
|
29
|
+
platformInstance: HarnessPlatformRunner;
|
|
30
|
+
bundleStartTimeout: number;
|
|
31
|
+
maxRestarts: number;
|
|
32
|
+
signal: AbortSignal;
|
|
33
|
+
}): Promise<void> => {
|
|
34
|
+
const {
|
|
35
|
+
metroEvents,
|
|
36
|
+
serverBridge,
|
|
37
|
+
platformInstance,
|
|
38
|
+
bundleStartTimeout,
|
|
39
|
+
maxRestarts,
|
|
40
|
+
signal,
|
|
41
|
+
} = options;
|
|
42
|
+
|
|
43
|
+
let restartCount = 0;
|
|
44
|
+
let isBundling = false;
|
|
45
|
+
let bundleTimeoutId: NodeJS.Timeout | null = null;
|
|
46
|
+
|
|
47
|
+
const clearBundleTimeout = () => {
|
|
48
|
+
if (bundleTimeoutId) {
|
|
49
|
+
clearTimeout(bundleTimeoutId);
|
|
50
|
+
bundleTimeoutId = null;
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return new Promise<void>((resolve, reject) => {
|
|
55
|
+
// Handle abort signal
|
|
56
|
+
signal.addEventListener('abort', () => {
|
|
57
|
+
clearBundleTimeout();
|
|
58
|
+
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Start/restart the bundle timeout
|
|
62
|
+
const startBundleTimeout = () => {
|
|
63
|
+
clearBundleTimeout();
|
|
64
|
+
bundleTimeoutId = setTimeout(() => {
|
|
65
|
+
if (isBundling) return; // Don't restart while bundling
|
|
66
|
+
|
|
67
|
+
if (restartCount >= maxRestarts) {
|
|
68
|
+
cleanup();
|
|
69
|
+
reject(new MaxAppRestartsError(restartCount + 1));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
restartCount++;
|
|
74
|
+
platformInstance.restartApp().catch(reject);
|
|
75
|
+
startBundleTimeout(); // Reset timer for next attempt
|
|
76
|
+
}, bundleStartTimeout);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Metro event listener
|
|
80
|
+
const onMetroEvent = (event: ReportableEvent) => {
|
|
81
|
+
if (event.type === 'bundle_build_started') {
|
|
82
|
+
isBundling = true;
|
|
83
|
+
clearBundleTimeout(); // Cancel restart timer while bundling
|
|
84
|
+
} else if (
|
|
85
|
+
event.type === 'bundle_build_done' ||
|
|
86
|
+
event.type === 'bundle_build_failed'
|
|
87
|
+
) {
|
|
88
|
+
isBundling = false;
|
|
89
|
+
startBundleTimeout(); // Reset timer after bundle completes
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Bridge ready listener
|
|
94
|
+
const onReady = () => {
|
|
95
|
+
cleanup();
|
|
96
|
+
resolve();
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const cleanup = () => {
|
|
100
|
+
clearBundleTimeout();
|
|
101
|
+
metroEvents.removeListener(onMetroEvent);
|
|
102
|
+
serverBridge.off('ready', onReady);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Setup listeners
|
|
106
|
+
metroEvents.addListener(onMetroEvent);
|
|
107
|
+
serverBridge.once('ready', onReady);
|
|
108
|
+
|
|
109
|
+
// Start the app and timeout
|
|
110
|
+
platformInstance.restartApp().catch(reject);
|
|
111
|
+
startBundleTimeout();
|
|
112
|
+
});
|
|
15
113
|
};
|
|
16
114
|
|
|
17
115
|
const getHarnessInternal = async (
|
|
@@ -39,6 +137,12 @@ const getHarnessInternal = async (
|
|
|
39
137
|
]);
|
|
40
138
|
};
|
|
41
139
|
|
|
140
|
+
const crashMonitor = createCrashMonitor({
|
|
141
|
+
interval: config.crashDetectionInterval,
|
|
142
|
+
platformRunner: platformInstance,
|
|
143
|
+
bridgeServer: serverBridge,
|
|
144
|
+
});
|
|
145
|
+
|
|
42
146
|
if (signal.aborted) {
|
|
43
147
|
await dispose();
|
|
44
148
|
|
|
@@ -46,23 +150,14 @@ const getHarnessInternal = async (
|
|
|
46
150
|
}
|
|
47
151
|
|
|
48
152
|
try {
|
|
49
|
-
await
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
platformInstance.restartApp().catch(reject);
|
|
58
|
-
}),
|
|
59
|
-
{
|
|
60
|
-
minTimeout: BRIDGE_READY_TIMEOUT,
|
|
61
|
-
maxTimeout: BRIDGE_READY_TIMEOUT,
|
|
62
|
-
retries: Infinity,
|
|
63
|
-
signal,
|
|
64
|
-
}
|
|
65
|
-
);
|
|
153
|
+
await waitForAppReady({
|
|
154
|
+
metroEvents: metroInstance.events,
|
|
155
|
+
serverBridge,
|
|
156
|
+
platformInstance: platformInstance as HarnessPlatformRunner,
|
|
157
|
+
bundleStartTimeout: config.bundleStartTimeout,
|
|
158
|
+
maxRestarts: config.maxAppRestarts,
|
|
159
|
+
signal,
|
|
160
|
+
});
|
|
66
161
|
} catch (error) {
|
|
67
162
|
await dispose();
|
|
68
163
|
throw error;
|
|
@@ -70,7 +165,11 @@ const getHarnessInternal = async (
|
|
|
70
165
|
|
|
71
166
|
const restart = () =>
|
|
72
167
|
new Promise<void>((resolve, reject) => {
|
|
73
|
-
|
|
168
|
+
crashMonitor.markIntentionalRestart();
|
|
169
|
+
serverBridge.once('ready', () => {
|
|
170
|
+
crashMonitor.clearIntentionalRestart();
|
|
171
|
+
resolve();
|
|
172
|
+
});
|
|
74
173
|
platformInstance.restartApp().catch(reject);
|
|
75
174
|
});
|
|
76
175
|
|
|
@@ -86,6 +185,7 @@ const getHarnessInternal = async (
|
|
|
86
185
|
},
|
|
87
186
|
restart,
|
|
88
187
|
dispose,
|
|
188
|
+
crashMonitor,
|
|
89
189
|
};
|
|
90
190
|
};
|
|
91
191
|
|
package/src/index.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { teardown } from './teardown.js';
|
|
|
17
17
|
import { HarnessError } from '@react-native-harness/tools';
|
|
18
18
|
import { getErrorMessage } from './logs.js';
|
|
19
19
|
import { DeviceNotRespondingError } from '@react-native-harness/bridge';
|
|
20
|
+
import { NativeCrashError } from './errors.js';
|
|
20
21
|
|
|
21
22
|
class CancelRun extends Error {
|
|
22
23
|
constructor(message?: string) {
|
|
@@ -104,17 +105,52 @@ export default class JestHarness implements CallbackTestRunnerInterface {
|
|
|
104
105
|
}
|
|
105
106
|
isFirstTest = false;
|
|
106
107
|
|
|
107
|
-
return onStart(test).then(() =>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
108
|
+
return onStart(test).then(async () => {
|
|
109
|
+
if (!harnessConfig.detectNativeCrashes) {
|
|
110
|
+
return runHarnessTestFile({
|
|
111
|
+
testPath: test.path,
|
|
112
|
+
harness,
|
|
113
|
+
globalConfig: this.#globalConfig,
|
|
114
|
+
projectConfig: test.context.config,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Start crash monitoring
|
|
119
|
+
const crashPromise = harness.crashMonitor.startMonitoring(
|
|
120
|
+
test.path
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
const result = await Promise.race([
|
|
125
|
+
runHarnessTestFile({
|
|
126
|
+
testPath: test.path,
|
|
127
|
+
harness,
|
|
128
|
+
globalConfig: this.#globalConfig,
|
|
129
|
+
projectConfig: test.context.config,
|
|
130
|
+
}),
|
|
131
|
+
crashPromise,
|
|
132
|
+
]);
|
|
133
|
+
|
|
134
|
+
return result;
|
|
135
|
+
} finally {
|
|
136
|
+
harness.crashMonitor.stopMonitoring();
|
|
137
|
+
}
|
|
138
|
+
});
|
|
115
139
|
})
|
|
116
140
|
.then((result) => onResult(test, result))
|
|
117
|
-
.catch((err) => {
|
|
141
|
+
.catch(async (err) => {
|
|
142
|
+
if (err instanceof NativeCrashError) {
|
|
143
|
+
onFailure(test, {
|
|
144
|
+
message: err.message,
|
|
145
|
+
stack: '',
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Restart the app for the next test file
|
|
149
|
+
await harness.restart();
|
|
150
|
+
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
118
154
|
if (err instanceof DeviceNotRespondingError) {
|
|
119
155
|
onFailure(test, {
|
|
120
156
|
message: err.message,
|
package/dist/cli-args.test.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"cli-args.test.d.ts","sourceRoot":"","sources":["../src/cli-args.test.ts"],"names":[],"mappings":""}
|
package/dist/cli-args.test.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/event-mapper.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/dist/event-mapper.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
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
|
-
};
|
package/dist/global-setup.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/dist/global-setup.js
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/dist/global-teardown.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|