diff --git a/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap b/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap index 18ccc60fbe68..a3759f42c96f 100644 --- a/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap +++ b/e2e/__tests__/__snapshots__/moduleNameMapper.test.ts.snap @@ -30,6 +30,6 @@ FAIL __tests__/index.js 12 | module.exports = () => 'test'; 13 | - at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:472:17) + at createNoMappedModuleFoundError (../../packages/jest-resolve/build/index.js:501:17) at Object.require (index.js:10:1) `; diff --git a/e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.ts.snap b/e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.ts.snap index f88d38ad9041..7a66f6098b01 100644 --- a/e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.ts.snap +++ b/e2e/__tests__/__snapshots__/resolveNoFileExtensions.test.ts.snap @@ -33,6 +33,6 @@ FAIL __tests__/test.js | ^ 4 | - at Resolver.resolveModule (../../packages/jest-resolve/build/index.js:230:17) + at Resolver.resolveModule (../../packages/jest-resolve/build/index.js:259:17) at Object.require (index.js:3:18) `; diff --git a/packages/jest-core/package.json b/packages/jest-core/package.json index 9f980af3c53d..60bf1868942f 100644 --- a/packages/jest-core/package.json +++ b/packages/jest-core/package.json @@ -19,6 +19,7 @@ "jest-haste-map": "^24.8.0", "jest-message-util": "^24.8.0", "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.8.0", "jest-resolve-dependencies": "^24.8.0", "jest-runner": "^24.8.0", "jest-runtime": "^24.8.0", diff --git a/packages/jest-core/src/__tests__/watch-file-changes.test.ts b/packages/jest-core/src/__tests__/watch-file-changes.test.ts new file mode 100644 index 000000000000..ac19096f1b00 --- /dev/null +++ b/packages/jest-core/src/__tests__/watch-file-changes.test.ts @@ -0,0 +1,187 @@ +/** + * 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. + * + */ + +'use strict'; + +import path from 'path'; +import fs from 'fs'; +import os from 'os'; +import {JestHook} from 'jest-watcher'; +import Runtime from 'jest-runtime'; +import {normalize} from 'jest-config'; +import HasteMap from 'jest-haste-map'; +import rimraf from 'rimraf'; +import {AggregatedResult} from '@jest/test-result'; + +describe('Watch mode flows with changed files', () => { + jest.resetModules(); + + let watch: any; + let pipe: NodeJS.ReadStream; + let stdin: MockStdin; + const testDirectory = path.resolve(os.tmpdir(), 'jest-tmp'); + const fileTargetPath = path.resolve(testDirectory, 'lost-file.js'); + const fileTargetPath2 = path.resolve( + testDirectory, + 'watch-test-fake.test.js', + ); + const cacheDirectory = path.resolve(os.tmpdir(), `tmp${Math.random()}`); + let hasteMapInstance: HasteMap; + + beforeEach(() => { + watch = require('../watch').default; + pipe = {write: jest.fn()} as any; + stdin = new MockStdin(); + rimraf.sync(cacheDirectory); + rimraf.sync(testDirectory); + fs.mkdirSync(testDirectory); + fs.mkdirSync(cacheDirectory); + }); + + afterEach(() => { + jest.resetModules(); + if (hasteMapInstance) { + hasteMapInstance.end(); + } + rimraf.sync(cacheDirectory); + rimraf.sync(testDirectory); + }); + + it('should correct require new files without legacy cache', async () => { + fs.writeFileSync( + fileTargetPath2, + ` + require('./lost-file.js'); + describe('Fake test', () => { + it('Hey', () => { + + }); + }); + `, + ); + + const config = normalize( + { + cache: false, + cacheDirectory, + coverageReporters: [], + maxConcurrency: 1, + maxWorkers: 1, + moduleDirectories: ['node_modules'], + onlyChanged: false, + reporters: [], + rootDir: testDirectory, + silent: true, + testRegex: ['watch-test-fake\\.test\\.js$'], + watch: false, + watchman: false, + }, + {} as any, + ).options; + + hasteMapInstance = await Runtime.createHasteMap(config, { + maxWorkers: 1, + resetCache: true, + watch: true, + watchman: false, + }); + + const realContext = await hasteMapInstance.build().then(hasteMap => ({ + config, + hasteFS: hasteMap.hasteFS, + moduleMap: hasteMap.moduleMap, + resolver: Runtime.createResolver(config, hasteMap.moduleMap), + })); + + const hook = new JestHook(); + const firstErrorPromise = new Promise(resolve => { + hook.getSubscriber().onTestRunComplete(resolve); + }); + await watch( + { + ...config, + watchPlugins: [], + }, + [realContext], + pipe, + [hasteMapInstance], + stdin, + hook, + ); + + await firstErrorPromise; + + const successPromise = new Promise(resolve => { + hook.getSubscriber().onTestRunComplete(resolve); + }); + + // Create lost file + fs.writeFileSync( + fileTargetPath, + ` + describe('Fake group', () => { + it('Fake 1', () => {}); + it('Fake 2', () => {}); + it('Fake 3', () => {}); + }); + `, + ); + + const resultSuccessReport = await successPromise; + + expect(resultSuccessReport).toMatchObject({ + numFailedTestSuites: 0, + numFailedTests: 0, + numPassedTests: 4, + numRuntimeErrorTestSuites: 0, + success: true, + wasInterrupted: false, + }); + expect(resultSuccessReport.testResults[0]).toMatchObject({ + failureMessage: null, + }); + + const errorPromise = new Promise(resolve => { + hook.getSubscriber().onTestRunComplete(resolve); + }); + + // Remove again to ensure about no legacy cache + fs.unlinkSync(fileTargetPath); + + const resultErrorReport = await errorPromise; + + // After remove file we have to fail tests + expect(resultErrorReport).toMatchObject({ + numFailedTestSuites: 1, + numPassedTests: 0, + numRuntimeErrorTestSuites: 1, + success: false, + wasInterrupted: false, + }); + }); +}); + +class MockStdin { + private _callbacks: Array; + + constructor() { + this._callbacks = []; + } + + resume() {} + + setEncoding() {} + + on(_: any, callback: any) { + this._callbacks.push(callback); + } + + emit(key: string) { + this._callbacks.forEach(cb => cb(key)); + } +} diff --git a/packages/jest-core/src/watch.ts b/packages/jest-core/src/watch.ts index 5b97d1623709..483983a34aa0 100644 --- a/packages/jest-core/src/watch.ts +++ b/packages/jest-core/src/watch.ts @@ -15,6 +15,7 @@ import {formatExecError} from 'jest-message-util'; import {isInteractive, preRunMessage, specialChars} from 'jest-util'; import {ValidationError} from 'jest-validate'; import {Context} from 'jest-runtime'; +import Resolver from 'jest-resolve'; import {Config} from '@jest/types'; import { AllowedConfigOptions, @@ -275,6 +276,9 @@ export default function watch( isRunning = true; const configs = contexts.map(context => context.config); const changedFilesPromise = getChangedFilesPromise(globalConfig, configs); + // Clear cache for required modules + Resolver.clearDefaultResolverCache(); + return runJest({ changedFilesPromise, contexts, diff --git a/packages/jest-resolve/src/defaultResolver.ts b/packages/jest-resolve/src/defaultResolver.ts index 42a0d31ee939..8422ebcfc519 100644 --- a/packages/jest-resolve/src/defaultResolver.ts +++ b/packages/jest-resolve/src/defaultResolver.ts @@ -44,6 +44,10 @@ export default function defaultResolver( }); } +export const clearDefaultResolverCache = () => { + checkedPaths.clear(); +}; + const REGEX_RELATIVE_IMPORT = /^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[\\\/])/; function resolveSync( diff --git a/packages/jest-resolve/src/index.ts b/packages/jest-resolve/src/index.ts index a3e09432dde2..9e21e5faa806 100644 --- a/packages/jest-resolve/src/index.ts +++ b/packages/jest-resolve/src/index.ts @@ -12,7 +12,7 @@ import {sync as realpath} from 'realpath-native'; import chalk from 'chalk'; import nodeModulesPaths from './nodeModulesPaths'; import isBuiltinModule from './isBuiltinModule'; -import defaultResolver from './defaultResolver'; +import defaultResolver, {clearDefaultResolverCache} from './defaultResolver'; import {ResolverConfig} from './types'; type FindNodeModuleConfig = { @@ -79,6 +79,10 @@ class Resolver { this._modulePathCache = new Map(); } + static clearDefaultResolverCache() { + clearDefaultResolverCache(); + } + static findNodeModule( path: Config.Path, options: FindNodeModuleConfig,