diff --git a/CHANGELOG.md b/CHANGELOG.md index b0a118b441ed..8df91a3b87b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ### Features +- `[@jest/core, @jest/test-sequencer]` Move `testSequencer` to individual package `@jest/test-sequencer` ([#8223](https://github.com/facebook/jest/pull/8223)) +- `[@jest/core, jest-cli, jest-config]` Add option `testSequencer` allow user use custom sequencer. ([#8223](https://github.com/facebook/jest/pull/8223)) + ### Fixes ### Chore & Maintenance diff --git a/docs/CLI.md b/docs/CLI.md index 92b7e943441f..f6b483391f7b 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -297,6 +297,10 @@ An array of regexp pattern strings that is tested against all tests paths before Lets you specify a custom test runner. +### `--testSequencer=` + +Lets you specify a custom test sequencer. Please refer to the documentation of the corresponding configuration property for details. + ### `--updateSnapshot` Alias: `-u`. Use this flag to re-record every snapshot that fails during this test run. Can be used together with a test suite pattern or with `--testNamePattern` to re-record snapshots. diff --git a/docs/Configuration.md b/docs/Configuration.md index 39801978f964..aec1b074f6d4 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1018,6 +1018,31 @@ function testRunner( An example of such function can be found in our default [jasmine2 test runner package](https://github.com/facebook/jest/blob/master/packages/jest-jasmine2/src/index.js). +### `testSequencer` [string] + +Default: `@jest/test-sequencer` + +This option allows you to use a custom sequencer instead of Jest's default. + +Example: + +Sort test path alphabetically + +```js +const Sequencer = require('@jest/test-sequencer').default; + +class CustomSequencer extends Sequencer { + sort(tests) { + // Test structure information + // https://github.com/facebook/jest/blob/6b8b1404a1d9254e7d5d90a8934087a9c9899dab/packages/jest-runner/src/types.ts#L17-L21 + const copyTests = Array.from(tests); + return copyTests.sort((testA, testB) => (testA.path > testB.path ? 1 : -1)); + } +} + +module.exports = CustomSequencer; +``` + ### `testURL` [string] Default: `http://localhost` diff --git a/e2e/__tests__/__snapshots__/showConfig.test.ts.snap b/e2e/__tests__/__snapshots__/showConfig.test.ts.snap index 8b27dc1ba115..44f0f007f29a 100644 --- a/e2e/__tests__/__snapshots__/showConfig.test.ts.snap +++ b/e2e/__tests__/__snapshots__/showConfig.test.ts.snap @@ -117,6 +117,7 @@ exports[`--showConfig outputs config info and exits 1`] = ` "testFailureExitCode": 1, "testPathPattern": "", "testResultsProcessor": null, + "testSequencer": "@jest/test-sequencer", "updateSnapshot": "all", "useStderr": false, "verbose": null, diff --git a/e2e/__tests__/customTestSequencers.test.ts b/e2e/__tests__/customTestSequencers.test.ts new file mode 100644 index 000000000000..d9cb5f039db4 --- /dev/null +++ b/e2e/__tests__/customTestSequencers.test.ts @@ -0,0 +1,26 @@ +/** + * 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 path from 'path'; +import runJest from '../runJest'; +import {extractSummary} from '../Utils'; +const dir = path.resolve(__dirname, '../custom-test-sequencer'); + +test('run prioritySequence first', () => { + const result = runJest(dir, ['-i']); + expect(result.status).toBe(0); + const sequence = extractSummary(result.stderr) + .rest.replace(/PASS /g, '') + .split('\n'); + expect(sequence).toEqual([ + './a.test.js', + './b.test.js', + './c.test.js', + './d.test.js', + './e.test.js', + ]); +}); diff --git a/e2e/custom-test-sequencer/a.test.js b/e2e/custom-test-sequencer/a.test.js new file mode 100644 index 000000000000..1187e8309f87 --- /dev/null +++ b/e2e/custom-test-sequencer/a.test.js @@ -0,0 +1,7 @@ +/** + * 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. + */ +test('a', () => {}); diff --git a/e2e/custom-test-sequencer/b.test.js b/e2e/custom-test-sequencer/b.test.js new file mode 100644 index 000000000000..04e77e777bb1 --- /dev/null +++ b/e2e/custom-test-sequencer/b.test.js @@ -0,0 +1,7 @@ +/** + * 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. + */ +test('b', () => {}); diff --git a/e2e/custom-test-sequencer/c.test.js b/e2e/custom-test-sequencer/c.test.js new file mode 100644 index 000000000000..332d8682ab00 --- /dev/null +++ b/e2e/custom-test-sequencer/c.test.js @@ -0,0 +1,7 @@ +/** + * 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. + */ +test('c', () => {}); diff --git a/e2e/custom-test-sequencer/d.test.js b/e2e/custom-test-sequencer/d.test.js new file mode 100644 index 000000000000..4102fe8c1925 --- /dev/null +++ b/e2e/custom-test-sequencer/d.test.js @@ -0,0 +1,7 @@ +/** + * 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. + */ +test('d', () => {}); diff --git a/e2e/custom-test-sequencer/e.test.js b/e2e/custom-test-sequencer/e.test.js new file mode 100644 index 000000000000..6b4bbf07b9b4 --- /dev/null +++ b/e2e/custom-test-sequencer/e.test.js @@ -0,0 +1,7 @@ +/** + * 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. + */ +test('e', () => {}); diff --git a/e2e/custom-test-sequencer/package.json b/e2e/custom-test-sequencer/package.json new file mode 100644 index 000000000000..80ef569e593c --- /dev/null +++ b/e2e/custom-test-sequencer/package.json @@ -0,0 +1,6 @@ +{ + "jest": { + "testEnvironment": "node", + "testSequencer": "/testSequencer.js" + } +} diff --git a/e2e/custom-test-sequencer/testSequencer.js b/e2e/custom-test-sequencer/testSequencer.js new file mode 100644 index 000000000000..c4248cf62a6c --- /dev/null +++ b/e2e/custom-test-sequencer/testSequencer.js @@ -0,0 +1,17 @@ +/** + * 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. + */ + +const Sequencer = require('@jest/test-sequencer').default; + +class CustomSequencer extends Sequencer { + sort(tests) { + const copyTests = Array.from(tests); + return copyTests.sort((testA, testB) => (testA.path > testB.path ? 1 : -1)); + } +} + +module.exports = CustomSequencer; diff --git a/packages/jest-cli/src/cli/args.ts b/packages/jest-cli/src/cli/args.ts index b98baa1791b1..4c68b6e1a9c9 100644 --- a/packages/jest-cli/src/cli/args.ts +++ b/packages/jest-cli/src/cli/args.ts @@ -615,6 +615,13 @@ export const options = { '`/path/to/testRunner.js`.', type: 'string' as 'string', }, + testSequencer: { + description: + 'Allows to specify a custom test sequencer. The default is ' + + '`@jest/test-sequencer`. A path to a custom test sequencer can be ' + + 'provided: `/path/to/testSequencer.js`', + type: 'string' as 'string', + }, testURL: { description: 'This option sets the URL for the jsdom environment.', type: 'string' as 'string', diff --git a/packages/jest-config/package.json b/packages/jest-config/package.json index 6492f315bc76..2d79bde69b57 100644 --- a/packages/jest-config/package.json +++ b/packages/jest-config/package.json @@ -11,6 +11,7 @@ "types": "build/index.d.ts", "dependencies": { "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^24.6.0", "@jest/types": "^24.6.0", "babel-jest": "^24.6.0", "chalk": "^2.0.1", diff --git a/packages/jest-config/src/Defaults.ts b/packages/jest-config/src/Defaults.ts index 15bde5d04bd2..4058e7a59f18 100644 --- a/packages/jest-config/src/Defaults.ts +++ b/packages/jest-config/src/Defaults.ts @@ -71,6 +71,7 @@ const defaultOptions: Config.DefaultOptions = { testRegex: [], testResultsProcessor: null, testRunner: 'jasmine2', + testSequencer: '@jest/test-sequencer', testURL: 'http://localhost', timers: 'real', transform: null, diff --git a/packages/jest-config/src/ValidConfig.ts b/packages/jest-config/src/ValidConfig.ts index 6cdd558591b5..aa5ec67c747d 100644 --- a/packages/jest-config/src/ValidConfig.ts +++ b/packages/jest-config/src/ValidConfig.ts @@ -114,6 +114,7 @@ const initialOptions: Config.InitialOptions = { ), testResultsProcessor: 'processor-node-module', testRunner: 'jasmine2', + testSequencer: '@jest/test-sequencer', testURL: 'http://localhost', timers: 'real', transform: { diff --git a/packages/jest-config/src/index.ts b/packages/jest-config/src/index.ts index 45bebcafcdac..9f929864554b 100644 --- a/packages/jest-config/src/index.ts +++ b/packages/jest-config/src/index.ts @@ -148,6 +148,7 @@ const groupOptions = ( testNamePattern: options.testNamePattern, testPathPattern: options.testPathPattern, testResultsProcessor: options.testResultsProcessor, + testSequencer: options.testSequencer, updateSnapshot: options.updateSnapshot, useStderr: options.useStderr, verbose: options.verbose, diff --git a/packages/jest-config/src/normalize.ts b/packages/jest-config/src/normalize.ts index 819f792b188b..335b8c1e2293 100644 --- a/packages/jest-config/src/normalize.ts +++ b/packages/jest-config/src/normalize.ts @@ -29,6 +29,7 @@ import { getRunner, getWatchPlugin, resolve, + getSequencer, } from './utils'; import {DEFAULT_JS_PATTERN, DEFAULT_REPORTER_LABEL} from './constants'; import {validateReporters} from './ReporterValidationErrors'; @@ -588,6 +589,17 @@ export default function normalize( }); } break; + case 'testSequencer': + { + const option = oldOptions[key]; + value = + option && + getSequencer(newOptions.resolver, { + filePath: option, + rootDir: options.rootDir, + }); + } + break; case 'prettierPath': { // We only want this to throw if "prettierPath" is explicitly passed diff --git a/packages/jest-config/src/utils.ts b/packages/jest-config/src/utils.ts index ca7c5d9d0047..3cd9f3965a51 100644 --- a/packages/jest-config/src/utils.ts +++ b/packages/jest-config/src/utils.ts @@ -223,3 +223,15 @@ export const isJSONString = (text?: JSONString | string): text is JSONString => typeof text === 'string' && text.startsWith('{') && text.endsWith('}'); + +export const getSequencer = ( + resolver: string | undefined | null, + {filePath, rootDir}: {filePath: string; rootDir: Config.Path}, +) => + resolveWithPrefix(resolver, { + filePath, + humanOptionName: 'Jest Sequencer', + optionName: 'testSequencer', + prefix: 'jest-sequencer-', + rootDir, + }); diff --git a/packages/jest-core/package.json b/packages/jest-core/package.json index 1da737dca08b..34bd6e9e7990 100644 --- a/packages/jest-core/package.json +++ b/packages/jest-core/package.json @@ -34,6 +34,7 @@ "strip-ansi": "^5.0.0" }, "devDependencies": { + "@jest/test-sequencer": "^24.6.0", "@types/ansi-escapes": "^3.0.1", "@types/exit": "^0.1.30", "@types/graceful-fs": "^4.1.2", diff --git a/packages/jest-core/src/__tests__/run_jest.test.js b/packages/jest-core/src/__tests__/run_jest.test.js index 3c4c6d170cbc..3293dd4fbcbc 100644 --- a/packages/jest-core/src/__tests__/run_jest.test.js +++ b/packages/jest-core/src/__tests__/run_jest.test.js @@ -16,7 +16,7 @@ describe('runJest', () => { await runJest({ changedFilesPromise: Promise.resolve({repos: {git: {size: 0}}}), contexts: [], - globalConfig: {watch: true}, + globalConfig: {testSequencer: '@jest/test-sequencer', watch: true}, onComplete: () => null, outputStream: {}, startRun: {}, diff --git a/packages/jest-core/src/runJest.ts b/packages/jest-core/src/runJest.ts index fad2435d8ee0..fd88593d05e4 100644 --- a/packages/jest-core/src/runJest.ts +++ b/packages/jest-core/src/runJest.ts @@ -9,7 +9,7 @@ import path from 'path'; import chalk from 'chalk'; import {sync as realpath} from 'realpath-native'; import {CustomConsole} from '@jest/console'; -import {formatTestResults} from 'jest-util'; +import {formatTestResults, interopRequireDefault} from 'jest-util'; import exit from 'exit'; import fs from 'graceful-fs'; import {JestHook, JestHookEmitter} from 'jest-watcher'; @@ -20,12 +20,12 @@ import { AggregatedResult, makeEmptyAggregatedTestResult, } from '@jest/test-result'; +import TestSequencer from '@jest/test-sequencer'; // eslint-disable-line import/no-extraneous-dependencies import {ChangedFiles, ChangedFilesPromise} from 'jest-changed-files'; import getNoTestsFoundMessage from './getNoTestsFoundMessage'; import runGlobalHook from './runGlobalHook'; import SearchSource from './SearchSource'; import TestScheduler, {TestSchedulerContext} from './TestScheduler'; -import TestSequencer from './TestSequencer'; import FailedTestsCache from './FailedTestsCache'; import collectNodeHandles from './collectHandles'; import TestWatcher from './TestWatcher'; @@ -143,7 +143,10 @@ export default (async function runJest({ failedTestsCache?: FailedTestsCache; filter?: Filter; }) { - const sequencer = new TestSequencer(); + const Sequencer: typeof TestSequencer = interopRequireDefault( + require(globalConfig.testSequencer), + ).default; + const sequencer = new Sequencer(); let allTests: Array = []; if (changedFilesPromise && globalConfig.watch) { diff --git a/packages/jest-core/tsconfig.json b/packages/jest-core/tsconfig.json index 79f9f35b848b..f943dccf39d0 100644 --- a/packages/jest-core/tsconfig.json +++ b/packages/jest-core/tsconfig.json @@ -17,6 +17,7 @@ {"path": "../jest-runtime"}, {"path": "../jest-snapshot"}, {"path": "../jest-test-result"}, + {"path": "../jest-test-sequencer"}, {"path": "../jest-types"}, {"path": "../jest-transform"}, {"path": "../jest-util"}, diff --git a/packages/jest-test-sequencer/.npmignore b/packages/jest-test-sequencer/.npmignore new file mode 100644 index 000000000000..85e48fe7b0a4 --- /dev/null +++ b/packages/jest-test-sequencer/.npmignore @@ -0,0 +1,3 @@ +**/__mocks__/** +**/__tests__/** +src diff --git a/packages/jest-test-sequencer/package.json b/packages/jest-test-sequencer/package.json new file mode 100644 index 000000000000..aec99f7bfab3 --- /dev/null +++ b/packages/jest-test-sequencer/package.json @@ -0,0 +1,25 @@ +{ + "name": "@jest/test-sequencer", + "version": "24.6.0", + "repository": { + "type": "git", + "url": "https://github.com/facebook/jest.git", + "directory": "packages/test-sequencer" + }, + "license": "MIT", + "main": "build/index.js", + "types": "build/index.d.ts", + "dependencies": { + "@jest/test-result": "^24.6.0", + "jest-haste-map": "^24.6.0", + "jest-runner": "^24.6.0", + "jest-runtime": "^24.6.0" + }, + "engines": { + "node": ">= 6" + }, + "publishConfig": { + "access": "public" + }, + "gitHead": "800533020f5b2f153615c821ed7cb12fd868fa6f" +} diff --git a/packages/jest-core/src/__tests__/test_sequencer.test.js b/packages/jest-test-sequencer/src/__tests__/test_sequencer.test.js similarity index 99% rename from packages/jest-core/src/__tests__/test_sequencer.test.js rename to packages/jest-test-sequencer/src/__tests__/test_sequencer.test.js index 2e0c8d00f0ea..1252dc1e6e15 100644 --- a/packages/jest-core/src/__tests__/test_sequencer.test.js +++ b/packages/jest-test-sequencer/src/__tests__/test_sequencer.test.js @@ -6,7 +6,7 @@ */ 'use strict'; -import TestSequencer from '../TestSequencer'; +import TestSequencer from '../index'; jest.mock('fs'); diff --git a/packages/jest-core/src/TestSequencer.ts b/packages/jest-test-sequencer/src/index.ts similarity index 100% rename from packages/jest-core/src/TestSequencer.ts rename to packages/jest-test-sequencer/src/index.ts diff --git a/packages/jest-test-sequencer/tsconfig.json b/packages/jest-test-sequencer/tsconfig.json new file mode 100644 index 000000000000..78fc3a6be9ed --- /dev/null +++ b/packages/jest-test-sequencer/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "build" + }, + "references": [ + {"path": "../jest-haste-map"}, + {"path": "../jest-runner"}, + {"path": "../jest-test-result"}, + {"path": "../jest-types"} + ] +} diff --git a/packages/jest-types/src/Config.ts b/packages/jest-types/src/Config.ts index 4aed202a21a6..4d6da36d6bd8 100644 --- a/packages/jest-types/src/Config.ts +++ b/packages/jest-types/src/Config.ts @@ -88,6 +88,7 @@ export type DefaultOptions = { testRegex: Array; testResultsProcessor: string | null | undefined; testRunner: string | null | undefined; + testSequencer: string; testURL: string; timers: 'real' | 'fake'; transform: @@ -203,6 +204,7 @@ export type InitialOptions = { testRegex?: string | Array; testResultsProcessor?: string | null | undefined; testRunner?: string; + testSequencer?: string; testURL?: string; timers?: 'real' | 'fake'; transform?: { @@ -340,6 +342,7 @@ export type GlobalConfig = { testNamePattern: string; testPathPattern: string; testResultsProcessor: string | null | undefined; + testSequencer: string; updateSnapshot: SnapshotUpdateState; useStderr: boolean; verbose: boolean | null | undefined; @@ -483,6 +486,7 @@ export type Argv = Arguments< testRegex: string | Array; testResultsProcessor: string | null | undefined; testRunner: string; + testSequencer: string; testURL: string; timers: string; transform: string;