Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/master' into hoist-global-import
Browse files Browse the repository at this point in the history
* upstream/master:
  Prints the Symbol name into the error message with a custom asymmetric matcher (jestjs#9888)
  Show coverage of sources related to tests in changed files (jestjs#9769)
  fix: don't /// <reference types="jest" /> (jestjs#9875)
  • Loading branch information
jeysal committed Apr 26, 2020
2 parents 80dabd0 + d814646 commit 76f0c38
Show file tree
Hide file tree
Showing 23 changed files with 371 additions and 40 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Expand Up @@ -3,10 +3,13 @@
### Features

- `[@jest/globals]` New package so Jest's globals can be explicitly imported ([#9801](https://github.com/facebook/jest/pull/9801))
- `[jest-core]` Show coverage of sources related to tests in changed files ([#9769](https://github.com/facebook/jest/pull/9769))
- `[jest-runtime]` Populate `require.cache` ([#9841](https://github.com/facebook/jest/pull/9841))

### Fixes

- `[expect]` Prints the Symbol name into the error message with a custom asymmetric matcher ([#9888](https://github.com/facebook/jest/pull/9888))
- `[@jest/environment]` Make sure not to reference Jest types ([#9875](https://github.com/facebook/jest/pull/9875))
- `[jest-message-util]` Code frame printing should respect `--noStackTrace` flag ([#9866](https://github.com/facebook/jest/pull/9866))
- `[jest-runtime]` Support importing CJS from ESM using `import` statements ([#9850](https://github.com/facebook/jest/pull/9850))
- `[jest-runtime]` Support importing parallel dynamic `import`s ([#9858](https://github.com/facebook/jest/pull/9858))
Expand Down
33 changes: 33 additions & 0 deletions e2e/__tests__/onlyChanged.test.ts
Expand Up @@ -137,6 +137,39 @@ test('report test coverage for only changed files', () => {
expect(stdout).not.toMatch('b.js');
});

test('report test coverage of source on test file change under only changed files', () => {
writeFiles(DIR, {
'__tests__/a.test.js': `
require('../a');
test('a1', () => expect(1).toBe(1));
`,
'a.js': 'module.exports = {}',
'package.json': JSON.stringify({
jest: {
collectCoverage: true,
coverageReporters: ['text'],
testEnvironment: 'node',
},
}),
});

run(`${GIT} init`, DIR);
run(`${GIT} add .`, DIR);
run(`${GIT} commit --no-gpg-sign -m "first"`, DIR);

writeFiles(DIR, {
'__tests__/a.test.js': `
require('../a');
test('a1', () => expect(1).toBe(1));
test('a2', () => expect(2).toBe(2));
`,
});

const {stdout} = runJest(DIR, ['--only-changed']);

expect(stdout).toMatch('a.js');
});

test('do not pickup non-tested files when reporting coverage on only changed files', () => {
writeFiles(DIR, {
'a.js': 'module.exports = {}',
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -48,6 +48,7 @@
"fast-check": "^1.13.0",
"find-process": "^1.4.1",
"glob": "^7.1.1",
"globby": "^10.0.2",
"graceful-fs": "^4.2.3",
"isbinaryfile": "^4.0.0",
"istanbul-lib-coverage": "^3.0.0",
Expand Down
12 changes: 12 additions & 0 deletions packages/expect/src/__tests__/__snapshots__/extend.test.js.snap
Expand Up @@ -53,3 +53,15 @@ exports[`is available globally when matcher is unary 1`] = `expected 15 to be di
exports[`is available globally when matcher is variadic 1`] = `expected 15 to be within range 1 - 3`;
exports[`is ok if there is no message specified 1`] = `<r>No message was specified for this matcher.</>`;
exports[`prints the Symbol into the error message 1`] = `
<d>expect(</><r>received</><d>).</>toEqual<d>(</><g>expected</><d>) // deep equality</>
<g>- Expected - 1</>
<r>+ Received + 1</>
<d> Object {</>
<g>- "a": toBeSymbol<Symbol(bar)>,</>
<r>+ "a": Symbol(foo),</>
<d> }</>
`;
17 changes: 17 additions & 0 deletions packages/expect/src/__tests__/extend.test.js
Expand Up @@ -23,6 +23,12 @@ jestExpect.extend({

return {message, pass};
},
toBeSymbol(actual, expected) {
const pass = actual === expected;
const message = () => `expected ${actual} to be Symbol ${expected}`;

return {message, pass};
},
toBeWithinRange(actual, floor, ceiling) {
const pass = actual >= floor && actual <= ceiling;
const message = pass
Expand Down Expand Up @@ -137,3 +143,14 @@ it('defines asymmetric variadic matchers that can be prefixed by not', () => {
}),
).not.toThrow();
});

it('prints the Symbol into the error message', () => {
const foo = Symbol('foo');
const bar = Symbol('bar');

expect(() =>
jestExpect({a: foo}).toEqual({
a: jestExpect.toBeSymbol(bar),
}),
).toThrowErrorMatchingSnapshot();
});
2 changes: 1 addition & 1 deletion packages/expect/src/jestMatchersObject.ts
Expand Up @@ -78,7 +78,7 @@ export const setMatchers = (
}

toAsymmetricMatcher() {
return `${this.toString()}<${this.sample.join(', ')}>`;
return `${this.toString()}<${this.sample.map(String).join(', ')}>`;
}
}

Expand Down
59 changes: 46 additions & 13 deletions packages/jest-core/src/SearchSource.ts
Expand Up @@ -51,13 +51,22 @@ const toTests = (context: Context, tests: Array<Config.Path>) =>
path,
}));

const hasSCM = (changedFilesInfo: ChangedFiles) => {
const {repos} = changedFilesInfo;
// no SCM (git/hg/...) is found in any of the roots.
const noSCM = Object.values(repos).every(scm => scm.size === 0);
return !noSCM;
};

export default class SearchSource {
private _context: Context;
private _dependencyResolver: DependencyResolver | null;
private _testPathCases: TestPathCases = [];

constructor(context: Context) {
const {config} = context;
this._context = context;
this._dependencyResolver = null;

const rootPattern = new RegExp(
config.roots.map(dir => escapePathForRegex(dir + path.sep)).join('|'),
Expand Down Expand Up @@ -92,6 +101,17 @@ export default class SearchSource {
}
}

private _getOrBuildDependencyResolver(): DependencyResolver {
if (!this._dependencyResolver) {
this._dependencyResolver = new DependencyResolver(
this._context.resolver,
this._context.hasteFS,
buildSnapshotResolver(this._context.config),
);
}
return this._dependencyResolver;
}

private _filterTestPathsWithStats(
allPaths: Array<Test>,
testPathPattern?: string,
Expand Down Expand Up @@ -155,11 +175,7 @@ export default class SearchSource {
allPaths: Set<Config.Path>,
collectCoverage: boolean,
): SearchResult {
const dependencyResolver = new DependencyResolver(
this._context.resolver,
this._context.hasteFS,
buildSnapshotResolver(this._context.config),
);
const dependencyResolver = this._getOrBuildDependencyResolver();

if (!collectCoverage) {
return {
Expand Down Expand Up @@ -240,14 +256,11 @@ export default class SearchSource {
changedFilesInfo: ChangedFiles,
collectCoverage: boolean,
): SearchResult {
const {repos, changedFiles} = changedFilesInfo;
// no SCM (git/hg/...) is found in any of the roots.
const noSCM = (Object.keys(repos) as Array<
keyof ChangedFiles['repos']
>).every(scm => repos[scm].size === 0);
return noSCM
? {noSCM: true, tests: []}
: this.findRelatedTests(changedFiles, collectCoverage);
if (!hasSCM(changedFilesInfo)) {
return {noSCM: true, tests: []};
}
const {changedFiles} = changedFilesInfo;
return this.findRelatedTests(changedFiles, collectCoverage);
}

private _getTestPaths(
Expand Down Expand Up @@ -328,4 +341,24 @@ export default class SearchSource {

return searchResult;
}

findRelatedSourcesFromTestsInChangedFiles(
changedFilesInfo: ChangedFiles,
): Array<string> {
if (!hasSCM(changedFilesInfo)) {
return [];
}
const {changedFiles} = changedFilesInfo;
const dependencyResolver = this._getOrBuildDependencyResolver();
const relatedSourcesSet = new Set<string>();
changedFiles.forEach(filePath => {
if (this.isTestFilePath(filePath)) {
const sourcePaths = dependencyResolver.resolve(filePath, {
skipNodeResolution: this._context.config.skipNodeResolution,
});
sourcePaths.forEach(sourcePath => relatedSourcesSet.add(sourcePath));
}
});
return Array.from(relatedSourcesSet);
}
}
13 changes: 10 additions & 3 deletions packages/jest-core/src/TestScheduler.ts
Expand Up @@ -44,6 +44,7 @@ export type TestSchedulerContext = {
firstRun: boolean;
previousSuccess: boolean;
changedFiles?: Set<Config.Path>;
sourcesRelatedToTestsInChangedFiles?: Set<Config.Path>;
};
export default class TestScheduler {
private _dispatcher: ReporterDispatcher;
Expand Down Expand Up @@ -180,7 +181,9 @@ export default class TestScheduler {
if (!testRunners[config.runner]) {
const Runner: typeof TestRunner = require(config.runner);
testRunners[config.runner] = new Runner(this._globalConfig, {
changedFiles: this._context && this._context.changedFiles,
changedFiles: this._context?.changedFiles,
sourcesRelatedToTestsInChangedFiles: this._context
?.sourcesRelatedToTestsInChangedFiles,
});
}
});
Expand Down Expand Up @@ -272,7 +275,9 @@ export default class TestScheduler {
if (!isDefault && collectCoverage) {
this.addReporter(
new CoverageReporter(this._globalConfig, {
changedFiles: this._context && this._context.changedFiles,
changedFiles: this._context?.changedFiles,
sourcesRelatedToTestsInChangedFiles: this._context
?.sourcesRelatedToTestsInChangedFiles,
}),
);
}
Expand Down Expand Up @@ -302,7 +307,9 @@ export default class TestScheduler {
if (collectCoverage) {
this.addReporter(
new CoverageReporter(this._globalConfig, {
changedFiles: this._context && this._context.changedFiles,
changedFiles: this._context?.changedFiles,
sourcesRelatedToTestsInChangedFiles: this._context
?.sourcesRelatedToTestsInChangedFiles,
}),
);
}
Expand Down
60 changes: 60 additions & 0 deletions packages/jest-core/src/__tests__/SearchSource.test.ts
Expand Up @@ -531,4 +531,64 @@ describe('SearchSource', () => {
}
});
});

describe('findRelatedSourcesFromTestsInChangedFiles', () => {
const rootDir = path.resolve(
__dirname,
'../../../jest-runtime/src/__tests__/test_root',
);

beforeEach(async () => {
const {options: config} = normalize(
{
haste: {
hasteImplModulePath: path.resolve(
__dirname,
'../../../jest-haste-map/src/__tests__/haste_impl.js',
),
providesModuleNodeModules: [],
},
name: 'SearchSource-findRelatedSourcesFromTestsInChangedFiles-tests',
rootDir,
},
{} as Config.Argv,
);
const context = await Runtime.createContext(config, {
maxWorkers,
watchman: false,
});
searchSource = new SearchSource(context);
});

it('return empty set if no SCM', () => {
const requireRegularModule = path.join(
rootDir,
'RequireRegularModule.js',
);
const sources = searchSource.findRelatedSourcesFromTestsInChangedFiles({
changedFiles: new Set([requireRegularModule]),
repos: {
git: new Set(),
hg: new Set(),
},
});
expect(sources).toEqual([]);
});

it('return sources required by tests', () => {
const regularModule = path.join(rootDir, 'RegularModule.js');
const requireRegularModule = path.join(
rootDir,
'RequireRegularModule.js',
);
const sources = searchSource.findRelatedSourcesFromTestsInChangedFiles({
changedFiles: new Set([requireRegularModule]),
repos: {
git: new Set('/path/to/git'),
hg: new Set(),
},
});
expect(sources).toEqual([regularModule]);
});
});
});
29 changes: 22 additions & 7 deletions packages/jest-core/src/runJest.ts
Expand Up @@ -34,13 +34,12 @@ import type {Filter, TestRunData} from './types';

const getTestPaths = async (
globalConfig: Config.GlobalConfig,
context: Context,
source: SearchSource,
outputStream: NodeJS.WriteStream,
changedFiles: ChangedFiles | undefined,
jestHooks: JestHookEmitter,
filter?: Filter,
) => {
const source = new SearchSource(context);
const data = await source.getTestPaths(globalConfig, changedFiles, filter);

if (!data.tests.length && globalConfig.onlyChanged && data.noSCM) {
Expand Down Expand Up @@ -167,11 +166,14 @@ export default async function runJest({
}
}

const searchSources = contexts.map(context => new SearchSource(context));

const testRunData: TestRunData = await Promise.all(
contexts.map(async context => {
contexts.map(async (context, index) => {
const searchSource = searchSources[index];
const matches = await getTestPaths(
globalConfig,
context,
searchSource,
outputStream,
changedFilesPromise && (await changedFilesPromise),
jestHooks,
Expand Down Expand Up @@ -242,9 +244,22 @@ export default async function runJest({
}

if (changedFilesPromise) {
testSchedulerContext.changedFiles = (
await changedFilesPromise
).changedFiles;
const changedFilesInfo = await changedFilesPromise;
if (changedFilesInfo.changedFiles) {
testSchedulerContext.changedFiles = changedFilesInfo.changedFiles;
const sourcesRelatedToTestsInChangedFilesArray = contexts
.map((_, index) => {
const searchSource = searchSources[index];
const relatedSourceFromTestsInChangedFiles = searchSource.findRelatedSourcesFromTestsInChangedFiles(
changedFilesInfo,
);
return relatedSourceFromTestsInChangedFiles;
})
.reduce((total, paths) => total.concat(paths), []);
testSchedulerContext.sourcesRelatedToTestsInChangedFiles = new Set(
sourcesRelatedToTestsInChangedFilesArray,
);
}
}

const results = await new TestScheduler(
Expand Down
2 changes: 2 additions & 0 deletions packages/jest-environment/tsconfig.json
@@ -1,6 +1,8 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
// we don't want `@types/jest` to be referenced
"types": ["node"],
"rootDir": "src",
"outDir": "build"
},
Expand Down
Expand Up @@ -41,6 +41,7 @@ test('resolves to the result of generateEmptyCoverage upon success', async () =>
globalConfig,
config,
undefined,
undefined,
);

expect(result).toEqual(42);
Expand Down

0 comments on commit 76f0c38

Please sign in to comment.