@react-native-harness/jest 1.0.0-canary.1766225407244 → 1.1.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/__tests__/client-log-handler.test.d.ts +2 -0
  2. package/dist/__tests__/client-log-handler.test.d.ts.map +1 -0
  3. package/dist/__tests__/client-log-handler.test.js +262 -0
  4. package/dist/__tests__/crash-supervisor.test.d.ts +2 -0
  5. package/dist/__tests__/crash-supervisor.test.d.ts.map +1 -0
  6. package/dist/__tests__/crash-supervisor.test.js +151 -0
  7. package/dist/__tests__/errors.test.d.ts +2 -0
  8. package/dist/__tests__/errors.test.d.ts.map +1 -0
  9. package/dist/__tests__/errors.test.js +54 -0
  10. package/dist/__tests__/harness.test.d.ts +2 -0
  11. package/dist/__tests__/harness.test.d.ts.map +1 -0
  12. package/dist/__tests__/harness.test.js +106 -0
  13. package/dist/cli-args.test.d.ts +2 -0
  14. package/dist/cli-args.test.d.ts.map +1 -0
  15. package/dist/cli-args.test.js +1 -0
  16. package/dist/client-log-handler.d.ts +23 -0
  17. package/dist/client-log-handler.d.ts.map +1 -0
  18. package/dist/client-log-handler.js +84 -0
  19. package/dist/crash-monitor.d.ts +16 -0
  20. package/dist/crash-monitor.d.ts.map +1 -0
  21. package/dist/crash-monitor.js +75 -0
  22. package/dist/crash-supervisor.d.ts +21 -0
  23. package/dist/crash-supervisor.d.ts.map +1 -0
  24. package/dist/crash-supervisor.js +209 -0
  25. package/dist/errors.d.ts +12 -0
  26. package/dist/errors.d.ts.map +1 -1
  27. package/dist/errors.js +51 -0
  28. package/dist/event-mapper.d.ts +16 -0
  29. package/dist/event-mapper.d.ts.map +1 -0
  30. package/dist/event-mapper.js +120 -0
  31. package/dist/global-setup.d.ts +3 -0
  32. package/dist/global-setup.d.ts.map +1 -0
  33. package/dist/global-setup.js +60 -0
  34. package/dist/global-teardown.d.ts +3 -0
  35. package/dist/global-teardown.d.ts.map +1 -0
  36. package/dist/global-teardown.js +11 -0
  37. package/dist/harness-reporter.d.ts +8 -0
  38. package/dist/harness-reporter.d.ts.map +1 -0
  39. package/dist/harness-reporter.js +12 -0
  40. package/dist/harness.d.ts +18 -4
  41. package/dist/harness.d.ts.map +1 -1
  42. package/dist/harness.js +112 -21
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +41 -9
  45. package/dist/jest-options.d.ts +2 -0
  46. package/dist/jest-options.d.ts.map +1 -0
  47. package/dist/jest-options.js +1 -0
  48. package/dist/logs.d.ts +1 -0
  49. package/dist/logs.d.ts.map +1 -1
  50. package/dist/logs.js +3 -0
  51. package/dist/run.d.ts.map +1 -1
  52. package/dist/run.js +1 -0
  53. package/dist/setup.d.ts.map +1 -1
  54. package/dist/setup.js +6 -4
  55. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  56. package/dist/types.d.ts +17 -0
  57. package/dist/types.d.ts.map +1 -0
  58. package/dist/types.js +1 -0
  59. package/eslint.config.mjs +5 -2
  60. package/package.json +6 -7
  61. package/src/__tests__/client-log-handler.test.ts +328 -0
  62. package/src/__tests__/crash-supervisor.test.ts +200 -0
  63. package/src/__tests__/errors.test.ts +70 -0
  64. package/src/__tests__/harness.test.ts +129 -0
  65. package/src/client-log-handler.ts +101 -0
  66. package/src/crash-supervisor.ts +314 -0
  67. package/src/errors.ts +75 -0
  68. package/src/harness.ts +176 -32
  69. package/src/index.ts +52 -18
  70. package/src/logs.ts +6 -0
  71. package/src/run.ts +1 -0
  72. package/src/setup.ts +8 -5
  73. package/tsconfig.json +3 -3
  74. package/vite.config.ts +18 -0
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=client-log-handler.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-log-handler.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/client-log-handler.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,262 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { formatClientLogMessage, formatClientLogLine, handleClientLogEvent, createClientLogListener, } from '../client-log-handler.js';
3
+ // Mock the log function
4
+ vi.mock('../logs.js', () => ({
5
+ log: vi.fn(),
6
+ }));
7
+ import { log } from '../logs.js';
8
+ describe('client-log-handler', () => {
9
+ beforeEach(() => {
10
+ vi.clearAllMocks();
11
+ });
12
+ describe('formatClientLogMessage', () => {
13
+ it('should format a single string', () => {
14
+ const result = formatClientLogMessage(['Hello, world!']);
15
+ expect(result).toBe('Hello, world!');
16
+ });
17
+ it('should join multiple strings with spaces', () => {
18
+ const result = formatClientLogMessage(['Hello', 'world', '!']);
19
+ expect(result).toBe('Hello world !');
20
+ });
21
+ it('should format objects', () => {
22
+ const result = formatClientLogMessage([{ key: 'value' }]);
23
+ // util.format uses inspect-style output for objects
24
+ expect(result).toContain('key');
25
+ expect(result).toContain('value');
26
+ });
27
+ it('should format arrays', () => {
28
+ const result = formatClientLogMessage([[1, 2, 3]]);
29
+ expect(result).toContain('1');
30
+ expect(result).toContain('2');
31
+ expect(result).toContain('3');
32
+ });
33
+ it('should handle mixed types', () => {
34
+ const result = formatClientLogMessage([
35
+ 'Message:',
36
+ { count: 42 },
37
+ 'items',
38
+ ]);
39
+ expect(result).toContain('Message:');
40
+ expect(result).toContain('count');
41
+ expect(result).toContain('42');
42
+ expect(result).toContain('items');
43
+ });
44
+ it('should format numbers', () => {
45
+ const result = formatClientLogMessage([123, 456]);
46
+ expect(result).toBe('123 456');
47
+ });
48
+ it('should format booleans', () => {
49
+ const result = formatClientLogMessage([true, false]);
50
+ expect(result).toBe('true false');
51
+ });
52
+ it('should format null and undefined', () => {
53
+ const result = formatClientLogMessage([null, undefined]);
54
+ expect(result).toBe('null undefined');
55
+ });
56
+ it('should handle empty array', () => {
57
+ const result = formatClientLogMessage([]);
58
+ expect(result).toBe('');
59
+ });
60
+ describe('printf-style format specifiers', () => {
61
+ it('should handle %s string substitution', () => {
62
+ const result = formatClientLogMessage(['%s world', 'hello']);
63
+ expect(result).toBe('hello world');
64
+ });
65
+ it('should handle %d integer substitution', () => {
66
+ const result = formatClientLogMessage(['Count: %d', 42]);
67
+ expect(result).toBe('Count: 42');
68
+ });
69
+ it('should handle %i integer substitution', () => {
70
+ const result = formatClientLogMessage(['Value: %i', 123]);
71
+ expect(result).toBe('Value: 123');
72
+ });
73
+ it('should handle %f float substitution', () => {
74
+ const result = formatClientLogMessage(['Pi: %f', 3.14159]);
75
+ expect(result).toContain('3.14159');
76
+ });
77
+ it('should handle multiple substitutions', () => {
78
+ const result = formatClientLogMessage(['Hello %s, you have %d messages', 'Alice', 5]);
79
+ expect(result).toBe('Hello Alice, you have 5 messages');
80
+ });
81
+ it('should handle %j JSON substitution', () => {
82
+ const result = formatClientLogMessage(['Data: %j', { key: 'value' }]);
83
+ expect(result).toBe('Data: {"key":"value"}');
84
+ });
85
+ it('should handle %o object substitution', () => {
86
+ const result = formatClientLogMessage(['Object: %o', { a: 1 }]);
87
+ // %o produces inspect-style output, just check it contains the key
88
+ expect(result).toContain('a');
89
+ });
90
+ it('should handle %% as literal percent when substituting', () => {
91
+ // %% is only converted to % when there are substitutions
92
+ const result = formatClientLogMessage(['%s is 100%% complete', 'Task']);
93
+ expect(result).toBe('Task is 100% complete');
94
+ });
95
+ it('should append extra arguments after substitution', () => {
96
+ const result = formatClientLogMessage(['Hello %s', 'world', 'extra', 'args']);
97
+ expect(result).toBe('Hello world extra args');
98
+ });
99
+ });
100
+ });
101
+ describe('formatClientLogLine', () => {
102
+ it('should format log level event', () => {
103
+ const event = {
104
+ type: 'client_log',
105
+ level: 'log',
106
+ data: ['Test message'],
107
+ };
108
+ const result = formatClientLogLine(event);
109
+ // The result will contain ANSI codes for styling, so we check it contains the message
110
+ expect(result).toContain('Test message');
111
+ });
112
+ it('should format error level event', () => {
113
+ const event = {
114
+ type: 'client_log',
115
+ level: 'error',
116
+ data: ['Error occurred'],
117
+ };
118
+ const result = formatClientLogLine(event);
119
+ expect(result).toContain('Error occurred');
120
+ });
121
+ it('should format warn level event', () => {
122
+ const event = {
123
+ type: 'client_log',
124
+ level: 'warn',
125
+ data: ['Warning message'],
126
+ };
127
+ const result = formatClientLogLine(event);
128
+ expect(result).toContain('Warning message');
129
+ });
130
+ it('should format info level event', () => {
131
+ const event = {
132
+ type: 'client_log',
133
+ level: 'info',
134
+ data: ['Info message'],
135
+ };
136
+ const result = formatClientLogLine(event);
137
+ expect(result).toContain('Info message');
138
+ });
139
+ it('should format debug level event', () => {
140
+ const event = {
141
+ type: 'client_log',
142
+ level: 'debug',
143
+ data: ['Debug message'],
144
+ };
145
+ const result = formatClientLogLine(event);
146
+ expect(result).toContain('Debug message');
147
+ });
148
+ it('should format trace level event', () => {
149
+ const event = {
150
+ type: 'client_log',
151
+ level: 'trace',
152
+ data: ['Trace message'],
153
+ };
154
+ const result = formatClientLogLine(event);
155
+ expect(result).toContain('Trace message');
156
+ });
157
+ it('should handle multiple data items', () => {
158
+ const event = {
159
+ type: 'client_log',
160
+ level: 'log',
161
+ data: ['User:', { id: 1, name: 'Test' }],
162
+ };
163
+ const result = formatClientLogLine(event);
164
+ expect(result).toContain('User:');
165
+ expect(result).toContain('id');
166
+ expect(result).toContain('Test');
167
+ });
168
+ });
169
+ describe('handleClientLogEvent', () => {
170
+ it('should handle client_log events and call log', () => {
171
+ const event = {
172
+ type: 'client_log',
173
+ level: 'log',
174
+ data: ['Test message'],
175
+ };
176
+ const result = handleClientLogEvent(event);
177
+ expect(result).toBe(true);
178
+ expect(log).toHaveBeenCalledTimes(1);
179
+ expect(log).toHaveBeenCalledWith(expect.stringContaining('Test message'));
180
+ });
181
+ it('should return false for non-client_log events', () => {
182
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
183
+ const event = { type: 'bundle_build_started' };
184
+ const result = handleClientLogEvent(event);
185
+ expect(result).toBe(false);
186
+ expect(log).not.toHaveBeenCalled();
187
+ });
188
+ it('should handle error level logs', () => {
189
+ const event = {
190
+ type: 'client_log',
191
+ level: 'error',
192
+ data: ['Something went wrong'],
193
+ };
194
+ handleClientLogEvent(event);
195
+ expect(log).toHaveBeenCalledWith(expect.stringContaining('Something went wrong'));
196
+ });
197
+ it('should handle warn level logs', () => {
198
+ const event = {
199
+ type: 'client_log',
200
+ level: 'warn',
201
+ data: ['Deprecation warning'],
202
+ };
203
+ handleClientLogEvent(event);
204
+ expect(log).toHaveBeenCalledWith(expect.stringContaining('Deprecation warning'));
205
+ });
206
+ });
207
+ describe('createClientLogListener', () => {
208
+ it('should create a listener function', () => {
209
+ const listener = createClientLogListener();
210
+ expect(typeof listener).toBe('function');
211
+ });
212
+ it('should handle client_log events when called', () => {
213
+ const listener = createClientLogListener();
214
+ const event = {
215
+ type: 'client_log',
216
+ level: 'info',
217
+ data: ['Listener test'],
218
+ };
219
+ listener(event);
220
+ expect(log).toHaveBeenCalledWith(expect.stringContaining('Listener test'));
221
+ });
222
+ it('should ignore non-client_log events', () => {
223
+ const listener = createClientLogListener();
224
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
225
+ const event = { type: 'initialize_done' };
226
+ listener(event);
227
+ expect(log).not.toHaveBeenCalled();
228
+ });
229
+ });
230
+ describe('group events', () => {
231
+ it('should handle group events without logging', () => {
232
+ const event = {
233
+ type: 'client_log',
234
+ level: 'group',
235
+ data: ['Group label'],
236
+ };
237
+ const result = handleClientLogEvent(event);
238
+ expect(result).toBe(true);
239
+ expect(log).not.toHaveBeenCalled();
240
+ });
241
+ it('should handle groupCollapsed events without logging', () => {
242
+ const event = {
243
+ type: 'client_log',
244
+ level: 'groupCollapsed',
245
+ data: ['Collapsed'],
246
+ };
247
+ const result = handleClientLogEvent(event);
248
+ expect(result).toBe(true);
249
+ expect(log).not.toHaveBeenCalled();
250
+ });
251
+ it('should handle groupEnd events without logging', () => {
252
+ const event = {
253
+ type: 'client_log',
254
+ level: 'groupEnd',
255
+ data: [],
256
+ };
257
+ const result = handleClientLogEvent(event);
258
+ expect(result).toBe(true);
259
+ expect(log).not.toHaveBeenCalled();
260
+ });
261
+ });
262
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=crash-supervisor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crash-supervisor.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/crash-supervisor.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,151 @@
1
+ import { describe, expect, it, vi } from 'vitest';
2
+ import { createCrashSupervisor } from '../crash-supervisor.js';
3
+ const createMockAppMonitor = (options) => {
4
+ const listeners = new Set();
5
+ const noop = async () => undefined;
6
+ const appMonitor = {
7
+ start: noop,
8
+ stop: noop,
9
+ dispose: noop,
10
+ addListener: (listener) => {
11
+ listeners.add(listener);
12
+ },
13
+ removeListener: (listener) => {
14
+ listeners.delete(listener);
15
+ },
16
+ };
17
+ if (options?.getCrashDetails) {
18
+ appMonitor.getCrashDetails = options.getCrashDetails;
19
+ }
20
+ const emit = (event) => {
21
+ for (const listener of listeners) {
22
+ listener(event);
23
+ }
24
+ };
25
+ return {
26
+ appMonitor,
27
+ emit,
28
+ };
29
+ };
30
+ const createPlatformRunner = ({ isAppRunning, getCrashDetails, }) => ({
31
+ startApp: async () => undefined,
32
+ restartApp: async () => undefined,
33
+ stopApp: async () => undefined,
34
+ dispose: async () => undefined,
35
+ isAppRunning,
36
+ createAppMonitor: () => {
37
+ throw new Error('Not used in unit tests');
38
+ },
39
+ getCrashDetails,
40
+ });
41
+ describe('crash-supervisor', () => {
42
+ it('rejects the active test when the app exits during launch', async () => {
43
+ const monitor = createMockAppMonitor();
44
+ const supervisor = createCrashSupervisor({
45
+ appMonitor: monitor.appMonitor,
46
+ platformRunner: createPlatformRunner({
47
+ isAppRunning: vi.fn().mockResolvedValue(false),
48
+ }),
49
+ });
50
+ supervisor.beginLaunch('/tmp/startup.harness.ts');
51
+ const crashPromise = supervisor.waitForCrash('/tmp/startup.harness.ts');
52
+ const expectation = expect(crashPromise).rejects.toMatchObject({
53
+ name: 'NativeCrashError',
54
+ phase: 'startup',
55
+ details: {
56
+ signal: 'SIGSEGV',
57
+ processName: 'com.harnessplayground',
58
+ },
59
+ });
60
+ monitor.emit({
61
+ type: 'app_exited',
62
+ source: 'logs',
63
+ crashDetails: {
64
+ source: 'logs',
65
+ signal: 'SIGSEGV',
66
+ processName: 'com.harnessplayground',
67
+ rawLines: ['Fatal signal 11 (SIGSEGV)'],
68
+ },
69
+ });
70
+ await expectation;
71
+ await supervisor.dispose();
72
+ });
73
+ it('treats polling exit events as immediately confirmed crashes', async () => {
74
+ const monitor = createMockAppMonitor();
75
+ const isAppRunning = vi.fn().mockResolvedValue(true);
76
+ const supervisor = createCrashSupervisor({
77
+ appMonitor: monitor.appMonitor,
78
+ platformRunner: createPlatformRunner({
79
+ isAppRunning,
80
+ }),
81
+ });
82
+ supervisor.beginLaunch('/tmp/polling-exit.harness.ts');
83
+ const crashPromise = supervisor.waitForCrash('/tmp/polling-exit.harness.ts');
84
+ monitor.emit({ type: 'app_exited', source: 'polling' });
85
+ await expect(crashPromise).rejects.toMatchObject({
86
+ name: 'NativeCrashError',
87
+ phase: 'startup',
88
+ });
89
+ expect(isAppRunning).not.toHaveBeenCalled();
90
+ await supervisor.dispose();
91
+ });
92
+ it('does not reject when monitoring is stopped', async () => {
93
+ const monitor = createMockAppMonitor();
94
+ const supervisor = createCrashSupervisor({
95
+ appMonitor: monitor.appMonitor,
96
+ platformRunner: createPlatformRunner({
97
+ isAppRunning: vi.fn().mockResolvedValue(false),
98
+ }),
99
+ });
100
+ supervisor.beginTestRun('/tmp/restart.harness.ts');
101
+ await supervisor.stop();
102
+ const reject = vi.fn();
103
+ void supervisor.waitForCrash('/tmp/restart.harness.ts').catch(reject);
104
+ monitor.emit({ type: 'app_exited', source: 'polling' });
105
+ await Promise.resolve();
106
+ expect(reject).not.toHaveBeenCalled();
107
+ await supervisor.dispose();
108
+ });
109
+ it('prefers crash details from the started app monitor over the runner', async () => {
110
+ const monitorGetCrashDetails = vi.fn().mockResolvedValue({
111
+ source: 'logs',
112
+ summary: 'full crash block',
113
+ rawLines: ['full crash block'],
114
+ });
115
+ const runnerGetCrashDetails = vi.fn().mockResolvedValue({
116
+ source: 'logs',
117
+ summary: 'runner details',
118
+ rawLines: ['runner details'],
119
+ });
120
+ const monitor = createMockAppMonitor({
121
+ getCrashDetails: monitorGetCrashDetails,
122
+ });
123
+ const supervisor = createCrashSupervisor({
124
+ appMonitor: monitor.appMonitor,
125
+ platformRunner: createPlatformRunner({
126
+ isAppRunning: vi.fn().mockResolvedValue(false),
127
+ getCrashDetails: runnerGetCrashDetails,
128
+ }),
129
+ });
130
+ supervisor.beginTestRun('/tmp/monitor-details.harness.ts');
131
+ const crashPromise = supervisor.waitForCrash('/tmp/monitor-details.harness.ts');
132
+ monitor.emit({
133
+ type: 'possible_crash',
134
+ source: 'logs',
135
+ crashDetails: {
136
+ source: 'logs',
137
+ processName: 'com.harnessplayground',
138
+ pid: 1234,
139
+ rawLines: ['partial line'],
140
+ },
141
+ });
142
+ await expect(crashPromise).rejects.toMatchObject({
143
+ details: {
144
+ summary: 'full crash block',
145
+ },
146
+ });
147
+ expect(monitorGetCrashDetails).toHaveBeenCalled();
148
+ expect(runnerGetCrashDetails).not.toHaveBeenCalled();
149
+ await supervisor.dispose();
150
+ });
151
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=errors.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/errors.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,54 @@
1
+ import { describe, expect, it } from 'vitest';
2
+ import { NativeCrashError } from '../errors.js';
3
+ describe('NativeCrashError', () => {
4
+ it('formats the extracted stack trace in the error message', () => {
5
+ const error = new NativeCrashError('/tmp/crash.harness.ts', {
6
+ phase: 'execution',
7
+ processName: 'HarnessPlayground',
8
+ pid: 1234,
9
+ signal: 'SIGTRAP',
10
+ exceptionType: 'EXC_BREAKPOINT',
11
+ stackTrace: [
12
+ '0 AppDelegate.crashIfRequested() (AppDelegate.swift:31)',
13
+ '1 AppDelegate.application(_:didFinishLaunchingWithOptions:) (AppDelegate.swift:56)',
14
+ ],
15
+ });
16
+ expect(error.message).toContain(' 0 AppDelegate.crashIfRequested() (AppDelegate.swift:31)');
17
+ expect(error.message).toContain(' 1 AppDelegate.application(_:didFinishLaunchingWithOptions:) (AppDelegate.swift:56)');
18
+ });
19
+ it('omits single-line iOS summaries from the rendered error message', () => {
20
+ const error = new NativeCrashError('/tmp/crash.harness.ts', {
21
+ phase: 'startup',
22
+ artifactType: 'ios-crash-report',
23
+ summary: '2026-03-12 13:46:18.154 Df HarnessPlayground[18007:65e716] [com.apple.dt.xctest:Default] notify_get_state check indicated test daemon not ready.',
24
+ processName: 'HarnessPlayground',
25
+ pid: 18007,
26
+ signal: 'SIGABRT',
27
+ exceptionType: 'EXC_CRASH',
28
+ });
29
+ expect(error.message).not.toContain('notify_get_state check indicated test daemon not ready');
30
+ expect(error.message).toContain('Signal: SIGABRT');
31
+ expect(error.message).toContain('Exception: EXC_CRASH');
32
+ expect(error.message).toContain('Process: HarnessPlayground (pid 18007)');
33
+ });
34
+ it('does not duplicate stack frames when the summary already contains a crash block', () => {
35
+ const frame = '03-13 07:59:44.943 20373 20373 E AndroidRuntime: \tat com.harnessplayground.MainActivity.onCreate(MainActivity.kt:38)';
36
+ const error = new NativeCrashError('/tmp/crash.harness.ts', {
37
+ phase: 'startup',
38
+ summary: [
39
+ '--------- beginning of crash',
40
+ '03-13 07:59:44.943 20373 20373 E AndroidRuntime: FATAL EXCEPTION: main',
41
+ frame,
42
+ ].join('\n'),
43
+ stackTrace: [frame],
44
+ });
45
+ expect(error.message.match(/MainActivity\.onCreate\(MainActivity\.kt:38\)/g)).toHaveLength(1);
46
+ });
47
+ it('collapses the native crash stack header so jest does not reprint multiline messages', () => {
48
+ const error = new NativeCrashError('/tmp/crash.harness.ts', {
49
+ phase: 'startup',
50
+ summary: ['line one', 'line two'].join('\n'),
51
+ });
52
+ expect(error.stack).toBe('NativeCrashError: The native app crashed while preparing to run this test file.');
53
+ });
54
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=harness.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"harness.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/harness.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,106 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { waitForAppReady } from '../harness.js';
4
+ import { createCrashSupervisor } from '../crash-supervisor.js';
5
+ const createBridgeServer = () => {
6
+ const emitter = new EventEmitter();
7
+ return {
8
+ serverBridge: {
9
+ on: emitter.on.bind(emitter),
10
+ once: emitter.once.bind(emitter),
11
+ off: emitter.off.bind(emitter),
12
+ },
13
+ emitReady: () => {
14
+ emitter.emit('ready');
15
+ },
16
+ };
17
+ };
18
+ const createAppMonitor = () => {
19
+ const listeners = new Set();
20
+ return {
21
+ start: async () => undefined,
22
+ stop: async () => undefined,
23
+ dispose: async () => undefined,
24
+ addListener: (listener) => {
25
+ listeners.add(listener);
26
+ },
27
+ removeListener: (listener) => {
28
+ listeners.delete(listener);
29
+ },
30
+ };
31
+ };
32
+ const createPlatformRunner = (restartApp) => ({
33
+ startApp: async () => undefined,
34
+ restartApp,
35
+ stopApp: async () => undefined,
36
+ dispose: async () => undefined,
37
+ isAppRunning: async () => true,
38
+ createAppMonitor: createAppMonitor,
39
+ });
40
+ describe('waitForAppReady', () => {
41
+ it('passes launch options to the initial launch', async () => {
42
+ const { serverBridge, emitReady } = createBridgeServer();
43
+ const restartApp = vi.fn().mockResolvedValue(undefined);
44
+ const platformInstance = createPlatformRunner(restartApp);
45
+ const crashSupervisor = createCrashSupervisor({
46
+ appMonitor: createAppMonitor(),
47
+ platformRunner: platformInstance,
48
+ });
49
+ const promise = waitForAppReady({
50
+ serverBridge,
51
+ platformInstance,
52
+ bridgeTimeout: 5000,
53
+ testFilePath: '/tmp/test.harness.ts',
54
+ crashSupervisor,
55
+ appLaunchOptions: {
56
+ extras: {
57
+ mode: 'startup',
58
+ },
59
+ },
60
+ });
61
+ await Promise.resolve();
62
+ expect(restartApp).toHaveBeenCalledWith({
63
+ extras: {
64
+ mode: 'startup',
65
+ },
66
+ });
67
+ emitReady();
68
+ await promise;
69
+ await crashSupervisor.dispose();
70
+ });
71
+ it('does not retry launch when the app never becomes ready', async () => {
72
+ vi.useFakeTimers();
73
+ const { serverBridge } = createBridgeServer();
74
+ const restartApp = vi.fn().mockResolvedValue(undefined);
75
+ const platformInstance = createPlatformRunner(restartApp);
76
+ const crashSupervisor = createCrashSupervisor({
77
+ appMonitor: createAppMonitor(),
78
+ platformRunner: platformInstance,
79
+ });
80
+ const promise = waitForAppReady({
81
+ serverBridge,
82
+ platformInstance,
83
+ bridgeTimeout: 1000,
84
+ testFilePath: '/tmp/test.harness.ts',
85
+ crashSupervisor,
86
+ appLaunchOptions: {
87
+ extras: {
88
+ mode: 'startup',
89
+ },
90
+ },
91
+ });
92
+ await Promise.resolve();
93
+ await vi.advanceTimersByTimeAsync(1000);
94
+ await expect(promise).rejects.toMatchObject({
95
+ name: 'AbortError',
96
+ });
97
+ expect(restartApp).toHaveBeenCalledTimes(1);
98
+ expect(restartApp).toHaveBeenCalledWith({
99
+ extras: {
100
+ mode: 'startup',
101
+ },
102
+ });
103
+ await crashSupervisor.dispose();
104
+ vi.useRealTimers();
105
+ });
106
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cli-args.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-args.test.d.ts","sourceRoot":"","sources":["../src/cli-args.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { ReportableEvent } from '@react-native-harness/bundler-metro';
2
+ export type ClientLogEvent = Extract<ReportableEvent, {
3
+ type: 'client_log';
4
+ }>;
5
+ /**
6
+ * Formats a client log event data array into a string message.
7
+ * Uses util.format for printf-style format specifier support (%s, %d, %j, etc.)
8
+ */
9
+ export declare const formatClientLogMessage: (data: unknown[]) => string;
10
+ /**
11
+ * Formats a client log event into a log line with styled level prefix.
12
+ */
13
+ export declare const formatClientLogLine: (event: ClientLogEvent) => string;
14
+ /**
15
+ * Handles a client_log event by formatting and logging it.
16
+ * Returns true if the event was handled, false otherwise.
17
+ */
18
+ export declare const handleClientLogEvent: (event: ReportableEvent) => boolean;
19
+ /**
20
+ * Creates a client log event listener that can be added to the Metro reporter.
21
+ */
22
+ export declare const createClientLogListener: () => ((event: ReportableEvent) => void);
23
+ //# sourceMappingURL=client-log-handler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client-log-handler.d.ts","sourceRoot":"","sources":["../src/client-log-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAC;AAK3E,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC,eAAe,EAAE;IAAE,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC,CAAC;AA6C9E;;;GAGG;AACH,eAAO,MAAM,sBAAsB,GAAI,MAAM,OAAO,EAAE,KAAG,MAKxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,mBAAmB,GAAI,OAAO,cAAc,KAAG,MAI3D,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAO,eAAe,KAAG,OAiB7D,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,QAAO,CAAC,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,CAI3E,CAAC"}