diff --git a/CHANGELOG.md b/CHANGELOG.md index db63ad8a0581..aea4bdd7cdcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - `[jest-snapshot]` Correctly merge property matchers with the rest of the snapshot in `toMatchSnapshot`. ([#6528](https://github.com/facebook/jest/pull/6528)) - `[jest-snapshot]` Add error messages for invalid property matchers. ([#6528](https://github.com/facebook/jest/pull/6528)) - `[jest-cli]` Show open handles from inside test files as well ([#6263](https://github.com/facebook/jest/pull/6263)) +- `[jest-haste-map]` Fix a problem where creating folders ending with `.js` could cause a crash ([#6818](https://github.com/facebook/jest/pull/6818)) ### Chore & Maintenance diff --git a/packages/jest-haste-map/package.json b/packages/jest-haste-map/package.json index 1385629d6bf3..69c27870bcb0 100644 --- a/packages/jest-haste-map/package.json +++ b/packages/jest-haste-map/package.json @@ -10,6 +10,7 @@ "dependencies": { "fb-watchman": "^2.0.0", "graceful-fs": "^4.1.11", + "invariant": "^2.2.4", "jest-docblock": "^23.2.0", "jest-serializer": "^23.0.1", "jest-worker": "^23.2.0", diff --git a/packages/jest-haste-map/src/__tests__/index.test.js b/packages/jest-haste-map/src/__tests__/index.test.js index 4bb61a42bd47..e5966fa07bf1 100644 --- a/packages/jest-haste-map/src/__tests__/index.test.js +++ b/packages/jest-haste-map/src/__tests__/index.test.js @@ -1070,7 +1070,15 @@ describe('HasteMap', () => { expect(moduleMap.getModule('Banana')).toBeNull(); }); - const MOCK_STAT = {mtime: {getTime: () => 45}}; + const MOCK_STAT_FILE = { + isDirectory: () => false, + mtime: {getTime: () => 45}, + }; + + const MOCK_STAT_FOLDER = { + isDirectory: () => true, + mtime: {getTime: () => 45}, + }; hm_it('handles several change events at once', async hm => { mockFs['/fruits/tomato.js'] = [ @@ -1084,18 +1092,18 @@ describe('HasteMap', () => { ' */', ].join('\n'); const e = mockEmitters['/fruits']; - e.emit('all', 'add', 'tomato.js', '/fruits', MOCK_STAT); - e.emit('all', 'change', 'pear.js', '/fruits', MOCK_STAT); + e.emit('all', 'add', 'tomato.js', '/fruits', MOCK_STAT_FILE); + e.emit('all', 'change', 'pear.js', '/fruits', MOCK_STAT_FILE); const {eventsQueue, hasteFS, moduleMap} = await waitForItToChange(hm); expect(eventsQueue).toEqual([ { filePath: '/fruits/tomato.js', - stat: MOCK_STAT, + stat: MOCK_STAT_FILE, type: 'add', }, { filePath: '/fruits/pear.js', - stat: MOCK_STAT, + stat: MOCK_STAT_FILE, type: 'change', }, ]); @@ -1107,8 +1115,8 @@ describe('HasteMap', () => { hm_it('does not emit duplicate change events', async hm => { const e = mockEmitters['/fruits']; - e.emit('all', 'change', 'tomato.js', '/fruits', MOCK_STAT); - e.emit('all', 'change', 'tomato.js', '/fruits', MOCK_STAT); + e.emit('all', 'change', 'tomato.js', '/fruits', MOCK_STAT_FILE); + e.emit('all', 'change', 'tomato.js', '/fruits', MOCK_STAT_FILE); const {eventsQueue} = await waitForItToChange(hm); expect(eventsQueue).toHaveLength(1); }); @@ -1117,11 +1125,19 @@ describe('HasteMap', () => { 'emits a change even if a file in node_modules has changed', async hm => { const e = mockEmitters['/fruits']; - e.emit('all', 'add', 'apple.js', '/fruits/node_modules/', MOCK_STAT); + e.emit( + 'all', + 'add', + 'apple.js', + '/fruits/node_modules/', + MOCK_STAT_FILE, + ); const {eventsQueue, hasteFS} = await waitForItToChange(hm); const filePath = '/fruits/node_modules/apple.js'; expect(eventsQueue).toHaveLength(1); - expect(eventsQueue).toEqual([{filePath, stat: MOCK_STAT, type: 'add'}]); + expect(eventsQueue).toEqual([ + {filePath, stat: MOCK_STAT_FILE, type: 'add'}, + ]); expect(hasteFS.getModuleName(filePath)).toBeDefined(); }, ); @@ -1133,15 +1149,25 @@ describe('HasteMap', () => { expect(initMM.getModule('Orange', 'ios')).toBeTruthy(); expect(initMM.getModule('Orange', 'android')).toBeTruthy(); const e = mockEmitters['/fruits']; - e.emit('all', 'change', 'Orange.ios.js', '/fruits/', MOCK_STAT); - e.emit('all', 'change', 'Orange.android.js', '/fruits/', MOCK_STAT); + e.emit('all', 'change', 'Orange.ios.js', '/fruits/', MOCK_STAT_FILE); + e.emit( + 'all', + 'change', + 'Orange.android.js', + '/fruits/', + MOCK_STAT_FILE, + ); const {eventsQueue, hasteFS, moduleMap} = await waitForItToChange(hm); expect(eventsQueue).toHaveLength(2); expect(eventsQueue).toEqual([ - {filePath: '/fruits/Orange.ios.js', stat: MOCK_STAT, type: 'change'}, + { + filePath: '/fruits/Orange.ios.js', + stat: MOCK_STAT_FILE, + type: 'change', + }, { filePath: '/fruits/Orange.android.js', - stat: MOCK_STAT, + stat: MOCK_STAT_FILE, type: 'change', }, ]); @@ -1182,8 +1208,8 @@ describe('HasteMap', () => { ' */', ].join('\n'); const e = mockEmitters['/fruits']; - e.emit('all', 'change', 'pear.js', '/fruits', MOCK_STAT); - e.emit('all', 'add', 'blueberry.js', '/fruits', MOCK_STAT); + e.emit('all', 'change', 'pear.js', '/fruits', MOCK_STAT_FILE); + e.emit('all', 'add', 'blueberry.js', '/fruits', MOCK_STAT_FILE); const {hasteFS, moduleMap} = await waitForItToChange(hm); expect(hasteFS.exists('/fruits/blueberry.js')).toBe(true); try { @@ -1215,7 +1241,7 @@ describe('HasteMap', () => { ' */', ].join('\n'); const e = mockEmitters['/fruits']; - e.emit('all', 'change', 'pear.js', '/fruits', MOCK_STAT); + e.emit('all', 'change', 'pear.js', '/fruits', MOCK_STAT_FILE); const {moduleMap} = await waitForItToChange(hm); expect(moduleMap.getModule('Pear')).toBe('/fruits/blueberry.js'); expect(moduleMap.getModule('OldPear')).toBe('/fruits/pear.js'); @@ -1231,11 +1257,25 @@ describe('HasteMap', () => { ' */', ].join('\n'); const e = mockEmitters['/fruits']; - e.emit('all', 'change', 'blueberry.js', '/fruits', MOCK_STAT); + e.emit('all', 'change', 'blueberry.js', '/fruits', MOCK_STAT_FILE); const {moduleMap} = await waitForItToChange(hm); expect(moduleMap.getModule('Pear')).toBe('/fruits/pear.js'); expect(moduleMap.getModule('Blueberry')).toBe('/fruits/blueberry.js'); }); + + hm_it('ignore directories', async hm => { + const e = mockEmitters['/fruits']; + e.emit('all', 'change', 'tomato.js', '/fruits', MOCK_STAT_FOLDER); + e.emit( + 'all', + 'change', + 'tomato.js', + '/fruits/tomato.js/index.js', + MOCK_STAT_FILE, + ); + const {eventsQueue} = await waitForItToChange(hm); + expect(eventsQueue).toHaveLength(1); + }); }); }); }); diff --git a/packages/jest-haste-map/src/index.js b/packages/jest-haste-map/src/index.js index 3da0ae655621..0d5bad0fcb44 100644 --- a/packages/jest-haste-map/src/index.js +++ b/packages/jest-haste-map/src/index.js @@ -12,11 +12,13 @@ import {version as VERSION} from '../package.json'; import {getSha1, worker} from './worker'; import crypto from 'crypto'; import EventEmitter from 'events'; +import fs from 'fs'; import getMockName from './get_mock_name'; import getPlatformExtension from './lib/get_platform_extension'; import H from './constants'; import HasteFS from './haste_fs'; import HasteModuleMap from './module_map'; +import invariant from 'invariant'; // eslint-disable-next-line import/default import nodeCrawl from './crawlers/node'; import normalizePathSep from './lib/normalize_path_sep'; @@ -719,10 +721,11 @@ class HasteMap extends EventEmitter { type: string, filePath: Path, root: Path, - stat: {mtime: Date}, + stat: ?fs.Stats, ) => { filePath = path.join(root, normalizePathSep(filePath)); if ( + (stat && stat.isDirectory()) || this._ignore(filePath) || !extensions.some(extension => filePath.endsWith(extension)) ) { @@ -792,6 +795,10 @@ class HasteMap extends EventEmitter { // If the file was added or changed, // parse it and update the haste map. if (type === 'add' || type === 'change') { + invariant( + stat, + 'since the file exists or changed, it should have stats', + ); const fileMetadata = ['', stat.mtime.getTime(), 0, [], null]; hasteMap.files[filePath] = fileMetadata; const promise = this._processFile( diff --git a/yarn.lock b/yarn.lock index b30348942766..efaca57ec606 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5495,7 +5495,7 @@ interpret@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614" -invariant@^2.2.0, invariant@^2.2.2: +invariant@^2.2.0, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" dependencies: