@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,538 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { fn, spyOn, clearAllMocks } from '../spy/index.js';
3
+
4
+ describe('spies', () => {
5
+ beforeEach(() => {
6
+ clearAllMocks();
7
+ });
8
+
9
+ describe('fn()', () => {
10
+ it('should create a spy function', () => {
11
+ const spy = fn();
12
+ expect(typeof spy).toBe('function');
13
+ expect(spy.mock).toBeDefined();
14
+ expect(spy.mock.calls).toEqual([]);
15
+ expect(spy.mock.instances).toEqual([]);
16
+ expect(spy.mock.contexts).toEqual([]);
17
+ expect(spy.mock.results).toEqual([]);
18
+ expect(spy.mock.lastCall).toBeUndefined();
19
+ });
20
+
21
+ it('should track function calls', () => {
22
+ const spy = fn();
23
+
24
+ spy('arg1', 'arg2');
25
+ spy('arg3');
26
+
27
+ expect(spy.mock.calls).toEqual([['arg1', 'arg2'], ['arg3']]);
28
+ expect(spy.mock.lastCall).toEqual(['arg3']);
29
+ });
30
+
31
+ it('should track call contexts', () => {
32
+ const spy = fn();
33
+ const obj = { method: spy };
34
+
35
+ obj.method('test');
36
+ spy.call('custom-context', 'arg');
37
+
38
+ expect(spy.mock.contexts).toHaveLength(2);
39
+ expect(spy.mock.contexts[0]).toBe(obj);
40
+ expect(spy.mock.contexts[1]).toBe('custom-context');
41
+ });
42
+
43
+ it('should track return values and exceptions', () => {
44
+ const spy = fn();
45
+
46
+ spy(); // undefined return
47
+
48
+ expect(spy.mock.results).toEqual([{ type: 'return', value: undefined }]);
49
+ });
50
+
51
+ it('should work with implementation', () => {
52
+ const implementation = (a: number, b: number) => a + b;
53
+ const spy = fn(implementation);
54
+
55
+ const result = spy(2, 3);
56
+
57
+ expect(result).toBe(5);
58
+ expect(spy.mock.calls).toEqual([[2, 3]]);
59
+ expect(spy.mock.results[0]).toEqual({ type: 'return', value: 5 });
60
+ });
61
+
62
+ it('should handle constructor calls', () => {
63
+ const spy = fn(function (this: { name: string }, name: string) {
64
+ this.name = name;
65
+ });
66
+
67
+ const instance = new (spy as unknown as new (name: string) => {
68
+ name: string;
69
+ })('test');
70
+
71
+ expect(spy.mock.instances).toHaveLength(1);
72
+ expect(spy.mock.instances[0]).toBe(instance);
73
+ expect(instance.name).toBe('test');
74
+ });
75
+ });
76
+
77
+ describe('mockClear()', () => {
78
+ it('should clear call history but keep implementation', () => {
79
+ const spy = fn(() => 'result');
80
+
81
+ spy();
82
+ spy();
83
+
84
+ expect(spy.mock.calls).toHaveLength(2);
85
+
86
+ spy.mockClear();
87
+
88
+ expect(spy.mock.calls).toEqual([]);
89
+ expect(spy.mock.instances).toEqual([]);
90
+ expect(spy.mock.contexts).toEqual([]);
91
+ expect(spy.mock.results).toEqual([]);
92
+ expect(spy.mock.lastCall).toBeUndefined();
93
+
94
+ // Implementation should still work
95
+ const result = spy();
96
+ expect(result).toBe('result');
97
+ });
98
+ });
99
+
100
+ describe('mockReset()', () => {
101
+ it('should clear history and reset behavior', () => {
102
+ const spy = fn(() => 'original');
103
+ spy.mockReturnValue('mocked');
104
+
105
+ spy();
106
+ expect(spy()).toBe('mocked');
107
+
108
+ spy.mockReset();
109
+
110
+ expect(spy.mock.calls).toEqual([]);
111
+ expect(spy()).toBe('original'); // Back to original implementation
112
+ });
113
+ });
114
+
115
+ describe('mockReturnValue()', () => {
116
+ it('should mock return value', () => {
117
+ const spy = fn();
118
+ spy.mockReturnValue('mocked');
119
+
120
+ const result = spy('arg');
121
+
122
+ expect(result).toBe('mocked');
123
+ expect(spy.mock.calls).toEqual([['arg']]);
124
+ expect(spy.mock.results[0]).toEqual({ type: 'return', value: 'mocked' });
125
+ });
126
+ });
127
+
128
+ describe('mockReturnValueOnce()', () => {
129
+ it('should mock return value for one call only', () => {
130
+ const spy = fn(() => 'default');
131
+ spy.mockReturnValueOnce('once');
132
+
133
+ expect(spy()).toBe('once');
134
+ expect(spy()).toBe('default');
135
+ expect(spy()).toBe('default');
136
+ });
137
+ });
138
+
139
+ describe('mockResolvedValue()', () => {
140
+ it('should mock resolved promise value', async () => {
141
+ const spy = fn();
142
+ spy.mockResolvedValue('resolved');
143
+
144
+ const result = await spy();
145
+
146
+ expect(result).toBe('resolved');
147
+ expect(spy.mock.results[0].value).toBeInstanceOf(Promise);
148
+ });
149
+ });
150
+
151
+ describe('mockResolvedValueOnce()', () => {
152
+ it('should mock resolved value for one call only', async () => {
153
+ const spy = fn(() => Promise.resolve('default'));
154
+ spy.mockResolvedValueOnce('once');
155
+
156
+ expect(await spy()).toBe('once');
157
+ expect(await spy()).toBe('default');
158
+ });
159
+ });
160
+
161
+ describe('mockRejectedValue()', () => {
162
+ it('should mock rejected promise value', async () => {
163
+ const spy = fn();
164
+ spy.mockRejectedValue('error');
165
+
166
+ await expect(spy()).rejects.toBe('error');
167
+ expect(spy.mock.results[0].value).toBeInstanceOf(Promise);
168
+ });
169
+ });
170
+
171
+ describe('mockRejectedValueOnce()', () => {
172
+ it('should mock rejected value for one call only', async () => {
173
+ const spy = fn(() => Promise.resolve('default'));
174
+ spy.mockRejectedValueOnce('error');
175
+
176
+ await expect(spy()).rejects.toBe('error');
177
+ expect(await spy()).toBe('default');
178
+ });
179
+ });
180
+
181
+ describe('mockImplementation()', () => {
182
+ it('should replace implementation', () => {
183
+ const spy = fn(() => 'original');
184
+ spy.mockImplementation(() => 'mocked');
185
+
186
+ expect(spy()).toBe('mocked');
187
+ expect(spy.mock.calls).toEqual([[]]);
188
+ });
189
+
190
+ it('should handle constructor calls with mock implementation', () => {
191
+ const spy = fn();
192
+ spy.mockImplementation(function (this: unknown, name: string) {
193
+ (this as { name: string; mocked: boolean }).name = name;
194
+ (this as { name: string; mocked: boolean }).mocked = true;
195
+ });
196
+
197
+ const instance = new (spy as unknown as new (name: string) => {
198
+ name: string;
199
+ mocked: boolean;
200
+ })('test');
201
+
202
+ expect(instance.name).toBe('test');
203
+ expect(instance.mocked).toBe(true);
204
+ expect(spy.mock.instances[0]).toBe(instance);
205
+ });
206
+ });
207
+
208
+ describe('mockImplementationOnce()', () => {
209
+ it('should replace implementation for one call only', () => {
210
+ const spy = fn(() => 'original');
211
+ spy.mockImplementationOnce(() => 'once');
212
+
213
+ expect(spy()).toBe('once');
214
+ expect(spy()).toBe('original');
215
+ });
216
+ });
217
+
218
+ describe('mockReturnThis()', () => {
219
+ it('should return this context', () => {
220
+ const spy = fn();
221
+ spy.mockReturnThis();
222
+
223
+ const obj = { method: spy };
224
+ const result = obj.method();
225
+
226
+ expect(result).toBe(obj);
227
+ });
228
+ });
229
+
230
+ describe('Vitest spy assertions', () => {
231
+ describe('toHaveBeenCalled()', () => {
232
+ it('should verify spy was called', () => {
233
+ const spy = fn();
234
+ expect(spy).not.toHaveBeenCalled();
235
+
236
+ spy();
237
+ expect(spy).toHaveBeenCalled();
238
+ });
239
+ });
240
+
241
+ describe('toHaveBeenCalledTimes()', () => {
242
+ it('should check call count', () => {
243
+ const spy = fn();
244
+
245
+ expect(spy).toHaveBeenCalledTimes(0);
246
+ expect(spy).not.toHaveBeenCalledTimes(1);
247
+
248
+ spy();
249
+ spy();
250
+
251
+ expect(spy).toHaveBeenCalledTimes(2);
252
+ expect(spy).not.toHaveBeenCalledTimes(1);
253
+ });
254
+ });
255
+
256
+ describe('toHaveBeenCalledWith()', () => {
257
+ it('should check if called with specific arguments', () => {
258
+ const spy = fn();
259
+
260
+ spy('arg1', 'arg2');
261
+ spy('arg3');
262
+
263
+ expect(spy).toHaveBeenCalledWith('arg1', 'arg2');
264
+ expect(spy).toHaveBeenCalledWith('arg3');
265
+ expect(spy).not.toHaveBeenCalledWith('arg4');
266
+ expect(spy).not.toHaveBeenCalledWith('arg1'); // Wrong argument count
267
+ });
268
+ });
269
+
270
+ describe('toHaveBeenLastCalledWith()', () => {
271
+ it('should check if last call was with specific arguments', () => {
272
+ const spy = fn();
273
+
274
+ spy('arg1');
275
+ spy('arg2', 'arg3');
276
+
277
+ expect(spy).toHaveBeenLastCalledWith('arg2', 'arg3');
278
+ expect(spy).not.toHaveBeenLastCalledWith('arg1');
279
+ });
280
+
281
+ it('should return false if never called', () => {
282
+ const spy = fn();
283
+ expect(spy).not.toHaveBeenLastCalledWith('arg');
284
+ });
285
+ });
286
+
287
+ describe('toHaveBeenNthCalledWith()', () => {
288
+ it('should check nth call arguments', () => {
289
+ const spy = fn();
290
+
291
+ spy('first');
292
+ spy('second');
293
+ spy('third');
294
+
295
+ expect(spy).toHaveBeenNthCalledWith(1, 'first');
296
+ expect(spy).toHaveBeenNthCalledWith(2, 'second');
297
+ expect(spy).toHaveBeenNthCalledWith(3, 'third');
298
+ expect(spy).not.toHaveBeenNthCalledWith(1, 'wrong');
299
+ expect(spy).not.toHaveBeenNthCalledWith(4, 'fourth');
300
+ expect(spy).not.toHaveBeenNthCalledWith(0, 'zero');
301
+ });
302
+ });
303
+
304
+ describe('toHaveReturnedWith()', () => {
305
+ it('should check if spy returned specific value', () => {
306
+ const spy = fn();
307
+ spy.mockReturnValueOnce('value1');
308
+ spy.mockReturnValueOnce('value2');
309
+
310
+ spy();
311
+ spy();
312
+
313
+ expect(spy).toHaveReturnedWith('value1');
314
+ expect(spy).toHaveReturnedWith('value2');
315
+ expect(spy).not.toHaveReturnedWith('value3');
316
+ });
317
+ });
318
+
319
+ describe('toHaveLastReturnedWith()', () => {
320
+ it('should check if last call returned specific value', () => {
321
+ const spy = fn();
322
+ spy.mockReturnValueOnce('first');
323
+ spy.mockReturnValueOnce('last');
324
+
325
+ spy();
326
+ spy();
327
+
328
+ expect(spy).toHaveLastReturnedWith('last');
329
+ expect(spy).not.toHaveLastReturnedWith('first');
330
+ });
331
+
332
+ it('should return false if never called', () => {
333
+ const spy = fn();
334
+ expect(spy).not.toHaveLastReturnedWith('value');
335
+ });
336
+ });
337
+
338
+ describe('toHaveNthReturnedWith()', () => {
339
+ it('should check nth call return value', () => {
340
+ const spy = fn();
341
+ spy.mockReturnValueOnce('first');
342
+ spy.mockReturnValueOnce('second');
343
+
344
+ spy();
345
+ spy();
346
+
347
+ expect(spy).toHaveNthReturnedWith(1, 'first');
348
+ expect(spy).toHaveNthReturnedWith(2, 'second');
349
+ expect(spy).not.toHaveNthReturnedWith(1, 'wrong');
350
+ expect(spy).not.toHaveNthReturnedWith(3, 'third');
351
+ });
352
+ });
353
+
354
+ describe('toHaveReturnedTimes()', () => {
355
+ it('should count successful returns (not throws)', () => {
356
+ const spy = fn();
357
+ spy.mockReturnValueOnce('success');
358
+ spy.mockImplementationOnce(() => {
359
+ throw new Error('fail');
360
+ });
361
+ spy.mockReturnValueOnce('success2');
362
+
363
+ spy();
364
+ try {
365
+ spy();
366
+ } catch {
367
+ // This should throw - expected behavior
368
+ }
369
+ spy();
370
+
371
+ expect(spy).toHaveReturnedTimes(2);
372
+ expect(spy).not.toHaveReturnedTimes(3);
373
+ });
374
+ });
375
+
376
+ describe('Promise-specific methods', () => {
377
+ describe('promise resolution checks', () => {
378
+ it('should check if spy resolved with specific value', async () => {
379
+ const spy = fn();
380
+ spy.mockResolvedValueOnce('resolved1');
381
+ spy.mockResolvedValueOnce('resolved2');
382
+
383
+ const result1 = await spy();
384
+ const result2 = await spy();
385
+
386
+ expect(result1).toBe('resolved1');
387
+ expect(result2).toBe('resolved2');
388
+ expect(spy).toHaveBeenCalledTimes(2);
389
+ });
390
+
391
+ it('should handle non-promise returns', async () => {
392
+ const spy = fn();
393
+ spy.mockReturnValue('not-promise');
394
+
395
+ const result = spy();
396
+
397
+ expect(result).toBe('not-promise');
398
+ expect(spy).toHaveBeenCalledTimes(1);
399
+ });
400
+ });
401
+
402
+ describe('promise rejection checks', () => {
403
+ it('should check if spy rejected with specific value', async () => {
404
+ const spy = fn();
405
+ spy.mockRejectedValueOnce('error1');
406
+ spy.mockRejectedValueOnce('error2');
407
+
408
+ await expect(spy()).rejects.toBe('error1');
409
+ await expect(spy()).rejects.toBe('error2');
410
+
411
+ expect(spy).toHaveBeenCalledTimes(2);
412
+ });
413
+ });
414
+ });
415
+ });
416
+
417
+ describe('spyOn()', () => {
418
+ let testObject: { method: (x: number) => number; property: string };
419
+
420
+ beforeEach(() => {
421
+ testObject = {
422
+ method: (x: number) => x * 2,
423
+ property: 'original',
424
+ };
425
+ });
426
+
427
+ it('should spy on existing method', () => {
428
+ const spy = spyOn(testObject, 'method');
429
+
430
+ const result = testObject.method(5);
431
+
432
+ expect(result).toBe(10); // Original implementation
433
+ expect(spy.mock.calls).toEqual([[5]]);
434
+ expect(spy).toHaveBeenCalledWith(5);
435
+ });
436
+
437
+ it('should allow mocking the spied method', () => {
438
+ const spy = spyOn(testObject, 'method');
439
+ spy.mockReturnValue(99);
440
+
441
+ const result = testObject.method(5);
442
+
443
+ expect(result).toBe(99);
444
+ expect(spy.mock.calls).toEqual([[5]]);
445
+ });
446
+
447
+ it('should restore original method', () => {
448
+ const spy = spyOn(testObject, 'method');
449
+
450
+ spy.mockReturnValue(99);
451
+ expect(testObject.method(5)).toBe(99);
452
+
453
+ spy.mockRestore();
454
+ expect(testObject.method(5)).toBe(10); // Back to original
455
+ });
456
+
457
+ it('should track context correctly', () => {
458
+ const spy = spyOn(testObject, 'method');
459
+
460
+ testObject.method(5);
461
+
462
+ expect(spy.mock.contexts[0]).toBe(testObject);
463
+ });
464
+
465
+ it('should handle constructor spying', () => {
466
+ class TestClass {
467
+ constructor(public name: string) {}
468
+ method() {
469
+ return this.name;
470
+ }
471
+ }
472
+
473
+ const spy = spyOn(TestClass.prototype, 'method');
474
+
475
+ const instance = new TestClass('test');
476
+ const result = instance.method();
477
+
478
+ expect(result).toBe('test');
479
+ expect(spy.mock.calls).toHaveLength(1);
480
+ expect(spy.mock.contexts[0]).toBe(instance);
481
+ });
482
+ });
483
+
484
+ describe('clearAllMocks()', () => {
485
+ it('should clear all spies created', () => {
486
+ const spy1 = fn();
487
+ const spy2 = fn();
488
+ const testObj = { method: () => 'test' };
489
+ const spy3 = spyOn(testObj, 'method');
490
+
491
+ spy1('arg1');
492
+ spy2('arg2');
493
+ testObj.method();
494
+
495
+ expect(spy1.mock.calls).toHaveLength(1);
496
+ expect(spy2.mock.calls).toHaveLength(1);
497
+ expect(spy3.mock.calls).toHaveLength(1);
498
+
499
+ clearAllMocks();
500
+
501
+ // Spies should be cleared
502
+ expect(spy1.mock.calls).toHaveLength(0);
503
+ expect(spy2.mock.calls).toHaveLength(0);
504
+ expect(spy3.mock.calls).toHaveLength(0);
505
+ });
506
+ });
507
+
508
+ describe('TypeScript type safety', () => {
509
+ it('should maintain type safety for function spies', () => {
510
+ const typedFn = (a: string, b: number): boolean => true;
511
+ const spy = fn(typedFn);
512
+
513
+ // These should compile correctly
514
+ const result: boolean = spy('test', 123);
515
+ expect(result).toBe(true);
516
+
517
+ // Mock methods should also be type-safe
518
+ spy.mockReturnValue(false);
519
+ spy.mockImplementation((a: string, b: number) => a.length > b);
520
+ });
521
+
522
+ it('should maintain type safety for spyOn', () => {
523
+ const obj = {
524
+ method: (x: string): number => x.length,
525
+ };
526
+
527
+ const spy = spyOn(obj, 'method');
528
+
529
+ // Should maintain original types
530
+ const result: number = obj.method('test');
531
+ expect(result).toBe(4);
532
+
533
+ // Mock methods should be type-safe
534
+ spy.mockReturnValue(99);
535
+ spy.mockImplementation((x: string) => x.charCodeAt(0));
536
+ });
537
+ });
538
+ });
@@ -0,0 +1,19 @@
1
+ import { Platform } from 'react-native';
2
+ import { getDevServerUrl } from '../utils/dev-server.js';
3
+
4
+ const getModuleUrl = (fileName: string): string => {
5
+ const devServerUrl = getDevServerUrl();
6
+ const bundleName = fileName.split('.').slice(0, -1).join('.') + '.bundle';
7
+ const urlSearchParams = new URLSearchParams({
8
+ modulesOnly: 'true',
9
+ platform: Platform.OS,
10
+ });
11
+
12
+ return `${devServerUrl}/${bundleName}?${urlSearchParams.toString()}`;
13
+ };
14
+
15
+ export const fetchModule = async (fileName: string): Promise<string> => {
16
+ const url = getModuleUrl(fileName);
17
+ const response = await fetch(url);
18
+ return response.text();
19
+ };
@@ -0,0 +1,16 @@
1
+ export class ModuleNotFoundError extends Error {
2
+ constructor(public readonly modulePath: string) {
3
+ super(`Module ${modulePath} not found`);
4
+ this.name = 'ModuleNotFoundError';
5
+ }
6
+ }
7
+
8
+ export class MalformedModuleError extends Error {
9
+ constructor(
10
+ public readonly modulePath: string,
11
+ public readonly reason: string
12
+ ) {
13
+ super(`Module ${modulePath} is malformed: ${reason}`);
14
+ this.name = 'MalformedModuleError';
15
+ }
16
+ }
@@ -0,0 +1,25 @@
1
+ import { MalformedModuleError } from './errors.js';
2
+ import { EnvironmentError } from '../errors.js';
3
+
4
+ export const evaluateModule = (moduleJs: string, modulePath: string): void => {
5
+ const __rMatch = moduleJs.match(/__r\((\d+)\)/);
6
+
7
+ if (!__rMatch) {
8
+ throw new MalformedModuleError(modulePath, 'No __r function found');
9
+ }
10
+
11
+ const __rParam = __rMatch[1];
12
+
13
+ if (!__rParam) {
14
+ throw new MalformedModuleError(modulePath, 'No __r parameter found');
15
+ }
16
+
17
+ // eslint-disable-next-line no-eval
18
+ eval(moduleJs);
19
+
20
+ if (!__r) {
21
+ throw new EnvironmentError('module evaluation', '__r is not defined');
22
+ }
23
+
24
+ __r(Number(__rParam));
25
+ };
@@ -0,0 +1,2 @@
1
+ export { fetchModule } from './bundle.js';
2
+ export { evaluateModule } from './evaluate.js';
@@ -0,0 +1,56 @@
1
+ import type {
2
+ TestRunnerEvents,
3
+ TestCollectorEvents,
4
+ } from '@react-native-harness/bridge';
5
+ import { getBridgeClient } from '@react-native-harness/bridge/client';
6
+ import { store } from '../ui/state.js';
7
+ import { getTestRunner, TestRunner } from '../runner/index.js';
8
+ import { getTestCollector, TestCollector } from '../collector/index.js';
9
+ import { combineEventEmitters, EventEmitter } from '../utils/emitter.js';
10
+ import { getWSServer } from './getWSServer.js';
11
+ import { fetchModule, evaluateModule } from '../bundler/index.js';
12
+
13
+ export const getClient = async () => {
14
+ const client = await getBridgeClient(getWSServer(), {
15
+ runTests: async () => {
16
+ throw new Error('Not implemented');
17
+ },
18
+ });
19
+
20
+ client.rpc.$functions.runTests = async (path: string) => {
21
+ if (store.getState().status === 'running') {
22
+ throw new Error('Already running tests');
23
+ }
24
+
25
+ store.getState().setStatus('running');
26
+
27
+ let collector: TestCollector | null = null;
28
+ let runner: TestRunner | null = null;
29
+ let events: EventEmitter<TestRunnerEvents | TestCollectorEvents> | null =
30
+ null;
31
+
32
+ try {
33
+ collector = getTestCollector();
34
+ runner = getTestRunner();
35
+ events = combineEventEmitters(collector.events, runner.events);
36
+
37
+ events.addListener((event) => {
38
+ client.rpc.emitEvent(event.type, event);
39
+ });
40
+
41
+ const moduleJs = await fetchModule(path);
42
+ const collectionResult = await collector.collect(
43
+ () => evaluateModule(moduleJs, path),
44
+ path
45
+ );
46
+ return await runner.run(collectionResult.testSuite, path);
47
+ } finally {
48
+ collector?.dispose();
49
+ runner?.dispose();
50
+ events?.clearAllListeners();
51
+ store.getState().setStatus('idle');
52
+ }
53
+ };
54
+
55
+ return client;
56
+ };
@@ -0,0 +1,30 @@
1
+ import { Platform } from 'react-native';
2
+
3
+ export type DeviceDescriptor = {
4
+ platform: 'ios' | 'android';
5
+ manufacturer: string;
6
+ model: string;
7
+ osVersion: string;
8
+ };
9
+
10
+ export const getDeviceDescriptor = (): DeviceDescriptor => {
11
+ if (Platform.OS === 'ios') {
12
+ return {
13
+ platform: 'ios',
14
+ manufacturer: 'Apple',
15
+ model: 'Unknown',
16
+ osVersion: Platform.constants.osVersion,
17
+ };
18
+ }
19
+
20
+ if (Platform.OS === 'android') {
21
+ return {
22
+ platform: 'android',
23
+ manufacturer: Platform.constants.Manufacturer,
24
+ model: Platform.constants.Model,
25
+ osVersion: Platform.constants.Release,
26
+ };
27
+ }
28
+
29
+ throw new Error('Unsupported platform');
30
+ };
@@ -0,0 +1,9 @@
1
+ import { getDevServerUrl } from '../utils/dev-server.js';
2
+ import { WS_SERVER_PORT } from '../constants.js';
3
+
4
+ export const getWSServer = (): string => {
5
+ const devServerUrl = getDevServerUrl();
6
+ const hostname = devServerUrl.split('://')[1].split(':')[0];
7
+
8
+ return `ws://${hostname}:${WS_SERVER_PORT}`;
9
+ };
@@ -0,0 +1 @@
1
+ export { getClient } from './factory.js';