@react-native-harness/jest 1.1.0 → 1.2.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.d.ts +2 -0
- package/dist/__tests__/bridge.test.d.ts.map +1 -0
- package/dist/__tests__/bridge.test.js +142 -0
- package/dist/__tests__/client-log-handler.test.js +116 -72
- package/dist/__tests__/crash-monitor.test.d.ts +2 -0
- package/dist/__tests__/crash-monitor.test.d.ts.map +1 -0
- package/dist/__tests__/crash-monitor.test.js +212 -0
- package/dist/__tests__/execute-run.test.d.ts +2 -0
- package/dist/__tests__/execute-run.test.d.ts.map +1 -0
- package/dist/__tests__/execute-run.test.js +229 -0
- package/dist/__tests__/harness.test.js +35 -0
- package/dist/__tests__/hook-queue.test.d.ts +2 -0
- package/dist/__tests__/hook-queue.test.d.ts.map +1 -0
- package/dist/__tests__/hook-queue.test.js +56 -0
- package/dist/__tests__/index.test.js +94 -37
- package/dist/__tests__/resource-lock.test.js +24 -24
- package/dist/cli-args.test.d.ts +2 -0
- package/dist/cli-args.test.d.ts.map +1 -0
- package/dist/cli-args.test.js +1 -0
- package/dist/client-log-handler.d.ts +10 -11
- package/dist/client-log-handler.d.ts.map +1 -1
- package/dist/client-log-handler.js +36 -58
- package/dist/crash-monitor.d.ts +23 -0
- package/dist/crash-monitor.d.ts.map +1 -0
- package/dist/crash-monitor.js +176 -0
- package/dist/event-mapper.d.ts +16 -0
- package/dist/event-mapper.d.ts.map +1 -0
- package/dist/event-mapper.js +120 -0
- package/dist/execute-run.d.ts +4 -0
- package/dist/execute-run.d.ts.map +1 -0
- package/dist/execute-run.js +151 -0
- package/dist/global-setup.d.ts +3 -0
- package/dist/global-setup.d.ts.map +1 -0
- package/dist/global-setup.js +60 -0
- package/dist/global-teardown.d.ts +3 -0
- package/dist/global-teardown.d.ts.map +1 -0
- package/dist/global-teardown.js +11 -0
- package/dist/harness-reporter.d.ts +8 -0
- package/dist/harness-reporter.d.ts.map +1 -0
- package/dist/harness-reporter.js +12 -0
- package/dist/harness-session.d.ts +34 -0
- package/dist/harness-session.d.ts.map +1 -0
- package/dist/harness-session.js +523 -0
- package/dist/harness.d.ts.map +1 -1
- package/dist/harness.js +6 -1
- package/dist/hook-queue.d.ts +6 -0
- package/dist/hook-queue.d.ts.map +1 -0
- package/dist/hook-queue.js +18 -0
- package/dist/index.d.ts +0 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -230
- package/dist/jest-options.d.ts +2 -0
- package/dist/jest-options.d.ts.map +1 -0
- package/dist/jest-options.js +1 -0
- package/dist/logs.d.ts +1 -0
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +3 -0
- package/dist/resource-lock.d.ts +0 -1
- package/dist/resource-lock.d.ts.map +1 -1
- package/dist/resource-lock.js +1 -1
- package/dist/run.d.ts +2 -2
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +3 -3
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/types.d.ts +17 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +7 -8
- package/src/__tests__/bridge.test.ts +184 -0
- package/src/__tests__/client-log-handler.test.ts +117 -81
- package/src/__tests__/crash-monitor.test.ts +288 -0
- package/src/__tests__/execute-run.test.ts +325 -0
- package/src/__tests__/hook-queue.test.ts +77 -0
- package/src/__tests__/index.test.ts +135 -55
- package/src/__tests__/resource-lock.test.ts +29 -42
- package/src/client-log-handler.ts +48 -64
- package/src/crash-monitor.ts +248 -0
- package/src/execute-run.ts +199 -0
- package/src/harness-session.ts +734 -0
- package/src/hook-queue.ts +26 -0
- package/src/index.ts +17 -330
- package/src/logs.ts +4 -0
- package/src/resource-lock.ts +1 -1
- package/src/run.ts +5 -5
- package/tsconfig.tsbuildinfo +1 -0
- package/src/__tests__/crash-supervisor.test.ts +0 -200
- package/src/__tests__/harness-cache.test.ts +0 -74
- package/src/__tests__/harness.test.ts +0 -870
- package/src/crash-supervisor.ts +0 -321
- package/src/env.d.ts +0 -9
- package/src/harness.ts +0 -864
- package/src/setup.ts +0 -127
- package/src/teardown.ts +0 -14
|
@@ -1,61 +1,141 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import type {
|
|
3
|
-
import type {
|
|
4
|
-
import type {
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
2
|
+
import type { Config, Test, TestRunnerOptions, TestWatcher } from 'jest-runner';
|
|
3
|
+
import type { TestSuiteResult } from '@react-native-harness/bridge';
|
|
4
|
+
import type { HarnessSession } from '../harness-session.js';
|
|
5
|
+
import { HarnessError } from '@react-native-harness/tools';
|
|
5
6
|
import JestHarness from '../index.js';
|
|
6
|
-
|
|
7
|
+
|
|
8
|
+
const resolveUndefined = async () => undefined;
|
|
9
|
+
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
// Module mocks
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
const mockSession: HarnessSession = {
|
|
15
|
+
config: { metroPort: 8081 } as HarnessSession['config'],
|
|
16
|
+
context: {} as HarnessSession['context'],
|
|
17
|
+
ensureAppReady: vi.fn(resolveUndefined),
|
|
18
|
+
runTestFile: vi.fn(async (): Promise<TestSuiteResult> => ({
|
|
19
|
+
name: '',
|
|
20
|
+
tests: [],
|
|
21
|
+
suites: [],
|
|
22
|
+
status: 'passed',
|
|
23
|
+
duration: 0,
|
|
24
|
+
})),
|
|
25
|
+
restartApp: vi.fn(resolveUndefined),
|
|
26
|
+
resetCrashState: vi.fn(),
|
|
27
|
+
flushClientLogs: vi.fn(() => []),
|
|
28
|
+
callHook: vi.fn(resolveUndefined),
|
|
29
|
+
setRunState: vi.fn(),
|
|
30
|
+
dispose: vi.fn(resolveUndefined),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const mockCreateHarnessSession = vi.hoisted(() => vi.fn(async () => mockSession));
|
|
34
|
+
const mockExecuteRun = vi.hoisted(() => vi.fn(async () => undefined));
|
|
35
|
+
|
|
36
|
+
vi.mock('../harness-session.js', () => ({
|
|
37
|
+
createHarnessSession: mockCreateHarnessSession,
|
|
38
|
+
}));
|
|
39
|
+
|
|
40
|
+
vi.mock('../execute-run.js', () => ({
|
|
41
|
+
executeRun: mockExecuteRun,
|
|
42
|
+
}));
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Helpers
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
const makeOptions = (): TestRunnerOptions => ({ serial: true });
|
|
49
|
+
const makeWatcher = (): TestWatcher => ({ isInterrupted: () => false } as TestWatcher);
|
|
50
|
+
const makeTest = (): Test => ({
|
|
51
|
+
path: '/test/example.ts',
|
|
52
|
+
context: { config: {} as Config.ProjectConfig },
|
|
53
|
+
} as Test);
|
|
54
|
+
|
|
55
|
+
const makeGlobalConfig = (overrides: Partial<Config.GlobalConfig> = {}): Config.GlobalConfig =>
|
|
56
|
+
({ rootDir: '/project', watch: false, watchAll: false, collectCoverage: false, ...overrides } as Config.GlobalConfig);
|
|
57
|
+
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
// Tests
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
|
|
62
|
+
beforeEach(() => {
|
|
63
|
+
vi.clearAllMocks();
|
|
64
|
+
(mockSession.dispose as ReturnType<typeof vi.fn>).mockResolvedValue(undefined);
|
|
65
|
+
mockExecuteRun.mockResolvedValue(undefined);
|
|
66
|
+
mockCreateHarnessSession.mockResolvedValue(mockSession);
|
|
67
|
+
});
|
|
7
68
|
|
|
8
69
|
describe('JestHarness', () => {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
expect(onFailure).toHaveBeenCalledWith(
|
|
51
|
-
expect.objectContaining({
|
|
52
|
-
path: '/tmp/example.harness.ts',
|
|
53
|
-
}),
|
|
54
|
-
{
|
|
55
|
-
message:
|
|
56
|
-
'The app did not request its Metro bundle after 3 launch attempts within 1500ms. Last Metro status: unknown.',
|
|
57
|
-
stack: '',
|
|
70
|
+
describe('session lifecycle', () => {
|
|
71
|
+
it('creates a session on the first run', async () => {
|
|
72
|
+
const runner = new JestHarness(makeGlobalConfig());
|
|
73
|
+
|
|
74
|
+
await runner.runTests([makeTest()], makeWatcher(), vi.fn(), vi.fn(), vi.fn(), makeOptions());
|
|
75
|
+
|
|
76
|
+
expect(mockCreateHarnessSession).toHaveBeenCalledOnce();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('reuses the session across runs in watch mode', async () => {
|
|
80
|
+
const runner = new JestHarness(makeGlobalConfig({ watch: true }));
|
|
81
|
+
|
|
82
|
+
await runner.runTests([makeTest()], makeWatcher(), vi.fn(), vi.fn(), vi.fn(), makeOptions());
|
|
83
|
+
await runner.runTests([makeTest()], makeWatcher(), vi.fn(), vi.fn(), vi.fn(), makeOptions());
|
|
84
|
+
|
|
85
|
+
expect(mockCreateHarnessSession).toHaveBeenCalledOnce();
|
|
86
|
+
expect(mockSession.dispose).not.toHaveBeenCalled();
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('disposes the session after each run in normal mode', async () => {
|
|
90
|
+
const runner = new JestHarness(makeGlobalConfig());
|
|
91
|
+
|
|
92
|
+
await runner.runTests([makeTest()], makeWatcher(), vi.fn(), vi.fn(), vi.fn(), makeOptions());
|
|
93
|
+
|
|
94
|
+
expect(mockSession.dispose).toHaveBeenCalledOnce();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('creates a fresh session for each run in normal mode', async () => {
|
|
98
|
+
const runner = new JestHarness(makeGlobalConfig());
|
|
99
|
+
|
|
100
|
+
await runner.runTests([makeTest()], makeWatcher(), vi.fn(), vi.fn(), vi.fn(), makeOptions());
|
|
101
|
+
await runner.runTests([makeTest()], makeWatcher(), vi.fn(), vi.fn(), vi.fn(), makeOptions());
|
|
102
|
+
|
|
103
|
+
expect(mockCreateHarnessSession).toHaveBeenCalledTimes(2);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('error handling', () => {
|
|
108
|
+
it('converts HarnessError into a formatted string before throwing', async () => {
|
|
109
|
+
class TestHarnessError extends HarnessError {
|
|
110
|
+
constructor() { super('something went wrong'); this.name = 'TestHarnessError'; }
|
|
58
111
|
}
|
|
59
|
-
|
|
112
|
+
|
|
113
|
+
mockCreateHarnessSession.mockRejectedValue(new TestHarnessError());
|
|
114
|
+
|
|
115
|
+
const runner = new JestHarness(makeGlobalConfig());
|
|
116
|
+
|
|
117
|
+
await expect(
|
|
118
|
+
runner.runTests([makeTest()], makeWatcher(), vi.fn(), vi.fn(), vi.fn(), makeOptions()),
|
|
119
|
+
).rejects.toBeTypeOf('string');
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('propagates non-HarnessError exceptions as-is', async () => {
|
|
123
|
+
const cause = new TypeError('unexpected');
|
|
124
|
+
mockCreateHarnessSession.mockRejectedValue(cause);
|
|
125
|
+
|
|
126
|
+
const runner = new JestHarness(makeGlobalConfig());
|
|
127
|
+
|
|
128
|
+
await expect(
|
|
129
|
+
runner.runTests([makeTest()], makeWatcher(), vi.fn(), vi.fn(), vi.fn(), makeOptions()),
|
|
130
|
+
).rejects.toBe(cause);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it('throws when called without serial flag', async () => {
|
|
134
|
+
const runner = new JestHarness(makeGlobalConfig());
|
|
135
|
+
|
|
136
|
+
await expect(
|
|
137
|
+
runner.runTests([makeTest()], makeWatcher(), vi.fn(), vi.fn(), vi.fn(), { serial: false }),
|
|
138
|
+
).rejects.toThrow('Parallel test running is not supported');
|
|
139
|
+
});
|
|
60
140
|
});
|
|
61
141
|
});
|
|
@@ -2,10 +2,7 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import path from 'node:path';
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
|
-
import {
|
|
6
|
-
createResourceLockManager,
|
|
7
|
-
hashResourceLockKey,
|
|
8
|
-
} from '../resource-lock.js';
|
|
5
|
+
import { createResourceLockManager } from '../resource-lock.js';
|
|
9
6
|
|
|
10
7
|
describe('resource lock manager', () => {
|
|
11
8
|
let rootDir: string;
|
|
@@ -74,11 +71,11 @@ describe('resource lock manager', () => {
|
|
|
74
71
|
name: 'AbortError',
|
|
75
72
|
});
|
|
76
73
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
expect(queuedEntries).toHaveLength(0);
|
|
80
|
-
|
|
74
|
+
// The aborted waiter must have cleaned up its ticket. Verify by releasing
|
|
75
|
+
// the first lock and confirming a fresh acquire completes immediately.
|
|
81
76
|
await firstLease.release();
|
|
77
|
+
const cleanupLease = await manager.acquire(key);
|
|
78
|
+
await cleanupLease.release();
|
|
82
79
|
});
|
|
83
80
|
|
|
84
81
|
it('keeps queued tickets alive while the waiting process is still active', async () => {
|
|
@@ -102,38 +99,31 @@ describe('resource lock manager', () => {
|
|
|
102
99
|
});
|
|
103
100
|
|
|
104
101
|
it('reclaims a stale owner before granting the lock', async () => {
|
|
105
|
-
const
|
|
102
|
+
const key = 'web:browser:chromium';
|
|
103
|
+
|
|
104
|
+
// Simulate a live process holding the lock.
|
|
105
|
+
const manager1 = createResourceLockManager({
|
|
106
|
+
rootDir,
|
|
107
|
+
pollIntervalMs: 5,
|
|
108
|
+
heartbeatIntervalMs: 20,
|
|
109
|
+
});
|
|
110
|
+
const staleLease = await manager1.acquire(key);
|
|
111
|
+
|
|
112
|
+
// A second manager whose isProcessActive always returns false will consider
|
|
113
|
+
// any owner — including the live one above — immediately stale and reclaim it.
|
|
114
|
+
const manager2 = createResourceLockManager({
|
|
106
115
|
rootDir,
|
|
107
116
|
pollIntervalMs: 5,
|
|
108
117
|
heartbeatIntervalMs: 20,
|
|
109
118
|
staleLockTimeoutMs: 50,
|
|
110
119
|
isProcessActive: () => false,
|
|
111
120
|
});
|
|
112
|
-
const key = 'web:browser:chromium';
|
|
113
|
-
const keyDir = path.join(rootDir, hashResourceLockKey(key));
|
|
114
|
-
const queueDir = path.join(keyDir, 'queue');
|
|
115
|
-
const ownerFilePath = path.join(keyDir, 'owner.json');
|
|
116
|
-
|
|
117
|
-
await fs.mkdir(queueDir, { recursive: true });
|
|
118
|
-
await fs.writeFile(
|
|
119
|
-
ownerFilePath,
|
|
120
|
-
JSON.stringify({
|
|
121
|
-
ticketId: 'stale-owner',
|
|
122
|
-
key,
|
|
123
|
-
pid: 999999,
|
|
124
|
-
createdAt: Date.now() - 1000,
|
|
125
|
-
heartbeatAt: Date.now() - 1000,
|
|
126
|
-
}),
|
|
127
|
-
'utf8',
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
const lease = await manager.acquire(key);
|
|
131
|
-
const owner = JSON.parse(await fs.readFile(ownerFilePath, 'utf8')) as {
|
|
132
|
-
ticketId: string;
|
|
133
|
-
};
|
|
134
|
-
expect(owner.ticketId).not.toBe('stale-owner');
|
|
135
121
|
|
|
122
|
+
const lease = await manager2.acquire(key);
|
|
136
123
|
await lease.release();
|
|
124
|
+
|
|
125
|
+
// manager1 was evicted; its release is best-effort.
|
|
126
|
+
await staleLease.release();
|
|
137
127
|
});
|
|
138
128
|
|
|
139
129
|
it('keeps owner metadata valid when heartbeat writes overlap', async () => {
|
|
@@ -144,20 +134,12 @@ describe('resource lock manager', () => {
|
|
|
144
134
|
staleLockTimeoutMs: 200,
|
|
145
135
|
});
|
|
146
136
|
const key = 'ios:simulator:iPhone 17 Pro:26.2';
|
|
147
|
-
const ownerFilePath = path.join(
|
|
148
|
-
rootDir,
|
|
149
|
-
hashResourceLockKey(key),
|
|
150
|
-
'owner.json',
|
|
151
|
-
);
|
|
152
137
|
const actualWriteFile = fs.writeFile.bind(fs);
|
|
153
138
|
const writeFileSpy = vi
|
|
154
139
|
.spyOn(fs, 'writeFile')
|
|
155
140
|
.mockImplementation(async (file, data, options) => {
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
file.startsWith(ownerFilePath) &&
|
|
159
|
-
file !== ownerFilePath
|
|
160
|
-
) {
|
|
141
|
+
// Delay atomic temp-file writes to simulate overlapping heartbeat flushes.
|
|
142
|
+
if (typeof file === 'string' && file.startsWith(rootDir) && file.endsWith('.tmp')) {
|
|
161
143
|
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
162
144
|
}
|
|
163
145
|
|
|
@@ -166,6 +148,11 @@ describe('resource lock manager', () => {
|
|
|
166
148
|
|
|
167
149
|
try {
|
|
168
150
|
const lease = await manager.acquire(key);
|
|
151
|
+
|
|
152
|
+
// Discover the owner file after acquire creates the key directory.
|
|
153
|
+
const [keyDirName] = await fs.readdir(rootDir);
|
|
154
|
+
const ownerFilePath = path.join(rootDir, keyDirName, 'owner.json');
|
|
155
|
+
|
|
169
156
|
const initialOwner = JSON.parse(
|
|
170
157
|
await fs.readFile(ownerFilePath, 'utf8'),
|
|
171
158
|
) as ResourceLockOwner;
|
|
@@ -1,50 +1,33 @@
|
|
|
1
1
|
import type { ReportableEvent } from '@react-native-harness/bundler-metro';
|
|
2
|
-
import
|
|
2
|
+
import type { TestResult as JestTestResult } from '@jest/test-result';
|
|
3
3
|
import util from 'node:util';
|
|
4
|
-
import { log } from './logs.js';
|
|
5
4
|
|
|
6
5
|
export type ClientLogEvent = Extract<ReportableEvent, { type: 'client_log' }>;
|
|
6
|
+
export type ClientLogBuffer = NonNullable<JestTestResult['console']>;
|
|
7
|
+
export type ClientLogEntry = ClientLogBuffer[number];
|
|
8
|
+
export type ClientLogCollector = {
|
|
9
|
+
handleEvent: (event: ReportableEvent) => void;
|
|
10
|
+
flush: () => ClientLogBuffer;
|
|
11
|
+
};
|
|
7
12
|
|
|
8
13
|
type LogLevel = ClientLogEvent['level'];
|
|
14
|
+
type JestLogLevel = ClientLogEntry['type'];
|
|
9
15
|
|
|
10
16
|
/**
|
|
11
|
-
* Gets the
|
|
17
|
+
* Gets the Jest console level for a client log level.
|
|
12
18
|
* Note: Metro treats 'trace' as 'log' because Hermes doesn't include stack traces.
|
|
13
19
|
*/
|
|
14
|
-
const
|
|
15
|
-
// Metro converts trace to log for display (Hermes doesn't provide stack traces)
|
|
16
|
-
if (level === 'trace') {
|
|
17
|
-
return 'LOG';
|
|
18
|
-
}
|
|
19
|
-
return level.toUpperCase();
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Creates a styled tag for a log level with colored box appearance.
|
|
24
|
-
*/
|
|
25
|
-
const createLevelTag = (level: LogLevel): string => {
|
|
26
|
-
const displayLevel = getDisplayLevel(level);
|
|
27
|
-
const label = ` ${displayLevel} `;
|
|
28
|
-
|
|
29
|
-
if (!chalk.supportsColor) {
|
|
30
|
-
return `[${displayLevel}]`;
|
|
31
|
-
}
|
|
32
|
-
|
|
20
|
+
const getJestLogLevel = (level: LogLevel): JestLogLevel | null => {
|
|
33
21
|
switch (level) {
|
|
34
|
-
case '
|
|
35
|
-
|
|
36
|
-
case '
|
|
37
|
-
return
|
|
38
|
-
case 'info':
|
|
39
|
-
return chalk.reset.inverse.bold.cyan(label);
|
|
40
|
-
case 'debug':
|
|
41
|
-
return chalk.reset.inverse.bold.blue(label);
|
|
22
|
+
case 'group':
|
|
23
|
+
case 'groupCollapsed':
|
|
24
|
+
case 'groupEnd':
|
|
25
|
+
return null;
|
|
42
26
|
case 'trace':
|
|
43
|
-
|
|
44
|
-
return chalk.reset.inverse.bold.magenta(label);
|
|
27
|
+
return 'log';
|
|
45
28
|
case 'log':
|
|
46
29
|
default:
|
|
47
|
-
return
|
|
30
|
+
return level;
|
|
48
31
|
}
|
|
49
32
|
};
|
|
50
33
|
|
|
@@ -56,46 +39,47 @@ export const formatClientLogMessage = (data: unknown[]): string => {
|
|
|
56
39
|
if (data.length === 0) {
|
|
57
40
|
return '';
|
|
58
41
|
}
|
|
42
|
+
|
|
59
43
|
return util.format(...data);
|
|
60
44
|
};
|
|
61
45
|
|
|
62
46
|
/**
|
|
63
|
-
* Formats a client log event into
|
|
47
|
+
* Formats a client log event into Jest's buffered console entry format.
|
|
64
48
|
*/
|
|
65
|
-
export const
|
|
66
|
-
|
|
49
|
+
export const formatClientLogEntry = (
|
|
50
|
+
event: ClientLogEvent
|
|
51
|
+
): ClientLogEntry | null => {
|
|
52
|
+
const logLevel = getJestLogLevel(event.level);
|
|
53
|
+
if (!logLevel) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
|
|
67
57
|
const message = formatClientLogMessage(event.data);
|
|
68
|
-
return
|
|
58
|
+
return {
|
|
59
|
+
message,
|
|
60
|
+
origin: '',
|
|
61
|
+
type: logLevel,
|
|
62
|
+
};
|
|
69
63
|
};
|
|
70
64
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
* Returns true if the event was handled, false otherwise.
|
|
74
|
-
*/
|
|
75
|
-
export const handleClientLogEvent = (event: ReportableEvent): boolean => {
|
|
76
|
-
if (event.type !== 'client_log') {
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Skip group events - they don't produce output
|
|
81
|
-
if (
|
|
82
|
-
event.level === 'group' ||
|
|
83
|
-
event.level === 'groupCollapsed' ||
|
|
84
|
-
event.level === 'groupEnd'
|
|
85
|
-
) {
|
|
86
|
-
return true;
|
|
87
|
-
}
|
|
65
|
+
export const createClientLogCollector = (): ClientLogCollector => {
|
|
66
|
+
let buffer: ClientLogBuffer = [];
|
|
88
67
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
68
|
+
return {
|
|
69
|
+
handleEvent: (event) => {
|
|
70
|
+
if (event.type !== 'client_log') {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
93
73
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
74
|
+
const entry = formatClientLogEntry(event);
|
|
75
|
+
if (entry) {
|
|
76
|
+
buffer.push(entry);
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
flush: () => {
|
|
80
|
+
const flushed = buffer;
|
|
81
|
+
buffer = [];
|
|
82
|
+
return flushed;
|
|
83
|
+
},
|
|
100
84
|
};
|
|
101
85
|
};
|