@react-native-harness/cli 1.0.0-alpha.15 → 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/dist/commands/test.d.ts.map +1 -1
- package/dist/commands/test.js +4 -2
- package/dist/errors/errorHandler.d.ts +1 -1
- package/dist/errors/errorHandler.d.ts.map +1 -1
- package/dist/errors/errorHandler.js +111 -109
- package/dist/external.d.ts +11 -0
- package/dist/external.d.ts.map +1 -0
- package/dist/external.js +27 -0
- package/dist/index.js +58 -49
- package/dist/jest.d.ts +2 -0
- package/dist/jest.d.ts.map +1 -0
- package/dist/jest.js +7 -0
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +26 -8
- package/src/errors/errorHandler.ts +113 -121
- package/src/external.ts +47 -0
- package/src/index.ts +65 -72
- package/src/commands/test.ts +0 -228
- package/src/discovery/index.ts +0 -2
- package/src/discovery/testDiscovery.ts +0 -50
package/src/commands/test.ts
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getBridgeServer,
|
|
3
|
-
type BridgeServer,
|
|
4
|
-
} from '@react-native-harness/bridge/server';
|
|
5
|
-
import { TestExecutionOptions } from '@react-native-harness/bridge';
|
|
6
|
-
import {
|
|
7
|
-
Config,
|
|
8
|
-
getConfig,
|
|
9
|
-
TestRunnerConfig,
|
|
10
|
-
} from '@react-native-harness/config';
|
|
11
|
-
import { getPlatformAdapter } from '../platforms/platform-registry.js';
|
|
12
|
-
import { intro, logger, outro, spinner } from '@react-native-harness/tools';
|
|
13
|
-
import { type Environment } from '../platforms/platform-adapter.js';
|
|
14
|
-
import { BridgeTimeoutError } from '../errors/errors.js';
|
|
15
|
-
import { assert } from '../utils.js';
|
|
16
|
-
import {
|
|
17
|
-
EnvironmentInitializationError,
|
|
18
|
-
NoRunnerSpecifiedError,
|
|
19
|
-
RpcClientError,
|
|
20
|
-
RunnerNotFoundError,
|
|
21
|
-
} from '../errors/errors.js';
|
|
22
|
-
import { TestSuiteResult } from '@react-native-harness/bridge';
|
|
23
|
-
import {
|
|
24
|
-
discoverTestFiles,
|
|
25
|
-
type TestFilterOptions,
|
|
26
|
-
} from '../discovery/index.js';
|
|
27
|
-
|
|
28
|
-
type TestRunContext = {
|
|
29
|
-
config: Config;
|
|
30
|
-
runner: TestRunnerConfig;
|
|
31
|
-
bridge?: BridgeServer;
|
|
32
|
-
environment?: Environment;
|
|
33
|
-
testFiles?: string[];
|
|
34
|
-
results?: TestSuiteResult[];
|
|
35
|
-
projectRoot: string;
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
const setupEnvironment = async (context: TestRunContext): Promise<void> => {
|
|
39
|
-
const startSpinner = spinner();
|
|
40
|
-
const platform = context.runner.platform;
|
|
41
|
-
|
|
42
|
-
startSpinner.start(`Starting "${context.runner.name}" (${platform}) runner`);
|
|
43
|
-
|
|
44
|
-
const platformAdapter = await getPlatformAdapter(platform);
|
|
45
|
-
const serverBridge = await getBridgeServer({
|
|
46
|
-
port: 3001,
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
context.bridge = serverBridge;
|
|
50
|
-
|
|
51
|
-
const readyPromise = new Promise<void>((resolve, reject) => {
|
|
52
|
-
const timeout = setTimeout(() => {
|
|
53
|
-
reject(
|
|
54
|
-
new BridgeTimeoutError(
|
|
55
|
-
context.config.bridgeTimeout,
|
|
56
|
-
context.runner.name,
|
|
57
|
-
platform
|
|
58
|
-
)
|
|
59
|
-
);
|
|
60
|
-
}, context.config.bridgeTimeout);
|
|
61
|
-
|
|
62
|
-
serverBridge.once('ready', () => {
|
|
63
|
-
clearTimeout(timeout);
|
|
64
|
-
resolve();
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
context.environment = await platformAdapter.getEnvironment(context.runner);
|
|
69
|
-
|
|
70
|
-
logger.debug('Waiting for bridge to be ready');
|
|
71
|
-
await readyPromise;
|
|
72
|
-
logger.debug('Bridge is ready');
|
|
73
|
-
|
|
74
|
-
if (!context.environment) {
|
|
75
|
-
throw new EnvironmentInitializationError(
|
|
76
|
-
'Failed to initialize environment',
|
|
77
|
-
context.runner.name,
|
|
78
|
-
platform,
|
|
79
|
-
'Platform adapter returned null environment'
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
startSpinner.stop(`"${context.runner.name}" (${platform}) runner started`);
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
const findTestFiles = async (
|
|
87
|
-
context: TestRunContext,
|
|
88
|
-
options: TestFilterOptions = {}
|
|
89
|
-
): Promise<void> => {
|
|
90
|
-
const discoverSpinner = spinner();
|
|
91
|
-
discoverSpinner.start('Discovering tests');
|
|
92
|
-
|
|
93
|
-
context.testFiles = await discoverTestFiles(
|
|
94
|
-
context.projectRoot,
|
|
95
|
-
context.config.include,
|
|
96
|
-
options
|
|
97
|
-
);
|
|
98
|
-
|
|
99
|
-
discoverSpinner.stop(`Found ${context.testFiles.length} test files`);
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
const runTests = async (
|
|
103
|
-
context: TestRunContext,
|
|
104
|
-
options: TestFilterOptions = {}
|
|
105
|
-
): Promise<void> => {
|
|
106
|
-
const { bridge, environment, testFiles } = context;
|
|
107
|
-
assert(bridge != null, 'Bridge not initialized');
|
|
108
|
-
assert(environment != null, 'Environment not initialized');
|
|
109
|
-
assert(testFiles != null, 'Test files not initialized');
|
|
110
|
-
|
|
111
|
-
let runSpinner = spinner();
|
|
112
|
-
runSpinner.start('Running tests');
|
|
113
|
-
|
|
114
|
-
let shouldRestart = false;
|
|
115
|
-
|
|
116
|
-
for (const testFile of testFiles) {
|
|
117
|
-
if (shouldRestart) {
|
|
118
|
-
runSpinner = spinner();
|
|
119
|
-
runSpinner.start(`Restarting environment for next test file`);
|
|
120
|
-
|
|
121
|
-
await new Promise((resolve) => {
|
|
122
|
-
bridge.once('ready', resolve);
|
|
123
|
-
environment.restart();
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
runSpinner.message(`Running tests in ${testFile}`);
|
|
128
|
-
const client = bridge.rpc.clients.at(-1);
|
|
129
|
-
if (!client) {
|
|
130
|
-
throw new RpcClientError(
|
|
131
|
-
'No RPC client available',
|
|
132
|
-
3001,
|
|
133
|
-
'No clients connected'
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Pass only testNamePattern to runtime (file filtering already done)
|
|
138
|
-
const executionOptions: TestExecutionOptions = {
|
|
139
|
-
testNamePattern: options.testNamePattern,
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const result = await client.runTests(testFile, executionOptions);
|
|
143
|
-
context.results = [...(context.results ?? []), ...result.suites];
|
|
144
|
-
shouldRestart = true;
|
|
145
|
-
runSpinner.stop(`Test file ${testFile} completed`);
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
runSpinner.stop('Tests completed');
|
|
149
|
-
};
|
|
150
|
-
|
|
151
|
-
const cleanUp = async (context: TestRunContext): Promise<void> => {
|
|
152
|
-
if (context.bridge) {
|
|
153
|
-
context.bridge.dispose();
|
|
154
|
-
}
|
|
155
|
-
if (context.environment) {
|
|
156
|
-
await context.environment.dispose();
|
|
157
|
-
}
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const hasFailedTests = (results: TestSuiteResult[]): boolean => {
|
|
161
|
-
for (const suite of results) {
|
|
162
|
-
// Check if the suite itself failed
|
|
163
|
-
if (suite.status === 'failed') {
|
|
164
|
-
return true;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Check individual tests in the suite
|
|
168
|
-
for (const test of suite.tests) {
|
|
169
|
-
if (test.status === 'failed') {
|
|
170
|
-
return true;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// Recursively check nested suites
|
|
175
|
-
if (suite.suites && hasFailedTests(suite.suites)) {
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
return false;
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
export const testCommand = async (
|
|
184
|
-
runnerName?: string,
|
|
185
|
-
options: TestFilterOptions = {}
|
|
186
|
-
): Promise<void> => {
|
|
187
|
-
intro('React Native Test Harness');
|
|
188
|
-
|
|
189
|
-
const { config, projectRoot } = await getConfig(process.cwd());
|
|
190
|
-
const selectedRunnerName = runnerName ?? config.defaultRunner;
|
|
191
|
-
|
|
192
|
-
if (!selectedRunnerName) {
|
|
193
|
-
throw new NoRunnerSpecifiedError(config.runners);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
const runner = config.runners.find((r) => r.name === selectedRunnerName);
|
|
197
|
-
|
|
198
|
-
if (!runner) {
|
|
199
|
-
throw new RunnerNotFoundError(selectedRunnerName, config.runners);
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
const context: TestRunContext = {
|
|
203
|
-
config,
|
|
204
|
-
runner,
|
|
205
|
-
testFiles: [],
|
|
206
|
-
results: [],
|
|
207
|
-
projectRoot,
|
|
208
|
-
};
|
|
209
|
-
|
|
210
|
-
try {
|
|
211
|
-
await setupEnvironment(context);
|
|
212
|
-
await findTestFiles(context, options);
|
|
213
|
-
await runTests(context, options);
|
|
214
|
-
|
|
215
|
-
assert(context.results != null, 'Results not initialized');
|
|
216
|
-
config.reporter?.report(context.results);
|
|
217
|
-
} finally {
|
|
218
|
-
await cleanUp(context);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Check if any tests failed and exit with appropriate code
|
|
222
|
-
if (hasFailedTests(context.results)) {
|
|
223
|
-
outro('Test run completed with failures');
|
|
224
|
-
process.exit(1);
|
|
225
|
-
} else {
|
|
226
|
-
outro('Test run completed successfully');
|
|
227
|
-
}
|
|
228
|
-
};
|
package/src/discovery/index.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { Glob } from 'glob';
|
|
2
|
-
|
|
3
|
-
export type TestFilterOptions = {
|
|
4
|
-
testNamePattern?: string;
|
|
5
|
-
testPathPattern?: string;
|
|
6
|
-
testPathIgnorePatterns?: string[];
|
|
7
|
-
testMatch?: string[];
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Discovers test files based on patterns and filtering options
|
|
12
|
-
*/
|
|
13
|
-
export const discoverTestFiles = async (
|
|
14
|
-
projectRoot: string,
|
|
15
|
-
configInclude: string | string[],
|
|
16
|
-
options: TestFilterOptions = {}
|
|
17
|
-
): Promise<string[]> => {
|
|
18
|
-
// Priority: testMatch > configInclude
|
|
19
|
-
const patterns = options.testMatch || configInclude;
|
|
20
|
-
const patternArray = Array.isArray(patterns) ? patterns : [patterns];
|
|
21
|
-
|
|
22
|
-
// Glob discovery
|
|
23
|
-
const allFiles: string[] = [];
|
|
24
|
-
for (const pattern of patternArray) {
|
|
25
|
-
const glob = new Glob(pattern, { cwd: projectRoot, nodir: true });
|
|
26
|
-
const files = await glob.walk();
|
|
27
|
-
allFiles.push(...files);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// Remove duplicates
|
|
31
|
-
let uniqueFiles = [...new Set(allFiles)];
|
|
32
|
-
|
|
33
|
-
// Apply testPathPattern filtering
|
|
34
|
-
if (options.testPathPattern) {
|
|
35
|
-
const regex = new RegExp(options.testPathPattern);
|
|
36
|
-
uniqueFiles = uniqueFiles.filter((file) => regex.test(file));
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Apply testPathIgnorePatterns filtering
|
|
40
|
-
if (options.testPathIgnorePatterns?.length) {
|
|
41
|
-
const ignoreRegexes = options.testPathIgnorePatterns.map(
|
|
42
|
-
(p) => new RegExp(p)
|
|
43
|
-
);
|
|
44
|
-
uniqueFiles = uniqueFiles.filter(
|
|
45
|
-
(file) => !ignoreRegexes.some((regex) => regex.test(file))
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return uniqueFiles;
|
|
50
|
-
};
|