Skip to content

Commit

Permalink
feat(jest-runner): improve typings by exposing TestRunner abstract …
Browse files Browse the repository at this point in the history
…classes (#12646)
  • Loading branch information
mrazauskas committed Apr 8, 2022
1 parent a93def0 commit 420ba45
Show file tree
Hide file tree
Showing 11 changed files with 196 additions and 88 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -42,6 +42,7 @@
- `[jest-resolve, jest-runtime]` Add support for async resolver ([#11540](https://github.com/facebook/jest/pull/11540))
- `[jest-runner]` Allow `setupFiles` module to export an async function ([#12042](https://github.com/facebook/jest/pull/12042))
- `[jest-runner]` Allow passing `testEnvironmentOptions` via docblocks ([#12470](https://github.com/facebook/jest/pull/12470))
- `[jest-runner]` Exposing `CallbackTestRunner`, `EmittingTestRunner` abstract classes to help typing third party runners ([#12646](https://github.com/facebook/jest/pull/12646))
- `[jest-runtime]` [**BREAKING**] `Runtime.createHasteMap` now returns a promise ([#12008](https://github.com/facebook/jest/pull/12008))
- `[jest-runtime]` Calling `jest.resetModules` function will clear FS and transform cache ([#12531](https://github.com/facebook/jest/pull/12531))
- `[@jest/schemas]` New module for JSON schemas for Jest's config ([#12384](https://github.com/facebook/jest/pull/12384))
Expand Down
24 changes: 11 additions & 13 deletions packages/jest-core/src/TestScheduler.ts
Expand Up @@ -29,7 +29,7 @@ import {
import {createScriptTransformer} from '@jest/transform';
import type {Config} from '@jest/types';
import {formatExecError} from 'jest-message-util';
import type TestRunner from 'jest-runner';
import type {JestTestRunner, TestRunnerContext} from 'jest-runner';
import type {Context} from 'jest-runtime';
import {
buildSnapshotResolver,
Expand All @@ -40,6 +40,11 @@ import ReporterDispatcher from './ReporterDispatcher';
import type TestWatcher from './TestWatcher';
import {shouldRunInBand} from './testSchedulerHelper';

type TestRunnerConstructor = new (
globalConfig: Config.GlobalConfig,
context: TestRunnerContext,
) => JestTestRunner;

export type TestSchedulerOptions = {
startRun: (globalConfig: Config.GlobalConfig) => void;
};
Expand Down Expand Up @@ -206,14 +211,14 @@ class TestScheduler {
showStatus: !runInBand,
});

const testRunners: {[key: string]: TestRunner} = Object.create(null);
const contextsByTestRunner = new WeakMap<TestRunner, Context>();
const testRunners: Record<string, JestTestRunner> = Object.create(null);
const contextsByTestRunner = new WeakMap<JestTestRunner, Context>();
await Promise.all(
Array.from(contexts).map(async context => {
const {config} = context;
if (!testRunners[config.runner]) {
const transformer = await createScriptTransformer(config);
const Runner: typeof TestRunner =
const Runner: TestRunnerConstructor =
await transformer.requireAndTranspileModule(config.runner);
const runner = new Runner(this._globalConfig, {
changedFiles: this._context.changedFiles,
Expand Down Expand Up @@ -262,14 +267,7 @@ class TestScheduler {
),
];

await testRunner.runTests(
tests,
watcher,
undefined,
undefined,
undefined,
testRunnerOptions,
);
await testRunner.runTests(tests, watcher, testRunnerOptions);

unsubscribes.forEach(sub => sub());
} else {
Expand Down Expand Up @@ -310,7 +308,7 @@ class TestScheduler {
}

private _partitionTests(
testRunners: Record<string, TestRunner>,
testRunners: Record<string, JestTestRunner>,
tests: Array<Test>,
): Record<string, Array<Test>> | null {
if (Object.keys(testRunners).length > 1) {
Expand Down
73 changes: 73 additions & 0 deletions packages/jest-runner/__typetests__/jest-runner.test.ts
@@ -0,0 +1,73 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import {expectType} from 'tsd-lite';
import type {Test, TestEvents} from '@jest/test-result';
import type {Config} from '@jest/types';
import {CallbackTestRunner, EmittingTestRunner} from 'jest-runner';
import type {
OnTestFailure,
OnTestStart,
OnTestSuccess,
TestRunnerContext,
TestRunnerOptions,
TestWatcher,
UnsubscribeFn,
} from 'jest-runner';

const globalConfig = {} as Config.GlobalConfig;
const runnerContext = {} as TestRunnerContext;

// CallbackRunner

class CallbackRunner extends CallbackTestRunner {
async runTests(
tests: Array<Test>,
watcher: TestWatcher,
onStart: OnTestStart,
onResult: OnTestSuccess,
onFailure: OnTestFailure,
options: TestRunnerOptions,
): Promise<void> {
expectType<Config.GlobalConfig>(this._globalConfig);
expectType<TestRunnerContext>(this._context);

return;
}
}

const callbackRunner = new CallbackRunner(globalConfig, runnerContext);

expectType<boolean | undefined>(callbackRunner.isSerial);
expectType<false>(callbackRunner.supportsEventEmitters);

// EmittingRunner

class EmittingRunner extends EmittingTestRunner {
async runTests(
tests: Array<Test>,
watcher: TestWatcher,
options: TestRunnerOptions,
): Promise<void> {
expectType<Config.GlobalConfig>(this._globalConfig);
expectType<TestRunnerContext>(this._context);

return;
}

on<Name extends keyof TestEvents>(
eventName: string,
listener: (eventData: TestEvents[Name]) => void | Promise<void>,
): UnsubscribeFn {
return () => {};
}
}

const emittingRunner = new EmittingRunner(globalConfig, runnerContext);

expectType<boolean | undefined>(emittingRunner.isSerial);
expectType<true>(emittingRunner.supportsEventEmitters);
11 changes: 11 additions & 0 deletions packages/jest-runner/__typetests__/tsconfig.json
@@ -0,0 +1,11 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"noUnusedLocals": false,
"noUnusedParameters": false,
"skipLibCheck": true,

"types": []
},
"include": ["./**/*"]
}
4 changes: 3 additions & 1 deletion packages/jest-runner/package.json
Expand Up @@ -39,10 +39,12 @@
"throat": "^6.0.1"
},
"devDependencies": {
"@tsd/typescript": "~4.6.2",
"@types/exit": "^0.1.30",
"@types/graceful-fs": "^4.1.2",
"@types/source-map-support": "^0.5.0",
"jest-jasmine2": "^28.0.0-alpha.8"
"jest-jasmine2": "^28.0.0-alpha.8",
"tsd-lite": "^0.5.1"
},
"engines": {
"node": "^12.13.0 || ^14.15.0 || ^16.13.0 || >=17.0.0"
Expand Down
55 changes: 23 additions & 32 deletions packages/jest-runner/src/__tests__/testRunner.test.ts
Expand Up @@ -7,6 +7,7 @@
*/

import {TestWatcher} from '@jest/core';
import type {TestContext} from '@jest/test-result';
import {makeGlobalConfig, makeProjectConfig} from '@jest/test-utils';
import TestRunner from '../index';

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

await new TestRunner(globalConfig, {}).runTests(
await new TestRunner(globalConfig, runContext).runTests(
[
{context, path: './file.test.js'},
{context, path: './file2.test.js'},
{context: mockTestContext, path: './file.test.js'},
{context: mockTestContext, path: './file2.test.js'},
],
new TestWatcher({isWatchMode: globalConfig.watch}),
undefined,
undefined,
undefined,
{serial: false},
);

expect(mockWorkerFarm.worker.mock.calls).toEqual([
[
{
config,
context: runContext,
globalConfig,
path: './file.test.js',
},
],
[
{
config,
context: runContext,
globalConfig,
path: './file2.test.js',
},
],
]);
expect(mockWorkerFarm.worker).toBeCalledTimes(2);

expect(mockWorkerFarm.worker).nthCalledWith(1, {
config,
context: runContext,
globalConfig,
path: './file.test.js',
});

expect(mockWorkerFarm.worker).nthCalledWith(2, {
config,
context: runContext,
globalConfig,
path: './file2.test.js',
});
});

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

await new TestRunner(globalConfig, {}).runTests(
[{context, path: './file.test.js'}],
new TestWatcher({isWatchMode: globalConfig.watch}),
undefined,
undefined,
undefined,
{serial: true},
);

Expand Down

0 comments on commit 420ba45

Please sign in to comment.