@react-native-harness/runtime 1.0.0-alpha.14 → 1.0.0-alpha.17
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/assets/moduleSystem.flow.js +15 -3
- package/dist/bundler/evaluate.d.ts.map +1 -1
- package/dist/bundler/evaluate.js +7 -7
- package/dist/client/factory.d.ts.map +1 -1
- package/dist/client/factory.js +26 -12
- package/dist/client/setup-files.d.ts +12 -0
- package/dist/client/setup-files.d.ts.map +1 -0
- package/dist/client/setup-files.js +60 -0
- package/dist/collector/functions.d.ts +1 -1
- package/dist/collector/functions.d.ts.map +1 -1
- package/dist/collector/functions.js +2 -2
- package/dist/collector/types.d.ts +1 -1
- package/dist/collector/types.d.ts.map +1 -1
- 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 +1 -1
- package/dist/filtering/index.d.ts.map +1 -1
- package/dist/filtering/index.js +1 -1
- package/dist/filtering/testNameFilter.d.ts +6 -0
- package/dist/filtering/testNameFilter.d.ts.map +1 -1
- package/dist/filtering/testNameFilter.js +36 -5
- 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 +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -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/render/ErrorBoundary.d.ts +17 -0
- package/dist/render/ErrorBoundary.d.ts.map +1 -0
- package/dist/render/ErrorBoundary.js +73 -0
- package/dist/render/TestComponentOverlay.d.ts +3 -0
- package/dist/render/TestComponentOverlay.d.ts.map +1 -0
- package/dist/render/TestComponentOverlay.js +36 -0
- package/dist/render/cleanup.d.ts +2 -0
- package/dist/render/cleanup.d.ts.map +1 -0
- package/dist/render/cleanup.js +6 -0
- package/dist/render/index.d.ts +6 -0
- package/dist/render/index.d.ts.map +1 -0
- package/dist/render/index.js +66 -0
- package/dist/render/setup.d.ts +2 -0
- package/dist/render/setup.d.ts.map +1 -0
- package/dist/render/setup.js +7 -0
- package/dist/render/types.d.ts +12 -0
- package/dist/render/types.d.ts.map +1 -0
- package/dist/render/types.js +1 -0
- package/dist/runner/factory.d.ts.map +1 -1
- package/dist/runner/factory.js +6 -1
- package/dist/symbolicate.d.ts.map +1 -1
- package/dist/symbolicate.js +5 -4
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/dist/ui/ReadyScreen.d.ts.map +1 -1
- package/dist/ui/ReadyScreen.js +2 -1
- package/dist/ui/state.d.ts +14 -1
- package/dist/ui/state.d.ts.map +1 -1
- package/dist/ui/state.js +22 -0
- package/dist/utils/emitter.d.ts.map +1 -1
- package/dist/waitFor.d.ts.map +1 -1
- package/dist/waitFor.js +4 -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 +10 -4
- package/src/__tests__/collector.test.ts +55 -55
- package/src/__tests__/error-handling.test.ts +34 -34
- package/src/__tests__/expect.test.ts +13 -5
- package/src/bundler/bundle.ts +1 -2
- package/src/bundler/evaluate.ts +9 -9
- package/src/client/factory.ts +30 -14
- package/src/client/setup-files.ts +81 -0
- package/src/collector/functions.ts +4 -2
- package/src/collector/types.ts +4 -1
- 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 -1
- package/src/filtering/testNameFilter.ts +53 -8
- package/src/globals.ts +14 -2
- package/src/index.ts +1 -0
- package/src/initialize.ts +11 -1
- package/src/jest-mock.ts +32 -0
- package/src/mocker/metro-require.d.ts +1 -0
- package/src/react-native.d.ts +2 -10
- package/src/render/ErrorBoundary.tsx +108 -0
- package/src/render/TestComponentOverlay.tsx +47 -0
- package/src/render/cleanup.ts +7 -0
- package/src/render/index.ts +96 -0
- package/src/render/setup.ts +8 -0
- package/src/render/types.ts +11 -0
- package/src/runner/factory.ts +8 -1
- package/src/symbolicate.ts +6 -4
- package/src/ui/ReadyScreen.tsx +2 -0
- package/src/ui/state.ts +39 -0
- package/src/utils/emitter.ts +1 -0
- package/src/waitFor.ts +6 -1
- 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 -79
- package/src/utils/progressLogger.ts +0 -98
- package/types/global.d.ts +0 -2
- package/types/index.d.ts +0 -1
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { EventEmitter } from '../utils/emitter.js';
|
|
2
|
+
import { Bundler } from '../bundler/index.js';
|
|
3
|
+
import { BundlerEvents } from '@react-native-harness/bridge';
|
|
4
|
+
|
|
5
|
+
export type RunSetupFilesOptions = {
|
|
6
|
+
setupFiles: string[];
|
|
7
|
+
setupFilesAfterEnv: string[];
|
|
8
|
+
events: EventEmitter<BundlerEvents>;
|
|
9
|
+
bundler: Bundler;
|
|
10
|
+
evaluateModule: (moduleJs: string, filePath: string) => void;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const runSetupFiles = async ({
|
|
14
|
+
setupFiles,
|
|
15
|
+
setupFilesAfterEnv,
|
|
16
|
+
events,
|
|
17
|
+
bundler,
|
|
18
|
+
evaluateModule,
|
|
19
|
+
}: RunSetupFilesOptions) => {
|
|
20
|
+
for (const setupFile of setupFiles) {
|
|
21
|
+
const startTime = Date.now();
|
|
22
|
+
events.emit({
|
|
23
|
+
type: 'setup-file-bundling-started',
|
|
24
|
+
file: setupFile,
|
|
25
|
+
setupType: 'setupFiles',
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const setupModuleJs = await bundler.getModule(setupFile);
|
|
30
|
+
events.emit({
|
|
31
|
+
type: 'setup-file-bundling-finished',
|
|
32
|
+
file: setupFile,
|
|
33
|
+
setupType: 'setupFiles',
|
|
34
|
+
duration: Date.now() - startTime,
|
|
35
|
+
});
|
|
36
|
+
evaluateModule(setupModuleJs, setupFile);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
const errorMessage =
|
|
39
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
40
|
+
events.emit({
|
|
41
|
+
type: 'setup-file-bundling-failed',
|
|
42
|
+
file: setupFile,
|
|
43
|
+
setupType: 'setupFiles',
|
|
44
|
+
duration: Date.now() - startTime,
|
|
45
|
+
error: errorMessage,
|
|
46
|
+
});
|
|
47
|
+
throw error;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
for (const setupFile of setupFilesAfterEnv) {
|
|
52
|
+
const startTime = Date.now();
|
|
53
|
+
events.emit({
|
|
54
|
+
type: 'setup-file-bundling-started',
|
|
55
|
+
file: setupFile,
|
|
56
|
+
setupType: 'setupFilesAfterEnv',
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const setupModuleJs = await bundler.getModule(setupFile);
|
|
61
|
+
events.emit({
|
|
62
|
+
type: 'setup-file-bundling-finished',
|
|
63
|
+
file: setupFile,
|
|
64
|
+
setupType: 'setupFilesAfterEnv',
|
|
65
|
+
duration: Date.now() - startTime,
|
|
66
|
+
});
|
|
67
|
+
evaluateModule(setupModuleJs, setupFile);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
const errorMessage =
|
|
70
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
71
|
+
events.emit({
|
|
72
|
+
type: 'setup-file-bundling-failed',
|
|
73
|
+
file: setupFile,
|
|
74
|
+
setupType: 'setupFilesAfterEnv',
|
|
75
|
+
duration: Date.now() - startTime,
|
|
76
|
+
error: errorMessage,
|
|
77
|
+
});
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
@@ -370,11 +370,13 @@ const countTests = (suite: TestSuite): number => {
|
|
|
370
370
|
return count;
|
|
371
371
|
};
|
|
372
372
|
|
|
373
|
-
export const collectTests =
|
|
373
|
+
export const collectTests = async (
|
|
374
|
+
fn: () => void | Promise<void>
|
|
375
|
+
): Promise<CollectionResult> => {
|
|
374
376
|
currentContext = clearState();
|
|
375
377
|
|
|
376
378
|
try {
|
|
377
|
-
fn();
|
|
379
|
+
await fn();
|
|
378
380
|
|
|
379
381
|
// Convert raw structure to final structure using computation phase
|
|
380
382
|
const testSuite = convertRawTestSuiteToTestSuite(getRootSuite());
|
package/src/collector/types.ts
CHANGED
|
@@ -10,6 +10,9 @@ export type TestCollectorEventsEmitter = EventEmitter<TestCollectorEvents>;
|
|
|
10
10
|
|
|
11
11
|
export type TestCollector = {
|
|
12
12
|
events: TestCollectorEventsEmitter;
|
|
13
|
-
collect: (
|
|
13
|
+
collect: (
|
|
14
|
+
fn: () => void | Promise<void>,
|
|
15
|
+
testFilePath: string
|
|
16
|
+
) => Promise<CollectionResult>;
|
|
14
17
|
dispose: () => void;
|
|
15
18
|
};
|
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
package/src/filtering/index.ts
CHANGED
|
@@ -2,25 +2,68 @@ import { TestSuite } from '@react-native-harness/bridge';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
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
|
|
5
6
|
*/
|
|
6
7
|
export const filterTestsByName = (
|
|
7
|
-
suite: TestSuite,
|
|
8
|
+
suite: TestSuite,
|
|
8
9
|
testNamePattern: string
|
|
9
10
|
): TestSuite => {
|
|
10
11
|
const regex = new RegExp(testNamePattern);
|
|
11
12
|
return filterSuiteRecursively(suite, regex);
|
|
12
13
|
};
|
|
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
|
+
|
|
14
57
|
const filterSuiteRecursively = (suite: TestSuite, regex: RegExp): TestSuite => {
|
|
15
58
|
// Filter tests in current suite - match against test name or "suite test" combination
|
|
16
|
-
const filteredTests = suite.tests.filter(
|
|
17
|
-
regex.test(test.name) || regex.test(`${suite.name} ${test.name}`)
|
|
59
|
+
const filteredTests = suite.tests.filter(
|
|
60
|
+
(test) => regex.test(test.name) || regex.test(`${suite.name} ${test.name}`)
|
|
18
61
|
);
|
|
19
62
|
|
|
20
63
|
// Recursively filter child suites
|
|
21
64
|
const filteredChildSuites = suite.suites
|
|
22
|
-
.map(childSuite => filterSuiteRecursively(childSuite, regex))
|
|
23
|
-
.filter(childSuite => hasAnyActiveTests(childSuite));
|
|
65
|
+
.map((childSuite) => filterSuiteRecursively(childSuite, regex))
|
|
66
|
+
.filter((childSuite) => hasAnyActiveTests(childSuite));
|
|
24
67
|
|
|
25
68
|
return {
|
|
26
69
|
...suite,
|
|
@@ -30,8 +73,10 @@ const filterSuiteRecursively = (suite: TestSuite, regex: RegExp): TestSuite => {
|
|
|
30
73
|
};
|
|
31
74
|
|
|
32
75
|
const hasAnyActiveTests = (suite: TestSuite): boolean => {
|
|
33
|
-
const hasDirectTests = suite.tests.some(test => test.status === 'active');
|
|
34
|
-
const hasChildTests = suite.suites.some(childSuite =>
|
|
35
|
-
|
|
76
|
+
const hasDirectTests = suite.tests.some((test) => test.status === 'active');
|
|
77
|
+
const hasChildTests = suite.suites.some((childSuite) =>
|
|
78
|
+
hasAnyActiveTests(childSuite)
|
|
79
|
+
);
|
|
80
|
+
|
|
36
81
|
return hasDirectTests || hasChildTests;
|
|
37
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/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
|
-
}
|
|
@@ -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,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,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
|
+
};
|
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();
|