@react-native-harness/runtime 1.0.0-alpha.9 → 1.0.0-canary.1761729829908

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 (252) hide show
  1. package/README.md +23 -4
  2. package/assets/moduleSystem.flow.js +23 -3
  3. package/dist/bundler/bundle.d.ts.map +1 -1
  4. package/dist/bundler/bundle.js +1 -2
  5. package/dist/bundler/evaluate.d.ts.map +1 -1
  6. package/dist/bundler/evaluate.js +7 -7
  7. package/dist/bundler/factory.d.ts +3 -0
  8. package/dist/bundler/factory.d.ts.map +1 -0
  9. package/dist/bundler/factory.js +36 -0
  10. package/dist/bundler/index.d.ts +2 -1
  11. package/dist/bundler/index.d.ts.map +1 -1
  12. package/dist/bundler/index.js +1 -1
  13. package/dist/bundler/types.d.ts +7 -0
  14. package/dist/bundler/types.d.ts.map +1 -0
  15. package/dist/bundler/types.js +1 -0
  16. package/dist/client/factory.d.ts.map +1 -1
  17. package/dist/client/factory.js +33 -12
  18. package/dist/client/getDeviceDescriptor.d.ts +1 -1
  19. package/dist/client/getDeviceDescriptor.d.ts.map +1 -1
  20. package/dist/client/getDeviceDescriptor.js +18 -6
  21. package/dist/client/setup-files.d.ts +12 -0
  22. package/dist/client/setup-files.d.ts.map +1 -0
  23. package/dist/client/setup-files.js +60 -0
  24. package/dist/collector/functions.d.ts +1 -1
  25. package/dist/collector/functions.d.ts.map +1 -1
  26. package/dist/collector/functions.js +10 -2
  27. package/dist/collector/types.d.ts +1 -1
  28. package/dist/collector/types.d.ts.map +1 -1
  29. package/dist/constants.d.ts +0 -1
  30. package/dist/constants.d.ts.map +1 -1
  31. package/dist/constants.js +0 -1
  32. package/dist/entry-point.d.ts +2 -0
  33. package/dist/entry-point.d.ts.map +1 -0
  34. package/dist/entry-point.js +4 -0
  35. package/dist/expect/index.d.ts.map +1 -1
  36. package/dist/expect/index.js +2 -0
  37. package/dist/expect/setup.js +2 -0
  38. package/dist/filtering/index.d.ts +2 -0
  39. package/dist/filtering/index.d.ts.map +1 -0
  40. package/dist/filtering/index.js +1 -0
  41. package/dist/filtering/testNameFilter.d.ts +12 -0
  42. package/dist/filtering/testNameFilter.d.ts.map +1 -0
  43. package/dist/filtering/testNameFilter.js +56 -0
  44. package/dist/globals.d.ts +5 -2
  45. package/dist/globals.d.ts.map +1 -1
  46. package/dist/globals.js +7 -1
  47. package/dist/index.d.ts +3 -0
  48. package/dist/index.d.ts.map +1 -1
  49. package/dist/index.js +3 -0
  50. package/dist/initialize.js +7 -0
  51. package/dist/jest-mock.d.ts +2 -0
  52. package/dist/jest-mock.d.ts.map +1 -0
  53. package/dist/jest-mock.js +25 -0
  54. package/dist/mocker/index.d.ts +1 -1
  55. package/dist/mocker/index.d.ts.map +1 -1
  56. package/dist/mocker/index.js +1 -1
  57. package/dist/mocker/registry.d.ts +2 -2
  58. package/dist/mocker/registry.d.ts.map +1 -1
  59. package/dist/mocker/registry.js +10 -4
  60. package/dist/namespace.d.ts +18 -0
  61. package/dist/namespace.d.ts.map +1 -0
  62. package/dist/namespace.js +19 -0
  63. package/dist/render/ErrorBoundary.d.ts +17 -0
  64. package/dist/render/ErrorBoundary.d.ts.map +1 -0
  65. package/dist/render/ErrorBoundary.js +73 -0
  66. package/dist/render/TestComponentOverlay.d.ts +3 -0
  67. package/dist/render/TestComponentOverlay.d.ts.map +1 -0
  68. package/dist/render/TestComponentOverlay.js +36 -0
  69. package/dist/render/cleanup.d.ts +2 -0
  70. package/dist/render/cleanup.d.ts.map +1 -0
  71. package/dist/render/cleanup.js +6 -0
  72. package/dist/render/index.d.ts +6 -0
  73. package/dist/render/index.d.ts.map +1 -0
  74. package/dist/render/index.js +66 -0
  75. package/dist/render/setup.d.ts +2 -0
  76. package/dist/render/setup.d.ts.map +1 -0
  77. package/dist/render/setup.js +7 -0
  78. package/dist/render/types.d.ts +12 -0
  79. package/dist/render/types.d.ts.map +1 -0
  80. package/dist/render/types.js +1 -0
  81. package/dist/runner/factory.d.ts.map +1 -1
  82. package/dist/runner/factory.js +6 -1
  83. package/dist/runner/runSuite.d.ts.map +1 -1
  84. package/dist/runner/runSuite.js +37 -0
  85. package/dist/symbolicate.d.ts.map +1 -1
  86. package/dist/symbolicate.js +5 -4
  87. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  88. package/dist/ui/ReadyScreen.d.ts.map +1 -1
  89. package/dist/ui/ReadyScreen.js +3 -10
  90. package/dist/ui/WrongEnvironmentScreen.d.ts.map +1 -1
  91. package/dist/ui/WrongEnvironmentScreen.js +2 -10
  92. package/dist/ui/state.d.ts +13 -0
  93. package/dist/ui/state.d.ts.map +1 -1
  94. package/dist/ui/state.js +22 -0
  95. package/dist/utils/emitter.d.ts.map +1 -1
  96. package/dist/waitFor.d.ts +21 -0
  97. package/dist/waitFor.d.ts.map +1 -0
  98. package/dist/waitFor.js +137 -0
  99. package/eslint.config.mjs +1 -7
  100. package/out-tsc/vitest/src/__tests__/collector.test.d.ts +2 -0
  101. package/out-tsc/vitest/src/__tests__/collector.test.d.ts.map +1 -0
  102. package/out-tsc/vitest/src/__tests__/error-handling.test.d.ts +2 -0
  103. package/out-tsc/vitest/src/__tests__/error-handling.test.d.ts.map +1 -0
  104. package/out-tsc/vitest/src/__tests__/expect.test.d.ts +2 -0
  105. package/out-tsc/vitest/src/__tests__/expect.test.d.ts.map +1 -0
  106. package/out-tsc/vitest/src/__tests__/spy.test.d.ts +2 -0
  107. package/out-tsc/vitest/src/__tests__/spy.test.d.ts.map +1 -0
  108. package/out-tsc/vitest/src/bundler/bundle.d.ts +2 -0
  109. package/out-tsc/vitest/src/bundler/bundle.d.ts.map +1 -0
  110. package/out-tsc/vitest/src/bundler/errors.d.ts +15 -0
  111. package/out-tsc/vitest/src/bundler/errors.d.ts.map +1 -0
  112. package/out-tsc/vitest/src/bundler/evaluate.d.ts +2 -0
  113. package/out-tsc/vitest/src/bundler/evaluate.d.ts.map +1 -0
  114. package/out-tsc/vitest/src/bundler/factory.d.ts +3 -0
  115. package/out-tsc/vitest/src/bundler/factory.d.ts.map +1 -0
  116. package/out-tsc/vitest/src/bundler/index.d.ts +4 -0
  117. package/out-tsc/vitest/src/bundler/index.d.ts.map +1 -0
  118. package/out-tsc/vitest/src/bundler/types.d.ts +7 -0
  119. package/out-tsc/vitest/src/bundler/types.d.ts.map +1 -0
  120. package/out-tsc/vitest/src/client/factory.d.ts +2 -0
  121. package/out-tsc/vitest/src/client/factory.d.ts.map +1 -0
  122. package/out-tsc/vitest/src/client/getDeviceDescriptor.d.ts +8 -0
  123. package/out-tsc/vitest/src/client/getDeviceDescriptor.d.ts.map +1 -0
  124. package/out-tsc/vitest/src/client/getWSServer.d.ts +2 -0
  125. package/out-tsc/vitest/src/client/getWSServer.d.ts.map +1 -0
  126. package/out-tsc/vitest/src/client/index.d.ts +2 -0
  127. package/out-tsc/vitest/src/client/index.d.ts.map +1 -0
  128. package/out-tsc/vitest/src/collector/errors.d.ts +8 -0
  129. package/out-tsc/vitest/src/collector/errors.d.ts.map +1 -0
  130. package/out-tsc/vitest/src/collector/factory.d.ts +3 -0
  131. package/out-tsc/vitest/src/collector/factory.d.ts.map +1 -0
  132. package/out-tsc/vitest/src/collector/functions.d.ts +22 -0
  133. package/out-tsc/vitest/src/collector/functions.d.ts.map +1 -0
  134. package/out-tsc/vitest/src/collector/index.d.ts +5 -0
  135. package/out-tsc/vitest/src/collector/index.d.ts.map +1 -0
  136. package/out-tsc/vitest/src/collector/types.d.ts +10 -0
  137. package/out-tsc/vitest/src/collector/types.d.ts.map +1 -0
  138. package/out-tsc/vitest/src/collector/validation.d.ts +4 -0
  139. package/out-tsc/vitest/src/collector/validation.d.ts.map +1 -0
  140. package/out-tsc/vitest/src/constants.d.ts +3 -0
  141. package/out-tsc/vitest/src/constants.d.ts.map +1 -0
  142. package/out-tsc/vitest/src/entry-point.d.ts +2 -0
  143. package/out-tsc/vitest/src/entry-point.d.ts.map +1 -0
  144. package/out-tsc/vitest/src/errors.d.ts +6 -0
  145. package/out-tsc/vitest/src/errors.d.ts.map +1 -0
  146. package/out-tsc/vitest/src/expect/index.d.ts +9 -0
  147. package/out-tsc/vitest/src/expect/index.d.ts.map +1 -0
  148. package/out-tsc/vitest/src/expect/setup.d.ts +2 -0
  149. package/out-tsc/vitest/src/expect/setup.d.ts.map +1 -0
  150. package/out-tsc/vitest/src/filtering/index.d.ts +2 -0
  151. package/out-tsc/vitest/src/filtering/index.d.ts.map +1 -0
  152. package/out-tsc/vitest/src/filtering/testNameFilter.d.ts +12 -0
  153. package/out-tsc/vitest/src/filtering/testNameFilter.d.ts.map +1 -0
  154. package/out-tsc/vitest/src/globals.d.ts +8 -0
  155. package/out-tsc/vitest/src/globals.d.ts.map +1 -0
  156. package/out-tsc/vitest/src/index.d.ts +9 -0
  157. package/out-tsc/vitest/src/index.d.ts.map +1 -0
  158. package/out-tsc/vitest/src/initialize.d.ts +2 -0
  159. package/out-tsc/vitest/src/initialize.d.ts.map +1 -0
  160. package/out-tsc/vitest/src/mocker/index.d.ts +2 -0
  161. package/out-tsc/vitest/src/mocker/index.d.ts.map +1 -0
  162. package/out-tsc/vitest/src/mocker/registry.d.ts +7 -0
  163. package/out-tsc/vitest/src/mocker/registry.d.ts.map +1 -0
  164. package/out-tsc/vitest/src/mocker/types.d.ts +6 -0
  165. package/out-tsc/vitest/src/mocker/types.d.ts.map +1 -0
  166. package/out-tsc/vitest/src/namespace.d.ts +18 -0
  167. package/out-tsc/vitest/src/namespace.d.ts.map +1 -0
  168. package/out-tsc/vitest/src/runner/errors.d.ts +11 -0
  169. package/out-tsc/vitest/src/runner/errors.d.ts.map +1 -0
  170. package/out-tsc/vitest/src/runner/factory.d.ts +3 -0
  171. package/out-tsc/vitest/src/runner/factory.d.ts.map +1 -0
  172. package/out-tsc/vitest/src/runner/hooks.d.ts +4 -0
  173. package/out-tsc/vitest/src/runner/hooks.d.ts.map +1 -0
  174. package/out-tsc/vitest/src/runner/index.d.ts +4 -0
  175. package/out-tsc/vitest/src/runner/index.d.ts.map +1 -0
  176. package/out-tsc/vitest/src/runner/runSuite.d.ts +4 -0
  177. package/out-tsc/vitest/src/runner/runSuite.d.ts.map +1 -0
  178. package/out-tsc/vitest/src/runner/types.d.ts +13 -0
  179. package/out-tsc/vitest/src/runner/types.d.ts.map +1 -0
  180. package/out-tsc/vitest/src/spy/index.d.ts +2 -0
  181. package/out-tsc/vitest/src/spy/index.d.ts.map +1 -0
  182. package/out-tsc/vitest/src/symbolicate.d.ts +3 -0
  183. package/out-tsc/vitest/src/symbolicate.d.ts.map +1 -0
  184. package/out-tsc/vitest/src/ui/ReadyScreen.d.ts +2 -0
  185. package/out-tsc/vitest/src/ui/ReadyScreen.d.ts.map +1 -0
  186. package/out-tsc/vitest/src/ui/WrongEnvironmentScreen.d.ts +2 -0
  187. package/out-tsc/vitest/src/ui/WrongEnvironmentScreen.d.ts.map +1 -0
  188. package/out-tsc/vitest/src/ui/index.d.ts +2 -0
  189. package/out-tsc/vitest/src/ui/index.d.ts.map +1 -0
  190. package/out-tsc/vitest/src/ui/state.d.ts +7 -0
  191. package/out-tsc/vitest/src/ui/state.d.ts.map +1 -0
  192. package/out-tsc/vitest/src/utils/dev-server.d.ts +2 -0
  193. package/out-tsc/vitest/src/utils/dev-server.d.ts.map +1 -0
  194. package/out-tsc/vitest/src/utils/emitter.d.ts +16 -0
  195. package/out-tsc/vitest/src/utils/emitter.d.ts.map +1 -0
  196. package/out-tsc/vitest/src/waitFor.d.ts +21 -0
  197. package/out-tsc/vitest/src/waitFor.d.ts.map +1 -0
  198. package/out-tsc/vitest/tsconfig.spec.tsbuildinfo +1 -0
  199. package/out-tsc/vitest/vite.config.d.ts +3 -0
  200. package/out-tsc/vitest/vite.config.d.ts.map +1 -0
  201. package/package.json +10 -4
  202. package/src/__tests__/collector.test.ts +55 -55
  203. package/src/__tests__/error-handling.test.ts +34 -34
  204. package/src/__tests__/expect.test.ts +13 -5
  205. package/src/bundler/bundle.ts +1 -2
  206. package/src/bundler/evaluate.ts +9 -9
  207. package/src/bundler/factory.ts +43 -0
  208. package/src/bundler/index.ts +2 -1
  209. package/src/bundler/types.ts +7 -0
  210. package/src/client/factory.ts +51 -16
  211. package/src/client/getDeviceDescriptor.ts +29 -8
  212. package/src/client/setup-files.ts +81 -0
  213. package/src/collector/functions.ts +18 -2
  214. package/src/collector/types.ts +4 -1
  215. package/src/constants.ts +0 -1
  216. package/src/entry-point.ts +8 -0
  217. package/src/expect/index.ts +8 -2
  218. package/src/expect/setup.ts +3 -0
  219. package/src/filtering/index.ts +4 -0
  220. package/src/filtering/testNameFilter.ts +82 -0
  221. package/src/globals.ts +14 -2
  222. package/src/index.ts +3 -0
  223. package/src/initialize.ts +11 -1
  224. package/src/jest-mock.ts +32 -0
  225. package/src/mocker/index.ts +7 -1
  226. package/src/mocker/metro-require.d.ts +2 -0
  227. package/src/mocker/registry.ts +13 -5
  228. package/src/namespace.ts +41 -0
  229. package/src/react-native.d.ts +2 -10
  230. package/src/render/ErrorBoundary.tsx +108 -0
  231. package/src/render/TestComponentOverlay.tsx +47 -0
  232. package/src/render/cleanup.ts +7 -0
  233. package/src/render/index.ts +96 -0
  234. package/src/render/setup.ts +8 -0
  235. package/src/render/types.ts +11 -0
  236. package/src/runner/factory.ts +8 -1
  237. package/src/runner/runSuite.ts +43 -0
  238. package/src/symbolicate.ts +6 -4
  239. package/src/ui/ReadyScreen.tsx +2 -12
  240. package/src/ui/WrongEnvironmentScreen.tsx +1 -19
  241. package/src/ui/state.ts +39 -0
  242. package/src/utils/emitter.ts +1 -0
  243. package/src/waitFor.ts +199 -0
  244. package/tsconfig.spec.json +7 -3
  245. package/tsconfig.tsbuildinfo +1 -1
  246. package/assets/logo.png +0 -0
  247. package/dist/utils/progressLogger.d.ts +0 -8
  248. package/dist/utils/progressLogger.d.ts.map +0 -1
  249. package/dist/utils/progressLogger.js +0 -73
  250. package/src/utils/progressLogger.ts +0 -91
  251. package/types/global.d.ts +0 -2
  252. package/types/index.d.ts +0 -1
