Skip to content

Commit 420ba45

Browse files
authored
feat(jest-runner): improve typings by exposing TestRunner abstract classes (#12646)
1 parent a93def0 commit 420ba45

File tree

11 files changed

+196
-88
lines changed

11 files changed

+196
-88
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
- `[jest-resolve, jest-runtime]` Add support for async resolver ([#11540](https://github.com/facebook/jest/pull/11540))
4343
- `[jest-runner]` Allow `setupFiles` module to export an async function ([#12042](https://github.com/facebook/jest/pull/12042))
4444
- `[jest-runner]` Allow passing `testEnvironmentOptions` via docblocks ([#12470](https://github.com/facebook/jest/pull/12470))
45+
- `[jest-runner]` Exposing `CallbackTestRunner`, `EmittingTestRunner` abstract classes to help typing third party runners ([#12646](https://github.com/facebook/jest/pull/12646))
4546
- `[jest-runtime]` [**BREAKING**] `Runtime.createHasteMap` now returns a promise ([#12008](https://github.com/facebook/jest/pull/12008))
4647
- `[jest-runtime]` Calling `jest.resetModules` function will clear FS and transform cache ([#12531](https://github.com/facebook/jest/pull/12531))
4748
- `[@jest/schemas]` New module for JSON schemas for Jest's config ([#12384](https://github.com/facebook/jest/pull/12384))

packages/jest-core/src/TestScheduler.ts

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import {
2929
import {createScriptTransformer} from '@jest/transform';
3030
import type {Config} from '@jest/types';
3131
import {formatExecError} from 'jest-message-util';
32-
import type TestRunner from 'jest-runner';
32+
import type {JestTestRunner, TestRunnerContext} from 'jest-runner';
3333
import type {Context} from 'jest-runtime';
3434
import {
3535
buildSnapshotResolver,
@@ -40,6 +40,11 @@ import ReporterDispatcher from './ReporterDispatcher';
4040
import type TestWatcher from './TestWatcher';
4141
import {shouldRunInBand} from './testSchedulerHelper';
4242

43+
type TestRunnerConstructor = new (
44+
globalConfig: Config.GlobalConfig,
45+
context: TestRunnerContext,
46+
) => JestTestRunner;
47+
4348
export type TestSchedulerOptions = {
4449
startRun: (globalConfig: Config.GlobalConfig) => void;
4550
};
@@ -206,14 +211,14 @@ class TestScheduler {
206211
showStatus: !runInBand,
207212
});
208213

209-
const testRunners: {[key: string]: TestRunner} = Object.create(null);
210-
const contextsByTestRunner = new WeakMap<TestRunner, Context>();
214+
const testRunners: Record<string, JestTestRunner> = Object.create(null);
215+
const contextsByTestRunner = new WeakMap<JestTestRunner, Context>();
211216
await Promise.all(
212217
Array.from(contexts).map(async context => {
213218
const {config} = context;
214219
if (!testRunners[config.runner]) {
215220
const transformer = await createScriptTransformer(config);
216-
const Runner: typeof TestRunner =
221+
const Runner: TestRunnerConstructor =
217222
await transformer.requireAndTranspileModule(config.runner);
218223
const runner = new Runner(this._globalConfig, {
219224
changedFiles: this._context.changedFiles,
@@ -262,14 +267,7 @@ class TestScheduler {
262267
),
263268
];
264269

265-
await testRunner.runTests(
266-
tests,
267-
watcher,
268-
undefined,
269-
undefined,
270-
undefined,
271-
testRunnerOptions,
272-
);
270+
await testRunner.runTests(tests, watcher, testRunnerOptions);
273271

274272
unsubscribes.forEach(sub => sub());
275273
} else {
@@ -310,7 +308,7 @@ class TestScheduler {
310308
}
311309

312310
private _partitionTests(
313-
testRunners: Record<string, TestRunner>,
311+
testRunners: Record<string, JestTestRunner>,
314312
tests: Array<Test>,
315313
): Record<string, Array<Test>> | null {
316314
if (Object.keys(testRunners).length > 1) {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
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 {expectType} from 'tsd-lite';
9+
import type {Test, TestEvents} from '@jest/test-result';
10+
import type {Config} from '@jest/types';
11+
import {CallbackTestRunner, EmittingTestRunner} from 'jest-runner';
12+
import type {
13+
OnTestFailure,
14+
OnTestStart,
15+
OnTestSuccess,
16+
TestRunnerContext,
17+
TestRunnerOptions,
18+
TestWatcher,
19+
UnsubscribeFn,
20+
} from 'jest-runner';
21+
22+
const globalConfig = {} as Config.GlobalConfig;
23+
const runnerContext = {} as TestRunnerContext;
24+
25+
// CallbackRunner
26+
27+
class CallbackRunner extends CallbackTestRunner {
28+
async runTests(
29+
tests: Array<Test>,
30+
watcher: TestWatcher,
31+
onStart: OnTestStart,
32+
onResult: OnTestSuccess,
33+
onFailure: OnTestFailure,
34+
options: TestRunnerOptions,
35+
): Promise<void> {
36+
expectType<Config.GlobalConfig>(this._globalConfig);
37+
expectType<TestRunnerContext>(this._context);
38+
39+
return;
40+
}
41+
}
42+
43+
const callbackRunner = new CallbackRunner(globalConfig, runnerContext);
44+
45+
expectType<boolean | undefined>(callbackRunner.isSerial);
46+
expectType<false>(callbackRunner.supportsEventEmitters);
47+
48+
// EmittingRunner
49+
50+
class EmittingRunner extends EmittingTestRunner {
51+
async runTests(
52+
tests: Array<Test>,
53+
watcher: TestWatcher,
54+
options: TestRunnerOptions,
55+
): Promise<void> {
56+
expectType<Config.GlobalConfig>(this._globalConfig);
57+
expectType<TestRunnerContext>(this._context);
58+
59+
return;
60+
}
61+
62+
on<Name extends keyof TestEvents>(
63+
eventName: string,
64+
listener: (eventData: TestEvents[Name]) => void | Promise<void>,
65+
): UnsubscribeFn {
66+
return () => {};
67+
}
68+
}
69+
70+
const emittingRunner = new EmittingRunner(globalConfig, runnerContext);
71+
72+
expectType<boolean | undefined>(emittingRunner.isSerial);
73+
expectType<true>(emittingRunner.supportsEventEmitters);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"extends": "../../../tsconfig.json",
3+
"compilerOptions": {
4+
"noUnusedLocals": false,
5+
"noUnusedParameters": false,
6+
"skipLibCheck": true,
7+
8+
"types": []
9+
},
10+
"include": ["./**/*"]
11+
}

packages/jest-runner/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,12 @@
3939
"throat": "^6.0.1"
4040
},
4141
"devDependencies": {
42+
"@tsd/typescript": "~4.6.2",
4243
"@types/exit": "^0.1.30",
4344
"@types/graceful-fs": "^4.1.2",
4445
"@types/source-map-support": "^0.5.0",
45-
"jest-jasmine2": "^28.0.0-alpha.8"
46+
"jest-jasmine2": "^28.0.0-alpha.8",
47+
"tsd-lite": "^0.5.1"
4648
},
4749
"engines": {
4850
"node": "^12.13.0 || ^14.15.0 || ^16.13.0 || >=17.0.0"

packages/jest-runner/src/__tests__/testRunner.test.ts

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import {TestWatcher} from '@jest/core';
10+
import type {TestContext} from '@jest/test-result';
1011
import {makeGlobalConfig, makeProjectConfig} from '@jest/test-utils';
1112
import TestRunner from '../index';
1213

@@ -29,56 +30,46 @@ jest.mock('../testWorker', () => {});
2930
test('injects the serializable module map into each worker in watch mode', async () => {
3031
const globalConfig = makeGlobalConfig({maxWorkers: 2, watch: true});
3132
const config = makeProjectConfig({rootDir: '/path/'});
32-
const serializableModuleMap = jest.fn();
3333
const runContext = {};
34-
const context = {
34+
const mockTestContext = {
3535
config,
36-
moduleMap: {toJSON: () => serializableModuleMap},
37-
};
36+
moduleMap: {toJSON: jest.fn()},
37+
} as unknown as TestContext;
3838

39-
await new TestRunner(globalConfig, {}).runTests(
39+
await new TestRunner(globalConfig, runContext).runTests(
4040
[
41-
{context, path: './file.test.js'},
42-
{context, path: './file2.test.js'},
41+
{context: mockTestContext, path: './file.test.js'},
42+
{context: mockTestContext, path: './file2.test.js'},
4343
],
4444
new TestWatcher({isWatchMode: globalConfig.watch}),
45-
undefined,
46-
undefined,
47-
undefined,
4845
{serial: false},
4946
);
5047

51-
expect(mockWorkerFarm.worker.mock.calls).toEqual([
52-
[
53-
{
54-
config,
55-
context: runContext,
56-
globalConfig,
57-
path: './file.test.js',
58-
},
59-
],
60-
[
61-
{
62-
config,
63-
context: runContext,
64-
globalConfig,
65-
path: './file2.test.js',
66-
},
67-
],
68-
]);
48+
expect(mockWorkerFarm.worker).toBeCalledTimes(2);
49+
50+
expect(mockWorkerFarm.worker).nthCalledWith(1, {
51+
config,
52+
context: runContext,
53+
globalConfig,
54+
path: './file.test.js',
55+
});
56+
57+
expect(mockWorkerFarm.worker).nthCalledWith(2, {
58+
config,
59+
context: runContext,
60+
globalConfig,
61+
path: './file2.test.js',
62+
});
6963
});
7064

7165
test('assign process.env.JEST_WORKER_ID = 1 when in runInBand mode', async () => {
7266
const globalConfig = makeGlobalConfig({maxWorkers: 1, watch: false});
7367
const config = makeProjectConfig({rootDir: '/path/'});
74-
const context = {config};
68+
const context = {config} as TestContext;
7569

7670
await new TestRunner(globalConfig, {}).runTests(
7771
[{context, path: './file.test.js'}],
7872
new TestWatcher({isWatchMode: globalConfig.watch}),
79-
undefined,
80-
undefined,
81-
undefined,
8273
{serial: true},
8374
);
8475

0 commit comments

Comments
 (0)