diff --git a/CHANGELOG.md b/CHANGELOG.md index 502e1cdd3274..6b62f8d2922c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - `[jest-reporter]` Allow `node-notifier@10` as peer dependency ([#11523](https://github.com/facebook/jest/pull/11523)) - `[jest-reporter]` Update `v8-to-istanbul` ([#11523](https://github.com/facebook/jest/pull/11523)) +- `[jest-core]` Support special characters like `@`, `+` and `()` on windows with `--findRelatedTests` ([#11548](https://github.com/facebook/jest/pull/11548)) ### Chore & Maintenance diff --git a/packages/jest-core/src/SearchSource.ts b/packages/jest-core/src/SearchSource.ts index 8b10cf5e0323..ad03652d18f4 100644 --- a/packages/jest-core/src/SearchSource.ts +++ b/packages/jest-core/src/SearchSource.ts @@ -288,18 +288,7 @@ export default class SearchSource { let paths = globalConfig.nonFlagArgs; if (globalConfig.findRelatedTests && 'win32' === os.platform()) { - const allFiles = this._context.hasteFS.getAllFiles(); - const options = {nocase: true, windows: false}; - - paths = paths - .map(p => { - const relativePath = path - .resolve(this._context.config.cwd, p) - .replace(/\\/g, '\\\\'); - const match = micromatch(allFiles, relativePath, options); - return match[0]; - }) - .filter(Boolean); + paths = this.filterPathsWin32(paths); } if (globalConfig.runTestsByPath && paths && paths.length) { @@ -316,6 +305,32 @@ export default class SearchSource { } } + public filterPathsWin32(paths: Array): Array { + const allFiles = this._context.hasteFS.getAllFiles(); + const options = {nocase: true, windows: false}; + + function normalizePosix(filePath: string) { + return filePath.replace(/\\/g, '/'); + } + + paths = paths + .map(p => { + // micromatch works with forward slashes: https://github.com/micromatch/micromatch#backslashes + const normalizedPath = normalizePosix( + path.resolve(this._context.config.cwd, p), + ); + const match = micromatch( + allFiles.map(normalizePosix), + normalizedPath, + options, + ); + return match[0]; + }) + .filter(Boolean) + .map(p => path.resolve(p)); + return paths; + } + async getTestPaths( globalConfig: Config.GlobalConfig, changedFiles: ChangedFiles | undefined, diff --git a/packages/jest-core/src/__tests__/SearchSource.test.ts b/packages/jest-core/src/__tests__/SearchSource.test.ts index 763d8c58018b..76122f957363 100644 --- a/packages/jest-core/src/__tests__/SearchSource.test.ts +++ b/packages/jest-core/src/__tests__/SearchSource.test.ts @@ -406,6 +406,81 @@ describe('SearchSource', () => { }); }); + describe('filterPathsWin32', () => { + beforeEach(async () => { + const config = ( + await normalize( + { + name, + rootDir: '.', + roots: [], + }, + {} as Config.Argv, + ) + ).options; + const context = await Runtime.createContext(config, { + maxWorkers, + watchman: false, + }); + + searchSource = new SearchSource(context); + context.hasteFS.getAllFiles = () => [ + path.resolve('packages/lib/my-lib.ts'), + path.resolve('packages/@core/my-app.ts'), + path.resolve('packages/+cli/my-cli.ts'), + path.resolve('packages/.hidden/my-app-hidden.ts'), + path.resolve('packages/programs (x86)/my-program.ts'), + ]; + }); + + it('should allow a simple match', async () => { + const result = searchSource.filterPathsWin32(['packages/lib/my-lib.ts']); + expect(result).toEqual([path.resolve('packages/lib/my-lib.ts')]); + }); + it('should allow to match a file inside a hidden directory', async () => { + const result = searchSource.filterPathsWin32([ + 'packages/.hidden/my-app-hidden.ts', + ]); + expect(result).toEqual([ + path.resolve('packages/.hidden/my-app-hidden.ts'), + ]); + }); + it('should allow to match a file inside a directory prefixed with a "@"', async () => { + const result = searchSource.filterPathsWin32([ + 'packages/@core/my-app.ts', + ]); + expect(result).toEqual([path.resolve('packages/@core/my-app.ts')]); + }); + it('should allow to match a file inside a directory prefixed with a "+"', async () => { + const result = searchSource.filterPathsWin32(['packages/+cli/my-cli.ts']); + expect(result).toEqual([path.resolve('packages/+cli/my-cli.ts')]); + }); + it('should allow an @(pattern)', () => { + const result = searchSource.filterPathsWin32([ + 'packages/@(@core)/my-app.ts', + ]); + expect(result).toEqual([path.resolve('packages/@core/my-app.ts')]); + }); + it('should allow a +(pattern)', () => { + const result = searchSource.filterPathsWin32([ + 'packages/+(@core)/my-app.ts', + ]); + expect(result).toEqual([path.resolve('packages/@core/my-app.ts')]); + }); + it('should allow for (pattern) in file path', () => { + const result = searchSource.filterPathsWin32([ + 'packages/programs (x86)/my-program.ts', + ]); + expect(result).toEqual([ + path.resolve('packages/programs (x86)/my-program.ts'), + ]); + }); + it('should allow no results found', () => { + const result = searchSource.filterPathsWin32(['not/exists']); + expect(result).toHaveLength(0); + }); + }); + describe('findRelatedTests', () => { const rootDir = path.join( __dirname,