@react-native-harness/runtime 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (229) hide show
  1. package/.babelrc.js +23 -0
  2. package/LICENSE +20 -0
  3. package/README.md +7 -0
  4. package/assets/logo.png +0 -0
  5. package/assets/moduleSystem.flow.js +1062 -0
  6. package/dist/bundler/bundle.d.ts +2 -0
  7. package/dist/bundler/bundle.d.ts.map +1 -0
  8. package/dist/bundler/bundle.js +16 -0
  9. package/dist/bundler/dev-server.d.ts +2 -0
  10. package/dist/bundler/dev-server.d.ts.map +1 -0
  11. package/dist/bundler/dev-server.js +5 -0
  12. package/dist/bundler/errors.d.ts +10 -0
  13. package/dist/bundler/errors.d.ts.map +1 -0
  14. package/dist/bundler/errors.js +18 -0
  15. package/dist/bundler/evaluate.d.ts +2 -0
  16. package/dist/bundler/evaluate.d.ts.map +1 -0
  17. package/dist/bundler/evaluate.js +18 -0
  18. package/dist/bundler/index.d.ts +3 -0
  19. package/dist/bundler/index.d.ts.map +1 -0
  20. package/dist/bundler/index.js +2 -0
  21. package/dist/client/factory.d.ts +2 -0
  22. package/dist/client/factory.d.ts.map +1 -0
  23. package/dist/client/factory.js +41 -0
  24. package/dist/client/getDeviceDescriptor.d.ts +8 -0
  25. package/dist/client/getDeviceDescriptor.d.ts.map +1 -0
  26. package/dist/client/getDeviceDescriptor.js +20 -0
  27. package/dist/client/getWSServer.d.ts +2 -0
  28. package/dist/client/getWSServer.d.ts.map +1 -0
  29. package/dist/client/getWSServer.js +7 -0
  30. package/dist/client/index.d.ts +2 -0
  31. package/dist/client/index.d.ts.map +1 -0
  32. package/dist/client/index.js +1 -0
  33. package/dist/collector/errors.d.ts +8 -0
  34. package/dist/collector/errors.d.ts.map +1 -0
  35. package/dist/collector/errors.js +20 -0
  36. package/dist/collector/factory.d.ts +3 -0
  37. package/dist/collector/factory.d.ts.map +1 -0
  38. package/dist/collector/factory.js +25 -0
  39. package/dist/collector/functions.d.ts +22 -0
  40. package/dist/collector/functions.d.ts.map +1 -0
  41. package/dist/collector/functions.js +271 -0
  42. package/dist/collector/index.d.ts +5 -0
  43. package/dist/collector/index.d.ts.map +1 -0
  44. package/dist/collector/index.js +3 -0
  45. package/dist/collector/types.d.ts +10 -0
  46. package/dist/collector/types.d.ts.map +1 -0
  47. package/dist/collector/types.js +1 -0
  48. package/dist/collector/validation.d.ts +4 -0
  49. package/dist/collector/validation.d.ts.map +1 -0
  50. package/dist/collector/validation.js +15 -0
  51. package/dist/constants.d.ts +3 -0
  52. package/dist/constants.d.ts.map +1 -0
  53. package/dist/constants.js +2 -0
  54. package/dist/errors.d.ts +6 -0
  55. package/dist/errors.d.ts.map +1 -0
  56. package/dist/errors.js +13 -0
  57. package/dist/expect/index.d.ts +9 -0
  58. package/dist/expect/index.d.ts.map +1 -0
  59. package/dist/expect/index.js +71 -0
  60. package/dist/expect/setup.d.ts +2 -0
  61. package/dist/expect/setup.d.ts.map +1 -0
  62. package/dist/expect/setup.js +5 -0
  63. package/dist/exports.d.ts +7 -0
  64. package/dist/exports.d.ts.map +1 -0
  65. package/dist/exports.js +6 -0
  66. package/dist/getEntryComponent.d.ts +6 -0
  67. package/dist/getEntryComponent.d.ts.map +1 -0
  68. package/dist/getEntryComponent.js +6 -0
  69. package/dist/globals.d.ts +5 -0
  70. package/dist/globals.d.ts.map +1 -0
  71. package/dist/globals.js +1 -0
  72. package/dist/index.d.ts +7 -0
  73. package/dist/index.d.ts.map +1 -0
  74. package/dist/index.js +6 -0
  75. package/dist/initialize.d.ts +2 -0
  76. package/dist/initialize.d.ts.map +1 -0
  77. package/dist/initialize.js +16 -0
  78. package/dist/logger.d.ts +6 -0
  79. package/dist/logger.d.ts.map +1 -0
  80. package/dist/logger.js +14 -0
  81. package/dist/mock.d.ts +15 -0
  82. package/dist/mock.d.ts.map +1 -0
  83. package/dist/mock.js +37 -0
  84. package/dist/mocker/index.d.ts +2 -0
  85. package/dist/mocker/index.d.ts.map +1 -0
  86. package/dist/mocker/index.js +1 -0
  87. package/dist/mocker/registry.d.ts +7 -0
  88. package/dist/mocker/registry.d.ts.map +1 -0
  89. package/dist/mocker/registry.js +41 -0
  90. package/dist/mocker/types.d.ts +6 -0
  91. package/dist/mocker/types.d.ts.map +1 -0
  92. package/dist/mocker/types.js +1 -0
  93. package/dist/module.d.ts +3 -0
  94. package/dist/module.d.ts.map +1 -0
  95. package/dist/module.js +19 -0
  96. package/dist/module.web.d.ts +2 -0
  97. package/dist/module.web.d.ts.map +1 -0
  98. package/dist/module.web.js +12 -0
  99. package/dist/rntl/client.d.ts +3 -0
  100. package/dist/rntl/client.d.ts.map +1 -0
  101. package/dist/rntl/client.js +8 -0
  102. package/dist/rntl/describe.d.ts +2 -0
  103. package/dist/rntl/describe.d.ts.map +1 -0
  104. package/dist/rntl/describe.js +1 -0
  105. package/dist/rntl/expect.d.ts +128 -0
  106. package/dist/rntl/expect.d.ts.map +1 -0
  107. package/dist/rntl/expect.js +670 -0
  108. package/dist/rntl/fn.d.ts +2 -0
  109. package/dist/rntl/fn.d.ts.map +1 -0
  110. package/dist/rntl/fn.js +1 -0
  111. package/dist/rntl/mock.d.ts +2 -0
  112. package/dist/rntl/mock.d.ts.map +1 -0
  113. package/dist/rntl/mock.js +1 -0
  114. package/dist/rntl/render.d.ts +4 -0
  115. package/dist/rntl/render.d.ts.map +1 -0
  116. package/dist/rntl/render.js +11 -0
  117. package/dist/rntl/screen.d.ts +45 -0
  118. package/dist/rntl/screen.d.ts.map +1 -0
  119. package/dist/rntl/screen.js +31 -0
  120. package/dist/rntl/spies.d.ts +45 -0
  121. package/dist/rntl/spies.d.ts.map +1 -0
  122. package/dist/rntl/spies.js +553 -0
  123. package/dist/rntl/userEvent.d.ts +22 -0
  124. package/dist/rntl/userEvent.d.ts.map +1 -0
  125. package/dist/rntl/userEvent.js +19 -0
  126. package/dist/runner/errors.d.ts +9 -0
  127. package/dist/runner/errors.d.ts.map +1 -0
  128. package/dist/runner/errors.js +23 -0
  129. package/dist/runner/factory.d.ts +3 -0
  130. package/dist/runner/factory.d.ts.map +1 -0
  131. package/dist/runner/factory.js +17 -0
  132. package/dist/runner/hooks.d.ts +4 -0
  133. package/dist/runner/hooks.d.ts.map +1 -0
  134. package/dist/runner/hooks.js +39 -0
  135. package/dist/runner/index.d.ts +4 -0
  136. package/dist/runner/index.d.ts.map +1 -0
  137. package/dist/runner/index.js +2 -0
  138. package/dist/runner/runSuite.d.ts +4 -0
  139. package/dist/runner/runSuite.d.ts.map +1 -0
  140. package/dist/runner/runSuite.js +147 -0
  141. package/dist/runner/types.d.ts +13 -0
  142. package/dist/runner/types.d.ts.map +1 -0
  143. package/dist/runner/types.js +1 -0
  144. package/dist/runner.d.ts +7 -0
  145. package/dist/runner.d.ts.map +1 -0
  146. package/dist/runner.js +201 -0
  147. package/dist/runtime.d.ts +2 -0
  148. package/dist/runtime.d.ts.map +1 -0
  149. package/dist/runtime.js +44 -0
  150. package/dist/spy/index.d.ts +2 -0
  151. package/dist/spy/index.d.ts.map +1 -0
  152. package/dist/spy/index.js +2 -0
  153. package/dist/state.d.ts +25 -0
  154. package/dist/state.d.ts.map +1 -0
  155. package/dist/state.js +37 -0
  156. package/dist/tsconfig.lib.tsbuildinfo +1 -0
  157. package/dist/ui/ReadyScreen.d.ts +2 -0
  158. package/dist/ui/ReadyScreen.d.ts.map +1 -0
  159. package/dist/ui/ReadyScreen.js +110 -0
  160. package/dist/ui/UI.d.ts +13 -0
  161. package/dist/ui/UI.d.ts.map +1 -0
  162. package/dist/ui/UI.js +121 -0
  163. package/dist/ui/WrongEnvironmentScreen.d.ts +2 -0
  164. package/dist/ui/WrongEnvironmentScreen.d.ts.map +1 -0
  165. package/dist/ui/WrongEnvironmentScreen.js +87 -0
  166. package/dist/ui/index.d.ts +2 -0
  167. package/dist/ui/index.d.ts.map +1 -0
  168. package/dist/ui/index.js +3 -0
  169. package/dist/ui/state.d.ts +7 -0
  170. package/dist/ui/state.d.ts.map +1 -0
  171. package/dist/ui/state.js +6 -0
  172. package/dist/utils/dev-server.d.ts +2 -0
  173. package/dist/utils/dev-server.d.ts.map +1 -0
  174. package/dist/utils/dev-server.js +5 -0
  175. package/dist/utils/emitter.d.ts +16 -0
  176. package/dist/utils/emitter.d.ts.map +1 -0
  177. package/dist/utils/emitter.js +39 -0
  178. package/eslint.config.mjs +16 -0
  179. package/package.json +38 -0
  180. package/src/__tests__/collector.test.ts +553 -0
  181. package/src/__tests__/error-handling.test.ts +132 -0
  182. package/src/__tests__/expect.test.ts +619 -0
  183. package/src/__tests__/spy.test.ts +538 -0
  184. package/src/bundler/bundle.ts +19 -0
  185. package/src/bundler/errors.ts +16 -0
  186. package/src/bundler/evaluate.ts +25 -0
  187. package/src/bundler/index.ts +2 -0
  188. package/src/client/factory.ts +56 -0
  189. package/src/client/getDeviceDescriptor.ts +30 -0
  190. package/src/client/getWSServer.ts +9 -0
  191. package/src/client/index.ts +1 -0
  192. package/src/collector/errors.ts +27 -0
  193. package/src/collector/factory.ts +32 -0
  194. package/src/collector/functions.ts +376 -0
  195. package/src/collector/index.ts +12 -0
  196. package/src/collector/types.ts +15 -0
  197. package/src/collector/validation.ts +21 -0
  198. package/src/constants.ts +2 -0
  199. package/src/errors.ts +12 -0
  200. package/src/expect/index.ts +117 -0
  201. package/src/expect/setup.ts +10 -0
  202. package/src/globals.ts +5 -0
  203. package/src/index.ts +7 -0
  204. package/src/initialize.ts +22 -0
  205. package/src/mocker/index.ts +1 -0
  206. package/src/mocker/metro-require.d.ts +5 -0
  207. package/src/mocker/registry.ts +58 -0
  208. package/src/mocker/types.ts +6 -0
  209. package/src/react-native.d.ts +16 -0
  210. package/src/runner/errors.ts +31 -0
  211. package/src/runner/factory.ts +21 -0
  212. package/src/runner/hooks.ts +51 -0
  213. package/src/runner/index.ts +7 -0
  214. package/src/runner/runSuite.ts +201 -0
  215. package/src/runner/types.ts +19 -0
  216. package/src/spy/index.ts +2 -0
  217. package/src/ui/ReadyScreen.tsx +151 -0
  218. package/src/ui/WrongEnvironmentScreen.tsx +113 -0
  219. package/src/ui/index.ts +3 -0
  220. package/src/ui/state.ts +13 -0
  221. package/src/utils/dev-server.ts +6 -0
  222. package/src/utils/emitter.ts +64 -0
  223. package/tsconfig.json +16 -0
  224. package/tsconfig.lib.json +33 -0
  225. package/tsconfig.spec.json +30 -0
  226. package/tsconfig.tsbuildinfo +1 -0
  227. package/types/global.d.ts +2 -0
  228. package/types/index.d.ts +1 -0
  229. package/vite.config.ts +27 -0
