@react-native-harness/jest 1.2.0-rc.1 → 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.
Files changed (69) hide show
  1. package/dist/__tests__/bridge.test.d.ts +2 -0
  2. package/dist/__tests__/bridge.test.d.ts.map +1 -0
  3. package/dist/__tests__/bridge.test.js +142 -0
  4. package/dist/__tests__/client-log-handler.test.js +116 -72
  5. package/dist/__tests__/crash-monitor.test.d.ts +2 -0
  6. package/dist/__tests__/crash-monitor.test.d.ts.map +1 -0
  7. package/dist/__tests__/crash-monitor.test.js +212 -0
  8. package/dist/__tests__/execute-run.test.d.ts +2 -0
  9. package/dist/__tests__/execute-run.test.d.ts.map +1 -0
  10. package/dist/__tests__/execute-run.test.js +229 -0
  11. package/dist/__tests__/hook-queue.test.d.ts +2 -0
  12. package/dist/__tests__/hook-queue.test.d.ts.map +1 -0
  13. package/dist/__tests__/hook-queue.test.js +56 -0
  14. package/dist/__tests__/index.test.js +94 -37
  15. package/dist/__tests__/resource-lock.test.js +24 -24
  16. package/dist/client-log-handler.d.ts +10 -11
  17. package/dist/client-log-handler.d.ts.map +1 -1
  18. package/dist/client-log-handler.js +36 -58
  19. package/dist/crash-monitor.d.ts +17 -10
  20. package/dist/crash-monitor.d.ts.map +1 -1
  21. package/dist/crash-monitor.js +164 -63
  22. package/dist/execute-run.d.ts +4 -0
  23. package/dist/execute-run.d.ts.map +1 -0
  24. package/dist/execute-run.js +151 -0
  25. package/dist/harness-session.d.ts +34 -0
  26. package/dist/harness-session.d.ts.map +1 -0
  27. package/dist/harness-session.js +523 -0
  28. package/dist/hook-queue.d.ts +6 -0
  29. package/dist/hook-queue.d.ts.map +1 -0
  30. package/dist/hook-queue.js +18 -0
  31. package/dist/index.d.ts +0 -3
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +12 -230
  34. package/dist/logs.d.ts +1 -0
  35. package/dist/logs.d.ts.map +1 -1
  36. package/dist/logs.js +3 -0
  37. package/dist/resource-lock.d.ts +0 -1
  38. package/dist/resource-lock.d.ts.map +1 -1
  39. package/dist/resource-lock.js +1 -1
  40. package/dist/run.d.ts +2 -2
  41. package/dist/run.d.ts.map +1 -1
  42. package/dist/run.js +3 -3
  43. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  44. package/package.json +7 -8
  45. package/src/__tests__/bridge.test.ts +184 -0
  46. package/src/__tests__/client-log-handler.test.ts +117 -81
  47. package/src/__tests__/crash-monitor.test.ts +288 -0
  48. package/src/__tests__/execute-run.test.ts +325 -0
  49. package/src/__tests__/hook-queue.test.ts +77 -0
  50. package/src/__tests__/index.test.ts +135 -55
  51. package/src/__tests__/resource-lock.test.ts +29 -42
  52. package/src/client-log-handler.ts +48 -64
  53. package/src/crash-monitor.ts +248 -0
  54. package/src/execute-run.ts +199 -0
  55. package/src/harness-session.ts +734 -0
  56. package/src/hook-queue.ts +26 -0
  57. package/src/index.ts +17 -330
  58. package/src/logs.ts +4 -0
  59. package/src/resource-lock.ts +1 -1
  60. package/src/run.ts +5 -5
  61. package/tsconfig.tsbuildinfo +1 -0
  62. package/src/__tests__/crash-supervisor.test.ts +0 -200
  63. package/src/__tests__/harness-cache.test.ts +0 -74
  64. package/src/__tests__/harness.test.ts +0 -923
  65. package/src/crash-supervisor.ts +0 -321
  66. package/src/env.d.ts +0 -9
  67. package/src/harness.ts +0 -870
  68. package/src/setup.ts +0 -127
  69. package/src/teardown.ts +0 -14
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=bridge.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/bridge.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Integration test pairing createHarnessBridge (CLI side) with
3
+ * connectToHarness (app side). Tests the full connection lifecycle and
4
+ * RPC round-trip without knowing birpc internals.
5
+ */
6
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
7
+ import { createHarnessBridge } from '@react-native-harness/bridge/server';
8
+ import { connectToHarness } from '@react-native-harness/bridge/client';
9
+ const makeContext = () => ({
10
+ platform: {
11
+ name: 'ios',
12
+ platformId: 'ios',
13
+ runner: '/dev/null',
14
+ config: {},
15
+ },
16
+ });
17
+ // ---------------------------------------------------------------------------
18
+ // Test setup
19
+ // ---------------------------------------------------------------------------
20
+ let bridge;
21
+ let bridgePort;
22
+ beforeEach(async () => {
23
+ bridge = await createHarnessBridge({ port: 0, context: makeContext() });
24
+ bridgePort = bridge.ws.address().port;
25
+ });
26
+ afterEach(async () => {
27
+ bridge.dispose();
28
+ // Allow the server to close cleanly.
29
+ await new Promise((r) => setTimeout(r, 10));
30
+ });
31
+ const connect = (callbacks = { runTests: vi.fn() }) => connectToHarness(`ws://127.0.0.1:${bridgePort}`, callbacks);
32
+ const device = {
33
+ platform: 'ios',
34
+ manufacturer: 'Apple',
35
+ model: 'iPhone 17 Pro Simulator',
36
+ osVersion: '18.0',
37
+ };
38
+ // ---------------------------------------------------------------------------
39
+ // Tests
40
+ // ---------------------------------------------------------------------------
41
+ describe('bridge: createHarnessBridge + connectToHarness', () => {
42
+ describe('connection lifecycle', () => {
43
+ it('nextConnection() resolves once the app reports ready', async () => {
44
+ const connectionPromise = bridge.nextConnection();
45
+ const handle = await connect();
46
+ handle.reportReady(device);
47
+ const conn = await connectionPromise;
48
+ expect(conn.device).toEqual(device);
49
+ handle.disconnect();
50
+ });
51
+ it('bridge.connection is set after reportReady and cleared on disconnect', async () => {
52
+ const connectionPromise = bridge.nextConnection();
53
+ const handle = await connect();
54
+ handle.reportReady(device);
55
+ await connectionPromise;
56
+ expect(bridge.connection).not.toBeNull();
57
+ handle.disconnect();
58
+ // Allow close event to propagate.
59
+ await new Promise((r) => setTimeout(r, 20));
60
+ expect(bridge.connection).toBeNull();
61
+ });
62
+ it('nextConnection() returns immediately if app already connected', async () => {
63
+ // App connects before nextConnection() is called.
64
+ const handle = await connect();
65
+ handle.reportReady(device);
66
+ // Yield so the server processes the reportReady before we ask.
67
+ await new Promise((r) => setTimeout(r, 10));
68
+ const conn = await bridge.nextConnection();
69
+ expect(conn.device.platform).toBe('ios');
70
+ handle.disconnect();
71
+ });
72
+ it('emits connected / disconnected events', async () => {
73
+ const onConnected = vi.fn();
74
+ const onDisconnected = vi.fn();
75
+ bridge.on('connected', onConnected);
76
+ bridge.on('disconnected', onDisconnected);
77
+ const handle = await connect();
78
+ handle.reportReady(device);
79
+ await new Promise((r) => setTimeout(r, 10));
80
+ expect(onConnected).toHaveBeenCalledOnce();
81
+ handle.disconnect();
82
+ await new Promise((r) => setTimeout(r, 20));
83
+ expect(onDisconnected).toHaveBeenCalledOnce();
84
+ });
85
+ it('nextConnection() rejects when signal is already aborted', async () => {
86
+ const controller = new AbortController();
87
+ controller.abort();
88
+ await expect(bridge.nextConnection(controller.signal)).rejects.toMatchObject({
89
+ name: 'AbortError',
90
+ });
91
+ });
92
+ it('nextConnection() rejects when signal is aborted while waiting', async () => {
93
+ const controller = new AbortController();
94
+ const promise = bridge.nextConnection(controller.signal);
95
+ controller.abort();
96
+ await expect(promise).rejects.toMatchObject({ name: 'AbortError' });
97
+ });
98
+ });
99
+ describe('runTests round-trip', () => {
100
+ it('CLI conn.runTests() invokes the app-side runTests callback', async () => {
101
+ const suiteResult = {
102
+ name: 'suite',
103
+ tests: [{ name: 'passes', status: 'passed', duration: 10 }],
104
+ suites: [],
105
+ status: 'passed',
106
+ duration: 30,
107
+ };
108
+ const runTestsCb = vi.fn(async () => suiteResult);
109
+ const connectionPromise = bridge.nextConnection();
110
+ const handle = await connect({ runTests: runTestsCb });
111
+ handle.reportReady(device);
112
+ const conn = await connectionPromise;
113
+ const result = await conn.runTests('example.ts', {
114
+ runner: '/runner.js',
115
+ });
116
+ expect(runTestsCb).toHaveBeenCalledWith('example.ts', expect.objectContaining({ runner: '/runner.js' }));
117
+ expect(result.tests[0].name).toBe('passes');
118
+ handle.disconnect();
119
+ });
120
+ });
121
+ describe('bridge events', () => {
122
+ it('emitEvent on app side fires the event listener on bridge', async () => {
123
+ const onEvent = vi.fn();
124
+ bridge.on('event', onEvent);
125
+ const connectionPromise = bridge.nextConnection();
126
+ const handle = await connect();
127
+ handle.reportReady(device);
128
+ await connectionPromise;
129
+ handle.emitEvent({ type: 'collection-started', file: 'example.ts' });
130
+ await new Promise((r) => setTimeout(r, 10));
131
+ expect(onEvent).toHaveBeenCalledWith(expect.objectContaining({ type: 'collection-started', file: 'example.ts' }));
132
+ handle.disconnect();
133
+ });
134
+ });
135
+ describe('dispose', () => {
136
+ it('rejects pending nextConnection() waiters', async () => {
137
+ const pending = bridge.nextConnection();
138
+ bridge.dispose();
139
+ await expect(pending).rejects.toThrow('Bridge disposed');
140
+ });
141
+ });
142
+ });
@@ -1,10 +1,5 @@
1
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';
2
+ import { createClientLogCollector, formatClientLogMessage, formatClientLogEntry, } from '../client-log-handler.js';
8
3
  describe('client-log-handler', () => {
9
4
  beforeEach(() => {
10
5
  vi.clearAllMocks();
@@ -75,7 +70,11 @@ describe('client-log-handler', () => {
75
70
  expect(result).toContain('3.14159');
76
71
  });
77
72
  it('should handle multiple substitutions', () => {
78
- const result = formatClientLogMessage(['Hello %s, you have %d messages', 'Alice', 5]);
73
+ const result = formatClientLogMessage([
74
+ 'Hello %s, you have %d messages',
75
+ 'Alice',
76
+ 5,
77
+ ]);
79
78
  expect(result).toBe('Hello Alice, you have 5 messages');
80
79
  });
81
80
  it('should handle %j JSON substitution', () => {
@@ -93,21 +92,29 @@ describe('client-log-handler', () => {
93
92
  expect(result).toBe('Task is 100% complete');
94
93
  });
95
94
  it('should append extra arguments after substitution', () => {
96
- const result = formatClientLogMessage(['Hello %s', 'world', 'extra', 'args']);
95
+ const result = formatClientLogMessage([
96
+ 'Hello %s',
97
+ 'world',
98
+ 'extra',
99
+ 'args',
100
+ ]);
97
101
  expect(result).toBe('Hello world extra args');
98
102
  });
99
103
  });
100
104
  });
101
- describe('formatClientLogLine', () => {
105
+ describe('formatClientLogEntry', () => {
102
106
  it('should format log level event', () => {
103
107
  const event = {
104
108
  type: 'client_log',
105
109
  level: 'log',
106
110
  data: ['Test message'],
107
111
  };
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');
112
+ const result = formatClientLogEntry(event);
113
+ expect(result).toEqual({
114
+ message: 'Test message',
115
+ origin: '',
116
+ type: 'log',
117
+ });
111
118
  });
112
119
  it('should format error level event', () => {
113
120
  const event = {
@@ -115,8 +122,12 @@ describe('client-log-handler', () => {
115
122
  level: 'error',
116
123
  data: ['Error occurred'],
117
124
  };
118
- const result = formatClientLogLine(event);
119
- expect(result).toContain('Error occurred');
125
+ const result = formatClientLogEntry(event);
126
+ expect(result).toEqual({
127
+ message: 'Error occurred',
128
+ origin: '',
129
+ type: 'error',
130
+ });
120
131
  });
121
132
  it('should format warn level event', () => {
122
133
  const event = {
@@ -124,8 +135,12 @@ describe('client-log-handler', () => {
124
135
  level: 'warn',
125
136
  data: ['Warning message'],
126
137
  };
127
- const result = formatClientLogLine(event);
128
- expect(result).toContain('Warning message');
138
+ const result = formatClientLogEntry(event);
139
+ expect(result).toEqual({
140
+ message: 'Warning message',
141
+ origin: '',
142
+ type: 'warn',
143
+ });
129
144
  });
130
145
  it('should format info level event', () => {
131
146
  const event = {
@@ -133,8 +148,12 @@ describe('client-log-handler', () => {
133
148
  level: 'info',
134
149
  data: ['Info message'],
135
150
  };
136
- const result = formatClientLogLine(event);
137
- expect(result).toContain('Info message');
151
+ const result = formatClientLogEntry(event);
152
+ expect(result).toEqual({
153
+ message: 'Info message',
154
+ origin: '',
155
+ type: 'info',
156
+ });
138
157
  });
139
158
  it('should format debug level event', () => {
140
159
  const event = {
@@ -142,8 +161,12 @@ describe('client-log-handler', () => {
142
161
  level: 'debug',
143
162
  data: ['Debug message'],
144
163
  };
145
- const result = formatClientLogLine(event);
146
- expect(result).toContain('Debug message');
164
+ const result = formatClientLogEntry(event);
165
+ expect(result).toEqual({
166
+ message: 'Debug message',
167
+ origin: '',
168
+ type: 'debug',
169
+ });
147
170
  });
148
171
  it('should format trace level event', () => {
149
172
  const event = {
@@ -151,8 +174,12 @@ describe('client-log-handler', () => {
151
174
  level: 'trace',
152
175
  data: ['Trace message'],
153
176
  };
154
- const result = formatClientLogLine(event);
155
- expect(result).toContain('Trace message');
177
+ const result = formatClientLogEntry(event);
178
+ expect(result).toEqual({
179
+ message: 'Trace message',
180
+ origin: '',
181
+ type: 'log',
182
+ });
156
183
  });
157
184
  it('should handle multiple data items', () => {
158
185
  const event = {
@@ -160,103 +187,120 @@ describe('client-log-handler', () => {
160
187
  level: 'log',
161
188
  data: ['User:', { id: 1, name: 'Test' }],
162
189
  };
163
- const result = formatClientLogLine(event);
164
- expect(result).toContain('User:');
165
- expect(result).toContain('id');
166
- expect(result).toContain('Test');
190
+ const result = formatClientLogEntry(event);
191
+ expect(result?.message).toContain('User:');
192
+ expect(result?.message).toContain('id');
193
+ expect(result?.message).toContain('Test');
167
194
  });
168
195
  });
169
- describe('handleClientLogEvent', () => {
170
- it('should handle client_log events and call log', () => {
196
+ describe('createClientLogCollector', () => {
197
+ it('collects client_log events and flushes them', () => {
198
+ const collector = createClientLogCollector();
171
199
  const event = {
172
200
  type: 'client_log',
173
201
  level: 'log',
174
202
  data: ['Test message'],
175
203
  };
176
- const result = handleClientLogEvent(event);
177
- expect(result).toBe(true);
178
- expect(log).toHaveBeenCalledTimes(1);
179
- expect(log).toHaveBeenCalledWith(expect.stringContaining('Test message'));
204
+ collector.handleEvent(event);
205
+ expect(collector.flush()).toEqual([
206
+ {
207
+ message: 'Test message',
208
+ origin: '',
209
+ type: 'log',
210
+ },
211
+ ]);
180
212
  });
181
- it('should return false for non-client_log events', () => {
213
+ it('returns an empty array for non-client_log events', () => {
214
+ const collector = createClientLogCollector();
182
215
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
183
216
  const event = { type: 'bundle_build_started' };
184
- const result = handleClientLogEvent(event);
185
- expect(result).toBe(false);
186
- expect(log).not.toHaveBeenCalled();
217
+ collector.handleEvent(event);
218
+ expect(collector.flush()).toEqual([]);
187
219
  });
188
- it('should handle error level logs', () => {
220
+ it('collects error level logs', () => {
221
+ const collector = createClientLogCollector();
189
222
  const event = {
190
223
  type: 'client_log',
191
224
  level: 'error',
192
225
  data: ['Something went wrong'],
193
226
  };
194
- handleClientLogEvent(event);
195
- expect(log).toHaveBeenCalledWith(expect.stringContaining('Something went wrong'));
227
+ collector.handleEvent(event);
228
+ expect(collector.flush()).toEqual([
229
+ {
230
+ message: 'Something went wrong',
231
+ origin: '',
232
+ type: 'error',
233
+ },
234
+ ]);
196
235
  });
197
- it('should handle warn level logs', () => {
236
+ it('collects warn level logs', () => {
237
+ const collector = createClientLogCollector();
198
238
  const event = {
199
239
  type: 'client_log',
200
240
  level: 'warn',
201
241
  data: ['Deprecation warning'],
202
242
  };
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');
243
+ collector.handleEvent(event);
244
+ expect(collector.flush()).toEqual([
245
+ {
246
+ message: 'Deprecation warning',
247
+ origin: '',
248
+ type: 'warn',
249
+ },
250
+ ]);
211
251
  });
212
- it('should handle client_log events when called', () => {
213
- const listener = createClientLogListener();
214
- const event = {
252
+ it('flush clears the buffer', () => {
253
+ const collector = createClientLogCollector();
254
+ collector.handleEvent({
215
255
  type: 'client_log',
216
256
  level: 'info',
217
257
  data: ['Listener test'],
218
- };
219
- listener(event);
220
- expect(log).toHaveBeenCalledWith(expect.stringContaining('Listener test'));
258
+ });
259
+ expect(collector.flush()).toEqual([
260
+ {
261
+ message: 'Listener test',
262
+ origin: '',
263
+ type: 'info',
264
+ },
265
+ ]);
266
+ expect(collector.flush()).toEqual([]);
221
267
  });
222
- it('should ignore non-client_log events', () => {
223
- const listener = createClientLogListener();
268
+ it('ignores non-client_log events', () => {
269
+ const collector = createClientLogCollector();
224
270
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
225
271
  const event = { type: 'initialize_done' };
226
- listener(event);
227
- expect(log).not.toHaveBeenCalled();
272
+ collector.handleEvent(event);
273
+ expect(collector.flush()).toEqual([]);
228
274
  });
229
- });
230
- describe('group events', () => {
231
- it('should handle group events without logging', () => {
275
+ it('ignores group events', () => {
276
+ const collector = createClientLogCollector();
232
277
  const event = {
233
278
  type: 'client_log',
234
279
  level: 'group',
235
280
  data: ['Group label'],
236
281
  };
237
- const result = handleClientLogEvent(event);
238
- expect(result).toBe(true);
239
- expect(log).not.toHaveBeenCalled();
282
+ collector.handleEvent(event);
283
+ expect(collector.flush()).toEqual([]);
240
284
  });
241
- it('should handle groupCollapsed events without logging', () => {
285
+ it('ignores groupCollapsed events', () => {
286
+ const collector = createClientLogCollector();
242
287
  const event = {
243
288
  type: 'client_log',
244
289
  level: 'groupCollapsed',
245
290
  data: ['Collapsed'],
246
291
  };
247
- const result = handleClientLogEvent(event);
248
- expect(result).toBe(true);
249
- expect(log).not.toHaveBeenCalled();
292
+ collector.handleEvent(event);
293
+ expect(collector.flush()).toEqual([]);
250
294
  });
251
- it('should handle groupEnd events without logging', () => {
295
+ it('ignores groupEnd events', () => {
296
+ const collector = createClientLogCollector();
252
297
  const event = {
253
298
  type: 'client_log',
254
299
  level: 'groupEnd',
255
300
  data: [],
256
301
  };
257
- const result = handleClientLogEvent(event);
258
- expect(result).toBe(true);
259
- expect(log).not.toHaveBeenCalled();
302
+ collector.handleEvent(event);
303
+ expect(collector.flush()).toEqual([]);
260
304
  });
261
305
  });
262
306
  });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=crash-monitor.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crash-monitor.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/crash-monitor.test.ts"],"names":[],"mappings":""}