@react-native-harness/runtime 1.2.0-rc.1 → 1.3.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 (99) hide show
  1. package/dist/client/factory.d.ts +2 -1
  2. package/dist/client/factory.d.ts.map +1 -1
  3. package/dist/client/factory.js +53 -59
  4. package/dist/client/store.d.ts +3 -3
  5. package/dist/client/store.d.ts.map +1 -1
  6. package/dist/client/store.js +7 -7
  7. package/dist/collector/functions.d.ts +3 -3
  8. package/dist/collector/functions.d.ts.map +1 -1
  9. package/dist/collector/types.d.ts +3 -2
  10. package/dist/collector/types.d.ts.map +1 -1
  11. package/dist/collector/validation.d.ts +2 -2
  12. package/dist/collector/validation.d.ts.map +1 -1
  13. package/dist/device/index.d.ts +12 -0
  14. package/dist/device/index.d.ts.map +1 -0
  15. package/dist/device/index.js +62 -0
  16. package/dist/expect/matchers/toMatchImageSnapshot.d.ts +1 -1
  17. package/dist/expect/matchers/toMatchImageSnapshot.d.ts.map +1 -1
  18. package/dist/expect/matchers/toMatchImageSnapshot.js +4 -12
  19. package/dist/hmr.d.ts +2 -0
  20. package/dist/hmr.d.ts.map +1 -0
  21. package/dist/hmr.js +5 -0
  22. package/dist/initialize.js +14 -5
  23. package/dist/jsx/jsx-dev-runtime.d.ts +2 -1
  24. package/dist/jsx/jsx-dev-runtime.d.ts.map +1 -1
  25. package/dist/jsx/jsx-dev-runtime.js +16 -7
  26. package/dist/logbox.d.ts +4 -0
  27. package/dist/logbox.d.ts.map +1 -0
  28. package/dist/logbox.js +18 -0
  29. package/dist/runner/hooks.d.ts +2 -1
  30. package/dist/runner/hooks.d.ts.map +1 -1
  31. package/dist/runner/hooks.js +27 -17
  32. package/dist/runner/runSuite.d.ts.map +1 -1
  33. package/dist/runner/runSuite.js +56 -6
  34. package/dist/runner/test-context.d.ts +16 -0
  35. package/dist/runner/test-context.d.ts.map +1 -0
  36. package/dist/runner/test-context.js +57 -0
  37. package/dist/runner/types.d.ts +2 -1
  38. package/dist/runner/types.d.ts.map +1 -1
  39. package/dist/test-utils/react-native-url-polyfill.d.ts +9 -0
  40. package/dist/test-utils/react-native-url-polyfill.d.ts.map +1 -0
  41. package/dist/test-utils/react-native-url-polyfill.js +1 -0
  42. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  43. package/dist/waitFor.d.ts.map +1 -1
  44. package/dist/waitFor.js +5 -3
  45. package/out-tsc/vitest/src/__tests__/device.test.d.ts +2 -0
  46. package/out-tsc/vitest/src/__tests__/device.test.d.ts.map +1 -0
  47. package/out-tsc/vitest/src/__tests__/logbox.test.d.ts +2 -0
  48. package/out-tsc/vitest/src/__tests__/logbox.test.d.ts.map +1 -0
  49. package/out-tsc/vitest/src/__tests__/runner-context.test.d.ts +2 -0
  50. package/out-tsc/vitest/src/__tests__/runner-context.test.d.ts.map +1 -0
  51. package/out-tsc/vitest/src/client/factory.d.ts +2 -1
  52. package/out-tsc/vitest/src/client/factory.d.ts.map +1 -1
  53. package/out-tsc/vitest/src/client/store.d.ts +3 -3
  54. package/out-tsc/vitest/src/client/store.d.ts.map +1 -1
  55. package/out-tsc/vitest/src/collector/functions.d.ts +3 -3
  56. package/out-tsc/vitest/src/collector/functions.d.ts.map +1 -1
  57. package/out-tsc/vitest/src/collector/types.d.ts +3 -2
  58. package/out-tsc/vitest/src/collector/types.d.ts.map +1 -1
  59. package/out-tsc/vitest/src/collector/validation.d.ts +2 -2
  60. package/out-tsc/vitest/src/collector/validation.d.ts.map +1 -1
  61. package/out-tsc/vitest/src/device/index.d.ts +12 -0
  62. package/out-tsc/vitest/src/device/index.d.ts.map +1 -0
  63. package/out-tsc/vitest/src/expect/matchers/toMatchImageSnapshot.d.ts +1 -1
  64. package/out-tsc/vitest/src/expect/matchers/toMatchImageSnapshot.d.ts.map +1 -1
  65. package/out-tsc/vitest/src/hmr.d.ts +2 -0
  66. package/out-tsc/vitest/src/hmr.d.ts.map +1 -0
  67. package/out-tsc/vitest/src/jsx/jsx-dev-runtime.d.ts +2 -1
  68. package/out-tsc/vitest/src/jsx/jsx-dev-runtime.d.ts.map +1 -1
  69. package/out-tsc/vitest/src/logbox.d.ts +4 -0
  70. package/out-tsc/vitest/src/logbox.d.ts.map +1 -0
  71. package/out-tsc/vitest/src/runner/hooks.d.ts +2 -1
  72. package/out-tsc/vitest/src/runner/hooks.d.ts.map +1 -1
  73. package/out-tsc/vitest/src/runner/runSuite.d.ts.map +1 -1
  74. package/out-tsc/vitest/src/runner/test-context.d.ts +16 -0
  75. package/out-tsc/vitest/src/runner/test-context.d.ts.map +1 -0
  76. package/out-tsc/vitest/src/runner/types.d.ts +2 -1
  77. package/out-tsc/vitest/src/runner/types.d.ts.map +1 -1
  78. package/out-tsc/vitest/src/test-utils/react-native-url-polyfill.d.ts +9 -0
  79. package/out-tsc/vitest/src/test-utils/react-native-url-polyfill.d.ts.map +1 -0
  80. package/out-tsc/vitest/src/waitFor.d.ts.map +1 -1
  81. package/out-tsc/vitest/tsconfig.spec.tsbuildinfo +1 -1
  82. package/out-tsc/vitest/vite.config.d.ts.map +1 -1
  83. package/package.json +2 -2
  84. package/src/__tests__/runner-context.test.ts +483 -0
  85. package/src/client/factory.ts +63 -74
  86. package/src/client/store.ts +8 -8
  87. package/src/collector/functions.ts +5 -4
  88. package/src/collector/types.ts +4 -1
  89. package/src/collector/validation.ts +2 -2
  90. package/src/expect/matchers/toMatchImageSnapshot.ts +9 -23
  91. package/src/initialize.ts +14 -5
  92. package/src/jsx/jsx-dev-runtime.ts +34 -15
  93. package/src/runner/hooks.ts +43 -19
  94. package/src/runner/runSuite.ts +75 -9
  95. package/src/runner/test-context.ts +84 -0
  96. package/src/runner/types.ts +3 -0
  97. package/src/test-utils/react-native-url-polyfill.ts +1 -0
  98. package/src/waitFor.ts +8 -6
  99. package/vite.config.ts +4 -0
