Skip to content

Commit

Permalink
make --onlyfailures work in non-watch mode
Browse files Browse the repository at this point in the history
  • Loading branch information
victorphoenix3 committed Oct 23, 2020
1 parent 0eab327 commit 1c1d40e
Show file tree
Hide file tree
Showing 13 changed files with 123 additions and 14 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -7,6 +7,7 @@
### Fixes

- `[expect]` Fix `objectContaining` to work recursively into sub-objects ([#10508](https://github.com/facebook/jest/pull/10508))
- `[jest-cli, jest-core, jest-config, jest-types]` Fix `--onlyFailures` flag to work in non-watch mode ([#10678](https://github.com/facebook/jest/pull/10678/files))
- `[jest-config]` Fix for the `jest.config.ts` compiler to not interfere with `tsconfig.json` files ([#10675](https://github.com/facebook/jest/pull/10675))
- `[jest-message-util]` Update to work properly with Node 15 ([#10660](https://github.com/facebook/jest/pull/10660))
- `[jest-mock]` Allow to mock methods in getters (TypeScript 3.9 export) ([#10156](https://github.com/facebook/jest/pull/10156))
Expand Down
54 changes: 54 additions & 0 deletions e2e/__tests__/onlyFailuresNonWatch.test.ts
@@ -0,0 +1,54 @@
import {tmpdir} from 'os';
import * as path from 'path';
import {cleanup, writeFiles} from '../Utils';
import runJest from '../runJest';
import * as fs from 'graceful-fs';

const DIR = path.resolve(tmpdir(), 'non-watch-mode-onlyFailures');

beforeEach(() => cleanup(DIR));
afterEach(() => cleanup(DIR));

test('onlyFailures flag works in non-watch mode', () => {
writeFiles(DIR, {
'__tests__/a.js': `
test('bar', () => { expect('bar').toBe('foo'); });
`,
'__tests__/b.js': `
test('foo', () => { expect('foo').toBe('foo'); });
`,
'package.json': JSON.stringify({
jest: {
testEnvironment: 'node',
},
}),
});

let stdout, stderr;

({stdout, stderr} = runJest(DIR));
expect(stdout).toBe('');
expect(stderr).toMatch('FAIL __tests__/a.js');
expect(stderr).toMatch('PASS __tests__/b.js');

// only the failed test should run and it should fail
({stdout, stderr} = runJest(DIR,['--onlyFailures']));
expect(stdout).toBe('');
expect(stderr).toMatch('FAIL __tests__/a.js');
expect(stderr).not.toMatch('__tests__/b.js');

// fix the failing test
let data = "test('bar 1', () => { expect('bar').toBe('bar'); })";
fs.writeFileSync(path.join(DIR,'__tests__/a.js'), data);

// only the failed test should run and it should pass
({stdout, stderr} = runJest(DIR,['--onlyFailures']));
expect(stdout).toBe('');
expect(stderr).toMatch('PASS __tests__/a.js');
expect(stderr).not.toMatch('__tests__/b.js');

// No test should run
({stdout, stderr} = runJest(DIR,['--onlyFailures']));
expect(stdout).toBe('No failed test found.');
expect(stderr).toBe('');
});
7 changes: 7 additions & 0 deletions packages/jest-cli/src/__tests__/cli/args.test.ts
Expand Up @@ -31,6 +31,13 @@ describe('check', () => {
);
});

it('raises an exception if onlyFailures and watchAll are both specified', () => {
const argv = {onlyFailures: true, watchAll: true} as Config.Argv;
expect(() => check(argv)).toThrow(
'Both --onlyFailures and --watchAll were specified',
);
});

it('raises an exception when lastCommit and watchAll are both specified', () => {
const argv = {lastCommit: true, watchAll: true} as Config.Argv;
expect(() => check(argv)).toThrow(
Expand Down
7 changes: 7 additions & 0 deletions packages/jest-cli/src/cli/args.ts
Expand Up @@ -32,6 +32,13 @@ export function check(argv: Config.Argv): true {
}
}

if (argv.onlyFailures && argv.watchAll) {
throw new Error(
`Both --onlyFailures and --watchAll were specified, but these two ` +
'options do not make sense together.',
);
}

if (argv.findRelatedTests && argv._.length === 0) {
throw new Error(
'The --findRelatedTests option requires file paths to be specified.\n' +
Expand Down
1 change: 1 addition & 0 deletions packages/jest-config/src/ValidConfig.ts
Expand Up @@ -77,6 +77,7 @@ const initialOptions: Config.InitialOptions = {
notify: false,
notifyMode: 'failure-change',
onlyChanged: false,
onlyFailures: false,
preset: 'react-native',
prettierPath: '<rootDir>/node_modules/prettier',
projects: ['project-a', 'project-b/'],
Expand Down
5 changes: 4 additions & 1 deletion packages/jest-config/src/normalize.ts
Expand Up @@ -896,6 +896,7 @@ export default function normalize(
case 'notify':
case 'notifyMode':
case 'onlyChanged':
case 'onlyFailures':
case 'outputFile':
case 'passWithNoTests':
case 'replname':
Expand Down Expand Up @@ -985,10 +986,12 @@ export default function normalize(

if (argv.all) {
newOptions.onlyChanged = false;
newOptions.onlyFailures = false;
} else if (newOptions.testPathPattern) {
// When passing a test path pattern we don't want to only monitor changed
// files unless `--watch` is also passed.
// or failed files unless `--watch` is also passed.
newOptions.onlyChanged = newOptions.watch;
newOptions.onlyFailures = newOptions.watch;
}

if (!newOptions.onlyChanged) {
Expand Down
Expand Up @@ -2,10 +2,7 @@

exports[`getNoTestsFoundMessage returns correct message when monitoring only changed 1`] = `"<bold>No tests found related to files changed since last commit.</>"`;
exports[`getNoTestsFoundMessage returns correct message when monitoring only failures 1`] = `
"<bold>No failed test found.</>
<bold></><dim>Press \`f\` to quit \\"only failed tests\\" mode.</>"
`;
exports[`getNoTestsFoundMessage returns correct message when monitoring only failures 1`] = `"<bold>No failed test found.</>"`;
exports[`getNoTestsFoundMessage returns correct message with passWithNoTests 1`] = `"<bold>No tests found, exiting with code 0</>"`;
Expand Down
20 changes: 15 additions & 5 deletions packages/jest-core/src/getNoTestFoundFailed.ts
Expand Up @@ -6,10 +6,20 @@
*/

import chalk = require('chalk');
import type {Config} from '@jest/types';
import {isInteractive} from 'jest-util';

export default function getNoTestFoundFailed(): string {
return (
chalk.bold('No failed test found.\n') +
chalk.dim('Press `f` to quit "only failed tests" mode.')
);
export default function getNoTestFoundFailed(
globalConfig: Config.GlobalConfig,
): string {
let msg = chalk.bold('No failed test found.');
if (isInteractive) {
msg += chalk.dim(
'\n' +
(globalConfig.watch
? 'Press `f` to quit "only failed tests" mode.'
: 'Run Jest without `--onlyFailures` or with `--all` to run all tests.'),
);
}
return msg;
}
2 changes: 1 addition & 1 deletion packages/jest-core/src/getNoTestsFoundMessage.ts
Expand Up @@ -18,7 +18,7 @@ export default function getNoTestsFoundMessage(
globalConfig: Config.GlobalConfig,
): string {
if (globalConfig.onlyFailures) {
return getNoTestFoundFailed();
return getNoTestFoundFailed(globalConfig);
}
if (globalConfig.onlyChanged) {
return getNoTestFoundRelatedToChangedFiles(globalConfig);
Expand Down
10 changes: 7 additions & 3 deletions packages/jest-core/src/runJest.ts
Expand Up @@ -198,9 +198,13 @@ export default async function runJest({
return;
}

if (globalConfig.onlyFailures && failedTestsCache) {
allTests = failedTestsCache.filterTests(allTests);
globalConfig = failedTestsCache.updateConfig(globalConfig);
if (globalConfig.onlyFailures) {
if (failedTestsCache) {
allTests = failedTestsCache.filterTests(allTests);
globalConfig = failedTestsCache.updateConfig(globalConfig);
} else {
allTests = sequencer.allFailedTests(allTests);
}
}

const hasTests = allTests.length > 0;
Expand Down
15 changes: 15 additions & 0 deletions packages/jest-test-sequencer/src/__tests__/test_sequencer.test.js
Expand Up @@ -162,6 +162,21 @@ test('writes the cache based on results without existing cache', () => {
});
});

test('returns failed tests in sorted order', () => {
fs.readFileSync.mockImplementationOnce(() =>
JSON.stringify({
'/test-a.js': [SUCCESS, 5],
'/test-ab.js': [FAIL, 1],
'/test-c.js': [FAIL],
}),
);
const testPaths = ['/test-a.js', '/test-ab.js', '/test-c.js'];
expect(sequencer.allFailedTests(toTests(testPaths))).toEqual([
{context, duration: undefined, path: '/test-c.js'},
{context, duration: 1, path: '/test-ab.js'},
]);
});

test('writes the cache based on the results', () => {
fs.readFileSync.mockImplementationOnce(() =>
JSON.stringify({
Expand Down
8 changes: 8 additions & 0 deletions packages/jest-test-sequencer/src/index.ts
Expand Up @@ -109,6 +109,14 @@ export default class TestSequencer {
});
}

allFailedTests(tests: Array<Test>): Array<Test> {
const hasFailed = (cache: Cache, test: Test) =>
cache[test.path]?.[0] === FAIL;
return this.sort(
tests.filter(test => hasFailed(this._getCache(test), test)),
);
}

cacheResults(tests: Array<Test>, results: AggregatedResult): void {
const map = Object.create(null);
tests.forEach(test => (map[test.path] = test));
Expand Down
2 changes: 2 additions & 0 deletions packages/jest-types/src/Config.ts
Expand Up @@ -168,6 +168,7 @@ export type InitialOptions = Partial<{
notify: boolean;
notifyMode: string;
onlyChanged: boolean;
onlyFailures: boolean;
outputFile: Path;
passWithNoTests: boolean;
preprocessorIgnorePatterns: Array<Glob>;
Expand Down Expand Up @@ -418,6 +419,7 @@ export type Argv = Arguments<
notify: boolean;
notifyMode: string;
onlyChanged: boolean;
onlyFailures: boolean;
outputFile: string;
preset: string | null | undefined;
projects: Array<string>;
Expand Down

0 comments on commit 1c1d40e

Please sign in to comment.