@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
@@ -0,0 +1,108 @@
1
+ import React from 'react';
2
+ import { View, Text, StyleSheet, ScrollView } from 'react-native';
3
+
4
+ type ErrorBoundaryProps = {
5
+ children: React.ReactNode;
6
+ };
7
+
8
+ type ErrorBoundaryState = {
9
+ hasError: boolean;
10
+ error: Error | null;
11
+ };
12
+
13
+ export class ErrorBoundary extends React.Component<
14
+ ErrorBoundaryProps,
15
+ ErrorBoundaryState
16
+ > {
17
+ constructor(props: ErrorBoundaryProps) {
18
+ super(props);
19
+ this.state = { hasError: false, error: null };
20
+ }
21
+
22
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
23
+ return { hasError: true, error };
24
+ }
25
+
26
+ override componentDidCatch(error: Error, errorInfo: React.ErrorInfo): void {
27
+ console.error('Error caught by ErrorBoundary:', error, errorInfo);
28
+ }
29
+
30
+ override componentDidUpdate(prevProps: ErrorBoundaryProps): void {
31
+ // Reset error state when children change (new component rendered)
32
+ if (prevProps.children !== this.props.children && this.state.hasError) {
33
+ this.setState({ hasError: false, error: null });
34
+ }
35
+ }
36
+
37
+ override render(): React.ReactNode {
38
+ if (this.state.hasError && this.state.error) {
39
+ return (
40
+ <View style={styles.errorContainer}>
41
+ <View style={styles.errorContent}>
42
+ <Text style={styles.errorTitle}>Component Error</Text>
43
+ <Text style={styles.errorSubtitle}>
44
+ The rendered component threw an error:
45
+ </Text>
46
+ <ScrollView style={styles.errorScrollView}>
47
+ <Text style={styles.errorMessage}>
48
+ {this.state.error.message}
49
+ </Text>
50
+ {this.state.error.stack && (
51
+ <Text style={styles.errorStack}>{this.state.error.stack}</Text>
52
+ )}
53
+ </ScrollView>
54
+ </View>
55
+ </View>
56
+ );
57
+ }
58
+
59
+ return this.props.children;
60
+ }
61
+ }
62
+
63
+ const styles = StyleSheet.create({
64
+ errorContainer: {
65
+ flex: 1,
66
+ backgroundColor: 'rgba(220, 38, 38, 0.1)',
67
+ justifyContent: 'center',
68
+ alignItems: 'center',
69
+ padding: 20,
70
+ },
71
+ errorContent: {
72
+ backgroundColor: '#1f2937',
73
+ borderRadius: 12,
74
+ padding: 20,
75
+ maxWidth: 500,
76
+ width: '100%',
77
+ maxHeight: '80%',
78
+ borderWidth: 2,
79
+ borderColor: '#dc2626',
80
+ },
81
+ errorTitle: {
82
+ fontSize: 24,
83
+ fontWeight: '700',
84
+ color: '#dc2626',
85
+ marginBottom: 8,
86
+ },
87
+ errorSubtitle: {
88
+ fontSize: 14,
89
+ color: '#9ca3af',
90
+ marginBottom: 16,
91
+ },
92
+ errorScrollView: {
93
+ maxHeight: 400,
94
+ },
95
+ errorMessage: {
96
+ fontSize: 16,
97
+ fontWeight: '600',
98
+ color: '#fca5a5',
99
+ marginBottom: 12,
100
+ fontFamily: 'Courier',
101
+ },
102
+ errorStack: {
103
+ fontSize: 12,
104
+ color: '#d1d5db',
105
+ fontFamily: 'Courier',
106
+ lineHeight: 18,
107
+ },
108
+ });
@@ -0,0 +1,47 @@
1
+ import React, { useEffect } from 'react';
2
+ import { View, StyleSheet } from 'react-native';
3
+ import { useRenderedElement } from '../ui/state.js';
4
+ import { store } from '../ui/state.js';
5
+ import { ErrorBoundary } from './ErrorBoundary.js';
6
+
7
+ export const TestComponentOverlay = (): React.ReactElement | null => {
8
+ const { element, key } = useRenderedElement();
9
+
10
+ useEffect(() => {
11
+ // Call onRenderCallback when element changes
12
+ const callback = store.getState().onRenderCallback;
13
+
14
+ if (callback) {
15
+ callback();
16
+ store.getState().setOnRenderCallback(null);
17
+ }
18
+ }, [element]);
19
+
20
+ if (!element) {
21
+ return null;
22
+ }
23
+
24
+ const handleLayout = (): void => {
25
+ const callback = store.getState().onLayoutCallback;
26
+
27
+ if (callback) {
28
+ callback();
29
+ // Clear the callback after calling it
30
+ store.getState().setOnLayoutCallback(null);
31
+ }
32
+ };
33
+
34
+ return (
35
+ <View key={key} style={styles.overlay} onLayout={handleLayout}>
36
+ <ErrorBoundary>{element}</ErrorBoundary>
37
+ </View>
38
+ );
39
+ };
40
+
41
+ const styles = StyleSheet.create({
42
+ overlay: {
43
+ ...StyleSheet.absoluteFillObject,
44
+ backgroundColor: '#0a1628',
45
+ zIndex: 1000,
46
+ },
47
+ });
@@ -0,0 +1,7 @@
1
+ import { store } from '../ui/state.js';
2
+
3
+ export const cleanup = (): void => {
4
+ store.getState().setRenderedElement(null);
5
+ store.getState().setOnLayoutCallback(null);
6
+ store.getState().setOnRenderCallback(null);
7
+ };
@@ -0,0 +1,96 @@
1
+ import React from 'react';
2
+ import { store } from '../ui/state.js';
3
+ import type { RenderResult, RenderOptions } from './types.js';
4
+
5
+ const wrapElement = (
6
+ element: React.ReactElement,
7
+ wrapper?: React.ComponentType<{ children: React.ReactNode }>
8
+ ): React.ReactElement => {
9
+ if (!wrapper) {
10
+ return element;
11
+ }
12
+ return React.createElement(wrapper, { children: element });
13
+ };
14
+
15
+ export const render = async (
16
+ element: React.ReactElement,
17
+ options: RenderOptions = {}
18
+ ): Promise<RenderResult> => {
19
+ const { timeout = 1000, wrapper } = options;
20
+
21
+ // If an element is already rendered, unmount it first
22
+ if (store.getState().renderedElement !== null) {
23
+ store.getState().setRenderedElement(null);
24
+ store.getState().setOnLayoutCallback(null);
25
+ store.getState().setOnRenderCallback(null);
26
+ }
27
+
28
+ // Create a promise that resolves when the element is laid out
29
+ const layoutPromise = new Promise<void>((resolve, reject) => {
30
+ const timeoutId = setTimeout(() => {
31
+ store.getState().setOnLayoutCallback(null);
32
+ reject(
33
+ new Error(`Render timeout: Element did not mount within ${timeout}ms`)
34
+ );
35
+ }, timeout);
36
+
37
+ store.getState().setOnLayoutCallback(() => {
38
+ clearTimeout(timeoutId);
39
+ resolve();
40
+ });
41
+ });
42
+
43
+ // Wrap and set the element in state (key is generated automatically)
44
+ const wrappedElement = wrapElement(element, wrapper);
45
+ store.getState().setRenderedElement(wrappedElement);
46
+
47
+ // Wait for layout
48
+ await layoutPromise;
49
+
50
+ const rerender = async (newElement: React.ReactElement): Promise<void> => {
51
+ if (store.getState().renderedElement === null) {
52
+ throw new Error('No element is currently rendered. Call render() first.');
53
+ }
54
+
55
+ // Create a promise that resolves when the element is re-rendered
56
+ const renderPromise = new Promise<void>((resolve, reject) => {
57
+ const timeoutId = setTimeout(() => {
58
+ store.getState().setOnRenderCallback(null);
59
+ reject(
60
+ new Error(
61
+ `Rerender timeout: Element did not update within ${timeout}ms`
62
+ )
63
+ );
64
+ }, timeout);
65
+
66
+ store.getState().setOnRenderCallback(() => {
67
+ clearTimeout(timeoutId);
68
+ resolve();
69
+ });
70
+ });
71
+
72
+ const wrappedNewElement = wrapElement(newElement, wrapper);
73
+ store.getState().updateRenderedElement(wrappedNewElement);
74
+
75
+ // Wait for render
76
+ await renderPromise;
77
+ };
78
+
79
+ const unmount = (): void => {
80
+ if (store.getState().renderedElement === null) {
81
+ return;
82
+ }
83
+
84
+ store.getState().setRenderedElement(null);
85
+ store.getState().setOnLayoutCallback(null);
86
+ store.getState().setOnRenderCallback(null);
87
+ };
88
+
89
+ return {
90
+ rerender,
91
+ unmount,
92
+ };
93
+ };
94
+
95
+ export { cleanup } from './cleanup.js';
96
+ export type { RenderResult, RenderOptions } from './types.js';
@@ -0,0 +1,8 @@
1
+ import { afterEach } from '../collector/functions.js';
2
+ import { cleanup } from './cleanup.js';
3
+
4
+ export const setup = () => {
5
+ afterEach(() => {
6
+ cleanup();
7
+ });
8
+ };
@@ -0,0 +1,11 @@
1
+ import type React from 'react';
2
+
3
+ export type RenderResult = {
4
+ rerender: (element: React.ReactElement) => Promise<void>;
5
+ unmount: () => void;
6
+ };
7
+
8
+ export type RenderOptions = {
9
+ timeout?: number;
10
+ wrapper?: React.ComponentType<{ children: React.ReactNode }>;
11
+ };
@@ -9,10 +9,17 @@ export const getTestRunner = (): TestRunner => {
9
9
  return {
10
10
  events,
11
11
  run: async (testSuite, testFilePath) => {
12
- return runSuite(testSuite, {
12
+ const result = await runSuite(testSuite, {
13
13
  events,
14
14
  testFilePath,
15
15
  });
16
+
17
+ // If coverage is enabled, there will be a global variable called __coverage__
18
+ if ('__coverage__' in global && !!global.__coverage__) {
19
+ result.coverage = global.__coverage__;
20
+ }
21
+
22
+ return result;
16
23
  },
17
24
  dispose: () => {
18
25
  events.clearAllListeners();
@@ -137,6 +137,49 @@ export const runSuite = async (
137
137
  file: context.testFilePath,
138
138
  });
139
139
 
140
+ // Check if suite should be skipped or is todo
141
+ if (suite.status === 'skipped') {
142
+ const result = {
143
+ name: suite.name,
144
+ tests: [],
145
+ suites: [],
146
+ status: 'skipped' as const,
147
+ duration: 0,
148
+ };
149
+
150
+ // Emit suite-finished event
151
+ context.events.emit({
152
+ type: 'suite-finished',
153
+ file: context.testFilePath,
154
+ name: suite.name,
155
+ duration: 0,
156
+ status: 'skipped',
157
+ });
158
+
159
+ return result;
160
+ }
161
+
162
+ if (suite.status === 'todo') {
163
+ const result = {
164
+ name: suite.name,
165
+ tests: [],
166
+ suites: [],
167
+ status: 'todo' as const,
168
+ duration: 0,
169
+ };
170
+
171
+ // Emit suite-finished event
172
+ context.events.emit({
173
+ type: 'suite-finished',
174
+ file: context.testFilePath,
175
+ name: suite.name,
176
+ duration: 0,
177
+ status: 'todo',
178
+ });
179
+
180
+ return result;
181
+ }
182
+
140
183
  const testResults: TestResult[] = [];
141
184
  const suiteResults: TestSuiteResult[] = [];
142
185
 
@@ -6,16 +6,18 @@ export const getCodeFrame = async (error: Error): Promise<CodeFrame | null> => {
6
6
  const parsedStack = parseErrorStack(error.stack);
7
7
  const symbolicatedStack = await symbolicateStackTrace(parsedStack);
8
8
 
9
- if (!symbolicatedStack.codeFrame) {
9
+ if (!('codeFrame' in symbolicatedStack) || !symbolicatedStack.codeFrame) {
10
10
  return null;
11
11
  }
12
12
 
13
+ const codeFrame = symbolicatedStack.codeFrame as CodeFrame;
14
+
13
15
  // Normalize optionality (null -> undefined)
14
16
  return {
15
- ...symbolicatedStack.codeFrame,
16
- location: symbolicatedStack.codeFrame.location
17
+ ...codeFrame,
18
+ location: codeFrame.location
17
19
  ? {
18
- ...symbolicatedStack.codeFrame.location,
20
+ ...codeFrame.location,
19
21
  }
20
22
  : undefined,
21
23
  };
@@ -1,14 +1,13 @@
1
1
  import {
2
2
  View,
3
3
  Text,
4
- Image,
5
4
  StyleSheet,
6
5
  ActivityIndicator,
7
6
  StatusBar,
8
7
  Platform,
9
8
  } from 'react-native';
10
- import { LOGO_IMAGE } from '../constants.js';
11
9
  import { useRunnerStatus } from './state.js';
10
+ import { TestComponentOverlay } from '../render/TestComponentOverlay.js';
12
11
 
13
12
  require('../initialize.js');
14
13
 
@@ -18,9 +17,6 @@ export const ReadyScreen = () => {
18
17
  return (
19
18
  <View style={styles.container}>
20
19
  <View style={styles.contentContainer}>
21
- <View style={styles.logoContainer}>
22
- <Image style={styles.logo} source={LOGO_IMAGE} />
23
- </View>
24
20
  <Text style={styles.title}>React Native Harness</Text>
25
21
 
26
22
  {status === 'idle' ? (
@@ -44,6 +40,7 @@ export const ReadyScreen = () => {
44
40
  </View>
45
41
  )}
46
42
  </View>
43
+ <TestComponentOverlay />
47
44
  </View>
48
45
  );
49
46
  };
@@ -77,13 +74,6 @@ const styles = StyleSheet.create({
77
74
  shadowRadius: 30,
78
75
  maxWidth: 350,
79
76
  },
80
- logoContainer: {
81
- marginBottom: 12,
82
- },
83
- logo: {
84
- width: 128,
85
- height: 128,
86
- },
87
77
  title: {
88
78
  fontSize: 28,
89
79
  fontWeight: '700',
@@ -1,20 +1,9 @@
1
- import {
2
- View,
3
- Text,
4
- Image,
5
- StyleSheet,
6
- Platform,
7
- StatusBar,
8
- } from 'react-native';
9
- import { LOGO_IMAGE } from '../constants.js';
1
+ import { View, Text, StyleSheet, Platform, StatusBar } from 'react-native';
10
2
 
11
3
  export const WrongEnvironmentScreen = () => {
12
4
  return (
13
5
  <View style={styles.container}>
14
6
  <View style={styles.contentContainer}>
15
- <View style={styles.logoContainer}>
16
- <Image style={styles.logo} source={LOGO_IMAGE} />
17
- </View>
18
7
  <Text style={styles.title}>React Native Harness</Text>
19
8
 
20
9
  <View style={styles.errorIndicator}>
@@ -59,13 +48,6 @@ const styles = StyleSheet.create({
59
48
  shadowRadius: 30,
60
49
  maxWidth: 350,
61
50
  },
62
- logoContainer: {
63
- marginBottom: 12,
64
- },
65
- logo: {
66
- width: 128,
67
- height: 128,
68
- },
69
51
  title: {
70
52
  fontSize: 28,
71
53
  fontWeight: '700',
package/src/ui/state.ts CHANGED
@@ -1,13 +1,52 @@
1
1
  import { create, useStore } from 'zustand/react';
2
+ import type React from 'react';
3
+ import { shallow } from 'zustand/shallow';
4
+ import { useStoreWithEqualityFn } from 'zustand/traditional';
5
+
6
+ const generateRenderKey = (): string => {
7
+ return `render-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
8
+ };
2
9
 
3
10
  export type RunnerState = {
4
11
  status: 'loading' | 'idle' | 'running';
5
12
  setStatus: (status: 'loading' | 'idle' | 'running') => void;
13
+ renderedElement: React.ReactElement | null;
14
+ setRenderedElement: (element: React.ReactElement | null) => void;
15
+ updateRenderedElement: (element: React.ReactElement) => void;
16
+ renderKey: string | null;
17
+ onLayoutCallback: (() => void) | null;
18
+ setOnLayoutCallback: (callback: (() => void) | null) => void;
19
+ onRenderCallback: (() => void) | null;
20
+ setOnRenderCallback: (callback: (() => void) | null) => void;
6
21
  };
7
22
 
8
23
  export const store = create<RunnerState>((set) => ({
9
24
  status: 'loading',
10
25
  setStatus: (status) => set({ status }),
26
+ renderedElement: null,
27
+ setRenderedElement: (element) =>
28
+ set({
29
+ renderedElement: element,
30
+ renderKey: generateRenderKey(),
31
+ }),
32
+ updateRenderedElement: (element) =>
33
+ set({
34
+ renderedElement: element,
35
+ }),
36
+ renderKey: null,
37
+ onLayoutCallback: null,
38
+ setOnLayoutCallback: (callback) => set({ onLayoutCallback: callback }),
39
+ onRenderCallback: null,
40
+ setOnRenderCallback: (callback) => set({ onRenderCallback: callback }),
11
41
  }));
12
42
 
13
43
  export const useRunnerStatus = () => useStore(store, (state) => state.status);
44
+ export const useRenderedElement = () =>
45
+ useStoreWithEqualityFn(
46
+ store,
47
+ (state) => ({
48
+ element: state.renderedElement,
49
+ key: state.renderKey,
50
+ }),
51
+ shallow
52
+ );
@@ -26,6 +26,7 @@ export const getEmitter = <TEvents>() => {
26
26
  };
27
27
  };
28
28
 
29
+ /* eslint-disable no-redeclare */
29
30
  export function combineEventEmitters<TEvents0, TEvents1>(
30
31
  emitter0: EventEmitter<TEvents0>,
31
32
  emitter1: EventEmitter<TEvents1>