Skip to content

Commit

Permalink
Only match within relative path
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonchinn178 committed Sep 22, 2023
1 parent ae8ee58 commit 1911750
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 27 deletions.
12 changes: 8 additions & 4 deletions packages/jest-config/src/normalize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,10 @@ const normalizeReporters = ({
});
};

const buildTestPathPatterns = (argv: Config.Argv): TestPathPatterns => {
const buildTestPathPatterns = (
argv: Config.Argv,
rootDir: string,
): TestPathPatterns => {
const patterns = [];

if (argv._) {
Expand All @@ -401,10 +404,11 @@ const buildTestPathPatterns = (argv: Config.Argv): TestPathPatterns => {
patterns.push(...argv.testPathPattern);
}

const testPathPatterns = new TestPathPatterns(patterns);
const config = {rootDir};
const testPathPatterns = new TestPathPatterns(patterns, config);
if (!testPathPatterns.isValid()) {
showTestPathPatternsError(testPathPatterns);
return new TestPathPatterns([]);
return new TestPathPatterns([], config);
}
return testPathPatterns;
};
Expand Down Expand Up @@ -997,7 +1001,7 @@ export default async function normalize(
}

newOptions.nonFlagArgs = argv._?.map(arg => `${arg}`);
const testPathPatterns = buildTestPathPatterns(argv);
const testPathPatterns = buildTestPathPatterns(argv, options.rootDir);
newOptions.testPathPatterns = testPathPatterns.patterns;
newOptions.json = !!argv.json;

Expand Down
1 change: 1 addition & 0 deletions packages/jest-core/src/__tests__/SearchSource.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ describe('SearchSource', () => {
const {searchSource, config} = await initSearchSource(initialOptions);
const {tests: paths} = await searchSource.getTestPaths({
...config,
...initialOptions,
testPathPatterns: [],
});
return paths.map(({path: p}) => path.relative(rootDir, p)).sort();
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-core/src/watch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ export default async function watch(

const emitFileChange = () => {
if (hooks.isUsed('onFileChange')) {
const testPathPatterns = new TestPathPatterns([]);
const testPathPatterns = new TestPathPatterns([], globalConfig);
const projects = searchSources.map(({context, searchSource}) => ({
config: context.config,
testPaths: searchSource
Expand Down
30 changes: 26 additions & 4 deletions packages/jest-util/src/TestPathPatterns.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,35 +6,57 @@
*/

import type {Config} from '@jest/types';
import {replacePathSepForRegex} from 'jest-regex-util';
import {escapePathForRegex, replacePathSepForRegex} from 'jest-regex-util';

type PatternsConfig = Pick<Config.GlobalConfig, 'rootDir'>;
type PatternsFullConfig = PatternsConfig &
Pick<Config.GlobalConfig, 'testPathPatterns'>;

export default class TestPathPatterns {
readonly patterns: Array<string>;
private readonly rootDir: string;

private _regexString: string | null = null;

constructor(patterns: Array<string>, config?: PatternsConfig);
constructor(patterns: Array<string>, config: PatternsConfig);
constructor(config: PatternsFullConfig);
constructor(patternsOrConfig: Array<string> | PatternsFullConfig) {
let patterns;
constructor(
patternsOrConfig: Array<string> | PatternsFullConfig,
configArg?: PatternsConfig,
) {
let patterns, config;
if (Array.isArray(patternsOrConfig)) {
patterns = patternsOrConfig;
config = configArg!;
} else {
patterns = patternsOrConfig.testPathPatterns;
config = patternsOrConfig;
}

this.patterns = patterns;
this.rootDir = config.rootDir.replace(/\/*$/, '/');
}

private get regexString(): string {
if (this._regexString !== null) {
return this._regexString;
}
const rootDirRegex = escapePathForRegex(this.rootDir);
const regexString = this.patterns
.map(p => {
// absolute paths passed on command line should stay same
if (p.match(/^\//)) {
return p;
}

// explicit relative paths should resolve against rootDir
if (p.match(/^\.\//)) {
return p.replace(/^\.\//, rootDirRegex);
}

// all other patterns should only match the relative part of the test
return `${rootDirRegex}(.*)?${p}`;
})
.map(replacePathSepForRegex)
.join('|');
this._regexString = regexString;
Expand Down
43 changes: 25 additions & 18 deletions packages/jest-util/src/__tests__/TestPathPatterns.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,81 +21,88 @@ beforeEach(() => {
jest.resetAllMocks();
});

const config = {rootDir: ''};

describe('TestPathPatterns', () => {
describe('isSet', () => {
it('returns false if no patterns specified', () => {
const testPathPatterns = new TestPathPatterns([]);
const testPathPatterns = new TestPathPatterns([], config);
expect(testPathPatterns.isSet()).toBe(false);
});

it('returns true if patterns specified', () => {
const testPathPatterns = new TestPathPatterns(['a']);
const testPathPatterns = new TestPathPatterns(['a'], config);
expect(testPathPatterns.isSet()).toBe(true);
});
});

describe('isValid', () => {
it('returns true for empty patterns', () => {
const testPathPatterns = new TestPathPatterns([]);
const testPathPatterns = new TestPathPatterns([], config);
expect(testPathPatterns.isValid()).toBe(true);
});

it('returns true for valid patterns', () => {
const testPathPatterns = new TestPathPatterns(['abc+', 'z.*']);
const testPathPatterns = new TestPathPatterns(['abc+', 'z.*'], config);
expect(testPathPatterns.isValid()).toBe(true);
});

it('returns false for at least one invalid pattern', () => {
const testPathPatterns = new TestPathPatterns(['abc+', '(', 'z.*']);
const testPathPatterns = new TestPathPatterns(
['abc+', '(', 'z.*'],
config,
);
expect(testPathPatterns.isValid()).toBe(false);
});
});

describe('isMatch', () => {
it('returns true with no patterns', () => {
const testPathPatterns = new TestPathPatterns([]);
const testPathPatterns = new TestPathPatterns([], config);
expect(testPathPatterns.isMatch('/a/b')).toBe(true);
});

it('returns true for same path', () => {
const testPathPatterns = new TestPathPatterns(['/a/b']);
const testPathPatterns = new TestPathPatterns(['/a/b'], config);
expect(testPathPatterns.isMatch('/a/b')).toBe(true);
});

it('returns true for same path with case insensitive', () => {
const testPathPatternsUpper = new TestPathPatterns(['/A/B']);
const testPathPatternsUpper = new TestPathPatterns(['/A/B'], config);
expect(testPathPatternsUpper.isMatch('/a/b')).toBe(true);
expect(testPathPatternsUpper.isMatch('/A/B')).toBe(true);

const testPathPatternsLower = new TestPathPatterns(['/a/b']);
const testPathPatternsLower = new TestPathPatterns(['/a/b'], config);
expect(testPathPatternsLower.isMatch('/A/B')).toBe(true);
expect(testPathPatternsLower.isMatch('/a/b')).toBe(true);
});

it('returns true for contained path', () => {
const testPathPatterns = new TestPathPatterns(['b/c']);
const testPathPatterns = new TestPathPatterns(['b/c'], config);
expect(testPathPatterns.isMatch('/a/b/c/d')).toBe(true);
});

it('returns true for explicit relative path', () => {
const testPathPatterns = new TestPathPatterns(['./b/c']);
const testPathPatterns = new TestPathPatterns(['./b/c'], {
rootDir: '/a',
});
expect(testPathPatterns.isMatch('/a/b/c')).toBe(true);
});

it('returns true for partial file match', () => {
const testPathPatterns = new TestPathPatterns(['aaa']);
const testPathPatterns = new TestPathPatterns(['aaa'], config);
expect(testPathPatterns.isMatch('/foo/..aaa..')).toBe(true);
expect(testPathPatterns.isMatch('/foo/..aaa')).toBe(true);
expect(testPathPatterns.isMatch('/foo/aaa..')).toBe(true);
});

it('returns true for path suffix', () => {
const testPathPatterns = new TestPathPatterns(['c/d']);
const testPathPatterns = new TestPathPatterns(['c/d'], config);
expect(testPathPatterns.isMatch('/a/b/c/d')).toBe(true);
});

it('returns true if regex matches', () => {
const testPathPatterns = new TestPathPatterns(['ab*c?']);
const testPathPatterns = new TestPathPatterns(['ab*c?'], config);

expect(testPathPatterns.isMatch('/foo/a')).toBe(true);
expect(testPathPatterns.isMatch('/foo/ab')).toBe(true);
Expand Down Expand Up @@ -123,7 +130,7 @@ describe('TestPathPatterns', () => {
});

it('returns true if match any paths', () => {
const testPathPatterns = new TestPathPatterns(['a/b', 'c/d']);
const testPathPatterns = new TestPathPatterns(['a/b', 'c/d'], config);

expect(testPathPatterns.isMatch('/foo/a/b')).toBe(true);
expect(testPathPatterns.isMatch('/foo/c/d')).toBe(true);
Expand All @@ -134,20 +141,20 @@ describe('TestPathPatterns', () => {

it('does not normalize Windows paths on POSIX', () => {
mockSep.mockReturnValue('/');
const testPathPatterns = new TestPathPatterns(['a\\z', 'a\\\\z']);
const testPathPatterns = new TestPathPatterns(['a\\z', 'a\\\\z'], config);
expect(testPathPatterns.isMatch('/foo/a/z')).toBe(false);
});

it('normalizes paths for Windows', () => {
mockSep.mockReturnValue('\\');
const testPathPatterns = new TestPathPatterns(['a/b']);
const testPathPatterns = new TestPathPatterns(['a/b'], config);
expect(testPathPatterns.isMatch('\\foo\\a\\b')).toBe(true);
});
});

describe('toPretty', () => {
it('renders a human-readable string', () => {
const testPathPatterns = new TestPathPatterns(['a/b', 'c/d']);
const testPathPatterns = new TestPathPatterns(['a/b', 'c/d'], config);
expect(testPathPatterns.toPretty()).toMatchSnapshot();
});
});
Expand Down

0 comments on commit 1911750

Please sign in to comment.