-
-
Notifications
You must be signed in to change notification settings - Fork 6.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
add only-failures mode #4886
add only-failures mode #4886
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import FailedTestsCache from '../failed_tests_cache'; | ||
|
||
describe('FailedTestsCache', () => { | ||
test('should filter tests', () => { | ||
const failedTestsCache = new FailedTestsCache(); | ||
failedTestsCache.setTestResults([ | ||
{ | ||
numFailingTests: 0, | ||
testFilePath: '/path/to/passing.js', | ||
testResults: [ | ||
{fullName: 'test 1', status: 'passed'}, | ||
{fullName: 'test 2', status: 'passed'}, | ||
], | ||
}, | ||
{ | ||
numFailingTests: 2, | ||
testFilePath: '/path/to/failed_1.js', | ||
testResults: [ | ||
{fullName: 'test 3', status: 'failed'}, | ||
{fullName: 'test 4', status: 'failed'}, | ||
], | ||
}, | ||
{ | ||
numFailingTests: 1, | ||
testFilePath: '/path/to/failed_2.js', | ||
testResults: [ | ||
{fullName: 'test 5', status: 'failed'}, | ||
{fullName: 'test 6', status: 'passed'}, | ||
], | ||
}, | ||
]); | ||
|
||
const result = failedTestsCache.filterTests([ | ||
{ | ||
path: '/path/to/passing.js', | ||
}, | ||
{ | ||
path: '/path/to/failed_1.js', | ||
}, | ||
{ | ||
path: '/path/to/failed_2.js', | ||
}, | ||
{ | ||
path: '/path/to/unknown.js', | ||
}, | ||
]); | ||
expect(result).toMatchObject([ | ||
{ | ||
path: '/path/to/failed_1.js', | ||
}, | ||
{ | ||
path: '/path/to/failed_2.js', | ||
}, | ||
]); | ||
expect(failedTestsCache.updateConfig({})).toMatchObject({ | ||
enabledTestsMap: { | ||
'/path/to/failed_1.js': {'test 3': true, 'test 4': true}, | ||
'/path/to/failed_2.js': {'test 5': true}, | ||
}, | ||
}); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
/** | ||
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved. | ||
* | ||
* This source code is licensed under the MIT license found in the | ||
* LICENSE file in the root directory of this source tree. | ||
* | ||
* @flow | ||
*/ | ||
|
||
import type {Test} from 'types/TestRunner'; | ||
import type {TestResult} from 'types/TestResult'; | ||
import type {GlobalConfig} from 'types/Config'; | ||
|
||
export default class FailedTestsCache { | ||
_enabledTestsMap: ?{[key: string]: {[key: string]: boolean}}; | ||
|
||
filterTests(tests: Array<Test>): Array<Test> { | ||
if (!this._enabledTestsMap) { | ||
return tests; | ||
} | ||
// $FlowFixMe | ||
return tests.filter(testResult => this._enabledTestsMap[testResult.path]); | ||
} | ||
|
||
setTestResults(testResults: Array<TestResult>) { | ||
this._enabledTestsMap = (testResults || []) | ||
.filter(testResult => testResult.numFailingTests) | ||
.reduce((suiteMap, testResult) => { | ||
suiteMap[testResult.testFilePath] = testResult.testResults | ||
.filter(test => test.status === 'failed') | ||
.reduce((testMap, test) => { | ||
testMap[test.fullName] = true; | ||
return testMap; | ||
}, {}); | ||
return suiteMap; | ||
}, {}); | ||
this._enabledTestsMap = Object.freeze(this._enabledTestsMap); | ||
} | ||
|
||
updateConfig(globalConfig: GlobalConfig): GlobalConfig { | ||
if (!this._enabledTestsMap) { | ||
return globalConfig; | ||
} | ||
// $FlowFixMe Object.assign | ||
const newConfig: GlobalConfig = Object.assign({}, globalConfig); | ||
newConfig.enabledTestsMap = this._enabledTestsMap; | ||
return Object.freeze(newConfig); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import chalk from 'chalk'; | ||
|
||
export default function getNoTestFoundFailed() { | ||
return ( | ||
chalk.bold('No failed test found.\n') + | ||
chalk.dim('Press `f` to run all tests.') | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,6 +16,7 @@ type Options = { | |
updateSnapshot?: SnapshotUpdateState, | ||
mode?: 'watch' | 'watchAll', | ||
passWithNoTests?: boolean, | ||
onlyFailures?: boolean, | ||
}; | ||
|
||
export default (globalConfig: GlobalConfig, options: Options): GlobalConfig => { | ||
|
@@ -60,5 +61,9 @@ export default (globalConfig: GlobalConfig, options: Options): GlobalConfig => { | |
newConfig.passWithNoTests = true; | ||
} | ||
|
||
if ('onlyFailures' in options) { | ||
newConfig.onlyFailures = options.onlyFailures || false; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need to cast onlyFailures to boolean? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did that because of Flow warning. |
||
} | ||
|
||
return Object.freeze(newConfig); | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,7 @@ import TestWatcher from './test_watcher'; | |
import Prompt from './lib/Prompt'; | ||
import TestPathPatternPrompt from './test_path_pattern_prompt'; | ||
import TestNamePatternPrompt from './test_name_pattern_prompt'; | ||
import FailedTestsCache from './failed_tests_cache'; | ||
import WatchPluginRegistry from './lib/watch_plugin_registry'; | ||
import {KEYS, CLEAR} from './constants'; | ||
|
||
|
@@ -55,6 +56,7 @@ export default function watch( | |
} | ||
} | ||
|
||
const failedTestsCache = new FailedTestsCache(); | ||
const prompt = new Prompt(); | ||
const testPathPatternPrompt = new TestPathPatternPrompt(outputStream, prompt); | ||
const testNamePatternPrompt = new TestNamePatternPrompt(outputStream, prompt); | ||
|
@@ -120,6 +122,7 @@ export default function watch( | |
return runJest({ | ||
changedFilesPromise, | ||
contexts, | ||
failedTestsCache, | ||
globalConfig, | ||
onComplete: results => { | ||
isRunning = false; | ||
|
@@ -146,7 +149,7 @@ export default function watch( | |
} else { | ||
outputStream.write('\n'); | ||
} | ||
|
||
failedTestsCache.setTestResults(results.testResults); | ||
testNamePatternPrompt.updateCachedTestResults(results.testResults); | ||
}, | ||
outputStream, | ||
|
@@ -177,7 +180,9 @@ export default function watch( | |
if ( | ||
isRunning && | ||
testWatcher && | ||
[KEYS.Q, KEYS.ENTER, KEYS.A, KEYS.O, KEYS.P, KEYS.T].indexOf(key) !== -1 | ||
[KEYS.Q, KEYS.ENTER, KEYS.A, KEYS.O, KEYS.P, KEYS.T, KEYS.F].indexOf( | ||
key, | ||
) !== -1 | ||
) { | ||
testWatcher.setState({interrupted: true}); | ||
return; | ||
|
@@ -230,6 +235,12 @@ export default function watch( | |
}); | ||
startRun(globalConfig); | ||
break; | ||
case KEYS.F: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should pressing |
||
globalConfig = updateGlobalConfig(globalConfig, { | ||
onlyFailures: !globalConfig.onlyFailures, | ||
}); | ||
startRun(globalConfig); | ||
break; | ||
case KEYS.O: | ||
globalConfig = updateGlobalConfig(globalConfig, { | ||
mode: 'watch', | ||
|
@@ -342,6 +353,12 @@ const usage = ( | |
? chalk.dim(' \u203A Press ') + 'a' + chalk.dim(' to run all tests.') | ||
: null, | ||
|
||
globalConfig.onlyFailures | ||
? chalk.dim(' \u203A Press ') + 'f' + chalk.dim(' to run all tests.') | ||
: chalk.dim(' \u203A Press ') + | ||
'f' + | ||
chalk.dim(' to run only failed tests.'), | ||
|
||
(globalConfig.watchAll || | ||
globalConfig.testPathPattern || | ||
globalConfig.testNamePattern) && | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -132,7 +132,14 @@ async function jasmine2( | |
}, | ||
}); | ||
|
||
if (globalConfig.testNamePattern) { | ||
if (globalConfig.enabledTestsMap) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How would this work with |
||
env.specFilter = spec => { | ||
const suiteMap = | ||
globalConfig.enabledTestsMap && | ||
globalConfig.enabledTestsMap[spec.result.testPath]; | ||
return suiteMap && suiteMap[spec.result.fullName]; | ||
}; | ||
} else if (globalConfig.testNamePattern) { | ||
const testNameRegex = new RegExp(globalConfig.testNamePattern, 'i'); | ||
env.specFilter = spec => testNameRegex.test(spec.getFullName()); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should be done through
import updateGlobalConfig from './lib/update_global_config';