diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ccf8f3e6c99..81f8952cf0d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ - `[jest-resolve]`: Remove internal peer dependencies ([#8215](https://github.com/facebook/jest/pull/8215)) - `[jest-snapshot]`: Remove internal peer dependencies ([#8215](https://github.com/facebook/jest/pull/8215)) - `[jest-resolve]` Fix requireActual with moduleNameMapper ([#8210](https://github.com/facebook/jest/pull/8210)) +- `[jest-haste-map]` Fix haste map duplicate detection in watch mode ([#8237](https://github.com/facebook/jest/pull/8237)) ### Chore & Maintenance diff --git a/packages/jest-haste-map/src/ModuleMap.ts b/packages/jest-haste-map/src/ModuleMap.ts index 73390e105092..cfc308b970f4 100644 --- a/packages/jest-haste-map/src/ModuleMap.ts +++ b/packages/jest-haste-map/src/ModuleMap.ts @@ -12,7 +12,6 @@ import { ModuleMetaData, RawModuleMap, ModuleMapData, - DuplicatesIndex, MockData, } from './types'; @@ -25,7 +24,7 @@ const EMPTY_MAP = new Map(); type ValueType = T extends Map ? V : never; export type SerializableModuleMap = { - duplicates: ReadonlyArray<[string, ValueType]>; + duplicates: ReadonlyArray<[string, [string, [string, [string, number]]]]>; map: ReadonlyArray<[string, ValueType]>; mocks: ReadonlyArray<[string, ValueType]>; rootDir: Config.Path; @@ -36,6 +35,30 @@ export default class ModuleMap { private readonly _raw: RawModuleMap; private json: SerializableModuleMap | undefined; + private static mapToArrayRecursive( + map: Map, + ): Array<[string, unknown]> { + let arr = Array.from(map); + if (arr[0] && arr[0][1] instanceof Map) { + arr = arr.map( + el => [el[0], this.mapToArrayRecursive(el[1])] as [string, unknown], + ); + } + return arr; + } + + private static mapFromArrayRecursive( + arr: ReadonlyArray<[string, unknown]>, + ): Map { + if (arr[0] && Array.isArray(arr[1])) { + arr = arr.map(el => [ + el[0], + this.mapFromArrayRecursive(el[1] as Array<[string, unknown]>), + ]) as Array<[string, unknown]>; + } + return new Map(arr); + } + constructor(raw: RawModuleMap) { this._raw = raw; } @@ -87,7 +110,9 @@ export default class ModuleMap { toJSON(): SerializableModuleMap { if (!this.json) { this.json = { - duplicates: Array.from(this._raw.duplicates), + duplicates: ModuleMap.mapToArrayRecursive( + this._raw.duplicates, + ) as SerializableModuleMap['duplicates'], map: Array.from(this._raw.map), mocks: Array.from(this._raw.mocks), rootDir: this._raw.rootDir, @@ -98,7 +123,9 @@ export default class ModuleMap { static fromJSON(serializableModuleMap: SerializableModuleMap) { return new ModuleMap({ - duplicates: new Map(serializableModuleMap.duplicates), + duplicates: ModuleMap.mapFromArrayRecursive( + serializableModuleMap.duplicates, + ) as RawModuleMap['duplicates'], map: new Map(serializableModuleMap.map), mocks: new Map(serializableModuleMap.mocks), rootDir: serializableModuleMap.rootDir, diff --git a/packages/jest-runner/src/__tests__/testRunner.test.js b/packages/jest-runner/src/__tests__/testRunner.test.js index 18a1333137c7..99f1425a0c05 100644 --- a/packages/jest-runner/src/__tests__/testRunner.test.js +++ b/packages/jest-runner/src/__tests__/testRunner.test.js @@ -65,43 +65,6 @@ test('injects the serializable module map into each worker in watch mode', () => }); }); -test('does not inject the serializable module map in serial mode', () => { - const globalConfig = {maxWorkers: 1, watch: false}; - const config = {rootDir: '/path/'}; - const context = {config}; - const runContext = {}; - - return new TestRunner(globalConfig, runContext) - .runTests( - [{context, path: './file.test.js'}, {context, path: './file2.test.js'}], - new TestWatcher({isWatchMode: globalConfig.watch}), - () => {}, - () => {}, - () => {}, - {serial: false}, - ) - .then(() => { - expect(mockWorkerFarm.worker.mock.calls).toEqual([ - [ - { - config, - context: runContext, - globalConfig, - path: './file.test.js', - }, - ], - [ - { - config, - context: runContext, - globalConfig, - path: './file2.test.js', - }, - ], - ]); - }); -}); - test('assign process.env.JEST_WORKER_ID = 1 when in runInBand mode', () => { const globalConfig = {maxWorkers: 1, watch: false}; const config = {rootDir: '/path/'}; diff --git a/packages/jest-runner/src/index.ts b/packages/jest-runner/src/index.ts index 10712b94af9e..7cfdbc1fc7c7 100644 --- a/packages/jest-runner/src/index.ts +++ b/packages/jest-runner/src/index.ts @@ -103,16 +103,13 @@ class TestRunner { onResult: OnTestSuccess, onFailure: OnTestFailure, ) { - let resolvers: Map | undefined = undefined; - if (watcher.isWatchMode()) { - resolvers = new Map(); - for (const test of tests) { - if (!resolvers.has(test.context.config.name)) { - resolvers.set(test.context.config.name, { - config: test.context.config, - serializableModuleMap: test.context.moduleMap.toJSON(), - }); - } + const resolvers: Map = new Map(); + for (const test of tests) { + if (!resolvers.has(test.context.config.name)) { + resolvers.set(test.context.config.name, { + config: test.context.config, + serializableModuleMap: test.context.moduleMap.toJSON(), + }); } } @@ -121,13 +118,11 @@ class TestRunner { forkOptions: {stdio: 'pipe'}, maxRetries: 3, numWorkers: this._globalConfig.maxWorkers, - setupArgs: resolvers - ? [ - { - serializableResolvers: Array.from(resolvers.values()), - }, - ] - : undefined, + setupArgs: [ + { + serializableResolvers: Array.from(resolvers.values()), + }, + ], }) as WorkerInterface; if (worker.getStdout()) worker.getStdout().pipe(process.stdout); diff --git a/packages/jest-runner/src/testWorker.ts b/packages/jest-runner/src/testWorker.ts index a2b5edb0fa6d..a815feb652a3 100644 --- a/packages/jest-runner/src/testWorker.ts +++ b/packages/jest-runner/src/testWorker.ts @@ -8,7 +8,7 @@ import {Config} from '@jest/types'; import {SerializableError, TestResult} from '@jest/test-result'; -import HasteMap, {ModuleMap, SerializableModuleMap} from 'jest-haste-map'; +import HasteMap, {SerializableModuleMap} from 'jest-haste-map'; import exit from 'exit'; import {separateMessageFromStack} from 'jest-message-util'; import Runtime from 'jest-runtime'; @@ -53,34 +53,24 @@ const formatError = (error: string | ErrorWithCode): SerializableError => { }; const resolvers = new Map(); -const getResolver = (config: Config.ProjectConfig, moduleMap?: ModuleMap) => { - const name = config.name; - if (moduleMap || !resolvers.has(name)) { - resolvers.set( - name, - Runtime.createResolver( - config, - moduleMap || Runtime.createHasteMap(config).readModuleMap(), - ), - ); +const getResolver = (config: Config.ProjectConfig) => { + const resolver = resolvers.get(config.name); + if (!resolver) { + throw new Error('Cannot find resolver for: ' + config.name); } - return resolvers.get(name)!; + return resolver; }; -export function setup(setupData?: { +export function setup(setupData: { serializableResolvers: Array; }) { - // Setup data is only used in watch mode to pass the latest version of all - // module maps that will be used during the test runs. Otherwise, module maps - // are loaded from disk as needed. - if (setupData) { - for (const { - config, - serializableModuleMap, - } of setupData.serializableResolvers) { - const moduleMap = HasteMap.ModuleMap.fromJSON(serializableModuleMap); - getResolver(config, moduleMap); - } + // Module maps that will be needed for the test runs are passed. + for (const { + config, + serializableModuleMap, + } of setupData.serializableResolvers) { + const moduleMap = HasteMap.ModuleMap.fromJSON(serializableModuleMap); + resolvers.set(config.name, Runtime.createResolver(config, moduleMap)); } }