@react-native-harness/jest 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/bridge.test.js +29 -4
- package/dist/__tests__/crash-monitor.test.js +194 -176
- package/dist/__tests__/environment-reset.test.d.ts +2 -0
- package/dist/__tests__/environment-reset.test.d.ts.map +1 -0
- package/dist/__tests__/environment-reset.test.js +238 -0
- package/dist/__tests__/errors.test.js +39 -0
- package/dist/__tests__/execute-run.test.js +439 -29
- package/dist/__tests__/format-harness-error.test.d.ts +2 -0
- package/dist/__tests__/format-harness-error.test.d.ts.map +1 -0
- package/dist/__tests__/format-harness-error.test.js +27 -0
- package/dist/__tests__/harness-cache.test.js +62 -41
- package/dist/__tests__/harness-session-signal.test.d.ts +2 -0
- package/dist/__tests__/harness-session-signal.test.d.ts.map +1 -0
- package/dist/__tests__/harness-session-signal.test.js +298 -0
- package/dist/__tests__/harness-session.test.js +217 -234
- package/dist/__tests__/index.test.js +62 -11
- package/dist/__tests__/metro-port.test.js +2 -0
- package/dist/__tests__/run.test.d.ts +2 -0
- package/dist/__tests__/run.test.d.ts.map +1 -0
- package/dist/__tests__/run.test.js +48 -0
- package/dist/__tests__/test-file-platform-filter.test.d.ts +2 -0
- package/dist/__tests__/test-file-platform-filter.test.d.ts.map +1 -0
- package/dist/__tests__/test-file-platform-filter.test.js +47 -0
- package/dist/crash-monitor.d.ts +12 -4
- package/dist/crash-monitor.d.ts.map +1 -1
- package/dist/crash-monitor.js +135 -102
- package/dist/diagnostics/__tests__/derivers.test.d.ts +2 -0
- package/dist/diagnostics/__tests__/derivers.test.d.ts.map +1 -0
- package/dist/diagnostics/__tests__/derivers.test.js +248 -0
- package/dist/diagnostics/__tests__/report.test.d.ts +2 -0
- package/dist/diagnostics/__tests__/report.test.d.ts.map +1 -0
- package/dist/diagnostics/__tests__/report.test.js +97 -0
- package/dist/diagnostics/derivers.d.ts +24 -0
- package/dist/diagnostics/derivers.d.ts.map +1 -0
- package/dist/diagnostics/derivers.js +146 -0
- package/dist/diagnostics/index.d.ts +3 -0
- package/dist/diagnostics/index.d.ts.map +1 -0
- package/dist/diagnostics/index.js +2 -0
- package/dist/diagnostics/report.d.ts +7 -0
- package/dist/diagnostics/report.d.ts.map +1 -0
- package/dist/diagnostics/report.js +84 -0
- package/dist/environment-reset.d.ts +51 -0
- package/dist/environment-reset.d.ts.map +1 -0
- package/dist/environment-reset.js +83 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +41 -2
- package/dist/execute-run.d.ts +4 -2
- package/dist/execute-run.d.ts.map +1 -1
- package/dist/execute-run.js +179 -49
- package/dist/format-harness-error.d.ts +5 -0
- package/dist/format-harness-error.d.ts.map +1 -0
- package/dist/format-harness-error.js +43 -0
- package/dist/harness-session.d.ts +46 -1
- package/dist/harness-session.d.ts.map +1 -1
- package/dist/harness-session.js +386 -118
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -2
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +22 -4
- package/dist/test-file-platform-filter.d.ts +5 -0
- package/dist/test-file-platform-filter.d.ts.map +1 -0
- package/dist/test-file-platform-filter.js +40 -0
- package/dist/toTestResult.d.ts +1 -0
- package/dist/toTestResult.d.ts.map +1 -1
- package/dist/toTestResult.js +2 -2
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +8 -7
- package/src/__tests__/bridge.test.ts +45 -5
- package/src/__tests__/crash-monitor.test.ts +234 -199
- package/src/__tests__/environment-reset.test.ts +308 -0
- package/src/__tests__/errors.test.ts +75 -0
- package/src/__tests__/execute-run.test.ts +561 -37
- package/src/__tests__/format-harness-error.test.ts +32 -0
- package/src/__tests__/harness-cache.test.ts +91 -0
- package/src/__tests__/harness-session.test.ts +303 -0
- package/src/__tests__/index.test.ts +92 -12
- package/src/__tests__/metro-port.test.ts +2 -0
- package/src/__tests__/run.test.ts +74 -0
- package/src/__tests__/test-file-platform-filter.test.ts +83 -0
- package/src/crash-monitor.ts +220 -131
- package/src/diagnostics/__tests__/derivers.test.ts +300 -0
- package/src/diagnostics/__tests__/report.test.ts +128 -0
- package/src/diagnostics/derivers.ts +170 -0
- package/src/diagnostics/index.ts +2 -0
- package/src/diagnostics/report.ts +124 -0
- package/src/environment-reset.ts +138 -0
- package/src/errors.ts +62 -1
- package/src/execute-run.ts +230 -57
- package/src/format-harness-error.ts +73 -0
- package/src/harness-session.ts +535 -162
- package/src/index.ts +40 -11
- package/src/run.ts +24 -4
- package/src/test-file-platform-filter.ts +57 -0
- package/src/toTestResult.ts +3 -2
- package/tsconfig.json +3 -0
- package/tsconfig.lib.json +3 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Integration test pairing createHarnessBridge (CLI side) with
|
|
3
3
|
* connectToHarness (app side). Tests the full connection lifecycle and
|
|
4
|
-
* RPC round-trip without
|
|
4
|
+
* RPC round-trip without coupling tests to the transport internals.
|
|
5
5
|
*/
|
|
6
6
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
7
7
|
import { createHarnessBridge } from '@react-native-harness/bridge/server';
|
|
@@ -28,7 +28,10 @@ afterEach(async () => {
|
|
|
28
28
|
// Allow the server to close cleanly.
|
|
29
29
|
await new Promise((r) => setTimeout(r, 10));
|
|
30
30
|
});
|
|
31
|
-
const connect = (callbacks = {
|
|
31
|
+
const connect = (callbacks = {
|
|
32
|
+
runTests: vi.fn(),
|
|
33
|
+
resetEnvironment: vi.fn(),
|
|
34
|
+
}) => connectToHarness(`ws://127.0.0.1:${bridgePort}`, callbacks);
|
|
32
35
|
const device = {
|
|
33
36
|
platform: 'ios',
|
|
34
37
|
manufacturer: 'Apple',
|
|
@@ -107,7 +110,7 @@ describe('bridge: createHarnessBridge + connectToHarness', () => {
|
|
|
107
110
|
};
|
|
108
111
|
const runTestsCb = vi.fn(async () => suiteResult);
|
|
109
112
|
const connectionPromise = bridge.nextConnection();
|
|
110
|
-
const handle = await connect({ runTests: runTestsCb });
|
|
113
|
+
const handle = await connect({ runTests: runTestsCb, resetEnvironment: vi.fn() });
|
|
111
114
|
handle.reportReady(device);
|
|
112
115
|
const conn = await connectionPromise;
|
|
113
116
|
const result = await conn.runTests('example.ts', {
|
|
@@ -117,6 +120,28 @@ describe('bridge: createHarnessBridge + connectToHarness', () => {
|
|
|
117
120
|
expect(result.tests[0].name).toBe('passes');
|
|
118
121
|
handle.disconnect();
|
|
119
122
|
});
|
|
123
|
+
it('rejects pending app calls when a newer client replaces the session', async () => {
|
|
124
|
+
const firstHandle = await connect({
|
|
125
|
+
runTests: async () => await new Promise(() => undefined),
|
|
126
|
+
resetEnvironment: vi.fn(),
|
|
127
|
+
});
|
|
128
|
+
firstHandle.reportReady(device);
|
|
129
|
+
const firstConnection = await bridge.nextConnection();
|
|
130
|
+
const pendingRun = firstConnection.runTests('example.ts', {
|
|
131
|
+
runner: '/runner.js',
|
|
132
|
+
});
|
|
133
|
+
const pendingRunAssertion = expect(pendingRun).rejects.toThrow('The app bridge was replaced by a newer app connection.');
|
|
134
|
+
const secondHandle = await connect();
|
|
135
|
+
secondHandle.reportReady({
|
|
136
|
+
...device,
|
|
137
|
+
model: 'iPhone 17 Pro Replacement',
|
|
138
|
+
});
|
|
139
|
+
await pendingRunAssertion;
|
|
140
|
+
await new Promise((r) => setTimeout(r, 20));
|
|
141
|
+
expect(bridge.connection?.device.model).toBe('iPhone 17 Pro Replacement');
|
|
142
|
+
secondHandle.disconnect();
|
|
143
|
+
firstHandle.disconnect();
|
|
144
|
+
});
|
|
120
145
|
});
|
|
121
146
|
describe('bridge events', () => {
|
|
122
147
|
it('emitEvent on app side fires the event listener on bridge', async () => {
|
|
@@ -136,7 +161,7 @@ describe('bridge: createHarnessBridge + connectToHarness', () => {
|
|
|
136
161
|
it('rejects pending nextConnection() waiters', async () => {
|
|
137
162
|
const pending = bridge.nextConnection();
|
|
138
163
|
bridge.dispose();
|
|
139
|
-
await expect(pending).rejects.toThrow('
|
|
164
|
+
await expect(pending).rejects.toThrow('The app bridge was disposed before the test file finished running.');
|
|
140
165
|
});
|
|
141
166
|
});
|
|
142
167
|
});
|
|
@@ -1,212 +1,230 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import path from 'node:path';
|
|
2
3
|
import { createCrashMonitor, CrashWatchCancelledError, } from '../crash-monitor.js';
|
|
3
|
-
import { NativeCrashError } from '../errors.js';
|
|
4
|
+
import { NativeCrashError, RuntimeDisconnectError } from '../errors.js';
|
|
4
5
|
const noop = () => undefined;
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
stop: vi.fn(resolveUndefined),
|
|
14
|
-
dispose: vi.fn(resolveUndefined),
|
|
6
|
+
const waitForClassification = () => new Promise((resolve) => setTimeout(resolve, 1600));
|
|
7
|
+
const createAppSessionMock = (initialState = { status: 'running' }, logs = [], getCrashDetails) => {
|
|
8
|
+
let state = initialState;
|
|
9
|
+
let listener = null;
|
|
10
|
+
const session = {
|
|
11
|
+
dispose: vi.fn(async () => undefined),
|
|
12
|
+
getState: vi.fn(async () => state),
|
|
13
|
+
getLogs: vi.fn(() => logs),
|
|
15
14
|
addListener: vi.fn((l) => {
|
|
16
|
-
|
|
15
|
+
listener = l;
|
|
17
16
|
}),
|
|
18
17
|
removeListener: vi.fn((l) => {
|
|
19
|
-
if (
|
|
20
|
-
|
|
18
|
+
if (listener === l)
|
|
19
|
+
listener = null;
|
|
21
20
|
}),
|
|
22
21
|
};
|
|
22
|
+
if (getCrashDetails) {
|
|
23
|
+
session.getCrashDetails = getCrashDetails;
|
|
24
|
+
}
|
|
23
25
|
return {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
session,
|
|
27
|
+
setState: (nextState) => {
|
|
28
|
+
state = nextState;
|
|
29
|
+
},
|
|
30
|
+
emit: (event) => listener?.(event),
|
|
26
31
|
};
|
|
27
32
|
};
|
|
28
|
-
const createPlatformRunnerMock = (isRunning = false, crashDetails = null) => ({
|
|
29
|
-
isAppRunning: vi.fn(async () => isRunning),
|
|
30
|
-
getCrashDetails: vi.fn(async () => crashDetails),
|
|
31
|
-
startApp: vi.fn(resolveUndefined),
|
|
32
|
-
restartApp: vi.fn(resolveUndefined),
|
|
33
|
-
stopApp: vi.fn(resolveUndefined),
|
|
34
|
-
dispose: vi.fn(resolveUndefined),
|
|
35
|
-
createAppMonitor: vi.fn(),
|
|
36
|
-
});
|
|
37
|
-
// ---------------------------------------------------------------------------
|
|
38
|
-
// Tests
|
|
39
|
-
// ---------------------------------------------------------------------------
|
|
40
33
|
describe('createCrashMonitor', () => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
it('becomes alive when the app starts', () => {
|
|
48
|
-
const { monitor, emit } = createAppMonitorMock();
|
|
49
|
-
const cm = createCrashMonitor({ appMonitor: monitor, platformRunner: createPlatformRunnerMock() });
|
|
50
|
-
emit({ type: 'app_started' });
|
|
51
|
-
expect(cm.isAlive()).toBe(true);
|
|
52
|
-
});
|
|
53
|
-
it('becomes not alive after a confirmed crash', async () => {
|
|
54
|
-
const { monitor, emit } = createAppMonitorMock();
|
|
55
|
-
const cm = createCrashMonitor({ appMonitor: monitor, platformRunner: createPlatformRunnerMock() });
|
|
56
|
-
emit({ type: 'app_started' });
|
|
57
|
-
const watch = cm.watch('test.ts', 'execution');
|
|
58
|
-
watch.promise.catch(noop);
|
|
59
|
-
emit({ type: 'app_exited', isConfirmed: true });
|
|
60
|
-
await watch.promise.catch(noop);
|
|
61
|
-
expect(cm.isAlive()).toBe(false);
|
|
62
|
-
});
|
|
34
|
+
it('starts not alive and becomes alive when an app session is attached', () => {
|
|
35
|
+
const cm = createCrashMonitor();
|
|
36
|
+
const { session } = createAppSessionMock();
|
|
37
|
+
expect(cm.isAlive()).toBe(false);
|
|
38
|
+
cm.setAppSession(session);
|
|
39
|
+
expect(cm.isAlive()).toBe(true);
|
|
63
40
|
});
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
41
|
+
it('rejects a watch with NativeCrashError when the app session exits', async () => {
|
|
42
|
+
const { session, emit } = createAppSessionMock({
|
|
43
|
+
status: 'exited',
|
|
44
|
+
occurredAt: Date.now(),
|
|
45
|
+
reason: 'observed-exit',
|
|
46
|
+
});
|
|
47
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
48
|
+
const watch = cm.watch('/test/example.ts', 'execution');
|
|
49
|
+
watch.promise.catch(noop);
|
|
50
|
+
emit({ type: 'app_exited' });
|
|
51
|
+
const error = await watch.promise.catch((err) => err);
|
|
52
|
+
expect(error).toBeInstanceOf(NativeCrashError);
|
|
53
|
+
expect(error.testFilePath).toBe('/test/example.ts');
|
|
54
|
+
expect(error.details.phase).toBe('execution');
|
|
55
|
+
expect(cm.isAlive()).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
it('classifies bridge disconnect plus exited app as NativeCrashError', async () => {
|
|
58
|
+
const { session, setState } = createAppSessionMock();
|
|
59
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
60
|
+
const watch = cm.watch('test.ts', 'execution');
|
|
61
|
+
watch.promise.catch(noop);
|
|
62
|
+
cm.handleBridgeDisconnect();
|
|
63
|
+
setState({
|
|
64
|
+
status: 'exited',
|
|
65
|
+
occurredAt: Date.now(),
|
|
66
|
+
reason: 'process-gone',
|
|
67
|
+
});
|
|
68
|
+
await expect(watch.promise).rejects.toBeInstanceOf(NativeCrashError);
|
|
69
|
+
});
|
|
70
|
+
it('classifies bridge disconnect plus running app as RuntimeDisconnectError', async () => {
|
|
71
|
+
const { session } = createAppSessionMock({ status: 'running' });
|
|
72
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
73
|
+
const watch = cm.watch('test.ts', 'execution');
|
|
74
|
+
watch.promise.catch(noop);
|
|
75
|
+
cm.handleBridgeDisconnect();
|
|
76
|
+
await expect(watch.promise).rejects.toBeInstanceOf(RuntimeDisconnectError);
|
|
77
|
+
});
|
|
78
|
+
it('attaches matching session log evidence to native crash details', async () => {
|
|
79
|
+
const occurredAt = Date.now();
|
|
80
|
+
const logs = [
|
|
81
|
+
{ line: 'ordinary app log', occurredAt },
|
|
82
|
+
{ line: 'MyApp[123] fatal error: boom', occurredAt },
|
|
83
|
+
];
|
|
84
|
+
const { session, setState } = createAppSessionMock({ status: 'running' }, logs);
|
|
85
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
86
|
+
const watch = cm.watch('test.ts', 'execution');
|
|
87
|
+
watch.promise.catch(noop);
|
|
88
|
+
cm.handleBridgeDisconnect();
|
|
89
|
+
setState({ status: 'exited', occurredAt, reason: 'process-gone' });
|
|
90
|
+
const error = await watch.promise.catch((err) => err);
|
|
91
|
+
expect(error).toBeInstanceOf(NativeCrashError);
|
|
92
|
+
expect(error.details.rawLines).toEqual(['MyApp[123] fatal error: boom']);
|
|
93
|
+
});
|
|
94
|
+
it('asks the app session to extract native crash details for the current test', async () => {
|
|
95
|
+
const occurredAt = Date.now();
|
|
96
|
+
const getCrashDetails = vi.fn(async () => ({
|
|
97
|
+
artifactType: 'logcat',
|
|
98
|
+
artifactPath: '/tmp/.harness/crash-reports/crash-logcat.txt',
|
|
99
|
+
processName: 'com.harnessplayground',
|
|
100
|
+
pid: 7777,
|
|
101
|
+
}));
|
|
102
|
+
const { session, setState } = createAppSessionMock({ status: 'running' }, [], getCrashDetails);
|
|
103
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
104
|
+
const watch = cm.watch('/test/example.ts', 'execution');
|
|
105
|
+
watch.promise.catch(noop);
|
|
106
|
+
cm.handleBridgeDisconnect();
|
|
107
|
+
setState({
|
|
108
|
+
status: 'exited',
|
|
109
|
+
occurredAt,
|
|
110
|
+
pid: 7777,
|
|
111
|
+
reason: 'process-gone',
|
|
112
|
+
});
|
|
113
|
+
const error = await watch.promise.catch((err) => err);
|
|
114
|
+
expect(getCrashDetails).toHaveBeenCalledWith({
|
|
115
|
+
occurredAt: expect.any(Number),
|
|
116
|
+
pid: 7777,
|
|
117
|
+
processName: undefined,
|
|
118
|
+
testFilePath: '/test/example.ts',
|
|
119
|
+
});
|
|
120
|
+
expect(error.details.artifactPath).toBe('/tmp/.harness/crash-reports/crash-logcat.txt');
|
|
121
|
+
expect(error.message).toContain(`Harness extracted the crash log: ${path.relative(process.cwd(), '/tmp/.harness/crash-reports/crash-logcat.txt')}`);
|
|
122
|
+
});
|
|
123
|
+
it('falls back to log details when native crash extraction fails', async () => {
|
|
124
|
+
const occurredAt = Date.now();
|
|
125
|
+
const getCrashDetails = vi.fn(async () => {
|
|
126
|
+
throw new Error('copy failed');
|
|
127
|
+
});
|
|
128
|
+
const { session, setState } = createAppSessionMock({ status: 'running' }, [{ line: 'MyApp[123] fatal error: boom', occurredAt }], getCrashDetails);
|
|
129
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
130
|
+
const watch = cm.watch('/test/example.ts', 'execution');
|
|
131
|
+
watch.promise.catch(noop);
|
|
132
|
+
cm.handleBridgeDisconnect();
|
|
133
|
+
setState({ status: 'exited', occurredAt, reason: 'process-gone' });
|
|
134
|
+
const error = await watch.promise.catch((err) => err);
|
|
135
|
+
expect(error.details.rawLines).toEqual(['MyApp[123] fatal error: boom']);
|
|
136
|
+
expect(error.message).toContain("Harness couldn't extract the crash log.");
|
|
137
|
+
});
|
|
138
|
+
it('settles the promise with CrashWatchCancelledError on cancel()', async () => {
|
|
139
|
+
const cm = createCrashMonitor();
|
|
140
|
+
const watch = cm.watch('test.ts', 'execution');
|
|
141
|
+
watch.cancel();
|
|
142
|
+
await expect(watch.promise).rejects.toBeInstanceOf(CrashWatchCancelledError);
|
|
100
143
|
});
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
144
|
+
it('ignores session events while stopped', async () => {
|
|
145
|
+
const { session, emit } = createAppSessionMock({
|
|
146
|
+
status: 'exited',
|
|
147
|
+
occurredAt: Date.now(),
|
|
148
|
+
});
|
|
149
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
150
|
+
await cm.stop();
|
|
151
|
+
const watch = cm.watch('test.ts', 'execution');
|
|
152
|
+
const settled = vi.fn();
|
|
153
|
+
watch.promise.then(settled, settled);
|
|
154
|
+
emit({ type: 'app_exited' });
|
|
155
|
+
await waitForClassification();
|
|
156
|
+
expect(settled).not.toHaveBeenCalled();
|
|
157
|
+
watch.cancel();
|
|
158
|
+
});
|
|
159
|
+
it('removes the app session listener on dispose', async () => {
|
|
160
|
+
const { session } = createAppSessionMock();
|
|
161
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
162
|
+
await cm.dispose();
|
|
163
|
+
expect(session.removeListener).toHaveBeenCalledOnce();
|
|
164
|
+
expect(cm.isAlive()).toBe(false);
|
|
165
|
+
});
|
|
166
|
+
describe('expectDisconnect', () => {
|
|
167
|
+
it('suppresses handleBridgeDisconnect while the window is open', async () => {
|
|
168
|
+
const { session } = createAppSessionMock({ status: 'running' });
|
|
169
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
106
170
|
const watch = cm.watch('test.ts', 'execution');
|
|
107
171
|
watch.promise.catch(noop);
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
it('does not fire if isAppRunning returns true', async () => {
|
|
112
|
-
const { monitor, emit } = createAppMonitorMock();
|
|
113
|
-
const runner = createPlatformRunnerMock(true /* still running */);
|
|
114
|
-
const cm = createCrashMonitor({ appMonitor: monitor, platformRunner: runner });
|
|
115
|
-
const watch = cm.watch('test.ts', 'execution');
|
|
172
|
+
const closeWindow = cm.expectDisconnect();
|
|
173
|
+
cm.handleBridgeDisconnect();
|
|
174
|
+
await waitForClassification();
|
|
116
175
|
const settled = vi.fn();
|
|
117
176
|
watch.promise.then(settled, settled);
|
|
118
|
-
|
|
119
|
-
await new Promise((r) => setTimeout(r, 20));
|
|
177
|
+
await Promise.resolve();
|
|
120
178
|
expect(settled).not.toHaveBeenCalled();
|
|
179
|
+
closeWindow();
|
|
121
180
|
watch.cancel();
|
|
122
181
|
});
|
|
123
|
-
it('
|
|
124
|
-
const {
|
|
125
|
-
const cm = createCrashMonitor({
|
|
182
|
+
it('resumes classifying disconnects once the window closes', async () => {
|
|
183
|
+
const { session } = createAppSessionMock({ status: 'running' });
|
|
184
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
126
185
|
const watch = cm.watch('test.ts', 'execution');
|
|
127
186
|
watch.promise.catch(noop);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
it('
|
|
134
|
-
const {
|
|
135
|
-
const
|
|
136
|
-
processName: 'MyApp',
|
|
137
|
-
signal: 'SIGSEGV',
|
|
138
|
-
summary: 'Segmentation fault',
|
|
139
|
-
});
|
|
140
|
-
const cm = createCrashMonitor({ appMonitor: monitor, platformRunner: runner });
|
|
187
|
+
const closeWindow = cm.expectDisconnect();
|
|
188
|
+
closeWindow();
|
|
189
|
+
cm.handleBridgeDisconnect();
|
|
190
|
+
await expect(watch.promise).rejects.toBeInstanceOf(RuntimeDisconnectError);
|
|
191
|
+
});
|
|
192
|
+
it('keeps the window open until every opener has closed it (nested windows)', async () => {
|
|
193
|
+
const { session } = createAppSessionMock({ status: 'running' });
|
|
194
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
141
195
|
const watch = cm.watch('test.ts', 'execution');
|
|
142
196
|
watch.promise.catch(noop);
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
});
|
|
149
|
-
});
|
|
150
|
-
describe('stop / start', () => {
|
|
151
|
-
it('ignores events while stopped', async () => {
|
|
152
|
-
const { monitor, emit } = createAppMonitorMock();
|
|
153
|
-
const cm = createCrashMonitor({ appMonitor: monitor, platformRunner: createPlatformRunnerMock() });
|
|
154
|
-
await cm.stop();
|
|
155
|
-
const watch = cm.watch('test.ts', 'execution');
|
|
197
|
+
const closeFirst = cm.expectDisconnect();
|
|
198
|
+
const closeSecond = cm.expectDisconnect();
|
|
199
|
+
closeFirst();
|
|
200
|
+
cm.handleBridgeDisconnect();
|
|
201
|
+
await waitForClassification();
|
|
156
202
|
const settled = vi.fn();
|
|
157
203
|
watch.promise.then(settled, settled);
|
|
158
|
-
|
|
159
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
204
|
+
await Promise.resolve();
|
|
160
205
|
expect(settled).not.toHaveBeenCalled();
|
|
206
|
+
closeSecond();
|
|
161
207
|
watch.cancel();
|
|
162
208
|
});
|
|
163
|
-
it('
|
|
164
|
-
const {
|
|
165
|
-
const cm = createCrashMonitor({
|
|
166
|
-
await cm.stop();
|
|
167
|
-
await cm.start();
|
|
209
|
+
it('ignores a redundant close() call', async () => {
|
|
210
|
+
const { session } = createAppSessionMock({ status: 'running' });
|
|
211
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
168
212
|
const watch = cm.watch('test.ts', 'execution');
|
|
169
213
|
watch.promise.catch(noop);
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
watch.promise.catch(noop);
|
|
181
|
-
cm.reset();
|
|
182
|
-
expect(cm.isAlive()).toBe(false);
|
|
183
|
-
// The watcher was cleared; a crash fired now should not reach the old watch.
|
|
184
|
-
emit({ type: 'app_exited', isConfirmed: true });
|
|
185
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
186
|
-
// Old promise is still pending (we can verify by cancel resolving it).
|
|
187
|
-
watch.cancel();
|
|
188
|
-
await expect(watch.promise).rejects.toBeInstanceOf(CrashWatchCancelledError);
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
describe('dispose', () => {
|
|
192
|
-
it('ignores events after dispose', async () => {
|
|
193
|
-
const { monitor, emit } = createAppMonitorMock();
|
|
194
|
-
const cm = createCrashMonitor({ appMonitor: monitor, platformRunner: createPlatformRunnerMock() });
|
|
195
|
-
const watch = cm.watch('test.ts', 'execution');
|
|
196
|
-
watch.promise.catch(noop);
|
|
197
|
-
await cm.dispose();
|
|
198
|
-
emit({ type: 'app_exited', isConfirmed: true });
|
|
199
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
200
|
-
// After dispose watchers are cleared, so crash didn't propagate.
|
|
201
|
-
// The promise is still pending - cancel to settle it.
|
|
202
|
-
watch.cancel();
|
|
203
|
-
await expect(watch.promise).rejects.toBeInstanceOf(CrashWatchCancelledError);
|
|
204
|
-
});
|
|
205
|
-
it('calls appMonitor.dispose()', async () => {
|
|
206
|
-
const { monitor } = createAppMonitorMock();
|
|
207
|
-
const cm = createCrashMonitor({ appMonitor: monitor, platformRunner: createPlatformRunnerMock() });
|
|
214
|
+
const closeWindow = cm.expectDisconnect();
|
|
215
|
+
closeWindow();
|
|
216
|
+
closeWindow();
|
|
217
|
+
cm.handleBridgeDisconnect();
|
|
218
|
+
await expect(watch.promise).rejects.toBeInstanceOf(RuntimeDisconnectError);
|
|
219
|
+
});
|
|
220
|
+
it('clears the expected-disconnect window on dispose', async () => {
|
|
221
|
+
const { session } = createAppSessionMock({ status: 'running' });
|
|
222
|
+
const cm = createCrashMonitor({ appSession: session });
|
|
223
|
+
cm.expectDisconnect();
|
|
208
224
|
await cm.dispose();
|
|
209
|
-
|
|
225
|
+
// A disconnect after dispose should not throw or hang; monitoring is
|
|
226
|
+
// fully torn down regardless of the window state.
|
|
227
|
+
expect(() => cm.handleBridgeDisconnect()).not.toThrow();
|
|
210
228
|
});
|
|
211
229
|
});
|
|
212
230
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"environment-reset.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/environment-reset.test.ts"],"names":[],"mappings":""}
|