@@ -0,0 +1,39 @@
1
+ const collectInheritedHooks = (suite, hookType) => {
2
+ const hooks = [];
3
+ const suiteChain = [];
4
+ // Collect all suites from current to root
5
+ let currentSuite = suite;
6
+ while (currentSuite) {
7
+ suiteChain.push(currentSuite);
8
+ currentSuite = currentSuite.parent;
9
+ }
10
+ if (hookType === 'beforeEach' || hookType === 'beforeAll') {
11
+ // For beforeEach/beforeAll: run parent hooks first (reverse the chain)
12
+ for (let i = suiteChain.length - 1; i >= 0; i--) {
13
+ if (hookType === 'beforeEach') {
14
+ hooks.push(...suiteChain[i].beforeEach);
15
+ }
16
+ else {
17
+ hooks.push(...suiteChain[i].beforeAll);
18
+ }
19
+ }
20
+ }
21
+ else {
22
+ // For afterEach/afterAll: run child hooks first (use chain as-is)
23
+ for (const suiteInChain of suiteChain) {
24
+ if (hookType === 'afterEach') {
25
+ hooks.push(...suiteInChain.afterEach);
26
+ }
27
+ else {
28
+ hooks.push(...suiteInChain.afterAll);
29
+ }
30
+ }
31
+ }
32
+ return hooks;
33
+ };
34
+ export const runHooks = async (suite, hookType) => {
35
+ const hooks = collectInheritedHooks(suite, hookType);
36
+ for (const hook of hooks) {
37
+ await hook();
38
+ }
39
+ };
@@ -0,0 +1,4 @@
1
+ export type { TestRunnerEventsEmitter, TestRunner, TestRunnerContext, } from './types.js';
2
+ export { TestExecutionError } from './errors.js';
3
+ export { getTestRunner } from './factory.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/runner/index.ts"],"names":[],"mappings":"AAAA,YAAY,EACV,uBAAuB,EACvB,UAAU,EACV,iBAAiB,GAClB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { TestExecutionError } from './errors.js';
2
+ export { getTestRunner } from './factory.js';
@@ -0,0 +1,4 @@
1
+ import type { TestSuite, TestSuiteResult } from '@react-native-harness/bridge';
2
+ import { TestRunnerContext } from './types.js';
3
+ export declare const runSuite: (suite: TestSuite, context: TestRunnerContext) => Promise<TestSuiteResult>;
4
+ //# sourceMappingURL=runSuite.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runSuite.d.ts","sourceRoot":"","sources":["../../src/runner/runSuite.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAGV,SAAS,EACT,eAAe,EAChB,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAsH/C,eAAO,MAAM,QAAQ,GACnB,OAAO,SAAS,EAChB,SAAS,iBAAiB,KACzB,OAAO,CAAC,eAAe,CAuEzB,CAAC"}
@@ -0,0 +1,147 @@
1
+ import { runHooks } from './hooks.js';
2
+ import { TestExecutionError } from './errors.js';
3
+ const runTest = async (test, suite, context) => {
4
+ const startTime = Date.now();
5
+ // Emit test-started event
6
+ context.events.emit({
7
+ type: 'test-started',
8
+ name: test.name,
9
+ suite: suite.name,
10
+ file: context.testFilePath,
11
+ });
12
+ try {
13
+ if (test.status === 'skipped') {
14
+ const result = {
15
+ name: test.name,
16
+ status: 'skipped',
17
+ duration: 0,
18
+ };
19
+ // Emit test-finished event
20
+ context.events.emit({
21
+ type: 'test-finished',
22
+ name: test.name,
23
+ suite: suite.name,
24
+ file: context.testFilePath,
25
+ duration: 0,
26
+ status: 'skipped',
27
+ });
28
+ return result;
29
+ }
30
+ if (test.status === 'todo') {
31
+ console.log(`- ${test.name} (todo)`);
32
+ const result = {
33
+ name: test.name,
34
+ status: 'todo',
35
+ duration: 0,
36
+ };
37
+ // Emit test-finished event
38
+ context.events.emit({
39
+ type: 'test-finished',
40
+ name: test.name,
41
+ suite: suite.name,
42
+ file: context.testFilePath,
43
+ duration: 0,
44
+ status: 'todo',
45
+ });
46
+ return result;
47
+ }
48
+ // Run all beforeEach hooks from the current suite and its parents
49
+ await runHooks(suite, 'beforeEach');
50
+ // Run the actual test
51
+ await test.fn();
52
+ // Run all afterEach hooks from the current suite and its parents
53
+ await runHooks(suite, 'afterEach');
54
+ const duration = Date.now() - startTime;
55
+ const result = {
56
+ name: test.name,
57
+ status: 'passed',
58
+ duration,
59
+ };
60
+ // Emit test-finished event
61
+ context.events.emit({
62
+ type: 'test-finished',
63
+ file: context.testFilePath,
64
+ suite: suite.name,
65
+ name: test.name,
66
+ duration,
67
+ status: 'passed',
68
+ });
69
+ return result;
70
+ }
71
+ catch (error) {
72
+ const testError = new TestExecutionError(error, context.testFilePath, suite.name, test.name);
73
+ const duration = Date.now() - startTime;
74
+ const result = {
75
+ name: test.name,
76
+ status: 'failed',
77
+ error: testError.toSerializedJSON(),
78
+ duration,
79
+ };
80
+ // Emit test-finished event
81
+ context.events.emit({
82
+ type: 'test-finished',
83
+ file: context.testFilePath,
84
+ suite: suite.name,
85
+ name: test.name,
86
+ duration,
87
+ error: testError.toSerializedJSON(),
88
+ status: 'failed',
89
+ });
90
+ return result;
91
+ }
92
+ };
93
+ export const runSuite = async (suite, context) => {
94
+ const startTime = Date.now();
95
+ // Emit suite-started event
96
+ context.events.emit({
97
+ type: 'suite-started',
98
+ name: suite.name,
99
+ file: context.testFilePath,
100
+ });
101
+ const testResults = [];
102
+ const suiteResults = [];
103
+ // Run beforeAll hooks
104
+ await runHooks(suite, 'beforeAll');
105
+ // Run all tests in the current suite
106
+ for (const test of suite.tests) {
107
+ const result = await runTest(test, suite, context);
108
+ testResults.push(result);
109
+ }
110
+ // Run all child suites
111
+ for (const childSuite of suite.suites) {
112
+ const result = await runSuite(childSuite, context);
113
+ suiteResults.push(result);
114
+ }
115
+ // Run afterAll hooks
116
+ await runHooks(suite, 'afterAll');
117
+ const duration = Date.now() - startTime;
118
+ // Determine overall suite status
119
+ let status = 'passed';
120
+ // Check if any tests or child suites failed
121
+ const hasFailedTests = testResults.some((result) => result.status === 'failed');
122
+ const hasFailedSuites = suiteResults.some((result) => result.status === 'failed');
123
+ if (hasFailedTests || hasFailedSuites) {
124
+ status = 'failed';
125
+ }
126
+ else if ((testResults.every((result) => result.status === 'skipped') &&
127
+ suiteResults.every((result) => result.status === 'skipped') &&
128
+ testResults.length > 0) ||
129
+ suiteResults.length > 0) {
130
+ status = 'skipped';
131
+ }
132
+ // Emit suite-finished event
133
+ context.events.emit({
134
+ type: 'suite-finished',
135
+ file: context.testFilePath,
136
+ name: suite.name,
137
+ duration,
138
+ status,
139
+ });
140
+ return {
141
+ name: suite.name,
142
+ tests: testResults,
143
+ suites: suiteResults,
144
+ status,
145
+ duration,
146
+ };
147
+ };
@@ -0,0 +1,13 @@
1
+ import { EventEmitter } from '../utils/emitter.js';
2
+ import type { TestRunnerEvents, TestSuite, TestSuiteResult } from '@react-native-harness/bridge';
3
+ export type TestRunnerEventsEmitter = EventEmitter<TestRunnerEvents>;
4
+ export type TestRunnerContext = {
5
+ events: TestRunnerEventsEmitter;
6
+ testFilePath: string;
7
+ };
8
+ export type TestRunner = {
9
+ events: TestRunnerEventsEmitter;
10
+ run: (testSuite: TestSuite, testFilePath: string) => Promise<TestSuiteResult>;
11
+ dispose: () => void;
12
+ };
13
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/runner/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EACV,gBAAgB,EAChB,SAAS,EACT,eAAe,EAChB,MAAM,8BAA8B,CAAC;AAEtC,MAAM,MAAM,uBAAuB,GAAG,YAAY,CAAC,gBAAgB,CAAC,CAAC;AAErE,MAAM,MAAM,iBAAiB,GAAG;IAC9B,MAAM,EAAE,uBAAuB,CAAC;IAChC,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,MAAM,EAAE,uBAAuB,CAAC;IAChC,GAAG,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;IAC9E,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import type { TestSuite } from './collector/index.js';
2
+ import type { SuiteResult, BridgeEvents } from '@react-native-harness/bridge';
3
+ export type TestRunnerContext = {
4
+ testFilePath: string;
5
+ };
6
+ export declare function runSuite(suite: TestSuite, eventHandler: (event: BridgeEvents[keyof BridgeEvents]) => void, context: TestRunnerContext): Promise<SuiteResult>;
7
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAY,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAChE,OAAO,KAAK,EAEV,WAAW,EACX,YAAY,EACb,MAAM,8BAA8B,CAAC;AAEtC,MAAM,MAAM,iBAAiB,GAAG;IAC9B,YAAY,EAAE,MAAM,CAAC;CACtB,CAAC;AAsKF,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,SAAS,EAChB,YAAY,EAAE,CAAC,KAAK,EAAE,YAAY,CAAC,MAAM,YAAY,CAAC,KAAK,IAAI,EAC/D,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,WAAW,CAAC,CAmFtB"}
package/dist/runner.js ADDED
@@ -0,0 +1,201 @@
1
+ async function runHooks(hooks) {
2
+ for (const hook of hooks) {
3
+ await hook();
4
+ }
5
+ }
6
+ function collectInheritedHooks(suite, hookType) {
7
+ const hooks = [];
8
+ const suiteChain = [];
9
+ // Collect all suites from current to root
10
+ let currentSuite = suite;
11
+ while (currentSuite) {
12
+ suiteChain.push(currentSuite);
13
+ currentSuite = currentSuite.parent;
14
+ }
15
+ if (hookType === 'beforeEach') {
16
+ // For beforeEach: run parent hooks first (reverse the chain)
17
+ for (let i = suiteChain.length - 1; i >= 0; i--) {
18
+ hooks.push(...suiteChain[i].beforeEach);
19
+ }
20
+ }
21
+ else {
22
+ // For afterEach: run child hooks first (use chain as-is)
23
+ for (const suiteInChain of suiteChain) {
24
+ hooks.push(...suiteInChain.afterEach);
25
+ }
26
+ }
27
+ return hooks;
28
+ }
29
+ async function runTest(test, suite, eventHandler, context) {
30
+ const startTime = Date.now();
31
+ // Emit test-started event
32
+ eventHandler({
33
+ type: 'test-started',
34
+ path: context.testFilePath,
35
+ suite: suite.name,
36
+ name: test.name,
37
+ });
38
+ try {
39
+ if (test.status === 'skipped') {
40
+ console.log(`- ${test.name} (skipped)`);
41
+ const result = {
42
+ name: test.name,
43
+ status: 'skipped',
44
+ duration: 0,
45
+ };
46
+ // Emit test-finished event
47
+ eventHandler({
48
+ type: 'test-finished',
49
+ path: context.testFilePath,
50
+ suite: suite.name,
51
+ name: test.name,
52
+ duration: 0,
53
+ reason: 'skipped',
54
+ });
55
+ return result;
56
+ }
57
+ if (test.status === 'todo') {
58
+ console.log(`- ${test.name} (todo)`);
59
+ const result = {
60
+ name: test.name,
61
+ status: 'todo',
62
+ duration: 0,
63
+ };
64
+ // Emit test-finished event
65
+ eventHandler({
66
+ type: 'test-finished',
67
+ path: context.testFilePath,
68
+ suite: suite.name,
69
+ name: test.name,
70
+ duration: 0,
71
+ reason: 'todo',
72
+ });
73
+ return result;
74
+ }
75
+ // Run all beforeEach hooks from the current suite and its parents
76
+ const beforeEachHooks = collectInheritedHooks(suite, 'beforeEach');
77
+ await runHooks(beforeEachHooks);
78
+ // Run the actual test
79
+ await test.fn();
80
+ // Run all afterEach hooks from the current suite and its parents
81
+ const afterEachHooks = collectInheritedHooks(suite, 'afterEach');
82
+ await runHooks(afterEachHooks);
83
+ const duration = Date.now() - startTime;
84
+ console.log(`✓ ${test.name}`);
85
+ const result = {
86
+ name: test.name,
87
+ status: 'passed',
88
+ duration,
89
+ };
90
+ // Emit test-finished event
91
+ eventHandler({
92
+ type: 'test-finished',
93
+ path: context.testFilePath,
94
+ suite: suite.name,
95
+ name: test.name,
96
+ duration,
97
+ reason: 'passed',
98
+ });
99
+ return result;
100
+ }
101
+ catch (error) {
102
+ const duration = Date.now() - startTime;
103
+ console.error(`✗ ${test.name}`);
104
+ console.error(error);
105
+ const result = {
106
+ name: test.name,
107
+ status: 'failed',
108
+ error: {
109
+ name: typeof error === 'object' && error !== null && 'name' in error
110
+ ? error.name
111
+ : 'Unknown error',
112
+ message: typeof error === 'object' && error !== null && 'message' in error
113
+ ? error.message
114
+ : JSON.stringify(error),
115
+ stack: typeof error === 'object' && error !== null && 'stack' in error
116
+ ? error.stack
117
+ : undefined,
118
+ },
119
+ duration,
120
+ };
121
+ // Emit test-finished event
122
+ eventHandler({
123
+ type: 'test-finished',
124
+ path: context.testFilePath,
125
+ suite: suite.name,
126
+ name: test.name,
127
+ duration,
128
+ reason: 'failed',
129
+ });
130
+ return result;
131
+ }
132
+ }
133
+ export async function runSuite(suite, eventHandler, context) {
134
+ console.log(`\n${suite.name}`);
135
+ const startTime = Date.now();
136
+ // Emit suite-started event
137
+ eventHandler({
138
+ type: 'suite-started',
139
+ path: context.testFilePath,
140
+ name: suite.name,
141
+ });
142
+ const testResults = [];
143
+ const suiteResults = [];
144
+ let suiteError;
145
+ try {
146
+ // Run beforeAll hooks
147
+ await runHooks(suite.beforeAll);
148
+ // Run all tests in the current suite
149
+ for (const test of suite.tests) {
150
+ const result = await runTest(test, suite, eventHandler, context);
151
+ testResults.push(result);
152
+ }
153
+ // Run all child suites
154
+ for (const childSuite of suite.suites) {
155
+ const result = await runSuite(childSuite, eventHandler, context);
156
+ suiteResults.push(result);
157
+ }
158
+ // Run afterAll hooks
159
+ await runHooks(suite.afterAll);
160
+ }
161
+ catch (error) {
162
+ console.error(`Suite "${suite.name}" failed`);
163
+ suiteError = error;
164
+ }
165
+ const duration = Date.now() - startTime;
166
+ // Determine overall suite status
167
+ let status = 'passed';
168
+ if (suiteError) {
169
+ status = 'failed';
170
+ }
171
+ else {
172
+ // Check if any tests or child suites failed
173
+ const hasFailedTests = testResults.some((result) => result.status === 'failed');
174
+ const hasFailedSuites = suiteResults.some((result) => result.status === 'failed');
175
+ if (hasFailedTests || hasFailedSuites) {
176
+ status = 'failed';
177
+ }
178
+ else if ((testResults.every((result) => result.status === 'skipped') &&
179
+ suiteResults.every((result) => result.status === 'skipped') &&
180
+ testResults.length > 0) ||
181
+ suiteResults.length > 0) {
182
+ status = 'skipped';
183
+ }
184
+ }
185
+ // Emit suite-finished event
186
+ eventHandler({
187
+ type: 'suite-finished',
188
+ path: context.testFilePath,
189
+ name: suite.name,
190
+ duration,
191
+ reason: status,
192
+ });
193
+ return {
194
+ name: suite.name,
195
+ tests: testResults,
196
+ suites: suiteResults,
197
+ status,
198
+ error: suiteError,
199
+ duration,
200
+ };
201
+ }
@@ -0,0 +1,2 @@
1
+ export declare const getClient: () => Promise<import("@react-native-harness/bridge/client").BridgeClient>;
2
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,SAAS,2EA6CrB,CAAC"}
@@ -0,0 +1,44 @@
1
+ import { getBridgeClient } from '@react-native-harness/bridge/client';
2
+ import { fetchModule, evaluateModule } from './bundler/index.js';
3
+ import { state } from './state.js';
4
+ import { getTestRunner } from './runner/index.js';
5
+ import { getTestCollector } from './collector/index.js';
6
+ import { combineEventEmitters } from './utils/emitter.js';
7
+ import { getWSServer } from './client/getWSServer.js';
8
+ import { Alert } from 'react-native';
9
+ import { getDeviceDescriptor } from './client/getDeviceDescriptor.js';
10
+ export const getClient = async () => {
11
+ const client = await getBridgeClient(getWSServer(), {
12
+ runTests: async () => {
13
+ throw new Error('Not implemented');
14
+ },
15
+ });
16
+ Alert.alert('Device', JSON.stringify(getDeviceDescriptor()));
17
+ client.rpc.$functions.runTests = async (path) => {
18
+ if (state.getState().status === 'running') {
19
+ throw new Error('Already running tests');
20
+ }
21
+ state.getState().setStatus('running');
22
+ let collector = null;
23
+ let runner = null;
24
+ let events = null;
25
+ try {
26
+ collector = getTestCollector();
27
+ runner = getTestRunner();
28
+ events = combineEventEmitters(collector.events, runner.events);
29
+ events.addListener((event) => {
30
+ client.rpc.emitEvent(event.type, event);
31
+ });
32
+ const moduleJs = await fetchModule(path);
33
+ const collectionResult = await collector.collect(() => evaluateModule(moduleJs, path), path);
34
+ return await runner.run(collectionResult.testSuite, path);
35
+ }
36
+ finally {
37
+ collector?.dispose();
38
+ runner?.dispose();
39
+ events?.clearAllListeners();
40
+ state.getState().setStatus('idle');
41
+ }
42
+ };
43
+ return client;
44
+ };
@@ -0,0 +1,2 @@
1
+ export * from '@vitest/spy';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/spy/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ // @vitest/spy is available as a no-dependency package, so we can re-use it in React Native Harness!
2
+ export * from '@vitest/spy';
@@ -0,0 +1,25 @@
1
+ import { ReactNode } from 'react';
2
+ import { BridgeClient } from '@react-native-harness/bridge/client';
3
+ type ComponentHarnessState = {
4
+ node: ReactNode;
5
+ options: unknown;
6
+ layout: {
7
+ x: number;
8
+ y: number;
9
+ width: number;
10
+ height: number;
11
+ };
12
+ };
13
+ type RunnerState = {
14
+ status: 'loading' | 'idle' | 'running';
15
+ componentHarness: ComponentHarnessState | null;
16
+ client: BridgeClient | null;
17
+ setStatus: (status: 'idle' | 'running') => void;
18
+ render: (node: ReactNode, options: unknown) => void;
19
+ cleanup: () => void;
20
+ bootstrap: () => Promise<void>;
21
+ reportLayout: (x: number, y: number, width: number, height: number) => void;
22
+ };
23
+ export declare const state: import("zustand/react").UseBoundStore<import("zustand/vanilla").StoreApi<RunnerState>>;
24
+ export {};
25
+ //# sourceMappingURL=state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAElC,OAAO,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAGnE,KAAK,qBAAqB,GAAG;IAC3B,IAAI,EAAE,SAAS,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;CACjE,CAAC;AAEF,KAAK,WAAW,GAAG;IACjB,MAAM,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS,CAAC;IACvC,gBAAgB,EAAE,qBAAqB,GAAG,IAAI,CAAC;IAC/C,MAAM,EAAE,YAAY,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;IAChD,MAAM,EAAE,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IACpD,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,YAAY,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7E,CAAC;AAUF,eAAO,MAAM,KAAK,wFA+Bf,CAAC"}
package/dist/state.js ADDED
@@ -0,0 +1,37 @@
1
+ import { create } from 'zustand/react';
2
+ import { getClient } from './runtime.js';
3
+ function assertComponentHarnessReady(state) {
4
+ if (!state.componentHarness) {
5
+ throw new Error('ComponentHarness is not ready');
6
+ }
7
+ }
8
+ export const state = create((set) => ({
9
+ status: 'loading',
10
+ componentHarness: null,
11
+ client: null,
12
+ setStatus: (status) => set({ status }),
13
+ render: (node, options) => set({
14
+ componentHarness: {
15
+ node,
16
+ options,
17
+ layout: { x: 0, y: 0, width: 0, height: 0 },
18
+ },
19
+ }),
20
+ cleanup: () => set({ componentHarness: null }),
21
+ bootstrap: async () => {
22
+ const client = await getClient();
23
+ set({ client, status: 'idle' });
24
+ client.rpc.reportReady();
25
+ },
26
+ reportLayout: (x, y, width, height) => {
27
+ set((state) => {
28
+ assertComponentHarnessReady(state);
29
+ return {
30
+ componentHarness: {
31
+ ...state.componentHarness,
32
+ layout: { x, y, width, height },
33
+ },
34
+ };
35
+ });
36
+ },
37
+ }));