@@ -1,15 +1,19 @@
1
1
  import type {
2
2
  TestRunnerEvents,
3
3
  TestCollectorEvents,
4
+ BundlerEvents,
5
+ TestExecutionOptions,
4
6
  } from '@react-native-harness/bridge';
5
7
  import { getBridgeClient } from '@react-native-harness/bridge/client';
6
8
  import { store } from '../ui/state.js';
7
9
  import { getTestRunner, TestRunner } from '../runner/index.js';
8
10
  import { getTestCollector, TestCollector } from '../collector/index.js';
9
11
  import { combineEventEmitters, EventEmitter } from '../utils/emitter.js';
10
- import { attachProgressLogger } from '../utils/progressLogger.js';
11
12
  import { getWSServer } from './getWSServer.js';
12
- import { fetchModule, evaluateModule } from '../bundler/index.js';
13
+ import { getBundler, evaluateModule, Bundler } from '../bundler/index.js';
14
+ import { markTestsAsSkippedByName } from '../filtering/index.js';
15
+ import { setup } from '../render/setup.js';
16
+ import { runSetupFiles } from './setup-files.js';
13
17
 
14
18
  export const getClient = async () => {
15
19
  const client = await getBridgeClient(getWSServer(), {
@@ -18,7 +22,10 @@ export const getClient = async () => {
18
22
  },
19
23
  });
20
24
 
21
- client.rpc.$functions.runTests = async (path: string) => {
25
+ client.rpc.$functions.runTests = async (
26
+ path: string,
27
+ options: TestExecutionOptions = {}
28
+ ) => {
22
29
  if (store.getState().status === 'running') {
23
30
  throw new Error('Already running tests');
24
31
  }
@@ -27,30 +34,58 @@ export const getClient = async () => {
27
34
 
28
35
  let collector: TestCollector | null = null;
29
36
  let runner: TestRunner | null = null;
30
- let events: EventEmitter<TestRunnerEvents | TestCollectorEvents> | null =
31
- null;
37
+ let events: EventEmitter<
38
+ TestRunnerEvents | TestCollectorEvents | BundlerEvents
39
+ > | null = null;
40
+ let bundler: Bundler | null = null;
32
41
 
33
42
  try {
34
43
  collector = getTestCollector();
35
44
  runner = getTestRunner();
36
- events = combineEventEmitters(collector.events, runner.events);
45
+ bundler = getBundler();
46
+ events = combineEventEmitters(
47
+ collector.events,
48
+ runner.events,
49
+ bundler.events
50
+ );
37
51
 
38
52
  events.addListener((event) => {
39
53
  client.rpc.emitEvent(event.type, event);
40
54
  });
41
55
 
42
- // Add console logging for progress information
43
- attachProgressLogger(events, path);
56
+ await runSetupFiles({
57
+ setupFiles: options.setupFiles ?? [],
58
+ setupFilesAfterEnv: [],
59
+ events: events as EventEmitter<BundlerEvents>,
60
+ bundler: bundler as Bundler,
61
+ evaluateModule,
62
+ });
44
63
 
45
- const moduleJs = await fetchModule(path);
46
- const collectionResult = await collector.collect(
47
- () => evaluateModule(moduleJs, path),
48
- path
49
- );
50
- const result = await runner.run(collectionResult.testSuite, path);
64
+ const moduleJs = await bundler.getModule(path);
65
+ const collectionResult = await collector.collect(async () => {
66
+ await runSetupFiles({
67
+ setupFiles: [],
68
+ setupFilesAfterEnv: options.setupFilesAfterEnv ?? [],
69
+ events: events as EventEmitter<BundlerEvents>,
70
+ bundler: bundler as Bundler,
71
+ evaluateModule,
72
+ });
73
+
74
+ // Setup automatic cleanup for rendered components
75
+ setup();
76
+ evaluateModule(moduleJs, path);
77
+ }, path);
78
+
79
+ // Apply test name pattern by marking non-matching tests as skipped
80
+ const processedTestSuite = options.testNamePattern
81
+ ? markTestsAsSkippedByName(
82
+ collectionResult.testSuite,
83
+ options.testNamePattern
84
+ )
85
+ : collectionResult.testSuite;
86
+
87
+ const result = await runner.run(processedTestSuite, path);
51
88
  return result;
52
- } catch (error) {
53
- throw error;
54
89
  } finally {
55
90
  collector?.dispose();
56
91
  runner?.dispose();
@@ -1,28 +1,49 @@
1
- import { Platform } from 'react-native';
1
+ import { Platform, PlatformConstants, PlatformStatic } from 'react-native';
2
+
3
+ interface PlatformKeplerStatic extends PlatformStatic {
4
+ constants: PlatformConstants;
5
+ OS: 'kepler';
6
+ Version: number;
7
+ }
8
+
9
+ const getPlatform = (): Platform | PlatformKeplerStatic => {
10
+ return Platform as Platform | PlatformKeplerStatic;
11
+ };
2
12
 
3
13
  export type DeviceDescriptor = {
4
- platform: 'ios' | 'android';
14
+ platform: 'ios' | 'android' | 'vega';
5
15
  manufacturer: string;
6
16
  model: string;
7
17
  osVersion: string;
8
18
  };
9
19
 
10
20
  export const getDeviceDescriptor = (): DeviceDescriptor => {
11
- if (Platform.OS === 'ios') {
21
+ const platform = getPlatform();
22
+
23
+ if (platform.OS === 'ios') {
12
24
  return {
13
25
  platform: 'ios',
14
26
  manufacturer: 'Apple',
15
27
  model: 'Unknown',
16
- osVersion: Platform.constants.osVersion,
28
+ osVersion: platform.constants.osVersion,
17
29
  };
18
30
  }
19
31
 
20
- if (Platform.OS === 'android') {
32
+ if (platform.OS === 'android') {
21
33
  return {
22
34
  platform: 'android',
23
- manufacturer: Platform.constants.Manufacturer,
24
- model: Platform.constants.Model,
25
- osVersion: Platform.constants.Release,
35
+ manufacturer: platform.constants.Manufacturer,
36
+ model: platform.constants.Model,
37
+ osVersion: platform.constants.Release,
38
+ };
39
+ }
40
+
41
+ if (platform.OS === 'kepler') {
42
+ return {
43
+ platform: 'vega',
44
+ manufacturer: '',
45
+ model: '',
46
+ osVersion: '',
26
47
  };
27
48
  }
28
49
 
@@ -0,0 +1,81 @@
1
+ import { EventEmitter } from '../utils/emitter.js';
2
+ import { Bundler } from '../bundler/index.js';
3
+ import { BundlerEvents } from '@react-native-harness/bridge';
4
+
5
+ export type RunSetupFilesOptions = {
6
+ setupFiles: string[];
7
+ setupFilesAfterEnv: string[];
8
+ events: EventEmitter<BundlerEvents>;
9
+ bundler: Bundler;
10
+ evaluateModule: (moduleJs: string, filePath: string) => void;
11
+ };
12
+
13
+ export const runSetupFiles = async ({
14
+ setupFiles,
15
+ setupFilesAfterEnv,
16
+ events,
17
+ bundler,
18
+ evaluateModule,
19
+ }: RunSetupFilesOptions) => {
20
+ for (const setupFile of setupFiles) {
21
+ const startTime = Date.now();
22
+ events.emit({
23
+ type: 'setup-file-bundling-started',
24
+ file: setupFile,
25
+ setupType: 'setupFiles',
26
+ });
27
+
28
+ try {
29
+ const setupModuleJs = await bundler.getModule(setupFile);
30
+ events.emit({
31
+ type: 'setup-file-bundling-finished',
32
+ file: setupFile,
33
+ setupType: 'setupFiles',
34
+ duration: Date.now() - startTime,
35
+ });
36
+ evaluateModule(setupModuleJs, setupFile);
37
+ } catch (error) {
38
+ const errorMessage =
39
+ error instanceof Error ? error.message : 'Unknown error';
40
+ events.emit({
41
+ type: 'setup-file-bundling-failed',
42
+ file: setupFile,
43
+ setupType: 'setupFiles',
44
+ duration: Date.now() - startTime,
45
+ error: errorMessage,
46
+ });
47
+ throw error;
48
+ }
49
+ }
50
+
51
+ for (const setupFile of setupFilesAfterEnv) {
52
+ const startTime = Date.now();
53
+ events.emit({
54
+ type: 'setup-file-bundling-started',
55
+ file: setupFile,
56
+ setupType: 'setupFilesAfterEnv',
57
+ });
58
+
59
+ try {
60
+ const setupModuleJs = await bundler.getModule(setupFile);
61
+ events.emit({
62
+ type: 'setup-file-bundling-finished',
63
+ file: setupFile,
64
+ setupType: 'setupFilesAfterEnv',
65
+ duration: Date.now() - startTime,
66
+ });
67
+ evaluateModule(setupModuleJs, setupFile);
68
+ } catch (error) {
69
+ const errorMessage =
70
+ error instanceof Error ? error.message : 'Unknown error';
71
+ events.emit({
72
+ type: 'setup-file-bundling-failed',
73
+ file: setupFile,
74
+ setupType: 'setupFilesAfterEnv',
75
+ duration: Date.now() - startTime,
76
+ error: errorMessage,
77
+ });
78
+ throw error;
79
+ }
80
+ }
81
+ };
@@ -53,7 +53,21 @@ const computeSuiteStatus = (
53
53
  ): TestStatus => {
54
54
  if (suite.options.skip) return 'skipped';
55
55
  if (suite.options.only) return 'active';
56
+
57
+ // Check if this suite has any focused content (tests or child suites)
58
+ const hasFocusedTests = suite.tests.some((test) => test.options.only);
59
+ const hasFocusedChildren = suite.suites.some(
60
+ (childSuite) =>
61
+ childSuite.options.only ||
62
+ childSuite.tests.some((test) => test.options.only)
63
+ );
64
+
65
+ // If this suite has focused content, it should be active
66
+ if (hasFocusedTests || hasFocusedChildren) return 'active';
67
+
68
+ // If parent has focused children and this suite has no focused content, skip it
56
69
  if (parentContext.hasFocusedChildren) return 'skipped';
70
+
57
71
  return 'active';
58
72
  };
59
73
 
@@ -356,11 +370,13 @@ const countTests = (suite: TestSuite): number => {
356
370
  return count;
357
371
  };
358
372
 
359
- export const collectTests = (fn: () => void): CollectionResult => {
373
+ export const collectTests = async (
374
+ fn: () => void | Promise<void>
375
+ ): Promise<CollectionResult> => {
360
376
  currentContext = clearState();
361
377
 
362
378
  try {
363
- fn();
379
+ await fn();
364
380
 
365
381
  // Convert raw structure to final structure using computation phase
366
382
  const testSuite = convertRawTestSuiteToTestSuite(getRootSuite());
@@ -10,6 +10,9 @@ export type TestCollectorEventsEmitter = EventEmitter<TestCollectorEvents>;
10
10
 
11
11
  export type TestCollector = {
12
12
  events: TestCollectorEventsEmitter;
13
- collect: (fn: () => void, testFilePath: string) => Promise<CollectionResult>;
13
+ collect: (
14
+ fn: () => void | Promise<void>,
15
+ testFilePath: string
16
+ ) => Promise<CollectionResult>;
14
17
  dispose: () => void;
15
18
  };
package/src/constants.ts CHANGED
@@ -1,2 +1 @@
1
- export const LOGO_IMAGE = require('../assets/logo.png');
2
1
  export const WS_SERVER_PORT = 3001;
@@ -0,0 +1,8 @@
1
+ import { AppRegistry } from 'react-native';
2
+ import { getHarnessGlobal } from './globals.js';
3
+ import { UI } from './ui/index.js';
4
+
5
+ AppRegistry.registerComponent(
6
+ getHarnessGlobal().appRegistryComponentName,
7
+ () => UI
8
+ );
@@ -1,3 +1,6 @@
1
+ // This is adapted version of https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/integrations/chai/index.ts
2
+ // Credits to Vitest team for the original implementation.
3
+
1
4
  import type { Assertion, ExpectStatic, MatcherState } from '@vitest/expect';
2
5
  import {
3
6
  addCustomEqualityTesters,
@@ -13,13 +16,16 @@ import * as chai from 'chai';
13
16
  import './setup.js';
14
17
 
15
18
  export function createExpect(): ExpectStatic {
16
- const expect = ((value: any, message?: string): Assertion => {
19
+ const expect = ((value: unknown, message?: string): Assertion => {
17
20
  const { assertionCalls } = getState(expect);
18
21
  setState({ assertionCalls: assertionCalls + 1 }, expect);
19
22
  return chai.expect(value, message) as unknown as Assertion;
20
23
  }) as ExpectStatic;
21
24
  Object.assign(expect, chai.expect);
22
- Object.assign(expect, (globalThis as any)[ASYMMETRIC_MATCHERS_OBJECT]);
25
+ Object.assign(
26
+ expect,
27
+ globalThis[ASYMMETRIC_MATCHERS_OBJECT as unknown as keyof typeof globalThis]
28
+ );
23
29
 
24
30
  expect.getState = () => getState<MatcherState>(expect);
25
31
  expect.setState = (state) => setState(state as Partial<MatcherState>, expect);
@@ -1,3 +1,6 @@
1
+ // This is adapted version of https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/integrations/chai/setup.ts
2
+ // Credits to Vitest team for the original implementation.
3
+
1
4
  import {
2
5
  JestAsymmetricMatchers,
3
6
  JestChaiExpect,
@@ -0,0 +1,4 @@
1
+ export {
2
+ filterTestsByName,
3
+ markTestsAsSkippedByName,
4
+ } from './testNameFilter.js';
@@ -0,0 +1,82 @@
1
+ import { TestSuite } from '@react-native-harness/bridge';
2
+
3
+ /**
4
+ * Filters tests by name pattern, matching against test names and suite+test combinations
5
+ * @deprecated Use markTestsAsSkippedByName instead - this function will be removed in a future version
6
+ */
7
+ export const filterTestsByName = (
8
+ suite: TestSuite,
9
+ testNamePattern: string
10
+ ): TestSuite => {
11
+ const regex = new RegExp(testNamePattern);
12
+ return filterSuiteRecursively(suite, regex);
13
+ };
14
+
15
+ /**
16
+ * Marks tests as skipped based on name pattern, keeping all tests in the structure
17
+ * but setting non-matching tests to 'skipped' status
18
+ */
19
+ export const markTestsAsSkippedByName = (
20
+ suite: TestSuite,
21
+ testNamePattern: string
22
+ ): TestSuite => {
23
+ const regex = new RegExp(testNamePattern);
24
+ return markTestsRecursively(suite, regex);
25
+ };
26
+
27
+ const markTestsRecursively = (suite: TestSuite, regex: RegExp): TestSuite => {
28
+ // Mark tests in current suite - skip tests that don't match the pattern
29
+ const updatedTests = suite.tests.map((test) => {
30
+ const matches =
31
+ regex.test(test.name) || regex.test(`${suite.name} ${test.name}`);
32
+
33
+ // If test doesn't match pattern and is currently active, mark it as skipped
34
+ if (!matches && test.status === 'active') {
35
+ return {
36
+ ...test,
37
+ status: 'skipped' as const,
38
+ };
39
+ }
40
+
41
+ // Keep original status for matching tests or already skipped/todo tests
42
+ return test;
43
+ });
44
+
45
+ // Recursively process child suites
46
+ const updatedChildSuites = suite.suites.map((childSuite) =>
47
+ markTestsRecursively(childSuite, regex)
48
+ );
49
+
50
+ return {
51
+ ...suite,
52
+ tests: updatedTests,
53
+ suites: updatedChildSuites,
54
+ };
55
+ };
56
+
57
+ const filterSuiteRecursively = (suite: TestSuite, regex: RegExp): TestSuite => {
58
+ // Filter tests in current suite - match against test name or "suite test" combination
59
+ const filteredTests = suite.tests.filter(
60
+ (test) => regex.test(test.name) || regex.test(`${suite.name} ${test.name}`)
61
+ );
62
+
63
+ // Recursively filter child suites
64
+ const filteredChildSuites = suite.suites
65
+ .map((childSuite) => filterSuiteRecursively(childSuite, regex))
66
+ .filter((childSuite) => hasAnyActiveTests(childSuite));
67
+
68
+ return {
69
+ ...suite,
70
+ tests: filteredTests,
71
+ suites: filteredChildSuites,
72
+ };
73
+ };
74
+
75
+ const hasAnyActiveTests = (suite: TestSuite): boolean => {
76
+ const hasDirectTests = suite.tests.some((test) => test.status === 'active');
77
+ const hasChildTests = suite.suites.some((childSuite) =>
78
+ hasAnyActiveTests(childSuite)
79
+ );
80
+
81
+ return hasDirectTests || hasChildTests;
82
+ };
package/src/globals.ts CHANGED
@@ -1,5 +1,17 @@
1
+ export type HarnessGlobal = {
2
+ appRegistryComponentName: string;
3
+ };
4
+
1
5
  declare global {
2
- var RN_HARNESS: boolean | undefined;
6
+ var RN_HARNESS: HarnessGlobal | undefined;
3
7
  }
4
8
 
5
- export {};
9
+ export const getHarnessGlobal = (): HarnessGlobal => {
10
+ const harnessGlobal = global.RN_HARNESS;
11
+
12
+ if (!harnessGlobal) {
13
+ throw new Error('RN_HARNESS global is not set');
14
+ }
15
+
16
+ return harnessGlobal;
17
+ };
package/src/index.ts CHANGED
@@ -5,3 +5,6 @@ export * from './spy/index.js';
5
5
  export * from './expect/index.js';
6
6
  export * from './collector/index.js';
7
7
  export * from './mocker/index.js';
8
+ export * from './namespace.js';
9
+ export * from './waitFor.js';
10
+ export * from './render/index.js';
package/src/initialize.ts CHANGED
@@ -1,18 +1,23 @@
1
1
  import { getDeviceDescriptor } from './client/getDeviceDescriptor.js';
2
2
  import { getClient } from './client/index.js';
3
+ import { setupJestMock } from './jest-mock.js';
3
4
 
4
5
  // Polyfill for EventTarget
5
6
  const Shim = require('event-target-shim');
6
7
  globalThis.Event = Shim.Event;
7
8
  globalThis.EventTarget = Shim.EventTarget;
8
9
 
10
+ // Setup jest mock to warn users about using Jest APIs
11
+ setupJestMock();
12
+
9
13
  // Turn off LogBox
10
14
  const { LogBox } = require('react-native');
11
15
  LogBox.ignoreAllLogs(true);
12
16
 
13
17
  // Turn off HMR
14
18
  const HMRClientModule = require('react-native/Libraries/Utilities/HMRClient');
15
- const HMRClient = 'default' in HMRClientModule ? HMRClientModule.default : HMRClientModule;
19
+ const HMRClient =
20
+ 'default' in HMRClientModule ? HMRClientModule.default : HMRClientModule;
16
21
 
17
22
  // Wait for HMRClient to be initialized
18
23
  setTimeout(() => {
@@ -23,3 +28,8 @@ setTimeout(() => {
23
28
  client.rpc.reportReady(getDeviceDescriptor())
24
29
  );
25
30
  });
31
+
32
+ // Re-throw fatal errors
33
+ ErrorUtils.setGlobalHandler((error) => {
34
+ throw error;
35
+ });
@@ -0,0 +1,32 @@
1
+ // Mock jest global to warn users about using Jest APIs in Harness tests
2
+ export const setupJestMock = (): void => {
3
+ function throwError(): never {
4
+ throw new Error(
5
+ `Jest globals are not available in Harness tests. Import from 'react-native-harness' instead (e.g., import { harness } from 'react-native-harness'; harness.fn())`
6
+ );
7
+ }
8
+
9
+ const jestMock = new Proxy(
10
+ {},
11
+ {
12
+ get() {
13
+ throwError();
14
+ },
15
+ set() {
16
+ throwError();
17
+ },
18
+ has() {
19
+ throwError();
20
+ },
21
+ ownKeys() {
22
+ throwError();
23
+ },
24
+ }
25
+ );
26
+
27
+ Object.defineProperty(globalThis, 'jest', {
28
+ value: jestMock,
29
+ writable: false,
30
+ configurable: false,
31
+ });
32
+ };
@@ -1 +1,7 @@
1
- export { mock, requireActual, clearMocks } from './registry.js';
1
+ export {
2
+ mock,
3
+ requireActual,
4
+ clearMocks,
5
+ unmock,
6
+ resetModules,
7
+ } from './registry.js';
@@ -2,4 +2,6 @@ import type { Require } from './types.js';
2
2
 
3
3
  declare global {
4
4
  var __r: Require;
5
+ var __resetAllModules: () => void;
6
+ var __clearModule: (moduleId: number) => void;
5
7
  }
@@ -15,11 +15,7 @@ export const clearMocks = (): void => {
15
15
  mockCache.clear();
16
16
  };
17
17
 
18
- export const getMockRegistry = (): Map<number, ModuleFactory> => {
19
- return mockRegistry;
20
- };
21
-
22
- export const getMockImplementation = (moduleId: number): unknown | null => {
18
+ const getMockImplementation = (moduleId: number): unknown | null => {
23
19
  if (mockCache.has(moduleId)) {
24
20
  return mockCache.get(moduleId);
25
21
  }
@@ -39,6 +35,18 @@ export const requireActual = <T = any>(moduleId: string): T =>
39
35
  // babel plugin will transform 'moduleId' to a number
40
36
  originalRequire(moduleId as unknown as ModuleId) as T;
41
37
 
38
+ export const unmock = (moduleId: string) => {
39
+ mockRegistry.delete(moduleId as unknown as ModuleId);
40
+ mockCache.delete(moduleId as unknown as ModuleId);
41
+ };
42
+
43
+ export const resetModules = (): void => {
44
+ mockCache.clear();
45
+
46
+ // Reset Metro's module cache
47
+ global.__resetAllModules();
48
+ };
49
+
42
50
  const mockRequire = (moduleId: string) => {
43
51
  // babel plugin will transform 'moduleId' to a number
44
52
  const mockedModule = getMockImplementation(moduleId as unknown as ModuleId);
@@ -0,0 +1,41 @@
1
+ import {
2
+ spyOn,
3
+ fn,
4
+ clearAllMocks,
5
+ resetAllMocks,
6
+ restoreAllMocks,
7
+ } from './spy/index.js';
8
+ import { mock, unmock, requireActual, resetModules } from './mocker/index.js';
9
+ import { waitFor, waitUntil } from './waitFor.js';
10
+
11
+ export type HarnessNamespace = {
12
+ spyOn: typeof spyOn;
13
+ fn: typeof fn;
14
+ mock: typeof mock;
15
+ unmock: typeof unmock;
16
+ requireActual: typeof requireActual;
17
+ clearAllMocks: typeof clearAllMocks;
18
+ resetAllMocks: typeof resetAllMocks;
19
+ restoreAllMocks: typeof restoreAllMocks;
20
+ resetModules: typeof resetModules;
21
+ waitFor: typeof waitFor;
22
+ waitUntil: typeof waitUntil;
23
+ };
24
+
25
+ const createHarnessNamespace = (): HarnessNamespace => {
26
+ return {
27
+ spyOn,
28
+ fn,
29
+ mock,
30
+ unmock,
31
+ requireActual,
32
+ clearAllMocks,
33
+ resetAllMocks,
34
+ restoreAllMocks,
35
+ resetModules,
36
+ waitFor,
37
+ waitUntil,
38
+ };
39
+ };
40
+
41
+ export const harness = createHarnessNamespace();
@@ -16,7 +16,7 @@ declare module 'react-native/Libraries/Core/Devtools/symbolicateStackTrace' {
16
16
  | {
17
17
  row: number;
18
18
  column: number;
19
- [key: string]: any;
19
+ [key: string]: unknown;
20
20
  }
21
21
  | null
22
22
  | undefined;
@@ -30,7 +30,7 @@ declare module 'react-native/Libraries/Core/Devtools/symbolicateStackTrace' {
30
30
 
31
31
  export default function symbolicateStackTrace(
32
32
  stack: ReadonlyArray<StackFrame>,
33
- extraData?: any
33
+ extraData?: unknown
34
34
  ): Promise<SymbolicatedStackTrace>;
35
35
  }
36
36
 
@@ -43,11 +43,3 @@ declare module 'react-native/Libraries/Core/Devtools/parseErrorStack' {
43
43
  };
44
44
  export default function parseErrorStack(errorStack?: string): StackFrame[];
45
45
  }
46
-
47
- declare global {
48
- var __r:
49
- | {
50
- (moduleId: number): unknown;
51
- }
52
- | undefined;
53
- }