Skip to content

Commit 908c73d

Browse files
committed
feat(Programmatic API): Add programmatic API
Add a documented programmatic API to run jest. It allows this use case: ```ts import { run, readConfigs } from 'jest'; const { globalConfig, configs } = await readConfigs(process.argv, [process.cwd()]); // change globalConfig or configs as you see fit const results = await run(globalConfig, configs); console.log(results); ```
1 parent 285b40d commit 908c73d

File tree

4 files changed

+93
-39
lines changed

4 files changed

+93
-39
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
import {tmpdir} from 'os';
9+
import {resolve} from 'path';
10+
import {makeGlobalConfig, makeProjectConfig} from '@jest/test-utils';
11+
import {runCore} from '../';
12+
import runJest from '../runJest';
13+
14+
jest.mock('jest-runtime', () => ({
15+
createHasteMap: () => ({
16+
build: jest.fn(),
17+
}),
18+
}));
19+
jest.mock('../lib/createContext', () => jest.fn());
20+
jest.mock('../runJest', () =>
21+
jest.fn(({onComplete}) => {
22+
onComplete({results: {success: true}});
23+
}),
24+
);
25+
26+
describe(runCore, () => {
27+
it('should run once and provide the result', async () => {
28+
const actualResult = await runCore(makeGlobalConfig(), [
29+
makeProjectConfig({
30+
cacheDirectory: resolve(tmpdir(), 'jest_runCore_test'),
31+
}),
32+
]);
33+
expect(jest.mocked(runJest)).toHaveBeenCalled();
34+
expect(actualResult).toEqual({results: {success: true}});
35+
});
36+
});

packages/jest-core/src/cli/index.ts

Lines changed: 52 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ export async function runCLI(
4343
globalConfig: Config.GlobalConfig;
4444
}> {
4545
performance.mark('jest/runCLI:start');
46-
let results: AggregatedResult | undefined;
4746

4847
// If we output a JSON object, we can't write anything to stdout, since
4948
// it'll break the JSON structure and it won't be valid.
@@ -96,24 +95,13 @@ export async function runCLI(
9695
);
9796
}
9897

99-
await _run10000(
98+
const results = await runCore(
10099
globalConfig,
101100
configsOfProjectsToRun,
102101
hasDeprecationWarnings,
103102
outputStream,
104-
r => {
105-
results = r;
106-
},
107103
);
108104

109-
if (argv.watch || argv.watchAll) {
110-
// If in watch mode, return the promise that will never resolve.
111-
// If the watch mode is interrupted, watch should handle the process
112-
// shutdown.
113-
// eslint-disable-next-line @typescript-eslint/no-empty-function
114-
return new Promise(() => {});
115-
}
116-
117105
if (!results) {
118106
throw new Error(
119107
'AggregatedResult must be present after test run is complete',
@@ -167,13 +155,28 @@ const buildContextsAndHasteMaps = async (
167155
return {contexts, hasteMapInstances};
168156
};
169157

170-
const _run10000 = async (
158+
/**
159+
* Runs Jest either in watch mode or as a one-off. This is a lower-level API than `runCLI` and is intended for internal use by `runCLI` or externally.
160+
* Note that `process.exit` might be called when using `globalConfig.watch` or `globalConfig.watchAll` is true.
161+
*
162+
* @param globalConfig The global configuration to use for this run. It can be obtained using `readConfigs` (imported from 'jest-config').
163+
* @param configs The project configurations to run. It can be obtained using `readConfigs` (imported from 'jest-config').
164+
* @param warnForDeprecations Whether or not to warn for deprecation messages when `globalConfig.watch` or `globalConfig.watchAll` is true.
165+
* @param outputStream The stream to write output to. If not provided, it defaults to `process.stdout`.
166+
* @returns A Promise that resolves to the result, or never resolves when `globalConfig.watch` or `globalConfig.watchAll` is true.
167+
* @example
168+
* import { runCore, readConfigs } from 'jest';
169+
*
170+
* const { globalConfig, configs } = await readConfigs(process.argv, [process.cwd()]);
171+
* const results = await runCore(globalConfig, configs);
172+
* console.log(results);
173+
*/
174+
export const runCore = async (
171175
globalConfig: Config.GlobalConfig,
172176
configs: Array<Config.ProjectConfig>,
173-
hasDeprecationWarnings: boolean,
174-
outputStream: NodeJS.WriteStream,
175-
onComplete: OnCompleteCallback,
176-
) => {
177+
warnForDeprecations = false,
178+
outputStream: NodeJS.WriteStream = process.stdout,
179+
): Promise<AggregatedResult> => {
177180
// Queries to hg/git can take a while, so we need to start the process
178181
// as soon as possible, so by the time we need the result it's already there.
179182
const changedFilesPromise = getChangedFilesPromise(globalConfig, configs);
@@ -222,36 +225,47 @@ const _run10000 = async (
222225
);
223226
performance.mark('jest/buildContextsAndHasteMaps:end');
224227

225-
globalConfig.watch || globalConfig.watchAll
226-
? await runWatch(
227-
contexts,
228-
configs,
229-
hasDeprecationWarnings,
230-
globalConfig,
231-
outputStream,
232-
hasteMapInstances,
233-
filter,
234-
)
235-
: await runWithoutWatch(
236-
globalConfig,
237-
contexts,
238-
outputStream,
239-
onComplete,
240-
changedFilesPromise,
241-
filter,
242-
);
228+
if (globalConfig.watch || globalConfig.watchAll) {
229+
await runWatch(
230+
contexts,
231+
configs,
232+
warnForDeprecations,
233+
globalConfig,
234+
outputStream,
235+
hasteMapInstances,
236+
filter,
237+
);
238+
// If in watch mode, return the promise that will never resolve.
239+
// If the watch mode is interrupted, watch should handle the process
240+
// shutdown.
241+
// eslint-disable-next-line @typescript-eslint/no-empty-function
242+
return new Promise(() => {});
243+
} else {
244+
let result: AggregatedResult;
245+
await runWithoutWatch(
246+
globalConfig,
247+
contexts,
248+
outputStream,
249+
r => {
250+
result = r;
251+
},
252+
changedFilesPromise,
253+
filter,
254+
);
255+
return result!;
256+
}
243257
};
244258

245259
const runWatch = async (
246260
contexts: Array<TestContext>,
247261
_configs: Array<Config.ProjectConfig>,
248-
hasDeprecationWarnings: boolean,
262+
warnForDeprecations: boolean,
249263
globalConfig: Config.GlobalConfig,
250264
outputStream: NodeJS.WriteStream,
251265
hasteMapInstances: Array<IHasteMap>,
252266
filter?: Filter,
253267
) => {
254-
if (hasDeprecationWarnings) {
268+
if (warnForDeprecations) {
255269
try {
256270
await handleDeprecationWarnings(outputStream, process.stdin);
257271
return await watch(

packages/jest-core/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77

88
export {default as SearchSource} from './SearchSource';
99
export {createTestScheduler} from './TestScheduler';
10-
export {runCLI} from './cli';
10+
export {runCLI, runCore} from './cli';
1111
export {default as getVersion} from './version';
12+
export {readConfigs, readInitialOptions} from 'jest-config';

packages/jest/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ export {
1212
createTestScheduler,
1313
getVersion,
1414
runCLI,
15+
readConfigs,
16+
readInitialOptions,
17+
runCore,
1518
} from '@jest/core';
1619

1720
export {run} from 'jest-cli';

0 commit comments

Comments
 (0)