@react-native-harness/jest 1.4.0 → 1.5.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 (133) hide show
  1. package/dist/execute-run.d.ts.map +1 -1
  2. package/dist/execute-run.js +2 -1
  3. package/dist/harness-session.d.ts.map +1 -1
  4. package/dist/harness-session.js +74 -30
  5. package/dist/resource-lock.d.ts.map +1 -1
  6. package/dist/resource-lock.js +12 -2
  7. package/dist/toJestStatus.d.ts +4 -0
  8. package/dist/toJestStatus.d.ts.map +1 -0
  9. package/dist/toJestStatus.js +2 -0
  10. package/dist/toTestResult.d.ts +3 -2
  11. package/dist/toTestResult.d.ts.map +1 -1
  12. package/dist/toTestResult.js +2 -1
  13. package/package.json +8 -8
  14. package/src/execute-run.ts +2 -1
  15. package/src/harness-session.ts +86 -27
  16. package/src/resource-lock.ts +15 -2
  17. package/src/toJestStatus.ts +6 -0
  18. package/src/toTestResult.ts +5 -3
  19. package/dist/__tests__/bridge.test.d.ts +0 -2
  20. package/dist/__tests__/bridge.test.d.ts.map +0 -1
  21. package/dist/__tests__/bridge.test.js +0 -167
  22. package/dist/__tests__/client-log-handler.test.d.ts +0 -2
  23. package/dist/__tests__/client-log-handler.test.d.ts.map +0 -1
  24. package/dist/__tests__/client-log-handler.test.js +0 -306
  25. package/dist/__tests__/crash-monitor.test.d.ts +0 -2
  26. package/dist/__tests__/crash-monitor.test.d.ts.map +0 -1
  27. package/dist/__tests__/crash-monitor.test.js +0 -230
  28. package/dist/__tests__/crash-supervisor.test.d.ts +0 -2
  29. package/dist/__tests__/crash-supervisor.test.d.ts.map +0 -1
  30. package/dist/__tests__/crash-supervisor.test.js +0 -151
  31. package/dist/__tests__/environment-reset.test.d.ts +0 -2
  32. package/dist/__tests__/environment-reset.test.d.ts.map +0 -1
  33. package/dist/__tests__/environment-reset.test.js +0 -238
  34. package/dist/__tests__/errors.test.d.ts +0 -2
  35. package/dist/__tests__/errors.test.d.ts.map +0 -1
  36. package/dist/__tests__/errors.test.js +0 -98
  37. package/dist/__tests__/execute-run.test.d.ts +0 -2
  38. package/dist/__tests__/execute-run.test.d.ts.map +0 -1
  39. package/dist/__tests__/execute-run.test.js +0 -639
  40. package/dist/__tests__/fixtures/mock-platform-runner.d.ts +0 -7
  41. package/dist/__tests__/fixtures/mock-platform-runner.d.ts.map +0 -1
  42. package/dist/__tests__/fixtures/mock-platform-runner.js +0 -6
  43. package/dist/__tests__/format-harness-error.test.d.ts +0 -2
  44. package/dist/__tests__/format-harness-error.test.d.ts.map +0 -1
  45. package/dist/__tests__/format-harness-error.test.js +0 -27
  46. package/dist/__tests__/harness-cache.test.d.ts +0 -2
  47. package/dist/__tests__/harness-cache.test.d.ts.map +0 -1
  48. package/dist/__tests__/harness-cache.test.js +0 -72
  49. package/dist/__tests__/harness-session-signal.test.d.ts +0 -2
  50. package/dist/__tests__/harness-session-signal.test.d.ts.map +0 -1
  51. package/dist/__tests__/harness-session-signal.test.js +0 -298
  52. package/dist/__tests__/harness-session.test.d.ts +0 -2
  53. package/dist/__tests__/harness-session.test.d.ts.map +0 -1
  54. package/dist/__tests__/harness-session.test.js +0 -233
  55. package/dist/__tests__/harness.test.d.ts +0 -2
  56. package/dist/__tests__/harness.test.d.ts.map +0 -1
  57. package/dist/__tests__/harness.test.js +0 -636
  58. package/dist/__tests__/hook-queue.test.d.ts +0 -2
  59. package/dist/__tests__/hook-queue.test.d.ts.map +0 -1
  60. package/dist/__tests__/hook-queue.test.js +0 -56
  61. package/dist/__tests__/index.test.d.ts +0 -2
  62. package/dist/__tests__/index.test.d.ts.map +0 -1
  63. package/dist/__tests__/index.test.js +0 -150
  64. package/dist/__tests__/metro-port.test.d.ts +0 -2
  65. package/dist/__tests__/metro-port.test.d.ts.map +0 -1
  66. package/dist/__tests__/metro-port.test.js +0 -54
  67. package/dist/__tests__/resource-lock.test.d.ts +0 -2
  68. package/dist/__tests__/resource-lock.test.d.ts.map +0 -1
  69. package/dist/__tests__/resource-lock.test.js +0 -138
  70. package/dist/__tests__/run.test.d.ts +0 -2
  71. package/dist/__tests__/run.test.d.ts.map +0 -1
  72. package/dist/__tests__/run.test.js +0 -48
  73. package/dist/__tests__/test-file-platform-filter.test.d.ts +0 -2
  74. package/dist/__tests__/test-file-platform-filter.test.d.ts.map +0 -1
  75. package/dist/__tests__/test-file-platform-filter.test.js +0 -47
  76. package/dist/cli-args.test.d.ts +0 -2
  77. package/dist/cli-args.test.d.ts.map +0 -1
  78. package/dist/cli-args.test.js +0 -1
  79. package/dist/crash-supervisor.d.ts +0 -21
  80. package/dist/crash-supervisor.d.ts.map +0 -1
  81. package/dist/crash-supervisor.js +0 -210
  82. package/dist/diagnostics/__tests__/derivers.test.d.ts +0 -2
  83. package/dist/diagnostics/__tests__/derivers.test.d.ts.map +0 -1
  84. package/dist/diagnostics/__tests__/derivers.test.js +0 -248
  85. package/dist/diagnostics/__tests__/report.test.d.ts +0 -2
  86. package/dist/diagnostics/__tests__/report.test.d.ts.map +0 -1
  87. package/dist/diagnostics/__tests__/report.test.js +0 -97
  88. package/dist/event-mapper.d.ts +0 -16
  89. package/dist/event-mapper.d.ts.map +0 -1
  90. package/dist/event-mapper.js +0 -120
  91. package/dist/global-setup.d.ts +0 -3
  92. package/dist/global-setup.d.ts.map +0 -1
  93. package/dist/global-setup.js +0 -60
  94. package/dist/global-teardown.d.ts +0 -3
  95. package/dist/global-teardown.d.ts.map +0 -1
  96. package/dist/global-teardown.js +0 -11
  97. package/dist/harness-reporter.d.ts +0 -8
  98. package/dist/harness-reporter.d.ts.map +0 -1
  99. package/dist/harness-reporter.js +0 -12
  100. package/dist/harness.d.ts +0 -47
  101. package/dist/harness.d.ts.map +0 -1
  102. package/dist/harness.js +0 -619
  103. package/dist/jest-options.d.ts +0 -2
  104. package/dist/jest-options.d.ts.map +0 -1
  105. package/dist/jest-options.js +0 -1
  106. package/dist/setup.d.ts +0 -3
  107. package/dist/setup.d.ts.map +0 -1
  108. package/dist/setup.js +0 -80
  109. package/dist/teardown.d.ts +0 -3
  110. package/dist/teardown.d.ts.map +0 -1
  111. package/dist/teardown.js +0 -10
  112. package/dist/tsconfig.lib.tsbuildinfo +0 -1
  113. package/dist/types.d.ts +0 -17
  114. package/dist/types.d.ts.map +0 -1
  115. package/dist/types.js +0 -1
  116. package/src/__tests__/bridge.test.ts +0 -224
  117. package/src/__tests__/client-log-handler.test.ts +0 -364
  118. package/src/__tests__/crash-monitor.test.ts +0 -323
  119. package/src/__tests__/environment-reset.test.ts +0 -308
  120. package/src/__tests__/errors.test.ts +0 -157
  121. package/src/__tests__/execute-run.test.ts +0 -849
  122. package/src/__tests__/format-harness-error.test.ts +0 -32
  123. package/src/__tests__/harness-cache.test.ts +0 -91
  124. package/src/__tests__/harness-session.test.ts +0 -303
  125. package/src/__tests__/hook-queue.test.ts +0 -77
  126. package/src/__tests__/index.test.ts +0 -221
  127. package/src/__tests__/metro-port.test.ts +0 -63
  128. package/src/__tests__/resource-lock.test.ts +0 -179
  129. package/src/__tests__/run.test.ts +0 -74
  130. package/src/__tests__/test-file-platform-filter.test.ts +0 -83
  131. package/src/diagnostics/__tests__/derivers.test.ts +0 -300
  132. package/src/diagnostics/__tests__/report.test.ts +0 -128
  133. package/tsconfig.tsbuildinfo +0 -1
