@react-native-harness/runtime 1.1.0-rc.4 → 1.2.0-rc.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.
- package/dist/disableHMRWhenReady.d.ts.map +1 -1
- package/dist/disableHMRWhenReady.js +10 -4
- package/dist/expect/context.d.ts +20 -0
- package/dist/expect/context.d.ts.map +1 -0
- package/dist/expect/context.js +6 -0
- package/dist/expect/errors.d.ts +3 -0
- package/dist/expect/errors.d.ts.map +1 -0
- package/dist/expect/errors.js +45 -0
- package/dist/expect/expect.d.ts.map +1 -1
- package/dist/expect/expect.js +6 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/initialize.js +11 -1
- package/dist/jsx/jsx-runtime.d.ts +3 -1
- package/dist/jsx/jsx-runtime.d.ts.map +1 -1
- package/dist/jsx/jsx-runtime.js +8 -0
- package/dist/render/TestComponentOverlay.d.ts.map +1 -1
- package/dist/render/TestComponentOverlay.js +1 -2
- package/dist/runner/runSuite.d.ts.map +1 -1
- package/dist/runner/runSuite.js +16 -6
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/ui/ReadyScreen.d.ts.map +1 -1
- package/dist/ui/ReadyScreen.js +8 -97
- package/dist/ui/RunnerScreen.d.ts +8 -0
- package/dist/ui/RunnerScreen.d.ts.map +1 -0
- package/dist/ui/RunnerScreen.js +58 -0
- package/dist/ui/WrongEnvironmentScreen.d.ts.map +1 -1
- package/dist/ui/WrongEnvironmentScreen.js +3 -77
- package/dist/ui/images.d.ts +3 -0
- package/dist/ui/images.d.ts.map +1 -0
- package/dist/ui/images.js +2 -0
- package/out-tsc/vitest/src/client/getWSServer.d.ts.map +1 -1
- package/out-tsc/vitest/src/client/getWSServer.test.d.ts +2 -0
- package/out-tsc/vitest/src/client/getWSServer.test.d.ts.map +1 -0
- package/out-tsc/vitest/src/disableHMRWhenReady.d.ts.map +1 -1
- package/out-tsc/vitest/src/disableHMRWhenReady.test.d.ts +2 -0
- package/out-tsc/vitest/src/disableHMRWhenReady.test.d.ts.map +1 -0
- package/out-tsc/vitest/src/expect/context.d.ts +20 -0
- package/out-tsc/vitest/src/expect/context.d.ts.map +1 -0
- package/out-tsc/vitest/src/expect/errors.d.ts +3 -0
- package/out-tsc/vitest/src/expect/errors.d.ts.map +1 -0
- package/out-tsc/vitest/src/expect/expect.d.ts.map +1 -1
- package/out-tsc/vitest/src/index.d.ts +1 -0
- package/out-tsc/vitest/src/index.d.ts.map +1 -1
- package/out-tsc/vitest/src/jsx/jsx-runtime.d.ts +3 -1
- package/out-tsc/vitest/src/jsx/jsx-runtime.d.ts.map +1 -1
- package/out-tsc/vitest/src/render/TestComponentOverlay.d.ts.map +1 -1
- package/out-tsc/vitest/src/runner/runSuite.d.ts.map +1 -1
- package/out-tsc/vitest/src/ui/ReadyScreen.d.ts.map +1 -1
- package/out-tsc/vitest/src/ui/RunnerScreen.d.ts +8 -0
- package/out-tsc/vitest/src/ui/RunnerScreen.d.ts.map +1 -0
- package/out-tsc/vitest/src/ui/WrongEnvironmentScreen.d.ts.map +1 -1
- package/out-tsc/vitest/src/ui/images.d.ts +3 -0
- package/out-tsc/vitest/src/ui/images.d.ts.map +1 -0
- package/out-tsc/vitest/tsconfig.spec.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/disableHMRWhenReady.test.ts +42 -0
- package/src/disableHMRWhenReady.ts +15 -7
- package/src/expect/context.ts +30 -0
- package/src/expect/errors.ts +76 -0
- package/src/expect/expect.ts +16 -5
- package/src/index.ts +1 -0
- package/src/initialize.ts +11 -5
- package/src/jsx/jsx-runtime.ts +16 -1
- package/src/react-native.d.ts +14 -0
- package/src/render/TestComponentOverlay.tsx +5 -2
- package/src/runner/runSuite.ts +25 -11
- package/src/ui/ReadyScreen.tsx +10 -130
- package/src/ui/RunnerScreen.tsx +93 -0
- package/src/ui/WrongEnvironmentScreen.tsx +6 -90
- package/src/ui/images.ts +2 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@react-native-harness/runtime",
|
|
3
3
|
"description": "The core test runtime that executes on React Native devices, providing Jest-compatible APIs (describe, it, expect) and managing test collection, execution, and result reporting in native environments.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.2.0-rc.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"react-native-url-polyfill": "^3.0.0",
|
|
48
48
|
"use-sync-external-store": "^1.6.0",
|
|
49
49
|
"zustand": "^5.0.5",
|
|
50
|
-
"@react-native-harness/bridge": "1.
|
|
50
|
+
"@react-native-harness/bridge": "1.2.0-rc.1"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@types/chai": "^5.2.2"
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { disableHMRWhenReady } from './disableHMRWhenReady.js';
|
|
3
|
+
|
|
4
|
+
const mocks = vi.hoisted(() => ({
|
|
5
|
+
Platform: { OS: 'android' },
|
|
6
|
+
}));
|
|
7
|
+
|
|
8
|
+
vi.mock('react-native', () => ({
|
|
9
|
+
Platform: mocks.Platform,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
describe('disableHMRWhenReady', () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
mocks.Platform.OS = 'android';
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('resolves when HMR setup never becomes available', async () => {
|
|
18
|
+
const disable = vi.fn(() => {
|
|
19
|
+
throw new Error('Expected HMRClient.setup() call at startup.');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
await expect(disableHMRWhenReady(disable, 2, 0)).resolves.toBeUndefined();
|
|
23
|
+
expect(disable).toHaveBeenCalledTimes(3);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('rejects unexpected disable errors', async () => {
|
|
27
|
+
const error = new Error('boom');
|
|
28
|
+
const disable = vi.fn(() => {
|
|
29
|
+
throw error;
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
await expect(disableHMRWhenReady(disable, 2, 0)).rejects.toBe(error);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('skips disabling HMR on web', async () => {
|
|
36
|
+
mocks.Platform.OS = 'web';
|
|
37
|
+
const disable = vi.fn();
|
|
38
|
+
|
|
39
|
+
await expect(disableHMRWhenReady(disable, 2, 0)).resolves.toBeUndefined();
|
|
40
|
+
expect(disable).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
});
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { Platform } from
|
|
1
|
+
import { Platform } from 'react-native';
|
|
2
|
+
|
|
3
|
+
const HMR_SETUP_ERROR = 'Expected HMRClient.setup() call at startup.';
|
|
2
4
|
|
|
3
5
|
export function disableHMRWhenReady(
|
|
4
6
|
disable: () => void,
|
|
5
7
|
retriesLeft: number,
|
|
6
|
-
retryDelay = 10
|
|
8
|
+
retryDelay = 10,
|
|
7
9
|
) {
|
|
8
10
|
return new Promise<void>((resolve, reject) => {
|
|
9
11
|
if (Platform.OS === 'web') {
|
|
@@ -17,15 +19,21 @@ export function disableHMRWhenReady(
|
|
|
17
19
|
disable();
|
|
18
20
|
resolve();
|
|
19
21
|
} catch (error) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
) {
|
|
22
|
+
const isMissingHMRSetupError =
|
|
23
|
+
error instanceof Error && error.message.includes(HMR_SETUP_ERROR);
|
|
24
|
+
|
|
25
|
+
if (remaining > 0 && isMissingHMRSetupError) {
|
|
25
26
|
setTimeout(() => attempt(remaining - 1), retryDelay);
|
|
26
27
|
return;
|
|
27
28
|
}
|
|
28
29
|
|
|
30
|
+
// Expo's metro runtime does not guarantee that React Native's HMRClient
|
|
31
|
+
// is initialized, so disabling HMR is best-effort in that environment.
|
|
32
|
+
if (isMissingHMRSetupError) {
|
|
33
|
+
resolve();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
reject(error);
|
|
30
38
|
}
|
|
31
39
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
type HarnessExpectError = {
|
|
2
|
+
name?: string;
|
|
3
|
+
message?: string;
|
|
4
|
+
stack?: string;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export type HarnessExpectTestState = {
|
|
8
|
+
result?: {
|
|
9
|
+
state: 'pass' | 'fail';
|
|
10
|
+
errors?: HarnessExpectError[];
|
|
11
|
+
};
|
|
12
|
+
promises?: Promise<unknown>[];
|
|
13
|
+
onFinished?: Array<() => void | Promise<void>>;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
declare global {
|
|
17
|
+
var HARNESS_EXPECT_TEST_STATE: HarnessExpectTestState | undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const getCurrentExpectTestState = ():
|
|
21
|
+
| HarnessExpectTestState
|
|
22
|
+
| undefined => {
|
|
23
|
+
return globalThis.HARNESS_EXPECT_TEST_STATE;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const setCurrentExpectTestState = (
|
|
27
|
+
state: HarnessExpectTestState | undefined,
|
|
28
|
+
): void => {
|
|
29
|
+
globalThis.HARNESS_EXPECT_TEST_STATE = state;
|
|
30
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { HarnessExpectTestState } from './context.js';
|
|
2
|
+
|
|
3
|
+
type SerializedExpectError = {
|
|
4
|
+
name?: string;
|
|
5
|
+
message?: string;
|
|
6
|
+
stack?: string;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const formatErrorMessage = (error: unknown): string => {
|
|
10
|
+
if (error instanceof Error) {
|
|
11
|
+
return `${error.name}: ${error.message}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (error && typeof error === 'object') {
|
|
15
|
+
const maybeError = error as { name?: string; message?: string };
|
|
16
|
+
if (maybeError.name || maybeError.message) {
|
|
17
|
+
return [maybeError.name, maybeError.message].filter(Boolean).join(': ');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return String(error);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const createExpectError = (errors: unknown[], title?: string): Error => {
|
|
25
|
+
const message = [title, ...errors.map(formatErrorMessage)]
|
|
26
|
+
.filter(Boolean)
|
|
27
|
+
.join('\n\n');
|
|
28
|
+
|
|
29
|
+
const error = new Error(message);
|
|
30
|
+
const firstError = errors.find(
|
|
31
|
+
(value): value is SerializedExpectError =>
|
|
32
|
+
!!value && typeof value === 'object',
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
if (firstError?.name) {
|
|
36
|
+
error.name = firstError.name;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (firstError?.stack) {
|
|
40
|
+
error.stack = firstError.stack.replace(
|
|
41
|
+
/^([^\n]+)(\n|$)/,
|
|
42
|
+
`${error.name}: ${message}$2`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return error;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const flushExpectTestState = async (
|
|
50
|
+
state: HarnessExpectTestState,
|
|
51
|
+
): Promise<void> => {
|
|
52
|
+
if (state.promises?.length) {
|
|
53
|
+
const results = await Promise.allSettled(state.promises);
|
|
54
|
+
const rejected = results
|
|
55
|
+
.filter(
|
|
56
|
+
(result): result is PromiseRejectedResult =>
|
|
57
|
+
result.status === 'rejected',
|
|
58
|
+
)
|
|
59
|
+
.map((result) => result.reason);
|
|
60
|
+
|
|
61
|
+
if (rejected.length > 0) {
|
|
62
|
+
throw createExpectError(rejected);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
for (const hook of state.onFinished ?? []) {
|
|
67
|
+
await hook();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const softErrors = state.result?.errors ?? [];
|
|
71
|
+
if (softErrors.length === 0) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
throw createExpectError(softErrors, 'Soft assertion failures:');
|
|
76
|
+
};
|
package/src/expect/expect.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import * as chai from 'chai';
|
|
14
14
|
|
|
15
15
|
// Setup additional matchers
|
|
16
|
+
import { getCurrentExpectTestState } from './context.js';
|
|
16
17
|
import './setup.js';
|
|
17
18
|
import { toMatchImageSnapshot } from './matchers/toMatchImageSnapshot.js';
|
|
18
19
|
|
|
@@ -20,12 +21,22 @@ export function createExpect(): ExpectStatic {
|
|
|
20
21
|
const expect = ((value: unknown, message?: string): Assertion => {
|
|
21
22
|
const { assertionCalls } = getState(expect);
|
|
22
23
|
setState({ assertionCalls: assertionCalls + 1 }, expect);
|
|
23
|
-
|
|
24
|
+
|
|
25
|
+
const assertion = chai.expect(value, message) as unknown as Assertion & {
|
|
26
|
+
withTest?: (test: unknown) => Assertion;
|
|
27
|
+
};
|
|
28
|
+
const currentTest = getCurrentExpectTestState();
|
|
29
|
+
|
|
30
|
+
return currentTest && assertion.withTest
|
|
31
|
+
? assertion.withTest(currentTest)
|
|
32
|
+
: assertion;
|
|
24
33
|
}) as ExpectStatic;
|
|
25
34
|
Object.assign(expect, chai.expect);
|
|
26
35
|
Object.assign(
|
|
27
36
|
expect,
|
|
28
|
-
globalThis[
|
|
37
|
+
globalThis[
|
|
38
|
+
ASYMMETRIC_MATCHERS_OBJECT as unknown as keyof typeof globalThis
|
|
39
|
+
],
|
|
29
40
|
);
|
|
30
41
|
|
|
31
42
|
expect.getState = () => getState<MatcherState>(expect);
|
|
@@ -44,7 +55,7 @@ export function createExpect(): ExpectStatic {
|
|
|
44
55
|
expectedAssertionsNumber: null,
|
|
45
56
|
expectedAssertionsNumberErrorGen: null,
|
|
46
57
|
},
|
|
47
|
-
expect
|
|
58
|
+
expect,
|
|
48
59
|
);
|
|
49
60
|
|
|
50
61
|
// @ts-expect-error untyped
|
|
@@ -62,7 +73,7 @@ export function createExpect(): ExpectStatic {
|
|
|
62
73
|
// @ts-expect-error untyped
|
|
63
74
|
expect.unreachable = (message?: string) => {
|
|
64
75
|
chai.assert.fail(
|
|
65
|
-
`expected${message ? ` "${message}" ` : ' '}not to be reached
|
|
76
|
+
`expected${message ? ` "${message}" ` : ' '}not to be reached`,
|
|
66
77
|
);
|
|
67
78
|
};
|
|
68
79
|
|
|
@@ -71,7 +82,7 @@ export function createExpect(): ExpectStatic {
|
|
|
71
82
|
new Error(
|
|
72
83
|
`expected number of assertions to be ${expected}, but got ${
|
|
73
84
|
expect.getState().assertionCalls
|
|
74
|
-
}
|
|
85
|
+
}`,
|
|
75
86
|
);
|
|
76
87
|
if (Error.captureStackTrace) {
|
|
77
88
|
Error.captureStackTrace(errorGen(), assertions);
|
package/src/index.ts
CHANGED
package/src/initialize.ts
CHANGED
|
@@ -22,11 +22,17 @@ const HMRClient =
|
|
|
22
22
|
|
|
23
23
|
// Wait for HMRClient to be initialized
|
|
24
24
|
setTimeout(() => {
|
|
25
|
-
void
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
void (async () => {
|
|
26
|
+
try {
|
|
27
|
+
await disableHMRWhenReady(() => HMRClient.disable(), 50);
|
|
28
|
+
const client = await getClient();
|
|
29
|
+
|
|
30
|
+
const deviceDescriptor = getDeviceDescriptor();
|
|
31
|
+
await client.rpc.reportReady(deviceDescriptor);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('Failed to initialize React Native Harness', error);
|
|
34
|
+
}
|
|
35
|
+
})();
|
|
30
36
|
});
|
|
31
37
|
|
|
32
38
|
// Re-throw fatal errors
|
package/src/jsx/jsx-runtime.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
1
2
|
import { View } from 'react-native';
|
|
2
3
|
import * as ReactJSXRuntime from 'react/jsx-runtime';
|
|
3
4
|
import { getHarnessGlobal } from '../globals.js';
|
|
@@ -35,4 +36,18 @@ export function jsxs(
|
|
|
35
36
|
key?: React.Key,
|
|
36
37
|
): React.ReactElement {
|
|
37
38
|
return wrap(type, props, key, true);
|
|
38
|
-
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createElement(
|
|
42
|
+
type: React.ElementType,
|
|
43
|
+
props?: Record<string, unknown> | null,
|
|
44
|
+
...children: React.ReactNode[]
|
|
45
|
+
): React.ReactElement {
|
|
46
|
+
const disableViewFlattening = getHarnessGlobal().disableViewFlattening;
|
|
47
|
+
|
|
48
|
+
if (disableViewFlattening && type === View) {
|
|
49
|
+
props = { ...props, collapsable: false };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return React.createElement(type, props, ...children);
|
|
53
|
+
}
|
package/src/react-native.d.ts
CHANGED
|
@@ -16,3 +16,17 @@ declare module 'react-native/Libraries/Core/Devtools/parseErrorStack' {
|
|
|
16
16
|
};
|
|
17
17
|
export default function parseErrorStack(errorStack?: string): StackFrame[];
|
|
18
18
|
}
|
|
19
|
+
|
|
20
|
+
declare module '*.png' {
|
|
21
|
+
import type { ImageSourcePropType } from 'react-native';
|
|
22
|
+
|
|
23
|
+
const value: ImageSourcePropType;
|
|
24
|
+
export default value;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
declare module '*.jpg' {
|
|
28
|
+
import type { ImageSourcePropType } from 'react-native';
|
|
29
|
+
|
|
30
|
+
const value: ImageSourcePropType;
|
|
31
|
+
export default value;
|
|
32
|
+
}
|
|
@@ -52,7 +52,11 @@ export const TestComponentOverlay = (): React.ReactElement | null => {
|
|
|
52
52
|
};
|
|
53
53
|
|
|
54
54
|
return (
|
|
55
|
-
<View
|
|
55
|
+
<View
|
|
56
|
+
key={key}
|
|
57
|
+
style={[StyleSheet.absoluteFill, styles.overlay]}
|
|
58
|
+
onLayout={handleLayout}
|
|
59
|
+
>
|
|
56
60
|
<ErrorBoundary>{element}</ErrorBoundary>
|
|
57
61
|
</View>
|
|
58
62
|
);
|
|
@@ -60,7 +64,6 @@ export const TestComponentOverlay = (): React.ReactElement | null => {
|
|
|
60
64
|
|
|
61
65
|
const styles = StyleSheet.create({
|
|
62
66
|
overlay: {
|
|
63
|
-
...StyleSheet.absoluteFillObject,
|
|
64
67
|
backgroundColor: '#0a1628',
|
|
65
68
|
zIndex: 1000,
|
|
66
69
|
},
|
package/src/runner/runSuite.ts
CHANGED
|
@@ -4,6 +4,11 @@ import type {
|
|
|
4
4
|
TestSuite,
|
|
5
5
|
TestSuiteResult,
|
|
6
6
|
} from '@react-native-harness/bridge';
|
|
7
|
+
import {
|
|
8
|
+
setCurrentExpectTestState,
|
|
9
|
+
type HarnessExpectTestState,
|
|
10
|
+
} from '../expect/context.js';
|
|
11
|
+
import { flushExpectTestState } from '../expect/errors.js';
|
|
7
12
|
import { runHooks } from './hooks.js';
|
|
8
13
|
import { getTestExecutionError } from './errors.js';
|
|
9
14
|
import { TestRunnerContext } from './types.js';
|
|
@@ -15,7 +20,7 @@ declare global {
|
|
|
15
20
|
const runTest = async (
|
|
16
21
|
test: TestCase,
|
|
17
22
|
suite: TestSuite,
|
|
18
|
-
context: TestRunnerContext
|
|
23
|
+
context: TestRunnerContext,
|
|
19
24
|
): Promise<TestResult> => {
|
|
20
25
|
const startTime = Date.now();
|
|
21
26
|
|
|
@@ -69,14 +74,23 @@ const runTest = async (
|
|
|
69
74
|
return result;
|
|
70
75
|
}
|
|
71
76
|
|
|
72
|
-
|
|
73
|
-
|
|
77
|
+
const expectTestState: HarnessExpectTestState = {};
|
|
78
|
+
setCurrentExpectTestState(expectTestState);
|
|
74
79
|
|
|
75
|
-
|
|
76
|
-
|
|
80
|
+
try {
|
|
81
|
+
// Run all beforeEach hooks from the current suite and its parents
|
|
82
|
+
await runHooks(suite, 'beforeEach');
|
|
77
83
|
|
|
78
|
-
|
|
79
|
-
|
|
84
|
+
// Run the actual test
|
|
85
|
+
await test.fn();
|
|
86
|
+
|
|
87
|
+
// Run all afterEach hooks from the current suite and its parents
|
|
88
|
+
await runHooks(suite, 'afterEach');
|
|
89
|
+
|
|
90
|
+
await flushExpectTestState(expectTestState);
|
|
91
|
+
} finally {
|
|
92
|
+
setCurrentExpectTestState(undefined);
|
|
93
|
+
}
|
|
80
94
|
|
|
81
95
|
const duration = Date.now() - startTime;
|
|
82
96
|
|
|
@@ -102,7 +116,7 @@ const runTest = async (
|
|
|
102
116
|
error,
|
|
103
117
|
context.testFilePath,
|
|
104
118
|
suite.name,
|
|
105
|
-
test.name
|
|
119
|
+
test.name,
|
|
106
120
|
);
|
|
107
121
|
const duration = Date.now() - startTime;
|
|
108
122
|
|
|
@@ -130,7 +144,7 @@ const runTest = async (
|
|
|
130
144
|
|
|
131
145
|
export const runSuite = async (
|
|
132
146
|
suite: TestSuite,
|
|
133
|
-
context: TestRunnerContext
|
|
147
|
+
context: TestRunnerContext,
|
|
134
148
|
): Promise<TestSuiteResult> => {
|
|
135
149
|
const startTime = Date.now();
|
|
136
150
|
|
|
@@ -212,10 +226,10 @@ export const runSuite = async (
|
|
|
212
226
|
|
|
213
227
|
// Check if any tests or child suites failed
|
|
214
228
|
const hasFailedTests = testResults.some(
|
|
215
|
-
(result) => result.status === 'failed'
|
|
229
|
+
(result) => result.status === 'failed',
|
|
216
230
|
);
|
|
217
231
|
const hasFailedSuites = suiteResults.some(
|
|
218
|
-
(result) => result.status === 'failed'
|
|
232
|
+
(result) => result.status === 'failed',
|
|
219
233
|
);
|
|
220
234
|
|
|
221
235
|
if (hasFailedTests || hasFailedSuites) {
|
package/src/ui/ReadyScreen.tsx
CHANGED
|
@@ -1,142 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
View,
|
|
3
|
-
Text,
|
|
4
|
-
StyleSheet,
|
|
5
|
-
ActivityIndicator,
|
|
6
|
-
StatusBar,
|
|
7
|
-
Platform,
|
|
8
|
-
} from 'react-native';
|
|
9
1
|
import { useRunnerStatus } from './state.js';
|
|
10
2
|
import { TestComponentOverlay } from '../render/TestComponentOverlay.js';
|
|
3
|
+
import { RunnerScreen } from './RunnerScreen.js';
|
|
11
4
|
|
|
12
5
|
require('../initialize.js');
|
|
13
6
|
|
|
14
7
|
export const ReadyScreen = () => {
|
|
15
8
|
const status = useRunnerStatus();
|
|
9
|
+
const statusText =
|
|
10
|
+
status === 'loading'
|
|
11
|
+
? 'Loading...'
|
|
12
|
+
: status === 'idle'
|
|
13
|
+
? 'Idle'
|
|
14
|
+
: 'Running...';
|
|
16
15
|
|
|
17
16
|
return (
|
|
18
|
-
|
|
19
|
-
<
|
|
20
|
-
<View style={styles.contentContainer}>
|
|
21
|
-
<Text style={styles.title}>React Native Harness</Text>
|
|
22
|
-
|
|
23
|
-
{status === 'idle' ? (
|
|
24
|
-
<View style={styles.statusIndicator}>
|
|
25
|
-
<View style={styles.statusDot} />
|
|
26
|
-
<Text style={styles.statusText}>Idle</Text>
|
|
27
|
-
</View>
|
|
28
|
-
) : status === 'loading' ? (
|
|
29
|
-
<View style={styles.loadingContainer}>
|
|
30
|
-
<ActivityIndicator
|
|
31
|
-
size="small"
|
|
32
|
-
color="#3b82f6"
|
|
33
|
-
style={styles.loadingSpinner}
|
|
34
|
-
/>
|
|
35
|
-
<Text style={styles.loadingText}>Loading...</Text>
|
|
36
|
-
</View>
|
|
37
|
-
) : (
|
|
38
|
-
<View style={styles.statusIndicator}>
|
|
39
|
-
<View style={styles.statusDot} />
|
|
40
|
-
<Text style={styles.statusText}>Running...</Text>
|
|
41
|
-
</View>
|
|
42
|
-
)}
|
|
43
|
-
</View>
|
|
17
|
+
<>
|
|
18
|
+
<RunnerScreen title="Harness" statusText={statusText} />
|
|
44
19
|
<TestComponentOverlay />
|
|
45
|
-
|
|
20
|
+
</>
|
|
46
21
|
);
|
|
47
22
|
};
|
|
48
|
-
|
|
49
|
-
const styles = StyleSheet.create({
|
|
50
|
-
container: {
|
|
51
|
-
flex: 1,
|
|
52
|
-
justifyContent: 'center',
|
|
53
|
-
alignItems: 'center',
|
|
54
|
-
backgroundColor: '#0a1628',
|
|
55
|
-
position: 'relative',
|
|
56
|
-
},
|
|
57
|
-
safeArea: {
|
|
58
|
-
flex: 1,
|
|
59
|
-
backgroundColor: 'transparent',
|
|
60
|
-
paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0,
|
|
61
|
-
},
|
|
62
|
-
contentContainer: {
|
|
63
|
-
alignItems: 'center',
|
|
64
|
-
padding: 24,
|
|
65
|
-
borderRadius: 24,
|
|
66
|
-
backgroundColor: 'rgba(59, 130, 246, 0.1)',
|
|
67
|
-
borderWidth: 1,
|
|
68
|
-
borderColor: 'rgba(59, 130, 246, 0.2)',
|
|
69
|
-
shadowColor: '#000',
|
|
70
|
-
shadowOffset: {
|
|
71
|
-
width: 0,
|
|
72
|
-
height: 20,
|
|
73
|
-
},
|
|
74
|
-
shadowOpacity: 0.3,
|
|
75
|
-
shadowRadius: 30,
|
|
76
|
-
maxWidth: 350,
|
|
77
|
-
},
|
|
78
|
-
title: {
|
|
79
|
-
fontSize: 28,
|
|
80
|
-
fontWeight: '700',
|
|
81
|
-
color: '#38bdf8',
|
|
82
|
-
textAlign: 'center',
|
|
83
|
-
letterSpacing: 1,
|
|
84
|
-
marginBottom: 8,
|
|
85
|
-
},
|
|
86
|
-
subtitle: {
|
|
87
|
-
fontSize: 16,
|
|
88
|
-
fontWeight: '400',
|
|
89
|
-
color: 'rgba(255, 255, 255, 0.7)',
|
|
90
|
-
textAlign: 'center',
|
|
91
|
-
letterSpacing: 0.5,
|
|
92
|
-
marginBottom: 24,
|
|
93
|
-
},
|
|
94
|
-
statusIndicator: {
|
|
95
|
-
flexDirection: 'row',
|
|
96
|
-
alignItems: 'center',
|
|
97
|
-
backgroundColor: 'rgba(34, 211, 238, 0.15)',
|
|
98
|
-
paddingHorizontal: 16,
|
|
99
|
-
paddingVertical: 8,
|
|
100
|
-
height: 36,
|
|
101
|
-
borderRadius: 20,
|
|
102
|
-
borderWidth: 1,
|
|
103
|
-
borderColor: 'rgba(34, 211, 238, 0.4)',
|
|
104
|
-
},
|
|
105
|
-
statusDot: {
|
|
106
|
-
width: 8,
|
|
107
|
-
height: 8,
|
|
108
|
-
borderRadius: 4,
|
|
109
|
-
backgroundColor: '#22d3ee',
|
|
110
|
-
marginRight: 8,
|
|
111
|
-
shadowColor: '#22d3ee',
|
|
112
|
-
shadowOffset: { width: 0, height: 0 },
|
|
113
|
-
shadowOpacity: 0.8,
|
|
114
|
-
shadowRadius: 4,
|
|
115
|
-
},
|
|
116
|
-
statusText: {
|
|
117
|
-
fontSize: 14,
|
|
118
|
-
fontWeight: '500',
|
|
119
|
-
color: '#22d3ee',
|
|
120
|
-
letterSpacing: 0.5,
|
|
121
|
-
},
|
|
122
|
-
loadingContainer: {
|
|
123
|
-
flexDirection: 'row',
|
|
124
|
-
alignItems: 'center',
|
|
125
|
-
backgroundColor: 'rgba(59, 130, 246, 0.15)',
|
|
126
|
-
paddingHorizontal: 16,
|
|
127
|
-
paddingVertical: 8,
|
|
128
|
-
height: 36,
|
|
129
|
-
borderRadius: 20,
|
|
130
|
-
borderWidth: 1,
|
|
131
|
-
borderColor: 'rgba(59, 130, 246, 0.4)',
|
|
132
|
-
},
|
|
133
|
-
loadingSpinner: {
|
|
134
|
-
marginRight: 8,
|
|
135
|
-
},
|
|
136
|
-
loadingText: {
|
|
137
|
-
fontSize: 14,
|
|
138
|
-
fontWeight: '500',
|
|
139
|
-
color: '#3b82f6',
|
|
140
|
-
letterSpacing: 0.5,
|
|
141
|
-
},
|
|
142
|
-
});
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Image,
|
|
3
|
+
StatusBar,
|
|
4
|
+
StyleSheet,
|
|
5
|
+
Text,
|
|
6
|
+
View,
|
|
7
|
+
} from 'react-native';
|
|
8
|
+
import { CK_LOGO, POWERED_BY } from './images.js';
|
|
9
|
+
|
|
10
|
+
type RunnerScreenProps = {
|
|
11
|
+
title: string;
|
|
12
|
+
statusText: string;
|
|
13
|
+
message?: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const RunnerScreen = ({
|
|
17
|
+
title,
|
|
18
|
+
statusText,
|
|
19
|
+
message,
|
|
20
|
+
}: RunnerScreenProps) => {
|
|
21
|
+
return (
|
|
22
|
+
<View style={styles.container}>
|
|
23
|
+
<StatusBar hidden={true} />
|
|
24
|
+
<View style={styles.topSpacer} />
|
|
25
|
+
<View style={styles.content}>
|
|
26
|
+
<Image source={{ uri: CK_LOGO }} style={styles.logo} resizeMode="cover" />
|
|
27
|
+
<Text style={styles.title}>{title}</Text>
|
|
28
|
+
<Text style={styles.statusText}>{statusText}</Text>
|
|
29
|
+
{message ? <Text style={styles.message}>{message}</Text> : null}
|
|
30
|
+
</View>
|
|
31
|
+
<View style={styles.footer}>
|
|
32
|
+
<Image
|
|
33
|
+
source={{ uri: POWERED_BY }}
|
|
34
|
+
style={styles.poweredBy}
|
|
35
|
+
resizeMode="contain"
|
|
36
|
+
/>
|
|
37
|
+
</View>
|
|
38
|
+
</View>
|
|
39
|
+
);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const styles = StyleSheet.create({
|
|
43
|
+
container: {
|
|
44
|
+
flex: 1,
|
|
45
|
+
alignItems: 'center',
|
|
46
|
+
backgroundColor: '#fff',
|
|
47
|
+
paddingVertical: 16,
|
|
48
|
+
},
|
|
49
|
+
topSpacer: {
|
|
50
|
+
minHeight: 16,
|
|
51
|
+
},
|
|
52
|
+
content: {
|
|
53
|
+
flex: 1,
|
|
54
|
+
alignItems: 'center',
|
|
55
|
+
justifyContent: 'center',
|
|
56
|
+
paddingHorizontal: 24,
|
|
57
|
+
},
|
|
58
|
+
logo: {
|
|
59
|
+
width: 64,
|
|
60
|
+
height: 64,
|
|
61
|
+
borderRadius: 14,
|
|
62
|
+
},
|
|
63
|
+
title: {
|
|
64
|
+
marginTop: 16,
|
|
65
|
+
fontSize: 28,
|
|
66
|
+
fontWeight: '700',
|
|
67
|
+
color: '#000',
|
|
68
|
+
textAlign: 'center',
|
|
69
|
+
},
|
|
70
|
+
statusText: {
|
|
71
|
+
marginTop: 12,
|
|
72
|
+
fontSize: 16,
|
|
73
|
+
fontWeight: '500',
|
|
74
|
+
color: '#000',
|
|
75
|
+
textAlign: 'center',
|
|
76
|
+
},
|
|
77
|
+
message: {
|
|
78
|
+
marginTop: 12,
|
|
79
|
+
maxWidth: 320,
|
|
80
|
+
fontSize: 14,
|
|
81
|
+
lineHeight: 20,
|
|
82
|
+
color: '#000',
|
|
83
|
+
textAlign: 'center',
|
|
84
|
+
},
|
|
85
|
+
footer: {
|
|
86
|
+
padding: 16,
|
|
87
|
+
},
|
|
88
|
+
poweredBy: {
|
|
89
|
+
width: 180,
|
|
90
|
+
height: 44,
|
|
91
|
+
opacity: 0.8,
|
|
92
|
+
},
|
|
93
|
+
});
|