@react-native-harness/jest 1.1.0-rc.3 → 1.1.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__/harness.test.js +59 -1
- package/dist/__tests__/metro-port.test.d.ts +2 -0
- package/dist/__tests__/metro-port.test.d.ts.map +1 -0
- package/dist/__tests__/metro-port.test.js +52 -0
- package/dist/__tests__/resource-lock.test.js +36 -1
- package/dist/errors.d.ts +5 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +11 -0
- package/dist/harness.d.ts +1 -0
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +26 -13
- package/dist/logs.d.ts +1 -0
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +3 -0
- package/dist/metro-port.d.ts +16 -0
- package/dist/metro-port.d.ts.map +1 -0
- package/dist/metro-port.js +48 -0
- package/dist/resource-lock.d.ts.map +1 -1
- package/dist/resource-lock.js +32 -11
- package/dist/setup.js +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +7 -7
- package/src/__tests__/harness.test.ts +89 -1
- package/src/__tests__/metro-port.test.ts +61 -0
- package/src/__tests__/resource-lock.test.ts +58 -4
- package/src/errors.ts +13 -0
- package/src/harness.ts +34 -12
- package/src/logs.ts +11 -0
- package/src/metro-port.ts +76 -0
- package/src/resource-lock.ts +59 -37
- package/src/setup.ts +1 -1
- 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/crash-monitor.d.ts +0 -16
- package/dist/crash-monitor.d.ts.map +0 -1
- package/dist/crash-monitor.js +0 -75
- 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/src/resource-lock.ts
CHANGED
|
@@ -32,7 +32,7 @@ export type ResourceLease = {
|
|
|
32
32
|
export type ResourceLockManager = {
|
|
33
33
|
acquire: (
|
|
34
34
|
key: string,
|
|
35
|
-
options?: ResourceLockAcquireOptions
|
|
35
|
+
options?: ResourceLockAcquireOptions,
|
|
36
36
|
) => Promise<ResourceLease>;
|
|
37
37
|
};
|
|
38
38
|
|
|
@@ -115,6 +115,21 @@ const readJsonFile = async <T>(filePath: string): Promise<T | null> => {
|
|
|
115
115
|
}
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
+
const writeJsonFileAtomic = async <T>(
|
|
119
|
+
filePath: string,
|
|
120
|
+
value: T,
|
|
121
|
+
): Promise<void> => {
|
|
122
|
+
const tempPath = `${filePath}.${process.pid}.${crypto.randomUUID()}.tmp`;
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await fs.writeFile(tempPath, JSON.stringify(value), 'utf8');
|
|
126
|
+
await fs.rename(tempPath, filePath);
|
|
127
|
+
} catch (error) {
|
|
128
|
+
await removeFileIfPresent(tempPath);
|
|
129
|
+
throw error;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
118
133
|
const removeFileIfPresent = async (filePath: string): Promise<void> => {
|
|
119
134
|
try {
|
|
120
135
|
await fs.rm(filePath, { force: true });
|
|
@@ -143,7 +158,7 @@ const isMetadataStale = (
|
|
|
143
158
|
metadata: ResourceLockMetadata,
|
|
144
159
|
now: number,
|
|
145
160
|
staleLockTimeoutMs: number,
|
|
146
|
-
isProcessActive: (pid: number) => boolean
|
|
161
|
+
isProcessActive: (pid: number) => boolean,
|
|
147
162
|
): boolean => {
|
|
148
163
|
if (!isProcessActive(metadata.pid)) {
|
|
149
164
|
return true;
|
|
@@ -154,14 +169,14 @@ const isMetadataStale = (
|
|
|
154
169
|
|
|
155
170
|
const isQueuedTicketStale = (
|
|
156
171
|
metadata: ResourceLockMetadata,
|
|
157
|
-
isProcessActive: (pid: number) => boolean
|
|
172
|
+
isProcessActive: (pid: number) => boolean,
|
|
158
173
|
): boolean => {
|
|
159
174
|
return !isProcessActive(metadata.pid);
|
|
160
175
|
};
|
|
161
176
|
|
|
162
177
|
const waitForPollInterval = (
|
|
163
178
|
ms: number,
|
|
164
|
-
signal?: AbortSignal
|
|
179
|
+
signal?: AbortSignal,
|
|
165
180
|
): Promise<void> => {
|
|
166
181
|
if (!signal) {
|
|
167
182
|
return wait(ms);
|
|
@@ -187,7 +202,7 @@ const waitForPollInterval = (
|
|
|
187
202
|
};
|
|
188
203
|
|
|
189
204
|
const readQueueTickets = async (
|
|
190
|
-
queueDir: string
|
|
205
|
+
queueDir: string,
|
|
191
206
|
): Promise<ResourceLockMetadata[]> => {
|
|
192
207
|
const ticketEntries = await fs.readdir(queueDir, { withFileTypes: true });
|
|
193
208
|
const tickets = await Promise.all(
|
|
@@ -196,15 +211,15 @@ const readQueueTickets = async (
|
|
|
196
211
|
.map(async (entry) => ({
|
|
197
212
|
name: entry.name,
|
|
198
213
|
metadata: await readJsonFile<ResourceLockMetadata>(
|
|
199
|
-
path.join(queueDir, entry.name)
|
|
214
|
+
path.join(queueDir, entry.name),
|
|
200
215
|
),
|
|
201
|
-
}))
|
|
216
|
+
})),
|
|
202
217
|
);
|
|
203
218
|
|
|
204
219
|
return tickets
|
|
205
220
|
.filter(
|
|
206
221
|
(entry): entry is { name: string; metadata: ResourceLockMetadata } =>
|
|
207
|
-
entry.metadata !== null
|
|
222
|
+
entry.metadata !== null,
|
|
208
223
|
)
|
|
209
224
|
.sort((left, right) => left.name.localeCompare(right.name))
|
|
210
225
|
.map((entry) => entry.metadata);
|
|
@@ -229,10 +244,10 @@ const cleanupQueue = async (options: {
|
|
|
229
244
|
logger.debug(
|
|
230
245
|
'removing stale queued ticket %s for key %s',
|
|
231
246
|
ticket.ticketId,
|
|
232
|
-
ticket.key
|
|
247
|
+
ticket.key,
|
|
233
248
|
);
|
|
234
249
|
await removeFileIfPresent(
|
|
235
|
-
path.join(paths.queueDir, `${ticket.ticketId}.json`)
|
|
250
|
+
path.join(paths.queueDir, `${ticket.ticketId}.json`),
|
|
236
251
|
);
|
|
237
252
|
continue;
|
|
238
253
|
}
|
|
@@ -265,7 +280,7 @@ const maybeClearStaleOwner = async (options: {
|
|
|
265
280
|
logger.debug(
|
|
266
281
|
'removing stale owner ticket %s for key %s',
|
|
267
282
|
owner.ticketId,
|
|
268
|
-
owner.key
|
|
283
|
+
owner.key,
|
|
269
284
|
);
|
|
270
285
|
await removeFileIfPresent(ownerFilePath);
|
|
271
286
|
return null;
|
|
@@ -273,7 +288,7 @@ const maybeClearStaleOwner = async (options: {
|
|
|
273
288
|
|
|
274
289
|
const claimOwnership = async (
|
|
275
290
|
ownerFilePath: string,
|
|
276
|
-
metadata: ResourceLockMetadata
|
|
291
|
+
metadata: ResourceLockMetadata,
|
|
277
292
|
): Promise<boolean> => {
|
|
278
293
|
try {
|
|
279
294
|
await fs.writeFile(ownerFilePath, JSON.stringify(metadata), {
|
|
@@ -291,7 +306,7 @@ const claimOwnership = async (
|
|
|
291
306
|
};
|
|
292
307
|
|
|
293
308
|
export const createResourceLockManager = (
|
|
294
|
-
options: ResourceLockManagerOptions = {}
|
|
309
|
+
options: ResourceLockManagerOptions = {},
|
|
295
310
|
): ResourceLockManager => {
|
|
296
311
|
const rootDir = options.rootDir ?? DEFAULT_ROOT_DIR;
|
|
297
312
|
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
@@ -323,6 +338,7 @@ export const createResourceLockManager = (
|
|
|
323
338
|
scopedLogger.debug('queued ticket %s for key %s', ticketId, key);
|
|
324
339
|
|
|
325
340
|
let heartbeatTimer: NodeJS.Timeout | null = null;
|
|
341
|
+
let heartbeatInFlight = false;
|
|
326
342
|
let released = false;
|
|
327
343
|
let didNotifyWait = false;
|
|
328
344
|
const waitStartedAt = Date.now();
|
|
@@ -339,7 +355,7 @@ export const createResourceLockManager = (
|
|
|
339
355
|
}
|
|
340
356
|
|
|
341
357
|
const owner = await readJsonFile<ResourceLockMetadata>(
|
|
342
|
-
paths.ownerFilePath
|
|
358
|
+
paths.ownerFilePath,
|
|
343
359
|
);
|
|
344
360
|
if (owner?.ticketId === ticketId) {
|
|
345
361
|
await removeFileIfPresent(paths.ownerFilePath);
|
|
@@ -351,30 +367,36 @@ export const createResourceLockManager = (
|
|
|
351
367
|
|
|
352
368
|
const startHeartbeat = () => {
|
|
353
369
|
heartbeatTimer = setInterval(async () => {
|
|
354
|
-
|
|
355
|
-
const owner = await readJsonFile<ResourceLockMetadata>(
|
|
356
|
-
paths.ownerFilePath
|
|
357
|
-
);
|
|
358
|
-
|
|
359
|
-
if (released || owner?.ticketId !== ticketId) {
|
|
370
|
+
if (heartbeatInFlight) {
|
|
360
371
|
return;
|
|
361
372
|
}
|
|
362
373
|
|
|
363
|
-
|
|
364
|
-
...owner,
|
|
365
|
-
heartbeatAt: nextHeartbeatAt,
|
|
366
|
-
};
|
|
374
|
+
heartbeatInFlight = true;
|
|
367
375
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
376
|
+
try {
|
|
377
|
+
const nextHeartbeatAt = Date.now();
|
|
378
|
+
const owner = await readJsonFile<ResourceLockMetadata>(
|
|
379
|
+
paths.ownerFilePath,
|
|
380
|
+
);
|
|
371
381
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
382
|
+
if (released || owner?.ticketId !== ticketId) {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const nextMetadata: ResourceLockMetadata = {
|
|
387
|
+
...owner,
|
|
388
|
+
heartbeatAt: nextHeartbeatAt,
|
|
389
|
+
};
|
|
390
|
+
|
|
391
|
+
if (released) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
await writeJsonFileAtomic(paths.ownerFilePath, nextMetadata);
|
|
396
|
+
scopedLogger.debug('refreshed heartbeat for ticket %s', ticketId);
|
|
397
|
+
} finally {
|
|
398
|
+
heartbeatInFlight = false;
|
|
399
|
+
}
|
|
378
400
|
}, heartbeatIntervalMs);
|
|
379
401
|
heartbeatTimer.unref?.();
|
|
380
402
|
};
|
|
@@ -391,12 +413,12 @@ export const createResourceLockManager = (
|
|
|
391
413
|
isProcessActive,
|
|
392
414
|
});
|
|
393
415
|
const ownIndex = activeTickets.findIndex(
|
|
394
|
-
(entry) => entry.ticketId === ticketId
|
|
416
|
+
(entry) => entry.ticketId === ticketId,
|
|
395
417
|
);
|
|
396
418
|
|
|
397
419
|
if (ownIndex === -1) {
|
|
398
420
|
throw new Error(
|
|
399
|
-
`Queued ticket ${ticketId} disappeared before acquisition
|
|
421
|
+
`Queued ticket ${ticketId} disappeared before acquisition.`,
|
|
400
422
|
);
|
|
401
423
|
}
|
|
402
424
|
|
|
@@ -420,7 +442,7 @@ export const createResourceLockManager = (
|
|
|
420
442
|
scopedLogger.debug(
|
|
421
443
|
'acquired lock for key %s with ticket %s',
|
|
422
444
|
key,
|
|
423
|
-
ticketId
|
|
445
|
+
ticketId,
|
|
424
446
|
);
|
|
425
447
|
return { release };
|
|
426
448
|
}
|
|
@@ -436,7 +458,7 @@ export const createResourceLockManager = (
|
|
|
436
458
|
'waiting for key %s with ticket %s at queue position %d',
|
|
437
459
|
key,
|
|
438
460
|
ticketId,
|
|
439
|
-
ownIndex + 1
|
|
461
|
+
ownIndex + 1,
|
|
440
462
|
);
|
|
441
463
|
|
|
442
464
|
await waitForPollInterval(pollIntervalMs, acquireOptions.signal);
|
package/src/setup.ts
CHANGED
|
@@ -122,6 +122,6 @@ export const setup = async (globalConfig: JestConfig.GlobalConfig) => {
|
|
|
122
122
|
logTestEnvironmentReady(selectedRunner);
|
|
123
123
|
setupLogger.debug('Harness instance is ready');
|
|
124
124
|
|
|
125
|
-
global.HARNESS_CONFIG =
|
|
125
|
+
global.HARNESS_CONFIG = harness.config;
|
|
126
126
|
global.HARNESS = harness;
|
|
127
127
|
};
|
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/crash-monitor.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"}
|
package/dist/crash-monitor.js
DELETED
|
@@ -1,75 +0,0 @@
|
|
|
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
|
-
};
|
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"}
|
package/dist/harness-reporter.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
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
|
-
}
|
package/dist/jest-options.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"jest-options.d.ts","sourceRoot":"","sources":["../src/jest-options.ts"],"names":[],"mappings":""}
|
package/dist/jest-options.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/types.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { TestContext, TestResult } from '@jest/test-result';
|
|
2
|
-
import type { Config, TestRunnerOptions } from 'jest-runner';
|
|
3
|
-
import type { CustomEnvironment } from '@react-native-harness/cli';
|
|
4
|
-
export type RunTestOptions<ExtraOptions extends Record<string, unknown> = Record<string, unknown>> = {
|
|
5
|
-
config: Config.ProjectConfig;
|
|
6
|
-
extraOptions: ExtraOptions;
|
|
7
|
-
globalConfig: Config.GlobalConfig;
|
|
8
|
-
rawModuleMap: ReturnType<TestContext['moduleMap']['getRawModuleMap']> | null;
|
|
9
|
-
options: TestRunnerOptions;
|
|
10
|
-
testPath: string;
|
|
11
|
-
};
|
|
12
|
-
export type RunTest<ExtraOptions extends Record<string, unknown> = Record<string, unknown>> = (options: RunTestOptions<ExtraOptions>, environment: CustomEnvironment) => TestResult | Promise<TestResult>;
|
|
13
|
-
export interface TestDetail {
|
|
14
|
-
title: string;
|
|
15
|
-
path: string;
|
|
16
|
-
}
|
|
17
|
-
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAEnE,MAAM,MAAM,cAAc,CACxB,YAAY,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACpE;IACF,MAAM,EAAE,MAAM,CAAC,aAAa,CAAC;IAC7B,YAAY,EAAE,YAAY,CAAC;IAC3B,YAAY,EAAE,MAAM,CAAC,YAAY,CAAC;IAClC,YAAY,EAAE,UAAU,CAAC,WAAW,CAAC,WAAW,CAAC,CAAC,iBAAiB,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7E,OAAO,EAAE,iBAAiB,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF,MAAM,MAAM,OAAO,CACjB,YAAY,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,IACpE,CACF,OAAO,EAAE,cAAc,CAAC,YAAY,CAAC,EACrC,WAAW,EAAE,iBAAiB,KAC3B,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd"}
|
package/dist/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|