@react-native-harness/runtime 1.0.0-alpha.8 → 1.0.0-canary.1761046904277
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/README.md +23 -4
- package/assets/moduleSystem.flow.js +23 -3
- package/dist/bundler/bundle.d.ts.map +1 -1
- package/dist/bundler/bundle.js +1 -0
- package/dist/bundler/evaluate.d.ts.map +1 -1
- package/dist/bundler/evaluate.js +7 -7
- package/dist/bundler/factory.d.ts +3 -0
- package/dist/bundler/factory.d.ts.map +1 -0
- package/dist/bundler/factory.js +36 -0
- package/dist/bundler/index.d.ts +2 -1
- package/dist/bundler/index.d.ts.map +1 -1
- package/dist/bundler/index.js +1 -1
- package/dist/bundler/types.d.ts +7 -0
- package/dist/bundler/types.d.ts.map +1 -0
- package/dist/bundler/types.js +1 -0
- package/dist/client/factory.d.ts.map +1 -1
- package/dist/client/factory.js +75 -11
- package/dist/client/getDeviceDescriptor.d.ts +1 -1
- package/dist/client/getDeviceDescriptor.d.ts.map +1 -1
- package/dist/client/getDeviceDescriptor.js +18 -6
- package/dist/collector/functions.d.ts.map +1 -1
- package/dist/collector/functions.js +8 -0
- package/dist/entry-point.d.ts +2 -0
- package/dist/entry-point.d.ts.map +1 -0
- package/dist/entry-point.js +4 -0
- package/dist/expect/index.d.ts.map +1 -1
- package/dist/expect/index.js +2 -0
- package/dist/expect/setup.js +2 -0
- package/dist/filtering/index.d.ts +2 -0
- package/dist/filtering/index.d.ts.map +1 -0
- package/dist/filtering/index.js +1 -0
- package/dist/filtering/testNameFilter.d.ts +12 -0
- package/dist/filtering/testNameFilter.d.ts.map +1 -0
- package/dist/filtering/testNameFilter.js +56 -0
- package/dist/globals.d.ts +5 -2
- package/dist/globals.d.ts.map +1 -1
- package/dist/globals.js +7 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/initialize.js +7 -0
- package/dist/jest-mock.d.ts +2 -0
- package/dist/jest-mock.d.ts.map +1 -0
- package/dist/jest-mock.js +25 -0
- package/dist/mocker/index.d.ts +1 -1
- package/dist/mocker/index.d.ts.map +1 -1
- package/dist/mocker/index.js +1 -1
- package/dist/mocker/registry.d.ts +2 -2
- package/dist/mocker/registry.d.ts.map +1 -1
- package/dist/mocker/registry.js +10 -4
- package/dist/namespace.d.ts +18 -0
- package/dist/namespace.d.ts.map +1 -0
- package/dist/namespace.js +19 -0
- package/dist/runner/factory.d.ts.map +1 -1
- package/dist/runner/factory.js +6 -1
- package/dist/runner/runSuite.d.ts.map +1 -1
- package/dist/runner/runSuite.js +37 -0
- package/dist/symbolicate.d.ts.map +1 -1
- package/dist/symbolicate.js +5 -4
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/utils/emitter.d.ts.map +1 -1
- package/dist/waitFor.d.ts +21 -0
- package/dist/waitFor.d.ts.map +1 -0
- package/dist/waitFor.js +137 -0
- package/eslint.config.mjs +1 -7
- package/out-tsc/vitest/src/__tests__/collector.test.d.ts +2 -0
- package/out-tsc/vitest/src/__tests__/collector.test.d.ts.map +1 -0
- package/out-tsc/vitest/src/__tests__/error-handling.test.d.ts +2 -0
- package/out-tsc/vitest/src/__tests__/error-handling.test.d.ts.map +1 -0
- package/out-tsc/vitest/src/__tests__/expect.test.d.ts +2 -0
- package/out-tsc/vitest/src/__tests__/expect.test.d.ts.map +1 -0
- package/out-tsc/vitest/src/__tests__/spy.test.d.ts +2 -0
- package/out-tsc/vitest/src/__tests__/spy.test.d.ts.map +1 -0
- package/out-tsc/vitest/src/bundler/bundle.d.ts +2 -0
- package/out-tsc/vitest/src/bundler/bundle.d.ts.map +1 -0
- package/out-tsc/vitest/src/bundler/errors.d.ts +15 -0
- package/out-tsc/vitest/src/bundler/errors.d.ts.map +1 -0
- package/out-tsc/vitest/src/bundler/evaluate.d.ts +2 -0
- package/out-tsc/vitest/src/bundler/evaluate.d.ts.map +1 -0
- package/out-tsc/vitest/src/bundler/factory.d.ts +3 -0
- package/out-tsc/vitest/src/bundler/factory.d.ts.map +1 -0
- package/out-tsc/vitest/src/bundler/index.d.ts +4 -0
- package/out-tsc/vitest/src/bundler/index.d.ts.map +1 -0
- package/out-tsc/vitest/src/bundler/types.d.ts +7 -0
- package/out-tsc/vitest/src/bundler/types.d.ts.map +1 -0
- package/out-tsc/vitest/src/client/factory.d.ts +2 -0
- package/out-tsc/vitest/src/client/factory.d.ts.map +1 -0
- package/out-tsc/vitest/src/client/getDeviceDescriptor.d.ts +8 -0
- package/out-tsc/vitest/src/client/getDeviceDescriptor.d.ts.map +1 -0
- package/out-tsc/vitest/src/client/getWSServer.d.ts +2 -0
- package/out-tsc/vitest/src/client/getWSServer.d.ts.map +1 -0
- package/out-tsc/vitest/src/client/index.d.ts +2 -0
- package/out-tsc/vitest/src/client/index.d.ts.map +1 -0
- package/out-tsc/vitest/src/collector/errors.d.ts +8 -0
- package/out-tsc/vitest/src/collector/errors.d.ts.map +1 -0
- package/out-tsc/vitest/src/collector/factory.d.ts +3 -0
- package/out-tsc/vitest/src/collector/factory.d.ts.map +1 -0
- package/out-tsc/vitest/src/collector/functions.d.ts +22 -0
- package/out-tsc/vitest/src/collector/functions.d.ts.map +1 -0
- package/out-tsc/vitest/src/collector/index.d.ts +5 -0
- package/out-tsc/vitest/src/collector/index.d.ts.map +1 -0
- package/out-tsc/vitest/src/collector/types.d.ts +10 -0
- package/out-tsc/vitest/src/collector/types.d.ts.map +1 -0
- package/out-tsc/vitest/src/collector/validation.d.ts +4 -0
- package/out-tsc/vitest/src/collector/validation.d.ts.map +1 -0
- package/out-tsc/vitest/src/constants.d.ts +3 -0
- package/out-tsc/vitest/src/constants.d.ts.map +1 -0
- package/out-tsc/vitest/src/entry-point.d.ts +2 -0
- package/out-tsc/vitest/src/entry-point.d.ts.map +1 -0
- package/out-tsc/vitest/src/errors.d.ts +6 -0
- package/out-tsc/vitest/src/errors.d.ts.map +1 -0
- package/out-tsc/vitest/src/expect/index.d.ts +9 -0
- package/out-tsc/vitest/src/expect/index.d.ts.map +1 -0
- package/out-tsc/vitest/src/expect/setup.d.ts +2 -0
- package/out-tsc/vitest/src/expect/setup.d.ts.map +1 -0
- package/out-tsc/vitest/src/filtering/index.d.ts +2 -0
- package/out-tsc/vitest/src/filtering/index.d.ts.map +1 -0
- package/out-tsc/vitest/src/filtering/testNameFilter.d.ts +12 -0
- package/out-tsc/vitest/src/filtering/testNameFilter.d.ts.map +1 -0
- package/out-tsc/vitest/src/globals.d.ts +8 -0
- package/out-tsc/vitest/src/globals.d.ts.map +1 -0
- package/out-tsc/vitest/src/index.d.ts +9 -0
- package/out-tsc/vitest/src/index.d.ts.map +1 -0
- package/out-tsc/vitest/src/initialize.d.ts +2 -0
- package/out-tsc/vitest/src/initialize.d.ts.map +1 -0
- package/out-tsc/vitest/src/mocker/index.d.ts +2 -0
- package/out-tsc/vitest/src/mocker/index.d.ts.map +1 -0
- package/out-tsc/vitest/src/mocker/registry.d.ts +7 -0
- package/out-tsc/vitest/src/mocker/registry.d.ts.map +1 -0
- package/out-tsc/vitest/src/mocker/types.d.ts +6 -0
- package/out-tsc/vitest/src/mocker/types.d.ts.map +1 -0
- package/out-tsc/vitest/src/namespace.d.ts +18 -0
- package/out-tsc/vitest/src/namespace.d.ts.map +1 -0
- package/out-tsc/vitest/src/runner/errors.d.ts +11 -0
- package/out-tsc/vitest/src/runner/errors.d.ts.map +1 -0
- package/out-tsc/vitest/src/runner/factory.d.ts +3 -0
- package/out-tsc/vitest/src/runner/factory.d.ts.map +1 -0
- package/out-tsc/vitest/src/runner/hooks.d.ts +4 -0
- package/out-tsc/vitest/src/runner/hooks.d.ts.map +1 -0
- package/out-tsc/vitest/src/runner/index.d.ts +4 -0
- package/out-tsc/vitest/src/runner/index.d.ts.map +1 -0
- package/out-tsc/vitest/src/runner/runSuite.d.ts +4 -0
- package/out-tsc/vitest/src/runner/runSuite.d.ts.map +1 -0
- package/out-tsc/vitest/src/runner/types.d.ts +13 -0
- package/out-tsc/vitest/src/runner/types.d.ts.map +1 -0
- package/out-tsc/vitest/src/spy/index.d.ts +2 -0
- package/out-tsc/vitest/src/spy/index.d.ts.map +1 -0
- package/out-tsc/vitest/src/symbolicate.d.ts +3 -0
- package/out-tsc/vitest/src/symbolicate.d.ts.map +1 -0
- package/out-tsc/vitest/src/ui/ReadyScreen.d.ts +2 -0
- package/out-tsc/vitest/src/ui/ReadyScreen.d.ts.map +1 -0
- package/out-tsc/vitest/src/ui/WrongEnvironmentScreen.d.ts +2 -0
- package/out-tsc/vitest/src/ui/WrongEnvironmentScreen.d.ts.map +1 -0
- package/out-tsc/vitest/src/ui/index.d.ts +2 -0
- package/out-tsc/vitest/src/ui/index.d.ts.map +1 -0
- package/out-tsc/vitest/src/ui/state.d.ts +7 -0
- package/out-tsc/vitest/src/ui/state.d.ts.map +1 -0
- package/out-tsc/vitest/src/utils/dev-server.d.ts +2 -0
- package/out-tsc/vitest/src/utils/dev-server.d.ts.map +1 -0
- package/out-tsc/vitest/src/utils/emitter.d.ts +16 -0
- package/out-tsc/vitest/src/utils/emitter.d.ts.map +1 -0
- package/out-tsc/vitest/src/waitFor.d.ts +21 -0
- package/out-tsc/vitest/src/waitFor.d.ts.map +1 -0
- package/out-tsc/vitest/tsconfig.spec.tsbuildinfo +1 -0
- package/out-tsc/vitest/vite.config.d.ts +3 -0
- package/out-tsc/vitest/vite.config.d.ts.map +1 -0
- package/package.json +15 -7
- package/src/__tests__/expect.test.ts +13 -5
- package/src/bundler/bundle.ts +1 -0
- package/src/bundler/evaluate.ts +9 -9
- package/src/bundler/factory.ts +43 -0
- package/src/bundler/index.ts +2 -1
- package/src/bundler/types.ts +7 -0
- package/src/client/factory.ts +95 -12
- package/src/client/getDeviceDescriptor.ts +29 -8
- package/src/collector/functions.ts +14 -0
- package/src/entry-point.ts +8 -0
- package/src/expect/index.ts +8 -2
- package/src/expect/setup.ts +3 -0
- package/src/filtering/index.ts +4 -0
- package/src/filtering/testNameFilter.ts +82 -0
- package/src/globals.ts +14 -2
- package/src/index.ts +2 -0
- package/src/initialize.ts +11 -1
- package/src/jest-mock.ts +32 -0
- package/src/mocker/index.ts +7 -1
- package/src/mocker/metro-require.d.ts +2 -0
- package/src/mocker/registry.ts +13 -5
- package/src/namespace.ts +41 -0
- package/src/react-native.d.ts +2 -10
- package/src/runner/factory.ts +8 -1
- package/src/runner/runSuite.ts +43 -0
- package/src/symbolicate.ts +6 -4
- package/src/utils/emitter.ts +1 -0
- package/src/waitFor.ts +199 -0
- package/tsconfig.spec.json +7 -3
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/utils/progressLogger.d.ts +0 -8
- package/dist/utils/progressLogger.d.ts.map +0 -1
- package/dist/utils/progressLogger.js +0 -73
- package/src/utils/progressLogger.ts +0 -91
- package/types/global.d.ts +0 -2
- package/types/index.d.ts +0 -1
package/src/client/factory.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
TestRunnerEvents,
|
|
3
3
|
TestCollectorEvents,
|
|
4
|
+
BundlerEvents,
|
|
5
|
+
TestExecutionOptions,
|
|
4
6
|
} from '@react-native-harness/bridge';
|
|
5
7
|
import { getBridgeClient } from '@react-native-harness/bridge/client';
|
|
6
8
|
import { store } from '../ui/state.js';
|
|
7
9
|
import { getTestRunner, TestRunner } from '../runner/index.js';
|
|
8
10
|
import { getTestCollector, TestCollector } from '../collector/index.js';
|
|
9
11
|
import { combineEventEmitters, EventEmitter } from '../utils/emitter.js';
|
|
10
|
-
import { attachProgressLogger } from '../utils/progressLogger.js';
|
|
11
12
|
import { getWSServer } from './getWSServer.js';
|
|
12
|
-
import {
|
|
13
|
+
import { getBundler, evaluateModule, Bundler } from '../bundler/index.js';
|
|
14
|
+
import { markTestsAsSkippedByName } from '../filtering/index.js';
|
|
13
15
|
|
|
14
16
|
export const getClient = async () => {
|
|
15
17
|
const client = await getBridgeClient(getWSServer(), {
|
|
@@ -18,7 +20,10 @@ export const getClient = async () => {
|
|
|
18
20
|
},
|
|
19
21
|
});
|
|
20
22
|
|
|
21
|
-
client.rpc.$functions.runTests = async (
|
|
23
|
+
client.rpc.$functions.runTests = async (
|
|
24
|
+
path: string,
|
|
25
|
+
options: TestExecutionOptions = {}
|
|
26
|
+
) => {
|
|
22
27
|
if (store.getState().status === 'running') {
|
|
23
28
|
throw new Error('Already running tests');
|
|
24
29
|
}
|
|
@@ -27,30 +32,108 @@ export const getClient = async () => {
|
|
|
27
32
|
|
|
28
33
|
let collector: TestCollector | null = null;
|
|
29
34
|
let runner: TestRunner | null = null;
|
|
30
|
-
let events: EventEmitter<
|
|
31
|
-
|
|
35
|
+
let events: EventEmitter<
|
|
36
|
+
TestRunnerEvents | TestCollectorEvents | BundlerEvents
|
|
37
|
+
> | null = null;
|
|
38
|
+
let bundler: Bundler | null = null;
|
|
32
39
|
|
|
33
40
|
try {
|
|
34
41
|
collector = getTestCollector();
|
|
35
42
|
runner = getTestRunner();
|
|
36
|
-
|
|
43
|
+
bundler = getBundler();
|
|
44
|
+
events = combineEventEmitters(
|
|
45
|
+
collector.events,
|
|
46
|
+
runner.events,
|
|
47
|
+
bundler.events
|
|
48
|
+
);
|
|
37
49
|
|
|
38
50
|
events.addListener((event) => {
|
|
39
51
|
client.rpc.emitEvent(event.type, event);
|
|
40
52
|
});
|
|
41
53
|
|
|
42
|
-
//
|
|
43
|
-
|
|
54
|
+
// Execute setup files before test collection
|
|
55
|
+
if (options.setupFiles) {
|
|
56
|
+
for (const setupFile of options.setupFiles) {
|
|
57
|
+
const startTime = Date.now();
|
|
58
|
+
events.emit({
|
|
59
|
+
type: 'setup-file-bundling-started',
|
|
60
|
+
file: setupFile,
|
|
61
|
+
setupType: 'setupFiles',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const setupModuleJs = await bundler.getModule(setupFile);
|
|
66
|
+
events.emit({
|
|
67
|
+
type: 'setup-file-bundling-finished',
|
|
68
|
+
file: setupFile,
|
|
69
|
+
setupType: 'setupFiles',
|
|
70
|
+
duration: Date.now() - startTime,
|
|
71
|
+
});
|
|
72
|
+
evaluateModule(setupModuleJs, setupFile);
|
|
73
|
+
} catch (error) {
|
|
74
|
+
const errorMessage =
|
|
75
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
76
|
+
events.emit({
|
|
77
|
+
type: 'setup-file-bundling-failed',
|
|
78
|
+
file: setupFile,
|
|
79
|
+
setupType: 'setupFiles',
|
|
80
|
+
duration: Date.now() - startTime,
|
|
81
|
+
error: errorMessage,
|
|
82
|
+
});
|
|
83
|
+
throw error;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (options.setupFilesAfterEnv) {
|
|
89
|
+
for (const setupFile of options.setupFilesAfterEnv) {
|
|
90
|
+
const startTime = Date.now();
|
|
91
|
+
events.emit({
|
|
92
|
+
type: 'setup-file-bundling-started',
|
|
93
|
+
file: setupFile,
|
|
94
|
+
setupType: 'setupFilesAfterEnv',
|
|
95
|
+
});
|
|
44
96
|
|
|
45
|
-
|
|
97
|
+
try {
|
|
98
|
+
const setupModuleJs = await bundler.getModule(setupFile);
|
|
99
|
+
events.emit({
|
|
100
|
+
type: 'setup-file-bundling-finished',
|
|
101
|
+
file: setupFile,
|
|
102
|
+
setupType: 'setupFilesAfterEnv',
|
|
103
|
+
duration: Date.now() - startTime,
|
|
104
|
+
});
|
|
105
|
+
evaluateModule(setupModuleJs, setupFile);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
const errorMessage =
|
|
108
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
109
|
+
events.emit({
|
|
110
|
+
type: 'setup-file-bundling-failed',
|
|
111
|
+
file: setupFile,
|
|
112
|
+
setupType: 'setupFilesAfterEnv',
|
|
113
|
+
duration: Date.now() - startTime,
|
|
114
|
+
error: errorMessage,
|
|
115
|
+
});
|
|
116
|
+
throw error;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const moduleJs = await bundler.getModule(path);
|
|
46
122
|
const collectionResult = await collector.collect(
|
|
47
123
|
() => evaluateModule(moduleJs, path),
|
|
48
124
|
path
|
|
49
125
|
);
|
|
50
|
-
|
|
126
|
+
|
|
127
|
+
// Apply test name pattern by marking non-matching tests as skipped
|
|
128
|
+
const processedTestSuite = options.testNamePattern
|
|
129
|
+
? markTestsAsSkippedByName(
|
|
130
|
+
collectionResult.testSuite,
|
|
131
|
+
options.testNamePattern
|
|
132
|
+
)
|
|
133
|
+
: collectionResult.testSuite;
|
|
134
|
+
|
|
135
|
+
const result = await runner.run(processedTestSuite, path);
|
|
51
136
|
return result;
|
|
52
|
-
} catch (error) {
|
|
53
|
-
throw error;
|
|
54
137
|
} finally {
|
|
55
138
|
collector?.dispose();
|
|
56
139
|
runner?.dispose();
|
|
@@ -1,28 +1,49 @@
|
|
|
1
|
-
import { Platform } from 'react-native';
|
|
1
|
+
import { Platform, PlatformConstants, PlatformStatic } from 'react-native';
|
|
2
|
+
|
|
3
|
+
interface PlatformKeplerStatic extends PlatformStatic {
|
|
4
|
+
constants: PlatformConstants;
|
|
5
|
+
OS: 'kepler';
|
|
6
|
+
Version: number;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const getPlatform = (): Platform | PlatformKeplerStatic => {
|
|
10
|
+
return Platform as Platform | PlatformKeplerStatic;
|
|
11
|
+
};
|
|
2
12
|
|
|
3
13
|
export type DeviceDescriptor = {
|
|
4
|
-
platform: 'ios' | 'android';
|
|
14
|
+
platform: 'ios' | 'android' | 'vega';
|
|
5
15
|
manufacturer: string;
|
|
6
16
|
model: string;
|
|
7
17
|
osVersion: string;
|
|
8
18
|
};
|
|
9
19
|
|
|
10
20
|
export const getDeviceDescriptor = (): DeviceDescriptor => {
|
|
11
|
-
|
|
21
|
+
const platform = getPlatform();
|
|
22
|
+
|
|
23
|
+
if (platform.OS === 'ios') {
|
|
12
24
|
return {
|
|
13
25
|
platform: 'ios',
|
|
14
26
|
manufacturer: 'Apple',
|
|
15
27
|
model: 'Unknown',
|
|
16
|
-
osVersion:
|
|
28
|
+
osVersion: platform.constants.osVersion,
|
|
17
29
|
};
|
|
18
30
|
}
|
|
19
31
|
|
|
20
|
-
if (
|
|
32
|
+
if (platform.OS === 'android') {
|
|
21
33
|
return {
|
|
22
34
|
platform: 'android',
|
|
23
|
-
manufacturer:
|
|
24
|
-
model:
|
|
25
|
-
osVersion:
|
|
35
|
+
manufacturer: platform.constants.Manufacturer,
|
|
36
|
+
model: platform.constants.Model,
|
|
37
|
+
osVersion: platform.constants.Release,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (platform.OS === 'kepler') {
|
|
42
|
+
return {
|
|
43
|
+
platform: 'vega',
|
|
44
|
+
manufacturer: '',
|
|
45
|
+
model: '',
|
|
46
|
+
osVersion: '',
|
|
26
47
|
};
|
|
27
48
|
}
|
|
28
49
|
|
|
@@ -53,7 +53,21 @@ const computeSuiteStatus = (
|
|
|
53
53
|
): TestStatus => {
|
|
54
54
|
if (suite.options.skip) return 'skipped';
|
|
55
55
|
if (suite.options.only) return 'active';
|
|
56
|
+
|
|
57
|
+
// Check if this suite has any focused content (tests or child suites)
|
|
58
|
+
const hasFocusedTests = suite.tests.some((test) => test.options.only);
|
|
59
|
+
const hasFocusedChildren = suite.suites.some(
|
|
60
|
+
(childSuite) =>
|
|
61
|
+
childSuite.options.only ||
|
|
62
|
+
childSuite.tests.some((test) => test.options.only)
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// If this suite has focused content, it should be active
|
|
66
|
+
if (hasFocusedTests || hasFocusedChildren) return 'active';
|
|
67
|
+
|
|
68
|
+
// If parent has focused children and this suite has no focused content, skip it
|
|
56
69
|
if (parentContext.hasFocusedChildren) return 'skipped';
|
|
70
|
+
|
|
57
71
|
return 'active';
|
|
58
72
|
};
|
|
59
73
|
|
package/src/expect/index.ts
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// This is adapted version of https://github.com/vitest-dev/vitest/blob/main/packages/vitest/src/integrations/chai/index.ts
|
|
2
|
+
// Credits to Vitest team for the original implementation.
|
|
3
|
+
|
|
1
4
|
import type { Assertion, ExpectStatic, MatcherState } from '@vitest/expect';
|
|
2
5
|
import {
|
|
3
6
|
addCustomEqualityTesters,
|
|
@@ -13,13 +16,16 @@ import * as chai from 'chai';
|
|
|
13
16
|
import './setup.js';
|
|
14
17
|
|
|
15
18
|
export function createExpect(): ExpectStatic {
|
|
16
|
-
const expect = ((value:
|
|
19
|
+
const expect = ((value: unknown, message?: string): Assertion => {
|
|
17
20
|
const { assertionCalls } = getState(expect);
|
|
18
21
|
setState({ assertionCalls: assertionCalls + 1 }, expect);
|
|
19
22
|
return chai.expect(value, message) as unknown as Assertion;
|
|
20
23
|
}) as ExpectStatic;
|
|
21
24
|
Object.assign(expect, chai.expect);
|
|
22
|
-
Object.assign(
|
|
25
|
+
Object.assign(
|
|
26
|
+
expect,
|
|
27
|
+
globalThis[ASYMMETRIC_MATCHERS_OBJECT as unknown as keyof typeof globalThis]
|
|
28
|
+
);
|
|
23
29
|
|
|
24
30
|
expect.getState = () => getState<MatcherState>(expect);
|
|
25
31
|
expect.setState = (state) => setState(state as Partial<MatcherState>, expect);
|
package/src/expect/setup.ts
CHANGED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { TestSuite } from '@react-native-harness/bridge';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filters tests by name pattern, matching against test names and suite+test combinations
|
|
5
|
+
* @deprecated Use markTestsAsSkippedByName instead - this function will be removed in a future version
|
|
6
|
+
*/
|
|
7
|
+
export const filterTestsByName = (
|
|
8
|
+
suite: TestSuite,
|
|
9
|
+
testNamePattern: string
|
|
10
|
+
): TestSuite => {
|
|
11
|
+
const regex = new RegExp(testNamePattern);
|
|
12
|
+
return filterSuiteRecursively(suite, regex);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Marks tests as skipped based on name pattern, keeping all tests in the structure
|
|
17
|
+
* but setting non-matching tests to 'skipped' status
|
|
18
|
+
*/
|
|
19
|
+
export const markTestsAsSkippedByName = (
|
|
20
|
+
suite: TestSuite,
|
|
21
|
+
testNamePattern: string
|
|
22
|
+
): TestSuite => {
|
|
23
|
+
const regex = new RegExp(testNamePattern);
|
|
24
|
+
return markTestsRecursively(suite, regex);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const markTestsRecursively = (suite: TestSuite, regex: RegExp): TestSuite => {
|
|
28
|
+
// Mark tests in current suite - skip tests that don't match the pattern
|
|
29
|
+
const updatedTests = suite.tests.map((test) => {
|
|
30
|
+
const matches =
|
|
31
|
+
regex.test(test.name) || regex.test(`${suite.name} ${test.name}`);
|
|
32
|
+
|
|
33
|
+
// If test doesn't match pattern and is currently active, mark it as skipped
|
|
34
|
+
if (!matches && test.status === 'active') {
|
|
35
|
+
return {
|
|
36
|
+
...test,
|
|
37
|
+
status: 'skipped' as const,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Keep original status for matching tests or already skipped/todo tests
|
|
42
|
+
return test;
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Recursively process child suites
|
|
46
|
+
const updatedChildSuites = suite.suites.map((childSuite) =>
|
|
47
|
+
markTestsRecursively(childSuite, regex)
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
...suite,
|
|
52
|
+
tests: updatedTests,
|
|
53
|
+
suites: updatedChildSuites,
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const filterSuiteRecursively = (suite: TestSuite, regex: RegExp): TestSuite => {
|
|
58
|
+
// Filter tests in current suite - match against test name or "suite test" combination
|
|
59
|
+
const filteredTests = suite.tests.filter(
|
|
60
|
+
(test) => regex.test(test.name) || regex.test(`${suite.name} ${test.name}`)
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Recursively filter child suites
|
|
64
|
+
const filteredChildSuites = suite.suites
|
|
65
|
+
.map((childSuite) => filterSuiteRecursively(childSuite, regex))
|
|
66
|
+
.filter((childSuite) => hasAnyActiveTests(childSuite));
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
...suite,
|
|
70
|
+
tests: filteredTests,
|
|
71
|
+
suites: filteredChildSuites,
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const hasAnyActiveTests = (suite: TestSuite): boolean => {
|
|
76
|
+
const hasDirectTests = suite.tests.some((test) => test.status === 'active');
|
|
77
|
+
const hasChildTests = suite.suites.some((childSuite) =>
|
|
78
|
+
hasAnyActiveTests(childSuite)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
return hasDirectTests || hasChildTests;
|
|
82
|
+
};
|
package/src/globals.ts
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
export type HarnessGlobal = {
|
|
2
|
+
appRegistryComponentName: string;
|
|
3
|
+
};
|
|
4
|
+
|
|
1
5
|
declare global {
|
|
2
|
-
var RN_HARNESS:
|
|
6
|
+
var RN_HARNESS: HarnessGlobal | undefined;
|
|
3
7
|
}
|
|
4
8
|
|
|
5
|
-
export {
|
|
9
|
+
export const getHarnessGlobal = (): HarnessGlobal => {
|
|
10
|
+
const harnessGlobal = global.RN_HARNESS;
|
|
11
|
+
|
|
12
|
+
if (!harnessGlobal) {
|
|
13
|
+
throw new Error('RN_HARNESS global is not set');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return harnessGlobal;
|
|
17
|
+
};
|
package/src/index.ts
CHANGED
package/src/initialize.ts
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
1
|
import { getDeviceDescriptor } from './client/getDeviceDescriptor.js';
|
|
2
2
|
import { getClient } from './client/index.js';
|
|
3
|
+
import { setupJestMock } from './jest-mock.js';
|
|
3
4
|
|
|
4
5
|
// Polyfill for EventTarget
|
|
5
6
|
const Shim = require('event-target-shim');
|
|
6
7
|
globalThis.Event = Shim.Event;
|
|
7
8
|
globalThis.EventTarget = Shim.EventTarget;
|
|
8
9
|
|
|
10
|
+
// Setup jest mock to warn users about using Jest APIs
|
|
11
|
+
setupJestMock();
|
|
12
|
+
|
|
9
13
|
// Turn off LogBox
|
|
10
14
|
const { LogBox } = require('react-native');
|
|
11
15
|
LogBox.ignoreAllLogs(true);
|
|
12
16
|
|
|
13
17
|
// Turn off HMR
|
|
14
18
|
const HMRClientModule = require('react-native/Libraries/Utilities/HMRClient');
|
|
15
|
-
const HMRClient =
|
|
19
|
+
const HMRClient =
|
|
20
|
+
'default' in HMRClientModule ? HMRClientModule.default : HMRClientModule;
|
|
16
21
|
|
|
17
22
|
// Wait for HMRClient to be initialized
|
|
18
23
|
setTimeout(() => {
|
|
@@ -23,3 +28,8 @@ setTimeout(() => {
|
|
|
23
28
|
client.rpc.reportReady(getDeviceDescriptor())
|
|
24
29
|
);
|
|
25
30
|
});
|
|
31
|
+
|
|
32
|
+
// Re-throw fatal errors
|
|
33
|
+
ErrorUtils.setGlobalHandler((error) => {
|
|
34
|
+
throw error;
|
|
35
|
+
});
|
package/src/jest-mock.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// Mock jest global to warn users about using Jest APIs in Harness tests
|
|
2
|
+
export const setupJestMock = (): void => {
|
|
3
|
+
function throwError(): never {
|
|
4
|
+
throw new Error(
|
|
5
|
+
`Jest globals are not available in Harness tests. Import from 'react-native-harness' instead (e.g., import { harness } from 'react-native-harness'; harness.fn())`
|
|
6
|
+
);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const jestMock = new Proxy(
|
|
10
|
+
{},
|
|
11
|
+
{
|
|
12
|
+
get() {
|
|
13
|
+
throwError();
|
|
14
|
+
},
|
|
15
|
+
set() {
|
|
16
|
+
throwError();
|
|
17
|
+
},
|
|
18
|
+
has() {
|
|
19
|
+
throwError();
|
|
20
|
+
},
|
|
21
|
+
ownKeys() {
|
|
22
|
+
throwError();
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
Object.defineProperty(globalThis, 'jest', {
|
|
28
|
+
value: jestMock,
|
|
29
|
+
writable: false,
|
|
30
|
+
configurable: false,
|
|
31
|
+
});
|
|
32
|
+
};
|
package/src/mocker/index.ts
CHANGED
package/src/mocker/registry.ts
CHANGED
|
@@ -15,11 +15,7 @@ export const clearMocks = (): void => {
|
|
|
15
15
|
mockCache.clear();
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
return mockRegistry;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const getMockImplementation = (moduleId: number): unknown | null => {
|
|
18
|
+
const getMockImplementation = (moduleId: number): unknown | null => {
|
|
23
19
|
if (mockCache.has(moduleId)) {
|
|
24
20
|
return mockCache.get(moduleId);
|
|
25
21
|
}
|
|
@@ -39,6 +35,18 @@ export const requireActual = <T = any>(moduleId: string): T =>
|
|
|
39
35
|
// babel plugin will transform 'moduleId' to a number
|
|
40
36
|
originalRequire(moduleId as unknown as ModuleId) as T;
|
|
41
37
|
|
|
38
|
+
export const unmock = (moduleId: string) => {
|
|
39
|
+
mockRegistry.delete(moduleId as unknown as ModuleId);
|
|
40
|
+
mockCache.delete(moduleId as unknown as ModuleId);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const resetModules = (): void => {
|
|
44
|
+
mockCache.clear();
|
|
45
|
+
|
|
46
|
+
// Reset Metro's module cache
|
|
47
|
+
global.__resetAllModules();
|
|
48
|
+
};
|
|
49
|
+
|
|
42
50
|
const mockRequire = (moduleId: string) => {
|
|
43
51
|
// babel plugin will transform 'moduleId' to a number
|
|
44
52
|
const mockedModule = getMockImplementation(moduleId as unknown as ModuleId);
|
package/src/namespace.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import {
|
|
2
|
+
spyOn,
|
|
3
|
+
fn,
|
|
4
|
+
clearAllMocks,
|
|
5
|
+
resetAllMocks,
|
|
6
|
+
restoreAllMocks,
|
|
7
|
+
} from './spy/index.js';
|
|
8
|
+
import { mock, unmock, requireActual, resetModules } from './mocker/index.js';
|
|
9
|
+
import { waitFor, waitUntil } from './waitFor.js';
|
|
10
|
+
|
|
11
|
+
export type HarnessNamespace = {
|
|
12
|
+
spyOn: typeof spyOn;
|
|
13
|
+
fn: typeof fn;
|
|
14
|
+
mock: typeof mock;
|
|
15
|
+
unmock: typeof unmock;
|
|
16
|
+
requireActual: typeof requireActual;
|
|
17
|
+
clearAllMocks: typeof clearAllMocks;
|
|
18
|
+
resetAllMocks: typeof resetAllMocks;
|
|
19
|
+
restoreAllMocks: typeof restoreAllMocks;
|
|
20
|
+
resetModules: typeof resetModules;
|
|
21
|
+
waitFor: typeof waitFor;
|
|
22
|
+
waitUntil: typeof waitUntil;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const createHarnessNamespace = (): HarnessNamespace => {
|
|
26
|
+
return {
|
|
27
|
+
spyOn,
|
|
28
|
+
fn,
|
|
29
|
+
mock,
|
|
30
|
+
unmock,
|
|
31
|
+
requireActual,
|
|
32
|
+
clearAllMocks,
|
|
33
|
+
resetAllMocks,
|
|
34
|
+
restoreAllMocks,
|
|
35
|
+
resetModules,
|
|
36
|
+
waitFor,
|
|
37
|
+
waitUntil,
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const harness = createHarnessNamespace();
|
package/src/react-native.d.ts
CHANGED
|
@@ -16,7 +16,7 @@ declare module 'react-native/Libraries/Core/Devtools/symbolicateStackTrace' {
|
|
|
16
16
|
| {
|
|
17
17
|
row: number;
|
|
18
18
|
column: number;
|
|
19
|
-
[key: string]:
|
|
19
|
+
[key: string]: unknown;
|
|
20
20
|
}
|
|
21
21
|
| null
|
|
22
22
|
| undefined;
|
|
@@ -30,7 +30,7 @@ declare module 'react-native/Libraries/Core/Devtools/symbolicateStackTrace' {
|
|
|
30
30
|
|
|
31
31
|
export default function symbolicateStackTrace(
|
|
32
32
|
stack: ReadonlyArray<StackFrame>,
|
|
33
|
-
extraData?:
|
|
33
|
+
extraData?: unknown
|
|
34
34
|
): Promise<SymbolicatedStackTrace>;
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -43,11 +43,3 @@ declare module 'react-native/Libraries/Core/Devtools/parseErrorStack' {
|
|
|
43
43
|
};
|
|
44
44
|
export default function parseErrorStack(errorStack?: string): StackFrame[];
|
|
45
45
|
}
|
|
46
|
-
|
|
47
|
-
declare global {
|
|
48
|
-
var __r:
|
|
49
|
-
| {
|
|
50
|
-
(moduleId: number): unknown;
|
|
51
|
-
}
|
|
52
|
-
| undefined;
|
|
53
|
-
}
|
package/src/runner/factory.ts
CHANGED
|
@@ -9,10 +9,17 @@ export const getTestRunner = (): TestRunner => {
|
|
|
9
9
|
return {
|
|
10
10
|
events,
|
|
11
11
|
run: async (testSuite, testFilePath) => {
|
|
12
|
-
|
|
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();
|
package/src/runner/runSuite.ts
CHANGED
|
@@ -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
|
|
package/src/symbolicate.ts
CHANGED
|
@@ -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
|
-
...
|
|
16
|
-
location:
|
|
17
|
+
...codeFrame,
|
|
18
|
+
location: codeFrame.location
|
|
17
19
|
? {
|
|
18
|
-
...
|
|
20
|
+
...codeFrame.location,
|
|
19
21
|
}
|
|
20
22
|
: undefined,
|
|
21
23
|
};
|
package/src/utils/emitter.ts
CHANGED