@react-native-harness/jest 1.0.0-alpha.17 → 1.0.0-alpha.18
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/errors.d.ts +12 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +21 -0
- package/dist/harness.d.ts +10 -0
- package/dist/harness.d.ts.map +1 -0
- package/dist/harness.js +72 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -8
- package/dist/logs.d.ts +5 -3
- package/dist/logs.d.ts.map +1 -1
- package/dist/logs.js +6 -0
- package/dist/run.d.ts +1 -1
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +1 -2
- package/dist/setup.d.ts.map +1 -1
- package/dist/setup.js +10 -19
- package/dist/teardown.d.ts.map +1 -1
- package/dist/teardown.js +1 -2
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +7 -4
- package/src/errors.ts +22 -0
- package/src/harness.ts +105 -0
- package/src/index.ts +23 -8
- package/src/logs.ts +12 -3
- package/src/run.ts +2 -3
- package/src/setup.ts +12 -28
- package/src/teardown.ts +1 -2
- package/tsconfig.json +9 -0
- package/tsconfig.lib.json +9 -0
package/src/harness.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { getBridgeServer } from '@react-native-harness/bridge/server';
|
|
2
|
+
import { BridgeClientFunctions } from '@react-native-harness/bridge';
|
|
3
|
+
import { HarnessPlatform } from '@react-native-harness/platforms';
|
|
4
|
+
import { getMetroInstance } from '@react-native-harness/bundler-metro';
|
|
5
|
+
import { InitializationTimeoutError } from './errors.js';
|
|
6
|
+
import { Config as HarnessConfig } from '@react-native-harness/config';
|
|
7
|
+
import pRetry from 'p-retry';
|
|
8
|
+
|
|
9
|
+
const BRIDGE_READY_TIMEOUT = 10000;
|
|
10
|
+
|
|
11
|
+
export type Harness = {
|
|
12
|
+
runTests: BridgeClientFunctions['runTests'];
|
|
13
|
+
restart: () => Promise<void>;
|
|
14
|
+
dispose: () => Promise<void>;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const getHarnessInternal = async (
|
|
18
|
+
config: HarnessConfig,
|
|
19
|
+
platform: HarnessPlatform,
|
|
20
|
+
signal: AbortSignal
|
|
21
|
+
): Promise<Harness> => {
|
|
22
|
+
const [metroInstance, platformInstance, serverBridge] = await Promise.all([
|
|
23
|
+
getMetroInstance(),
|
|
24
|
+
platform.getInstance(),
|
|
25
|
+
getBridgeServer({
|
|
26
|
+
port: 3001,
|
|
27
|
+
timeout: config.bridgeTimeout,
|
|
28
|
+
}),
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const dispose = async () => {
|
|
32
|
+
await Promise.all([
|
|
33
|
+
serverBridge.dispose(),
|
|
34
|
+
platformInstance.dispose(),
|
|
35
|
+
metroInstance.dispose(),
|
|
36
|
+
]);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (signal.aborted) {
|
|
40
|
+
await dispose();
|
|
41
|
+
|
|
42
|
+
throw new DOMException('The operation was aborted', 'AbortError');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
await pRetry(
|
|
47
|
+
() =>
|
|
48
|
+
new Promise<void>((resolve, reject) => {
|
|
49
|
+
signal.addEventListener('abort', () => {
|
|
50
|
+
reject(new DOMException('The operation was aborted', 'AbortError'));
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
serverBridge.once('ready', () => resolve());
|
|
54
|
+
platformInstance.restartApp().catch(reject);
|
|
55
|
+
}),
|
|
56
|
+
{
|
|
57
|
+
minTimeout: BRIDGE_READY_TIMEOUT,
|
|
58
|
+
maxTimeout: BRIDGE_READY_TIMEOUT,
|
|
59
|
+
retries: Infinity,
|
|
60
|
+
signal,
|
|
61
|
+
}
|
|
62
|
+
);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
await dispose();
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const restart = () =>
|
|
69
|
+
new Promise<void>((resolve, reject) => {
|
|
70
|
+
serverBridge.once('ready', () => resolve());
|
|
71
|
+
platformInstance.restartApp().catch(reject);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
runTests: async (path, options) => {
|
|
76
|
+
const client = serverBridge.rpc.clients.at(-1);
|
|
77
|
+
|
|
78
|
+
if (!client) {
|
|
79
|
+
throw new Error('No client found');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return await client.runTests(path, options);
|
|
83
|
+
},
|
|
84
|
+
restart,
|
|
85
|
+
dispose,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export const getHarness = async (
|
|
90
|
+
config: HarnessConfig,
|
|
91
|
+
platform: HarnessPlatform
|
|
92
|
+
): Promise<Harness> => {
|
|
93
|
+
const abortSignal = AbortSignal.timeout(config.bridgeTimeout);
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const harness = await getHarnessInternal(config, platform, abortSignal);
|
|
97
|
+
return harness;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
100
|
+
throw new InitializationTimeoutError();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw error;
|
|
104
|
+
}
|
|
105
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -11,9 +11,12 @@ import type {
|
|
|
11
11
|
import pLimit from 'p-limit';
|
|
12
12
|
import { runHarnessTestFile } from './run.js';
|
|
13
13
|
import { Config as HarnessConfig } from '@react-native-harness/config';
|
|
14
|
-
import {
|
|
14
|
+
import { type Harness } from './harness.js';
|
|
15
15
|
import { setup } from './setup.js';
|
|
16
16
|
import { teardown } from './teardown.js';
|
|
17
|
+
import { HarnessError } from '@react-native-harness/tools';
|
|
18
|
+
import { getErrorMessage } from './logs.js';
|
|
19
|
+
import { DeviceNotRespondingError } from '@react-native-harness/bridge';
|
|
17
20
|
|
|
18
21
|
class CancelRun extends Error {
|
|
19
22
|
constructor(message?: string) {
|
|
@@ -60,8 +63,12 @@ export default class JestHarness implements CallbackTestRunnerInterface {
|
|
|
60
63
|
onFailure
|
|
61
64
|
);
|
|
62
65
|
} catch (error) {
|
|
63
|
-
|
|
64
|
-
|
|
66
|
+
if (error instanceof HarnessError) {
|
|
67
|
+
// Jest will print strings as they are, without processing them further.
|
|
68
|
+
throw getErrorMessage(error);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
throw error;
|
|
65
72
|
} finally {
|
|
66
73
|
// This is necessary as Harness may throw and we want to catch it and display a helpful error message.
|
|
67
74
|
await teardown(this.#globalConfig);
|
|
@@ -93,10 +100,7 @@ export default class JestHarness implements CallbackTestRunnerInterface {
|
|
|
93
100
|
harnessConfig.resetEnvironmentBetweenTestFiles &&
|
|
94
101
|
!isFirstTest
|
|
95
102
|
) {
|
|
96
|
-
await
|
|
97
|
-
harness.bridge.once('ready', resolve);
|
|
98
|
-
harness.environment.restart();
|
|
99
|
-
});
|
|
103
|
+
await harness.restart();
|
|
100
104
|
}
|
|
101
105
|
isFirstTest = false;
|
|
102
106
|
|
|
@@ -110,7 +114,18 @@ export default class JestHarness implements CallbackTestRunnerInterface {
|
|
|
110
114
|
);
|
|
111
115
|
})
|
|
112
116
|
.then((result) => onResult(test, result))
|
|
113
|
-
.catch((err) =>
|
|
117
|
+
.catch((err) => {
|
|
118
|
+
if (err instanceof DeviceNotRespondingError) {
|
|
119
|
+
onFailure(test, {
|
|
120
|
+
message: err.message,
|
|
121
|
+
stack: '',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
onFailure(test, err);
|
|
128
|
+
})
|
|
114
129
|
),
|
|
115
130
|
Promise.resolve()
|
|
116
131
|
);
|
package/src/logs.ts
CHANGED
|
@@ -1,21 +1,30 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { HarnessPlatform } from '@react-native-harness/platforms';
|
|
2
|
+
import { HarnessError } from '@react-native-harness/tools';
|
|
2
3
|
import chalk from 'chalk';
|
|
3
4
|
|
|
4
5
|
const TAG = chalk.supportsColor
|
|
5
6
|
? chalk.reset.inverse.bold.magenta(` HARNESS `)
|
|
6
7
|
: 'HARNESS';
|
|
7
8
|
|
|
9
|
+
const ERROR_TAG = chalk.supportsColor
|
|
10
|
+
? chalk.reset.inverse.bold.red(` HARNESS `)
|
|
11
|
+
: 'HARNESS';
|
|
12
|
+
|
|
8
13
|
// @see https://github.com/jestjs/jest/blob/main/packages/jest-reporters/src/BaseReporter.ts#L25
|
|
9
14
|
export const log = (message: string): void => {
|
|
10
15
|
process.stderr.write(`${message}\n`);
|
|
11
16
|
};
|
|
12
17
|
|
|
13
|
-
export const logTestRunHeader = (runner:
|
|
18
|
+
export const logTestRunHeader = (runner: HarnessPlatform): void => {
|
|
14
19
|
log(
|
|
15
20
|
`${TAG} Preparing to run tests using ${chalk.bold(runner.name)} runner\n`
|
|
16
21
|
);
|
|
17
22
|
};
|
|
18
23
|
|
|
19
|
-
export const logTestEnvironmentReady = (runner:
|
|
24
|
+
export const logTestEnvironmentReady = (runner: HarnessPlatform): void => {
|
|
20
25
|
log(`${TAG} Runner ${chalk.bold(runner.name)} ready\n`);
|
|
21
26
|
};
|
|
27
|
+
|
|
28
|
+
export const getErrorMessage = (error: HarnessError): string => {
|
|
29
|
+
return `${ERROR_TAG} ${error.message}\n`;
|
|
30
|
+
};
|
package/src/run.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
TestSuiteResult as HarnessTestSuiteResult,
|
|
6
6
|
TestResult as HarnessTestResult,
|
|
7
7
|
} from '@react-native-harness/bridge';
|
|
8
|
-
import type { Harness } from '
|
|
8
|
+
import type { Harness } from './harness.js';
|
|
9
9
|
import { formatResultsErrors } from 'jest-message-util';
|
|
10
10
|
import { toTestResult } from './toTestResult.js';
|
|
11
11
|
|
|
@@ -84,8 +84,7 @@ export const runHarnessTestFile: RunHarnessTestFile = async ({
|
|
|
84
84
|
(setupFile) => path.relative(globalConfig.rootDir, setupFile)
|
|
85
85
|
);
|
|
86
86
|
|
|
87
|
-
const
|
|
88
|
-
const results = await client.runTests(relativeTestPath, {
|
|
87
|
+
const results = await harness.runTests(relativeTestPath, {
|
|
89
88
|
testNamePattern: globalConfig.testNamePattern,
|
|
90
89
|
setupFiles,
|
|
91
90
|
setupFilesAfterEnv,
|
package/src/setup.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getConfig,
|
|
3
|
-
TestRunnerConfig,
|
|
4
3
|
type Config as HarnessConfig,
|
|
5
|
-
type TestRunnerConfig as HarnessTestRunnerConfig,
|
|
6
4
|
} from '@react-native-harness/config';
|
|
7
5
|
import type { Config as JestConfig } from 'jest-runner';
|
|
8
|
-
import {
|
|
9
|
-
getHarness as getHarnessExternal,
|
|
10
|
-
NoRunnerSpecifiedError,
|
|
11
|
-
RunnerNotFoundError,
|
|
12
|
-
type Harness,
|
|
13
|
-
} from '@react-native-harness/cli/external';
|
|
6
|
+
import { getHarness } from './harness.js';
|
|
14
7
|
import { preRunMessage } from 'jest-util';
|
|
15
8
|
import { getAdditionalCliArgs, HarnessCliArgs } from './cli-args.js';
|
|
16
9
|
import { logTestEnvironmentReady, logTestRunHeader } from './logs.js';
|
|
10
|
+
import { NoRunnerSpecifiedError, RunnerNotFoundError } from './errors.js';
|
|
11
|
+
import { HarnessPlatform } from '@react-native-harness/platforms';
|
|
17
12
|
|
|
18
13
|
const getHarnessConfig = async (
|
|
19
14
|
globalConfig: JestConfig.GlobalConfig
|
|
@@ -26,11 +21,11 @@ const getHarnessConfig = async (
|
|
|
26
21
|
const getHarnessRunner = (
|
|
27
22
|
config: HarnessConfig,
|
|
28
23
|
cliArgs: HarnessCliArgs
|
|
29
|
-
):
|
|
24
|
+
): HarnessPlatform => {
|
|
30
25
|
const selectedRunnerName = cliArgs.harnessRunner ?? config.defaultRunner;
|
|
31
26
|
|
|
32
27
|
if (!selectedRunnerName) {
|
|
33
|
-
throw new NoRunnerSpecifiedError(
|
|
28
|
+
throw new NoRunnerSpecifiedError();
|
|
34
29
|
}
|
|
35
30
|
|
|
36
31
|
const runner = config.runners.find(
|
|
@@ -38,21 +33,16 @@ const getHarnessRunner = (
|
|
|
38
33
|
);
|
|
39
34
|
|
|
40
35
|
if (!runner) {
|
|
41
|
-
throw new RunnerNotFoundError(selectedRunnerName
|
|
36
|
+
throw new RunnerNotFoundError(selectedRunnerName);
|
|
42
37
|
}
|
|
43
38
|
|
|
44
39
|
return runner;
|
|
45
40
|
};
|
|
46
41
|
|
|
47
|
-
const getHarness = async (runner: TestRunnerConfig): Promise<Harness> => {
|
|
48
|
-
return await getHarnessExternal(runner);
|
|
49
|
-
};
|
|
50
|
-
|
|
51
42
|
export const setup = async (globalConfig: JestConfig.GlobalConfig) => {
|
|
52
43
|
preRunMessage.remove(process.stderr);
|
|
53
44
|
const harnessConfig =
|
|
54
45
|
global.HARNESS_CONFIG ?? (await getHarnessConfig(globalConfig));
|
|
55
|
-
const isWatchMode = globalConfig.watch || globalConfig.watchAll;
|
|
56
46
|
|
|
57
47
|
if (global.HARNESS) {
|
|
58
48
|
// Do not setup again if HARNESS is already initialized
|
|
@@ -60,22 +50,16 @@ export const setup = async (globalConfig: JestConfig.GlobalConfig) => {
|
|
|
60
50
|
|
|
61
51
|
if (harnessConfig.resetEnvironmentBetweenTestFiles) {
|
|
62
52
|
// In watch mode, we want to restart the environment before each test run
|
|
63
|
-
await
|
|
64
|
-
global.HARNESS.bridge.once('ready', resolve);
|
|
65
|
-
global.HARNESS.environment.restart();
|
|
66
|
-
});
|
|
53
|
+
await global.HARNESS.restart();
|
|
67
54
|
}
|
|
68
55
|
|
|
69
56
|
return;
|
|
70
57
|
}
|
|
71
58
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
await global.HARNESS.environment.dispose();
|
|
77
|
-
});
|
|
78
|
-
}
|
|
59
|
+
// Gracefully dispose the Harness when the process exits.
|
|
60
|
+
process.on('exit', async () => {
|
|
61
|
+
await global.HARNESS.dispose();
|
|
62
|
+
});
|
|
79
63
|
|
|
80
64
|
const cliArgs = getAdditionalCliArgs();
|
|
81
65
|
const selectedRunner = getHarnessRunner(harnessConfig, cliArgs);
|
|
@@ -87,7 +71,7 @@ export const setup = async (globalConfig: JestConfig.GlobalConfig) => {
|
|
|
87
71
|
}
|
|
88
72
|
|
|
89
73
|
logTestRunHeader(selectedRunner);
|
|
90
|
-
const harness = await getHarness(selectedRunner);
|
|
74
|
+
const harness = await getHarness(harnessConfig, selectedRunner);
|
|
91
75
|
logTestEnvironmentReady(selectedRunner);
|
|
92
76
|
|
|
93
77
|
global.HARNESS_CONFIG = harnessConfig;
|
package/src/teardown.ts
CHANGED
package/tsconfig.json
CHANGED
package/tsconfig.lib.json
CHANGED
|
@@ -15,6 +15,15 @@
|
|
|
15
15
|
{
|
|
16
16
|
"path": "../cli/tsconfig.lib.json"
|
|
17
17
|
},
|
|
18
|
+
{
|
|
19
|
+
"path": "../bundler-metro/tsconfig.lib.json"
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"path": "../tools/tsconfig.lib.json"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"path": "../platforms/tsconfig.lib.json"
|
|
26
|
+
},
|
|
18
27
|
{
|
|
19
28
|
"path": "../config/tsconfig.lib.json"
|
|
20
29
|
},
|