@@ -1 +1 @@
1
- {"version":3,"file":"vite.config.d.ts","sourceRoot":"","sources":["../../vite.config.ts"],"names":[],"mappings":";AAIA,wBAsBI"}
1
+ {"version":3,"file":"vite.config.d.ts","sourceRoot":"","sources":["../../vite.config.ts"],"names":[],"mappings":";AAIA,wBA0BI"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@react-native-harness/runtime",
3
3
  "description": "The core test runtime that executes on React Native devices, providing Jest-compatible APIs (describe, it, expect) and managing test collection, execution, and result reporting in native environments.",
4
- "version": "1.2.0-rc.1",
4
+ "version": "1.3.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
7
7
  "module": "./dist/index.js",
@@ -47,7 +47,7 @@
47
47
  "react-native-url-polyfill": "^3.0.0",
48
48
  "use-sync-external-store": "^1.6.0",
49
49
  "zustand": "^5.0.5",
50
- "@react-native-harness/bridge": "1.2.0-rc.1"
50
+ "@react-native-harness/bridge": "1.3.0"
51
51
  },
52
52
  "devDependencies": {
53
53
  "@types/chai": "^5.2.2"
@@ -0,0 +1,483 @@
1
+ import {
2
+ afterEach,
3
+ beforeEach,
4
+ describe as harnessDescribe,
5
+ getTestCollector,
6
+ it as harnessIt,
7
+ } from '../collector/index.js';
8
+ import type { HarnessTestContext } from '@react-native-harness/bridge';
9
+ import { getTestRunner } from '../runner/index.js';
10
+ import { describe, expect, it, vi } from 'vitest';
11
+
12
+ vi.mock('../symbolicate.js', async () => {
13
+ return {
14
+ getCodeFrame: vi.fn(async () => null),
15
+ };
16
+ });
17
+
18
+ const getTask = (context: HarnessTestContext) => {
19
+ return context.task;
20
+ };
21
+
22
+ const getTaskContext = (context: HarnessTestContext) => {
23
+ return context;
24
+ };
25
+
26
+ describe('runner task context', () => {
27
+ it('passes minimal task metadata to tests and per-test hooks', async () => {
28
+ const observedTasks: Array<{
29
+ source: 'beforeEach' | 'test' | 'afterEach';
30
+ task: {
31
+ name: string;
32
+ type: 'test';
33
+ mode: 'run' | 'skip' | 'todo';
34
+ file: { name: string };
35
+ suite: { name: string };
36
+ };
37
+ }> = [];
38
+ const collector = getTestCollector();
39
+ const runner = getTestRunner();
40
+
41
+ try {
42
+ const collection = await collector.collect(() => {
43
+ harnessDescribe('Task Context Suite', () => {
44
+ beforeEach((context: HarnessTestContext) => {
45
+ observedTasks.push({ source: 'beforeEach', task: getTask(context) });
46
+ });
47
+
48
+ afterEach((context: HarnessTestContext) => {
49
+ observedTasks.push({ source: 'afterEach', task: getTask(context) });
50
+ });
51
+
52
+ harnessIt('exposes task metadata', (context: HarnessTestContext) => {
53
+ observedTasks.push({ source: 'test', task: getTask(context) });
54
+ });
55
+ });
56
+ }, 'runtime/context.test.ts');
57
+
58
+ const result = await runner.run({
59
+ testSuite: collection.testSuite,
60
+ testFilePath: 'runtime/context.test.ts',
61
+ runner: 'ios',
62
+ });
63
+
64
+ expect(result.status).toBe('passed');
65
+ expect(result.suites[0].tests[0]).toMatchObject({
66
+ name: 'exposes task metadata',
67
+ status: 'passed',
68
+ });
69
+ expect(observedTasks).toEqual([
70
+ {
71
+ source: 'beforeEach',
72
+ task: {
73
+ name: 'exposes task metadata',
74
+ type: 'test',
75
+ mode: 'run',
76
+ file: { name: 'runtime/context.test.ts' },
77
+ suite: { name: 'Task Context Suite' },
78
+ },
79
+ },
80
+ {
81
+ source: 'test',
82
+ task: {
83
+ name: 'exposes task metadata',
84
+ type: 'test',
85
+ mode: 'run',
86
+ file: { name: 'runtime/context.test.ts' },
87
+ suite: { name: 'Task Context Suite' },
88
+ },
89
+ },
90
+ {
91
+ source: 'afterEach',
92
+ task: {
93
+ name: 'exposes task metadata',
94
+ type: 'test',
95
+ mode: 'run',
96
+ file: { name: 'runtime/context.test.ts' },
97
+ suite: { name: 'Task Context Suite' },
98
+ },
99
+ },
100
+ ]);
101
+ } finally {
102
+ collector.dispose();
103
+ runner.dispose();
104
+ }
105
+ });
106
+
107
+ it('keeps zero-argument tests and hooks working', async () => {
108
+ const calls: string[] = [];
109
+ const collector = getTestCollector();
110
+ const runner = getTestRunner();
111
+
112
+ try {
113
+ const collection = await collector.collect(() => {
114
+ harnessDescribe('Compatibility Suite', () => {
115
+ beforeEach(() => {
116
+ calls.push('beforeEach');
117
+ });
118
+
119
+ afterEach(() => {
120
+ calls.push('afterEach');
121
+ });
122
+
123
+ harnessIt('still runs', () => {
124
+ calls.push('test');
125
+ });
126
+ });
127
+ }, 'runtime/compatibility.test.ts');
128
+
129
+ const result = await runner.run({
130
+ testSuite: collection.testSuite,
131
+ testFilePath: 'runtime/compatibility.test.ts',
132
+ runner: 'android',
133
+ });
134
+
135
+ expect(result.suites[0].tests[0]).toMatchObject({ status: 'passed' });
136
+ expect(calls).toEqual(['beforeEach', 'test', 'afterEach']);
137
+ } finally {
138
+ collector.dispose();
139
+ runner.dispose();
140
+ }
141
+ });
142
+
143
+ it('marks dynamically skipped tests as skipped and still runs afterEach', async () => {
144
+ const calls: string[] = [];
145
+ const collector = getTestCollector();
146
+ const runner = getTestRunner();
147
+
148
+ try {
149
+ const collection = await collector.collect(() => {
150
+ harnessDescribe('Skip Suite', () => {
151
+ afterEach(() => {
152
+ calls.push('afterEach');
153
+ });
154
+
155
+ harnessIt('skips from context', (context: HarnessTestContext) => {
156
+ const { skip } = getTaskContext(context);
157
+
158
+ calls.push('before-skip');
159
+ skip('skip this test');
160
+ calls.push('after-skip');
161
+ });
162
+
163
+ harnessIt('still runs sibling test', () => {
164
+ calls.push('sibling');
165
+ });
166
+ });
167
+ }, 'runtime/skip.test.ts');
168
+
169
+ const result = await runner.run({
170
+ testSuite: collection.testSuite,
171
+ testFilePath: 'runtime/skip.test.ts',
172
+ runner: 'ios',
173
+ });
174
+
175
+ expect(result.suites[0].tests).toMatchObject([
176
+ { name: 'skips from context', status: 'skipped' },
177
+ { name: 'still runs sibling test', status: 'passed' },
178
+ ]);
179
+ expect(calls).toEqual([
180
+ 'before-skip',
181
+ 'afterEach',
182
+ 'sibling',
183
+ 'afterEach',
184
+ ]);
185
+ } finally {
186
+ collector.dispose();
187
+ runner.dispose();
188
+ }
189
+ });
190
+
191
+ it('supports conditional skipping without changing false conditions', async () => {
192
+ const calls: string[] = [];
193
+ const collector = getTestCollector();
194
+ const runner = getTestRunner();
195
+
196
+ try {
197
+ const collection = await collector.collect(() => {
198
+ harnessDescribe('Conditional Skip Suite', () => {
199
+ harnessIt('continues when condition is false', (context: HarnessTestContext) => {
200
+ const { skip } = getTaskContext(context);
201
+
202
+ calls.push('before');
203
+ skip(false, 'do not skip');
204
+ calls.push('after');
205
+ });
206
+ });
207
+ }, 'runtime/conditional-skip.test.ts');
208
+
209
+ const result = await runner.run({
210
+ testSuite: collection.testSuite,
211
+ testFilePath: 'runtime/conditional-skip.test.ts',
212
+ runner: 'android',
213
+ });
214
+
215
+ expect(result.suites[0].tests[0]).toMatchObject({ status: 'passed' });
216
+ expect(calls).toEqual(['before', 'after']);
217
+ } finally {
218
+ collector.dispose();
219
+ runner.dispose();
220
+ }
221
+ });
222
+
223
+ it('runs onTestFinished after afterEach for passing tests', async () => {
224
+ const calls: string[] = [];
225
+ const collector = getTestCollector();
226
+ const runner = getTestRunner();
227
+
228
+ try {
229
+ const collection = await collector.collect(() => {
230
+ harnessDescribe('Finished Suite', () => {
231
+ afterEach(() => {
232
+ calls.push('afterEach');
233
+ });
234
+
235
+ harnessIt('runs finished callbacks', (context: HarnessTestContext) => {
236
+ const { onTestFinished } = getTaskContext(context);
237
+
238
+ onTestFinished(() => {
239
+ calls.push('onTestFinished:first');
240
+ });
241
+ onTestFinished(() => {
242
+ calls.push('onTestFinished:second');
243
+ });
244
+
245
+ calls.push('test');
246
+ });
247
+ });
248
+ }, 'runtime/on-test-finished-pass.test.ts');
249
+
250
+ const result = await runner.run({
251
+ testSuite: collection.testSuite,
252
+ testFilePath: 'runtime/on-test-finished-pass.test.ts',
253
+ runner: 'ios',
254
+ });
255
+
256
+ expect(result.suites[0].tests[0]).toMatchObject({ status: 'passed' });
257
+ expect(calls).toEqual([
258
+ 'test',
259
+ 'afterEach',
260
+ 'onTestFinished:second',
261
+ 'onTestFinished:first',
262
+ ]);
263
+ } finally {
264
+ collector.dispose();
265
+ runner.dispose();
266
+ }
267
+ });
268
+
269
+ it('runs onTestFinished for dynamically skipped tests', async () => {
270
+ const calls: string[] = [];
271
+ const collector = getTestCollector();
272
+ const runner = getTestRunner();
273
+
274
+ try {
275
+ const collection = await collector.collect(() => {
276
+ harnessDescribe('Finished Skip Suite', () => {
277
+ afterEach(() => {
278
+ calls.push('afterEach');
279
+ });
280
+
281
+ harnessIt(
282
+ 'runs finished callback after skip',
283
+ (context: HarnessTestContext) => {
284
+ const { onTestFinished, skip } = getTaskContext(context);
285
+
286
+ onTestFinished(() => {
287
+ calls.push('onTestFinished');
288
+ });
289
+
290
+ calls.push('before-skip');
291
+ skip();
292
+ },
293
+ );
294
+ });
295
+ }, 'runtime/on-test-finished-skip.test.ts');
296
+
297
+ const result = await runner.run({
298
+ testSuite: collection.testSuite,
299
+ testFilePath: 'runtime/on-test-finished-skip.test.ts',
300
+ runner: 'android',
301
+ });
302
+
303
+ expect(result.suites[0].tests[0]).toMatchObject({ status: 'skipped' });
304
+ expect(calls).toEqual(['before-skip', 'afterEach', 'onTestFinished']);
305
+ } finally {
306
+ collector.dispose();
307
+ runner.dispose();
308
+ }
309
+ });
310
+
311
+ it('runs onTestFinished for failed tests', async () => {
312
+ const calls: string[] = [];
313
+ const collector = getTestCollector();
314
+ const runner = getTestRunner();
315
+
316
+ try {
317
+ const collection = await collector.collect(() => {
318
+ harnessDescribe('Finished Failure Suite', () => {
319
+ afterEach(() => {
320
+ calls.push('afterEach');
321
+ });
322
+
323
+ harnessIt(
324
+ 'runs finished callback after failure',
325
+ (context: HarnessTestContext) => {
326
+ const { onTestFinished } = getTaskContext(context);
327
+
328
+ onTestFinished(() => {
329
+ calls.push('onTestFinished');
330
+ });
331
+
332
+ calls.push('test');
333
+ throw new Error('expected failure');
334
+ },
335
+ );
336
+ });
337
+ }, 'runtime/on-test-finished-failure.test.ts');
338
+
339
+ const result = await runner.run({
340
+ testSuite: collection.testSuite,
341
+ testFilePath: 'runtime/on-test-finished-failure.test.ts',
342
+ runner: 'ios',
343
+ });
344
+
345
+ expect(result.suites[0].tests[0]).toMatchObject({ status: 'failed' });
346
+ expect(calls).toEqual(['test', 'afterEach', 'onTestFinished']);
347
+ } finally {
348
+ collector.dispose();
349
+ runner.dispose();
350
+ }
351
+ });
352
+
353
+ it('runs onTestFailed after afterEach for failed tests', async () => {
354
+ const calls: string[] = [];
355
+ const collector = getTestCollector();
356
+ const runner = getTestRunner();
357
+
358
+ try {
359
+ const collection = await collector.collect(() => {
360
+ harnessDescribe('Failed Hook Suite', () => {
361
+ afterEach(() => {
362
+ calls.push('afterEach');
363
+ });
364
+
365
+ harnessIt('runs failed callbacks', (context: HarnessTestContext) => {
366
+ const { onTestFailed } = getTaskContext(context);
367
+
368
+ onTestFailed(() => {
369
+ calls.push('onTestFailed:first');
370
+ });
371
+ onTestFailed(() => {
372
+ calls.push('onTestFailed:second');
373
+ });
374
+
375
+ calls.push('test');
376
+ throw new Error('expected failure');
377
+ });
378
+ });
379
+ }, 'runtime/on-test-failed-failure.test.ts');
380
+
381
+ const result = await runner.run({
382
+ testSuite: collection.testSuite,
383
+ testFilePath: 'runtime/on-test-failed-failure.test.ts',
384
+ runner: 'ios',
385
+ });
386
+
387
+ expect(result.suites[0].tests[0]).toMatchObject({ status: 'failed' });
388
+ expect(calls).toEqual([
389
+ 'test',
390
+ 'afterEach',
391
+ 'onTestFailed:second',
392
+ 'onTestFailed:first',
393
+ ]);
394
+ } finally {
395
+ collector.dispose();
396
+ runner.dispose();
397
+ }
398
+ });
399
+
400
+ it('does not run onTestFailed for dynamically skipped tests', async () => {
401
+ const calls: string[] = [];
402
+ const collector = getTestCollector();
403
+ const runner = getTestRunner();
404
+
405
+ try {
406
+ const collection = await collector.collect(() => {
407
+ harnessDescribe('Failed Skip Suite', () => {
408
+ afterEach(() => {
409
+ calls.push('afterEach');
410
+ });
411
+
412
+ harnessIt(
413
+ 'does not run failed callbacks on skip',
414
+ (context: HarnessTestContext) => {
415
+ const { onTestFailed, skip } = getTaskContext(context);
416
+
417
+ onTestFailed(() => {
418
+ calls.push('onTestFailed');
419
+ });
420
+
421
+ calls.push('before-skip');
422
+ skip();
423
+ },
424
+ );
425
+ });
426
+ }, 'runtime/on-test-failed-skip.test.ts');
427
+
428
+ const result = await runner.run({
429
+ testSuite: collection.testSuite,
430
+ testFilePath: 'runtime/on-test-failed-skip.test.ts',
431
+ runner: 'android',
432
+ });
433
+
434
+ expect(result.suites[0].tests[0]).toMatchObject({ status: 'skipped' });
435
+ expect(calls).toEqual(['before-skip', 'afterEach']);
436
+ } finally {
437
+ collector.dispose();
438
+ runner.dispose();
439
+ }
440
+ });
441
+
442
+ it('runs onTestFailed when afterEach fails', async () => {
443
+ const calls: string[] = [];
444
+ const collector = getTestCollector();
445
+ const runner = getTestRunner();
446
+
447
+ try {
448
+ const collection = await collector.collect(() => {
449
+ harnessDescribe('Failed AfterEach Suite', () => {
450
+ afterEach(() => {
451
+ calls.push('afterEach');
452
+ throw new Error('afterEach failure');
453
+ });
454
+
455
+ harnessIt(
456
+ 'runs failed callback after afterEach failure',
457
+ (context: HarnessTestContext) => {
458
+ const { onTestFailed } = getTaskContext(context);
459
+
460
+ onTestFailed(() => {
461
+ calls.push('onTestFailed');
462
+ });
463
+
464
+ calls.push('test');
465
+ },
466
+ );
467
+ });
468
+ }, 'runtime/on-test-failed-after-each.test.ts');
469
+
470
+ const result = await runner.run({
471
+ testSuite: collection.testSuite,
472
+ testFilePath: 'runtime/on-test-failed-after-each.test.ts',
473
+ runner: 'ios',
474
+ });
475
+
476
+ expect(result.suites[0].tests[0]).toMatchObject({ status: 'failed' });
477
+ expect(calls).toEqual(['test', 'afterEach', 'onTestFailed']);
478
+ } finally {
479
+ collector.dispose();
480
+ runner.dispose();
481
+ }
482
+ });
483
+ });
@@ -4,7 +4,7 @@ import type {
4
4
  BundlerEvents,
5
5
  TestExecutionOptions,
6
6
  } from '@react-native-harness/bridge';
7
- import { getBridgeClient } from '@react-native-harness/bridge/client';
7
+ import { connectToHarness, type HarnessHandle } from '@react-native-harness/bridge/client';
8
8
  import { store } from '../ui/state.js';
9
9
  import { getTestRunner, TestRunner } from '../runner/index.js';
10
10
  import { getTestCollector, TestCollector } from '../collector/index.js';
@@ -14,92 +14,81 @@ import { getBundler, evaluateModule, Bundler } from '../bundler/index.js';
14
14
  import { markTestsAsSkippedByName } from '../filtering/index.js';
15
15
  import { setup } from '../render/setup.js';
16
16
  import { runSetupFiles } from './setup-files.js';
17
- import { setClient } from './store.js';
17
+ import { setHandle } from './store.js';
18
18
 
19
- export const getClient = async () => {
20
- const client = await getBridgeClient(getWSServer(), {
21
- runTests: async () => {
22
- throw new Error('Not implemented');
23
- },
24
- });
25
-
26
- setClient(client);
27
-
28
- client.rpc.$functions.runTests = async (
29
- path: string,
30
- options: TestExecutionOptions
31
- ) => {
32
- if (store.getState().status === 'running') {
33
- throw new Error('Already running tests');
34
- }
35
-
36
- store.getState().setStatus('running');
19
+ export const getClient = async (): Promise<HarnessHandle> => {
20
+ const handle = await connectToHarness(getWSServer(), {
21
+ runTests: async (path: string, options: TestExecutionOptions) => {
22
+ if (store.getState().status === 'running') {
23
+ throw new Error('Already running tests');
24
+ }
37
25
 
38
- let collector: TestCollector | null = null;
39
- let runner: TestRunner | null = null;
40
- let events: EventEmitter<
41
- TestRunnerEvents | TestCollectorEvents | BundlerEvents
42
- > | null = null;
43
- let bundler: Bundler | null = null;
26
+ store.getState().setStatus('running');
44
27
 
45
- try {
46
- collector = getTestCollector();
47
- runner = getTestRunner();
48
- bundler = getBundler();
49
- events = combineEventEmitters(
50
- collector.events,
51
- runner.events,
52
- bundler.events
53
- );
28
+ let collector: TestCollector | null = null;
29
+ let runner: TestRunner | null = null;
30
+ let events: EventEmitter<
31
+ TestRunnerEvents | TestCollectorEvents | BundlerEvents
32
+ > | null = null;
33
+ let bundler: Bundler | null = null;
54
34
 
55
- events.addListener((event) => {
56
- client.rpc.emitEvent(event.type, event);
57
- });
35
+ try {
36
+ collector = getTestCollector();
37
+ runner = getTestRunner();
38
+ bundler = getBundler();
39
+ events = combineEventEmitters(
40
+ collector.events,
41
+ runner.events,
42
+ bundler.events,
43
+ );
58
44
 
59
- await runSetupFiles({
60
- setupFiles: options.setupFiles ?? [],
61
- setupFilesAfterEnv: [],
62
- events: events as EventEmitter<BundlerEvents>,
63
- bundler: bundler as Bundler,
64
- evaluateModule,
65
- });
45
+ events.addListener((event) => {
46
+ handle.emitEvent(event);
47
+ });
66
48
 
67
- const moduleJs = await bundler.getModule(path);
68
- const collectionResult = await collector.collect(async () => {
69
49
  await runSetupFiles({
70
- setupFiles: [],
71
- setupFilesAfterEnv: options.setupFilesAfterEnv ?? [],
50
+ setupFiles: options.setupFiles ?? [],
51
+ setupFilesAfterEnv: [],
72
52
  events: events as EventEmitter<BundlerEvents>,
73
53
  bundler: bundler as Bundler,
74
54
  evaluateModule,
75
55
  });
76
56
 
77
- // Setup automatic cleanup for rendered components
78
- setup();
79
- evaluateModule(moduleJs, path);
80
- }, path);
57
+ const moduleJs = await bundler.getModule(path);
58
+ const collectionResult = await collector.collect(async () => {
59
+ await runSetupFiles({
60
+ setupFiles: [],
61
+ setupFilesAfterEnv: options.setupFilesAfterEnv ?? [],
62
+ events: events as EventEmitter<BundlerEvents>,
63
+ bundler: bundler as Bundler,
64
+ evaluateModule,
65
+ });
66
+
67
+ setup();
68
+ evaluateModule(moduleJs, path);
69
+ }, path);
81
70
 
82
- // Apply test name pattern by marking non-matching tests as skipped
83
- const processedTestSuite = options.testNamePattern
84
- ? markTestsAsSkippedByName(
85
- collectionResult.testSuite,
86
- options.testNamePattern
87
- )
88
- : collectionResult.testSuite;
71
+ const processedTestSuite = options.testNamePattern
72
+ ? markTestsAsSkippedByName(
73
+ collectionResult.testSuite,
74
+ options.testNamePattern,
75
+ )
76
+ : collectionResult.testSuite;
89
77
 
90
- const result = await runner.run({
91
- testSuite: processedTestSuite,
92
- testFilePath: path,
93
- runner: options.runner,
94
- });
95
- return result;
96
- } finally {
97
- collector?.dispose();
98
- runner?.dispose();
99
- events?.clearAllListeners();
100
- store.getState().setStatus('idle');
101
- }
102
- };
78
+ return await runner.run({
79
+ testSuite: processedTestSuite,
80
+ testFilePath: path,
81
+ runner: options.runner,
82
+ });
83
+ } finally {
84
+ collector?.dispose();
85
+ runner?.dispose();
86
+ events?.clearAllListeners();
87
+ store.getState().setStatus('idle');
88
+ }
89
+ },
90
+ });
103
91
 
104
- return client;
92
+ setHandle(handle);
93
+ return handle;
105
94
  };
@@ -1,16 +1,16 @@
1
- import type { BridgeClient } from '@react-native-harness/bridge/client';
1
+ import type { HarnessHandle } from '@react-native-harness/bridge/client';
2
2
 
3
- let clientInstance: BridgeClient | null = null;
3
+ let handle: HarnessHandle | null = null;
4
4
 
5
- export const setClient = (client: BridgeClient): void => {
6
- clientInstance = client;
5
+ export const setHandle = (h: HarnessHandle): void => {
6
+ handle = h;
7
7
  };
8
8
 
9
- export const getClientInstance = (): BridgeClient => {
10
- if (!clientInstance) {
9
+ export const getHandle = (): HarnessHandle => {
10
+ if (!handle) {
11
11
  throw new Error(
12
- 'Bridge client not initialized. This should not happen in normal operation.'
12
+ 'Harness not connected. This should not happen in normal operation.'
13
13
  );
14
14
  }
15
- return clientInstance;
15
+ return handle;
16
16
  };