@@ -1,636 +0,0 @@
1
- import { EventEmitter } from 'node:events';
2
- import { HARNESS_BRIDGE_PATH } from '@react-native-harness/bridge';
3
- import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
4
- import { MetroPortRangeExhaustedError } from '../errors.js';
5
- import { definePlugin } from '@react-native-harness/plugins';
6
- import { createCrashSupervisor } from '../crash-supervisor.js';
7
- const mocks = vi.hoisted(() => ({
8
- createCrashArtifactWriter: vi.fn(() => ({})),
9
- getBridgeServer: vi.fn(),
10
- getMetroInstance: vi.fn(),
11
- isMetroCacheReusable: vi.fn(() => false),
12
- logMetroCacheReused: vi.fn(),
13
- logMetroPortFallback: vi.fn(),
14
- logRunnerStarting: vi.fn(),
15
- logRunnerStillWaitingInQueue: vi.fn(),
16
- logRunnerWaitingInQueue: vi.fn(),
17
- waitForMetroBackedAppReady: vi.fn(),
18
- isPortAvailable: vi.fn(async () => true),
19
- }));
20
- vi.mock('@react-native-harness/bundler-metro', async () => {
21
- const actual = await vi.importActual('@react-native-harness/bundler-metro');
22
- return {
23
- ...actual,
24
- getMetroInstance: mocks.getMetroInstance,
25
- isPortAvailable: mocks.isPortAvailable,
26
- isMetroCacheReusable: mocks.isMetroCacheReusable,
27
- waitForMetroBackedAppReady: mocks.waitForMetroBackedAppReady,
28
- };
29
- });
30
- vi.mock('@react-native-harness/bridge/server', () => ({
31
- getBridgeServer: mocks.getBridgeServer,
32
- }));
33
- vi.mock('../logs.js', () => ({
34
- logMetroCacheReused: mocks.logMetroCacheReused,
35
- logMetroPortFallback: mocks.logMetroPortFallback,
36
- logRunnerStarting: mocks.logRunnerStarting,
37
- logRunnerStillWaitingInQueue: mocks.logRunnerStillWaitingInQueue,
38
- logRunnerWaitingInQueue: mocks.logRunnerWaitingInQueue,
39
- }));
40
- vi.mock('@react-native-harness/tools', async () => {
41
- const actual = await vi.importActual('@react-native-harness/tools');
42
- return {
43
- ...actual,
44
- createCrashArtifactWriter: mocks.createCrashArtifactWriter,
45
- };
46
- });
47
- import { getHarness, waitForAppReady } from '../harness.js';
48
- import { PlatformReadyTimeoutError, StartupStallError } from '../errors.js';
49
- const createBridgeServer = () => {
50
- const emitter = new EventEmitter();
51
- return {
52
- serverBridge: {
53
- ws: {},
54
- rpc: {
55
- clients: [],
56
- },
57
- on: emitter.on.bind(emitter),
58
- once: emitter.once.bind(emitter),
59
- off: emitter.off.bind(emitter),
60
- dispose: vi.fn(),
61
- },
62
- emitReady: () => {
63
- emitter.emit('ready', {
64
- platform: 'ios',
65
- manufacturer: 'Apple',
66
- model: 'Simulator',
67
- osVersion: '18.0',
68
- });
69
- },
70
- emitEvent: (event) => {
71
- emitter.emit('event', event);
72
- },
73
- emitDisconnect: () => {
74
- emitter.emit('disconnect');
75
- },
76
- };
77
- };
78
- const createReporter = () => {
79
- const listeners = new Set();
80
- return {
81
- addListener: (listener) => {
82
- listeners.add(listener);
83
- },
84
- removeListener: (listener) => {
85
- listeners.delete(listener);
86
- },
87
- emit: (event) => {
88
- listeners.forEach((listener) => listener(event));
89
- },
90
- clearAllListeners: () => {
91
- listeners.clear();
92
- },
93
- };
94
- };
95
- const createMetroInstance = (overrides = {}) => ({
96
- events: createReporter(),
97
- httpServer: {},
98
- websocketEndpoints: {},
99
- waitUntilHealthy: vi.fn(async () => 'HTTP 200: packager-status:running'),
100
- prewarm: vi.fn(async () => false),
101
- dispose: vi.fn(async () => undefined),
102
- ...overrides,
103
- });
104
- const createAppMonitor = () => {
105
- const listeners = new Set();
106
- return {
107
- appMonitor: {
108
- start: vi.fn(async () => undefined),
109
- stop: vi.fn(async () => undefined),
110
- dispose: vi.fn(async () => undefined),
111
- addListener: (listener) => {
112
- listeners.add(listener);
113
- },
114
- removeListener: (listener) => {
115
- listeners.delete(listener);
116
- },
117
- },
118
- emit: (event) => {
119
- for (const listener of listeners) {
120
- listener(event);
121
- }
122
- },
123
- };
124
- };
125
- const createPlatformRunner = (overrides = {}) => ({
126
- startApp: vi.fn(async () => undefined),
127
- restartApp: vi.fn(async () => undefined),
128
- stopApp: vi.fn(async () => undefined),
129
- dispose: vi.fn(async () => undefined),
130
- isAppRunning: vi.fn(async () => true),
131
- createAppMonitor: () => createAppMonitor().appMonitor,
132
- ...overrides,
133
- });
134
- const createHarnessConfig = (overrides = {}) => ({
135
- appRegistryComponentName: 'App',
136
- bridgeTimeout: 60_000,
137
- bundleStartTimeout: 60_000,
138
- crashDetectionInterval: 500,
139
- defaultRunner: 'ios',
140
- detectNativeCrashes: true,
141
- disableViewFlattening: false,
142
- entryPoint: 'index.js',
143
- forwardClientLogs: false,
144
- maxAppRestarts: 2,
145
- metroPort: 8081,
146
- platformReadyTimeout: 300_000,
147
- resetEnvironmentBetweenTestFiles: true,
148
- runners: [],
149
- unstable__enableMetroCache: false,
150
- unstable__skipAlreadyIncludedModules: false,
151
- webSocketPort: 3001,
152
- ...overrides,
153
- });
154
- beforeEach(() => {
155
- vi.clearAllMocks();
156
- mocks.isPortAvailable.mockReset();
157
- mocks.isPortAvailable.mockResolvedValue(true);
158
- });
159
- afterEach(() => {
160
- delete globalThis.__HARNESS_PLATFORM_RUNNER__;
161
- });
162
- describe('waitForAppReady', () => {
163
- it('delegates startup orchestration to bundler-metro and resolves readiness from the bridge', async () => {
164
- const { serverBridge, emitReady } = createBridgeServer();
165
- const metroInstance = createMetroInstance();
166
- const platformInstance = createPlatformRunner();
167
- const { appMonitor } = createAppMonitor();
168
- const crashSupervisor = createCrashSupervisor({
169
- appMonitor,
170
- platformRunner: platformInstance,
171
- });
172
- mocks.waitForMetroBackedAppReady.mockImplementationOnce(async (options) => {
173
- options.onAttemptStart?.();
174
- const readyPromise = options.waitForReady(new AbortController().signal);
175
- emitReady();
176
- await readyPromise;
177
- options.onAttemptReset?.();
178
- });
179
- await waitForAppReady({
180
- metroInstance,
181
- serverBridge,
182
- platformInstance,
183
- platformId: 'ios',
184
- bundleStartTimeout: 1_500,
185
- readyTimeout: 2_500,
186
- maxAppRestarts: 3,
187
- testFilePath: '/tmp/test.harness.ts',
188
- crashSupervisor,
189
- });
190
- expect(mocks.waitForMetroBackedAppReady).toHaveBeenCalledWith(expect.objectContaining({
191
- metro: metroInstance,
192
- platformId: 'ios',
193
- bundleStartTimeout: 1_500,
194
- readyTimeout: 2_500,
195
- maxAppRestarts: 3,
196
- startAttempt: expect.any(Function),
197
- waitForReady: expect.any(Function),
198
- waitForCrash: expect.any(Function),
199
- }));
200
- expect(crashSupervisor.isReady()).toBe(true);
201
- await crashSupervisor.dispose();
202
- });
203
- it('passes launch options through the shared Metro startup helper', async () => {
204
- const { serverBridge } = createBridgeServer();
205
- const metroInstance = createMetroInstance();
206
- const restartApp = vi.fn(async () => undefined);
207
- const platformInstance = createPlatformRunner({ restartApp });
208
- const { appMonitor } = createAppMonitor();
209
- const crashSupervisor = createCrashSupervisor({
210
- appMonitor,
211
- platformRunner: platformInstance,
212
- });
213
- mocks.waitForMetroBackedAppReady.mockImplementationOnce(async (options) => {
214
- await options.startAttempt();
215
- });
216
- await waitForAppReady({
217
- metroInstance,
218
- serverBridge,
219
- platformInstance,
220
- platformId: 'ios',
221
- bundleStartTimeout: 1_500,
222
- readyTimeout: 2_500,
223
- maxAppRestarts: 3,
224
- testFilePath: '/tmp/test.harness.ts',
225
- crashSupervisor,
226
- appLaunchOptions: {
227
- extras: {
228
- mode: 'startup',
229
- },
230
- },
231
- });
232
- expect(restartApp).toHaveBeenCalledWith({
233
- extras: {
234
- mode: 'startup',
235
- },
236
- });
237
- await crashSupervisor.dispose();
238
- });
239
- });
240
- describe('getHarness', () => {
241
- it('fails when the platform runner does not become ready within platformReadyTimeout', async () => {
242
- const { serverBridge } = createBridgeServer();
243
- const metroInstance = createMetroInstance();
244
- mocks.getBridgeServer.mockResolvedValue(serverBridge);
245
- mocks.getMetroInstance.mockResolvedValue(metroInstance);
246
- globalThis.__HARNESS_PLATFORM_RUNNER__ = vi.fn(async () => await new Promise((_, reject) => {
247
- setTimeout(() => {
248
- reject(new DOMException('The operation was aborted', 'AbortError'));
249
- }, 20);
250
- }));
251
- const platform = {
252
- config: {},
253
- getResourceLockKey: () => 'ios:test-platform-ready-timeout',
254
- name: 'ios',
255
- platformId: 'ios',
256
- runner: `data:text/javascript,${encodeURIComponent('export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);')}`,
257
- };
258
- await expect(getHarness(createHarnessConfig({
259
- platformReadyTimeout: 10,
260
- }), platform, '/tmp/project')).rejects.toBeInstanceOf(PlatformReadyTimeoutError);
261
- });
262
- it('passes a platform init signal to the runner factory', async () => {
263
- const { serverBridge } = createBridgeServer();
264
- const appMonitor = createAppMonitor();
265
- const platformInstance = createPlatformRunner({
266
- createAppMonitor: () => appMonitor.appMonitor,
267
- });
268
- const metroInstance = createMetroInstance();
269
- mocks.getBridgeServer.mockResolvedValue(serverBridge);
270
- mocks.getMetroInstance.mockResolvedValue(metroInstance);
271
- const runner = vi.fn(async () => platformInstance);
272
- globalThis.__HARNESS_PLATFORM_RUNNER__ = runner;
273
- const platform = {
274
- config: {},
275
- getResourceLockKey: () => 'ios:test-platform-init-signal',
276
- name: 'ios',
277
- platformId: 'ios',
278
- runner: `data:text/javascript,${encodeURIComponent('export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);')}`,
279
- };
280
- const harness = await getHarness(createHarnessConfig(), platform, '/tmp/project');
281
- expect(runner).toHaveBeenCalledWith(platform.config, expect.objectContaining({
282
- metroPort: 8081,
283
- }), expect.objectContaining({
284
- signal: expect.any(AbortSignal),
285
- }));
286
- await harness.dispose();
287
- });
288
- it('resolves and exposes a fallback Metro port before platform init', async () => {
289
- const { serverBridge } = createBridgeServer();
290
- const appMonitor = createAppMonitor();
291
- const platformInstance = createPlatformRunner({
292
- createAppMonitor: () => appMonitor.appMonitor,
293
- });
294
- const metroInstance = createMetroInstance();
295
- mocks.getBridgeServer.mockResolvedValue(serverBridge);
296
- mocks.getMetroInstance.mockResolvedValue(metroInstance);
297
- mocks.isPortAvailable
298
- .mockResolvedValueOnce(false)
299
- .mockResolvedValueOnce(true);
300
- const runner = vi.fn(async () => platformInstance);
301
- globalThis.__HARNESS_PLATFORM_RUNNER__ = runner;
302
- const platform = {
303
- config: {},
304
- getResourceLockKey: () => 'android:emulator:Pixel_8_API_35',
305
- name: 'android',
306
- platformId: 'android',
307
- runner: `data:text/javascript,${encodeURIComponent('export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);')}`,
308
- };
309
- const harness = await getHarness(createHarnessConfig(), platform, '/tmp/project');
310
- expect(harness.config.metroPort).toBe(8082);
311
- expect(mocks.getMetroInstance).toHaveBeenCalledWith(expect.objectContaining({
312
- harnessConfig: expect.objectContaining({
313
- metroPort: 8082,
314
- }),
315
- }), expect.any(AbortSignal));
316
- expect(runner).toHaveBeenCalledWith(platform.config, expect.objectContaining({
317
- metroPort: 8082,
318
- }), expect.objectContaining({
319
- signal: expect.any(AbortSignal),
320
- }));
321
- expect(mocks.logMetroPortFallback).toHaveBeenCalledWith(8081, 8082);
322
- await harness.dispose();
323
- });
324
- it('fails when no Metro port is available in the retry window', async () => {
325
- mocks.isPortAvailable.mockResolvedValue(false);
326
- const platform = {
327
- config: {},
328
- getResourceLockKey: () => 'android:emulator:Pixel_8_API_35',
329
- name: 'android',
330
- platformId: 'android',
331
- runner: 'data:text/javascript,export default async () => ({})',
332
- };
333
- await expect(getHarness(createHarnessConfig(), platform, '/tmp/project')).rejects.toBeInstanceOf(MetroPortRangeExhaustedError);
334
- expect(mocks.getBridgeServer).not.toHaveBeenCalled();
335
- expect(mocks.getMetroInstance).not.toHaveBeenCalled();
336
- });
337
- it('falls back to a default resource lock key for platforms without getResourceLockKey', async () => {
338
- const { serverBridge } = createBridgeServer();
339
- const appMonitor = createAppMonitor();
340
- const platformInstance = createPlatformRunner({
341
- createAppMonitor: () => appMonitor.appMonitor,
342
- });
343
- const metroInstance = createMetroInstance();
344
- mocks.getBridgeServer.mockResolvedValue(serverBridge);
345
- mocks.getMetroInstance.mockResolvedValue(metroInstance);
346
- globalThis.__HARNESS_PLATFORM_RUNNER__ = vi.fn(async () => platformInstance);
347
- const platform = {
348
- config: {},
349
- name: 'legacy-ios',
350
- platformId: 'ios',
351
- runner: `data:text/javascript,${encodeURIComponent('export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);')}`,
352
- };
353
- const harness = await getHarness(createHarnessConfig(), platform, '/tmp/project');
354
- await harness.dispose();
355
- });
356
- it('routes ensureAppReady through the shared Metro startup helper', async () => {
357
- const { serverBridge, emitReady } = createBridgeServer();
358
- const appMonitor = createAppMonitor();
359
- const restartApp = vi.fn(async () => undefined);
360
- const platformInstance = createPlatformRunner({
361
- restartApp,
362
- createAppMonitor: () => appMonitor.appMonitor,
363
- });
364
- const metroInstance = createMetroInstance();
365
- mocks.getBridgeServer.mockResolvedValue(serverBridge);
366
- mocks.getMetroInstance.mockResolvedValue(metroInstance);
367
- mocks.waitForMetroBackedAppReady.mockImplementationOnce(async (options) => {
368
- await options.startAttempt();
369
- const readyPromise = options.waitForReady(new AbortController().signal);
370
- emitReady();
371
- await readyPromise;
372
- });
373
- globalThis.__HARNESS_PLATFORM_RUNNER__ = vi.fn(async () => platformInstance);
374
- const platform = {
375
- config: {
376
- appLaunchOptions: {
377
- extras: {
378
- source: 'ensure-ready',
379
- },
380
- },
381
- },
382
- name: 'ios',
383
- platformId: 'ios',
384
- runner: `data:text/javascript,${encodeURIComponent('export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);')}`,
385
- getResourceLockKey: () => 'ios:simulator:iPhone 17 Pro:26.2',
386
- };
387
- const harness = await getHarness(createHarnessConfig({
388
- bridgeTimeout: 1,
389
- }), platform, '/tmp/project');
390
- await harness.ensureAppReady('/tmp/example.harness.ts');
391
- expect(mocks.waitForMetroBackedAppReady).toHaveBeenCalledTimes(1);
392
- expect(restartApp).toHaveBeenCalledWith({
393
- extras: {
394
- source: 'ensure-ready',
395
- },
396
- });
397
- await harness.dispose();
398
- });
399
- it('routes restart(testFilePath) through the shared Metro startup helper', async () => {
400
- const { serverBridge, emitReady } = createBridgeServer();
401
- const appMonitor = createAppMonitor();
402
- const restartApp = vi.fn(async () => undefined);
403
- const stopApp = vi.fn(async () => undefined);
404
- const platformInstance = createPlatformRunner({
405
- restartApp,
406
- stopApp,
407
- isAppRunning: vi.fn(async () => false),
408
- createAppMonitor: () => appMonitor.appMonitor,
409
- });
410
- const metroInstance = createMetroInstance();
411
- mocks.getBridgeServer.mockResolvedValue(serverBridge);
412
- mocks.getMetroInstance.mockResolvedValue(metroInstance);
413
- mocks.waitForMetroBackedAppReady.mockImplementationOnce(async (options) => {
414
- await options.startAttempt();
415
- const readyPromise = options.waitForReady(new AbortController().signal);
416
- emitReady();
417
- await readyPromise;
418
- });
419
- globalThis.__HARNESS_PLATFORM_RUNNER__ = vi.fn(async () => platformInstance);
420
- const platform = {
421
- config: {
422
- appLaunchOptions: {
423
- extras: {
424
- source: 'restart',
425
- },
426
- },
427
- },
428
- name: 'ios',
429
- platformId: 'ios',
430
- runner: `data:text/javascript,${encodeURIComponent('export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);')}`,
431
- getResourceLockKey: () => 'ios:simulator:iPhone 17 Pro:26.2',
432
- };
433
- const harness = await getHarness(createHarnessConfig(), platform, '/tmp/project');
434
- expect(mocks.getBridgeServer).toHaveBeenCalledWith(expect.objectContaining({
435
- noServer: true,
436
- }));
437
- expect(mocks.getMetroInstance).toHaveBeenCalledWith(expect.objectContaining({
438
- websocketEndpoints: {
439
- [HARNESS_BRIDGE_PATH]: serverBridge.ws,
440
- },
441
- }), expect.any(AbortSignal));
442
- await harness.restart('/tmp/restart.harness.ts');
443
- expect(stopApp).toHaveBeenCalledTimes(1);
444
- expect(mocks.waitForMetroBackedAppReady).toHaveBeenCalledTimes(1);
445
- expect(restartApp).toHaveBeenCalledWith({
446
- extras: {
447
- source: 'restart',
448
- },
449
- });
450
- await harness.dispose();
451
- });
452
- });
453
- describe('plugins', () => {
454
- it('invokes lifecycle and runtime plugin handlers for configured plugins', async () => {
455
- const { serverBridge, emitReady, emitEvent, emitDisconnect } = createBridgeServer();
456
- const appMonitor = createAppMonitor();
457
- const platformInstance = createPlatformRunner({
458
- createAppMonitor: () => appMonitor.appMonitor,
459
- });
460
- const observedHooks = [];
461
- mocks.getBridgeServer.mockResolvedValue(serverBridge);
462
- const metroInstance = createMetroInstance();
463
- mocks.getMetroInstance.mockResolvedValue(metroInstance);
464
- globalThis.__HARNESS_PLATFORM_RUNNER__ = vi.fn(async () => platformInstance);
465
- const plugin = definePlugin({
466
- name: 'test-plugin',
467
- createState: () => ({
468
- creationCount: 0,
469
- }),
470
- hooks: {
471
- harness: {
472
- beforeCreation: (ctx) => {
473
- ctx.state.creationCount += 1;
474
- observedHooks.push(`beforeCreation:${ctx.platform.platformId}:${ctx.appLaunchOptions == null
475
- ? 'no-launch-options'
476
- : 'launch-options'}`);
477
- },
478
- beforeRun: (ctx) => {
479
- observedHooks.push(`beforeRun:${ctx.platform.platformId}:${ctx.appLaunchOptions == null
480
- ? 'no-launch-options'
481
- : 'launch-options'}`);
482
- },
483
- afterRun: (ctx) => {
484
- observedHooks.push(`afterRun:${ctx.state.creationCount}:${ctx.reason}`);
485
- },
486
- beforeDispose: (ctx) => {
487
- observedHooks.push(`beforeDispose:${ctx.state.creationCount}:${ctx.reason}`);
488
- },
489
- },
490
- runtime: {
491
- ready: (ctx) => {
492
- observedHooks.push(`runtime.ready:${ctx.runId}:${ctx.device.platform}`);
493
- },
494
- disconnected: (ctx) => {
495
- observedHooks.push(`runtime.disconnected:${ctx.reason}`);
496
- },
497
- },
498
- collection: {
499
- started: (ctx) => {
500
- observedHooks.push(`collection.started:${ctx.file}`);
501
- },
502
- },
503
- },
504
- });
505
- const platform = {
506
- config: {
507
- appLaunchOptions: {
508
- environment: {
509
- MODE: 'test',
510
- },
511
- },
512
- },
513
- name: 'ios',
514
- platformId: 'ios',
515
- runner: `data:text/javascript,${encodeURIComponent('export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);')}`,
516
- getResourceLockKey: () => 'ios:simulator:iPhone 17 Pro:26.2',
517
- };
518
- const harness = await getHarness(createHarnessConfig({
519
- plugins: [plugin],
520
- }), platform, '/tmp/project');
521
- harness.setRunState({
522
- runId: 'run-1',
523
- startTime: Date.now(),
524
- testFiles: ['example.harness.ts'],
525
- watchMode: false,
526
- coverageEnabled: false,
527
- summary: {
528
- passed: 1,
529
- failed: 0,
530
- skipped: 0,
531
- todo: 0,
532
- },
533
- status: 'passed',
534
- });
535
- emitReady();
536
- emitEvent({
537
- type: 'collection-started',
538
- file: 'example.harness.ts',
539
- });
540
- emitDisconnect();
541
- await harness.dispose();
542
- expect(observedHooks).toEqual([
543
- 'beforeCreation:ios:launch-options',
544
- 'beforeRun:ios:launch-options',
545
- 'runtime.ready:run-1:ios',
546
- 'collection.started:example.harness.ts',
547
- 'runtime.disconnected:bridge-disconnected',
548
- 'afterRun:1:normal',
549
- 'beforeDispose:1:normal',
550
- ]);
551
- });
552
- it('disposes lifecycle hooks and platform resources only once', async () => {
553
- const { serverBridge } = createBridgeServer();
554
- const platformInstance = createPlatformRunner();
555
- const observedHooks = [];
556
- mocks.getBridgeServer.mockResolvedValue(serverBridge);
557
- mocks.getMetroInstance.mockResolvedValue(createMetroInstance());
558
- globalThis.__HARNESS_PLATFORM_RUNNER__ = vi.fn(async () => platformInstance);
559
- const plugin = definePlugin({
560
- name: 'dispose-plugin',
561
- hooks: {
562
- harness: {
563
- afterRun: () => {
564
- observedHooks.push('afterRun');
565
- },
566
- beforeDispose: () => {
567
- observedHooks.push('beforeDispose');
568
- },
569
- },
570
- },
571
- });
572
- const platform = {
573
- config: {},
574
- name: 'ios',
575
- platformId: 'ios',
576
- runner: `data:text/javascript,${encodeURIComponent('export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);')}`,
577
- getResourceLockKey: () => 'ios:simulator:iPhone 17 Pro:26.2',
578
- };
579
- const harness = await getHarness(createHarnessConfig({
580
- plugins: [plugin],
581
- }), platform, '/tmp/project');
582
- await harness.dispose();
583
- await harness.dispose();
584
- expect(observedHooks).toEqual(['afterRun', 'beforeDispose']);
585
- expect(platformInstance.dispose).toHaveBeenCalledTimes(1);
586
- });
587
- it('waits in queue before starting Metro and releases the lock on dispose', async () => {
588
- const resourceKey = 'ios:simulator:iPhone 17 Pro:26.2';
589
- const firstPlatformRunner = createPlatformRunner();
590
- const secondPlatformRunner = createPlatformRunner();
591
- const secondAppMonitor = createAppMonitor();
592
- const firstMetroInstance = createMetroInstance();
593
- const secondMetroInstance = createMetroInstance();
594
- const firstBridge = createBridgeServer();
595
- const secondBridge = createBridgeServer();
596
- mocks.getBridgeServer
597
- .mockResolvedValueOnce(firstBridge.serverBridge)
598
- .mockResolvedValueOnce(secondBridge.serverBridge);
599
- mocks.getMetroInstance
600
- .mockResolvedValueOnce(firstMetroInstance)
601
- .mockResolvedValueOnce(secondMetroInstance);
602
- let invocationCount = 0;
603
- globalThis.__HARNESS_PLATFORM_RUNNER__ = vi.fn(async () => {
604
- invocationCount += 1;
605
- return invocationCount === 1
606
- ? firstPlatformRunner
607
- : createPlatformRunner({
608
- createAppMonitor: () => secondAppMonitor.appMonitor,
609
- dispose: secondPlatformRunner.dispose,
610
- });
611
- });
612
- const platform = {
613
- config: {},
614
- name: 'ios',
615
- platformId: 'ios',
616
- runner: `data:text/javascript,${encodeURIComponent('export default (...args) => globalThis.__HARNESS_PLATFORM_RUNNER__(...args);')}`,
617
- getResourceLockKey: () => resourceKey,
618
- };
619
- const firstHarness = await getHarness(createHarnessConfig(), platform, '/tmp/project');
620
- const secondHarnessPromise = getHarness(createHarnessConfig(), platform, '/tmp/project');
621
- await new Promise((resolve) => setTimeout(resolve, 1100));
622
- expect(mocks.logRunnerWaitingInQueue).toHaveBeenCalledWith(platform);
623
- expect(mocks.logRunnerStarting).not.toHaveBeenCalled();
624
- expect(mocks.getMetroInstance).toHaveBeenCalledTimes(1);
625
- await firstHarness.dispose();
626
- const secondHarness = await secondHarnessPromise;
627
- expect(mocks.logRunnerStarting).toHaveBeenCalledWith(platform);
628
- expect(mocks.getMetroInstance).toHaveBeenCalledTimes(2);
629
- await secondHarness.dispose();
630
- });
631
- });
632
- describe('StartupStallError', () => {
633
- it('includes the configured timeout and attempt count', () => {
634
- expect(new StartupStallError(1_500, 4).message).toBe('The app did not request its Metro bundle after 4 launch attempts within 1500ms. Last Metro status: unknown.');
635
- });
636
- });
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=hook-queue.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hook-queue.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/hook-queue.test.ts"],"names":[],"mappings":""}