diff --git a/packages/jest-editor-support/index.d.ts b/packages/jest-editor-support/index.d.ts index 7479838ab6cb..a69949aedb72 100644 --- a/packages/jest-editor-support/index.d.ts +++ b/packages/jest-editor-support/index.d.ts @@ -38,7 +38,8 @@ export class Settings extends EventEmitter { getConfig(completed: Function): void; jestVersionMajor: number | null; settings: { - testRegex: string; + testRegex: string, + testMatch: string[], }; } @@ -49,12 +50,14 @@ export class ProjectWorkspace { pathToConfig: string, localJestMajorVersin: number, collectCoverage?: boolean, + debug?: boolean, ); pathToJest: string; pathToConfig: string; rootPath: string; localJestMajorVersion: number; collectCoverage?: boolean; + debug?: boolean; } export interface IParseResults { @@ -163,6 +166,7 @@ export interface JestTotalResultsMeta { export enum messageTypes { noTests = 1, + testResults = 3, unknown = 0, watchUsage = 2, } @@ -182,3 +186,11 @@ export class Snapshot { constructor(parser?: any, customMatchers?: string[]); getMetadata(filepath: string): SnapshotMetadata[]; } + +type FormattedTestResults = { + testResults: TestResult[] +} + +type TestResult = { + name: string +} \ No newline at end of file diff --git a/packages/jest-editor-support/src/Process.js b/packages/jest-editor-support/src/Process.js index b0af41d1d0a3..52e3c7623a46 100644 --- a/packages/jest-editor-support/src/Process.js +++ b/packages/jest-editor-support/src/Process.js @@ -47,5 +47,12 @@ export const createProcess = ( env, shell: options.shell, }; + + if (workspace.debug) { + console.log( + `spawning process with command=${command}, args=${runtimeArgs.toString()}`, + ); + } + return spawn(command, runtimeArgs, spawnOptions); }; diff --git a/packages/jest-editor-support/src/Runner.js b/packages/jest-editor-support/src/Runner.js index 8bd124625ad6..42bed8945ad4 100644 --- a/packages/jest-editor-support/src/Runner.js +++ b/packages/jest-editor-support/src/Runner.js @@ -82,37 +82,14 @@ export default class Runner extends EventEmitter { this.debugprocess = this._createProcess(this.workspace, args, options); this.debugprocess.stdout.on('data', (data: Buffer) => { - // Make jest save to a file, otherwise we get chunked data - // and it can be hard to put it back together. - const stringValue = data.toString().trim(); - - if (stringValue.startsWith('Test results written to')) { - readFile(this.outputPath, 'utf8', (err, data) => { - if (err) { - const message = `JSON report not found at ${this.outputPath}`; - this.emit('terminalError', message); - } else { - const noTestsFound = this.doResultsFollowNoTestsFoundMessage(); - this.emit('executableJSON', JSON.parse(data), {noTestsFound}); - } - }); - } else { - this.emit('executableOutput', stringValue.replace('', '')); - } - this.prevMessageTypes.length = 0; + this._parseOutput(data, false); }); this.debugprocess.stderr.on('data', (data: Buffer) => { - const type = this.findMessageType(data); - if (type === messageTypes.unknown) { - this.prevMessageTypes.length = 0; - } else { - this.prevMessageTypes.push(type); - } - - this.emit('executableStdErr', data, {type}); + // jest 23 could send test results message to stderr + // see https://github.com/facebook/jest/pull/4858 + this._parseOutput(data, true); }); - this.debugprocess.on('exit', () => { this.emit('debuggerProcessExit'); this.prevMessageTypes.length = 0; @@ -129,10 +106,52 @@ export default class Runner extends EventEmitter { }); } + _parseOutput(data: Buffer, isStdErr: boolean): MessageType { + const msgType = this.findMessageType(data); + switch (msgType) { + case messageTypes.testResults: + readFile(this.outputPath, 'utf8', (err, data) => { + if (err) { + const message = `JSON report not found at ${this.outputPath}`; + this.emit('terminalError', message); + } else { + const noTestsFound = this.doResultsFollowNoTestsFoundMessage(); + this.emit('executableJSON', JSON.parse(data), { + noTestsFound, + }); + } + }); + this.prevMessageTypes.length = 0; + break; + case messageTypes.watchUsage: + case messageTypes.noTests: + this.prevMessageTypes.push(msgType); + this.emit('executableStdErr', data, { + type: msgType, + }); + break; + default: + // no special action needed, just report the output by its source + if (isStdErr) { + this.emit('executableStdErr', data, { + type: msgType, + }); + } else { + this.emit('executableOutput', data.toString().replace('', '')); + } + this.prevMessageTypes.length = 0; + break; + } + + return msgType; + } + runJestWithUpdateForSnapshots(completion: any, args: string[]) { const defaultArgs = ['--updateSnapshot']; - const options = {shell: this.options.shell}; + const options = { + shell: this.options.shell, + }; const updateProcess = this._createProcess( this.workspace, [...defaultArgs, ...(args ? args : [])], @@ -156,7 +175,7 @@ export default class Runner extends EventEmitter { delete this.debugprocess; } - findMessageType(buf: Buffer) { + findMessageType(buf: Buffer): MessageType { const str = buf.toString('utf8', 0, 58); if (str === 'No tests found related to files changed since last commit.') { return messageTypes.noTests; @@ -166,6 +185,9 @@ export default class Runner extends EventEmitter { return messageTypes.watchUsage; } + if (str.trim().startsWith('Test results written to')) { + return messageTypes.testResults; + } return messageTypes.unknown; } diff --git a/packages/jest-editor-support/src/Settings.js b/packages/jest-editor-support/src/Settings.js index 6e589ab5a5a5..cbaddb7b985e 100644 --- a/packages/jest-editor-support/src/Settings.js +++ b/packages/jest-editor-support/src/Settings.js @@ -46,12 +46,15 @@ export default class Settings extends EventEmitter { settings: ConfigRepresentation; workspace: ProjectWorkspace; spawnOptions: SpawnOptions; + _jsonPattern: RegExp; constructor(workspace: ProjectWorkspace, options?: Options) { super(); this.workspace = workspace; this._createProcess = (options && options.createProcess) || createProcess; - this.spawnOptions = {shell: options && options.shell}; + this.spawnOptions = { + shell: options && options.shell, + }; // Defaults for a Jest project this.settings = { @@ -60,6 +63,34 @@ export default class Settings extends EventEmitter { }; this.configs = [this.settings]; + this._jsonPattern = new RegExp(/^[\s]*\{/gm); + } + + _parseConfig(text: string): void { + let settings = null; + + try { + settings = JSON.parse(text); + } catch (err) { + // skip the non-json content, if any + const idx = text.search(this._jsonPattern); + if (idx > 0) { + if (this.workspace.debug) { + console.log(`skip config output noise: ${text.substring(0, idx)}`); + } + this._parseConfig(text.substring(idx)); + return; + } + console.warn(`failed to parse config: \n${text}\nerror: ${err}`); + throw err; + } + this.jestVersionMajor = parseInt(settings.version.split('.').shift(), 10); + this.configs = + this.jestVersionMajor >= 21 ? settings.configs : [settings.config]; + + if (this.workspace.debug) { + console.log(`found config jestVersionMajor=${this.jestVersionMajor}`); + } } getConfigs(completed: any) { @@ -70,10 +101,7 @@ export default class Settings extends EventEmitter { ); this.getConfigProcess.stdout.on('data', (data: Buffer) => { - const settings = JSON.parse(data.toString()); - this.jestVersionMajor = parseInt(settings.version.split('.').shift(), 10); - this.configs = - this.jestVersionMajor >= 21 ? settings.configs : [settings.config]; + this._parseConfig(data.toString()); }); // They could have an older build of Jest which diff --git a/packages/jest-editor-support/src/__tests__/runner.test.js b/packages/jest-editor-support/src/__tests__/runner.test.js index ac6557dcc4c5..dcd0ac474fe6 100644 --- a/packages/jest-editor-support/src/__tests__/runner.test.js +++ b/packages/jest-editor-support/src/__tests__/runner.test.js @@ -345,20 +345,27 @@ describe('events', () => { runner.start(); }); - it('expects JSON from stdout, then it passes the JSON', () => { + it('expects JSON from both stdout and stderr, then it passes the JSON', () => { const data = jest.fn(); runner.on('executableJSON', data); runner.outputPath = `${fixtures}/failing-jsons/failing_jest_json.json`; - // Emitting data through stdout should trigger sending JSON - fakeProcess.stdout.emit('data', 'Test results written to file'); - expect(data).toBeCalled(); + const doTest = (out: stream$Readable) => { + data.mockClear(); - // And lets check what we emit - const dataAtPath = readFileSync(runner.outputPath); - const storedJSON = JSON.parse(dataAtPath.toString()); - expect(data.mock.calls[0][0]).toEqual(storedJSON); + // Emitting data through stdout should trigger sending JSON + out.emit('data', 'Test results written to file'); + expect(data).toBeCalled(); + + // And lets check what we emit + const dataAtPath = readFileSync(runner.outputPath); + const storedJSON = JSON.parse(dataAtPath.toString()); + expect(data.mock.calls[0][0]).toEqual(storedJSON); + }; + + doTest(fakeProcess.stdout); + doTest(fakeProcess.stderr); }); it('emits errors when process errors', () => { diff --git a/packages/jest-editor-support/src/__tests__/settings.test.js b/packages/jest-editor-support/src/__tests__/settings.test.js index f84e24c41161..9bcbae225f0a 100644 --- a/packages/jest-editor-support/src/__tests__/settings.test.js +++ b/packages/jest-editor-support/src/__tests__/settings.test.js @@ -21,7 +21,9 @@ describe('Settings', () => { 'test', 1000, ); - const options = {shell: true}; + const options = { + shell: true, + }; const settings = new Settings(workspace, options); expect(settings.workspace).toEqual(workspace); expect(settings.settings).toEqual(expect.any(Object)); @@ -36,7 +38,10 @@ describe('Settings', () => { 1000, ); const completed = jest.fn(); - const config = {cacheDirectory: '/tmp/jest', name: '[md5 hash]'}; + const config = { + cacheDirectory: '/tmp/jest', + name: '[md5 hash]', + }; const json = { config, version: '19.0.0', @@ -46,7 +51,9 @@ describe('Settings', () => { mockProcess.stdout = new EventEmitter(); const createProcess = () => mockProcess; const buffer = makeBuffer(JSON.stringify(json)); - const settings = new Settings(workspace, {createProcess}); + const settings = new Settings(workspace, { + createProcess, + }); settings.getConfig(completed); settings.getConfigProcess.stdout.emit('data', buffer); @@ -65,7 +72,12 @@ describe('Settings', () => { 1000, ); const completed = jest.fn(); - const configs = [{cacheDirectory: '/tmp/jest', name: '[md5 hash]'}]; + const configs = [ + { + cacheDirectory: '/tmp/jest', + name: '[md5 hash]', + }, + ]; const json = { configs, version: '21.0.0', @@ -75,7 +87,9 @@ describe('Settings', () => { mockProcess.stdout = new EventEmitter(); const createProcess = () => mockProcess; const buffer = makeBuffer(JSON.stringify(json)); - const settings = new Settings(workspace, {createProcess}); + const settings = new Settings(workspace, { + createProcess, + }); settings.getConfig(completed); settings.getConfigProcess.stdout.emit('data', buffer); @@ -94,7 +108,12 @@ describe('Settings', () => { 1000, ); const completed = jest.fn(); - const configs = [{cacheDirectory: '/tmp/jest', name: '[md5 hash]'}]; + const configs = [ + { + cacheDirectory: '/tmp/jest', + name: '[md5 hash]', + }, + ]; const json = { configs, version: '21.0.0', @@ -104,7 +123,9 @@ describe('Settings', () => { mockProcess.stdout = new EventEmitter(); const createProcess = () => mockProcess; const buffer = makeBuffer(JSON.stringify(json)); - const settings = new Settings(workspace, {createProcess}); + const settings = new Settings(workspace, { + createProcess, + }); settings.getConfigs(completed); settings.getConfigProcess.stdout.emit('data', buffer); @@ -127,7 +148,9 @@ describe('Settings', () => { const mockProcess: any = new EventEmitter(); mockProcess.stdout = new EventEmitter(); const createProcess = () => mockProcess; - const settings = new Settings(workspace, {createProcess}); + const settings = new Settings(workspace, { + createProcess, + }); settings.getConfig(completed); settings.getConfigProcess.emit('close'); @@ -167,8 +190,74 @@ describe('Settings', () => { rootPath, }, ['--showConfig'], - {shell: true}, + { + shell: true, + }, + ); + }); + + describe('parse config', () => { + const workspace = new ProjectWorkspace( + 'root_path', + 'path_to_jest', + 'test', + 1000, ); + const createProcess = jest.fn(); + + const json = `{ + "version": "23.2.0", + "configs": [{ + "testRegex": "some-regex" + }] + }`; + const run_test = ( + text: string, + expected_version: number = 23, + expected_regex: string = 'some-regex', + ): void => { + settings._parseConfig(text); + const target = settings.configs[0]; + expect(settings.jestVersionMajor).toBe(expected_version); + expect(target.testRegex).toBe(expected_regex); + }; + + let settings; + beforeEach(() => { + settings = new Settings(workspace, { + createProcess, + }); + }); + + it('test regex', () => { + const regex = settings._jsonPattern; + + let text = ` > abc {} + { abc } + `; + let index = text.search(regex); + expect(index).not.toBe(-1); + expect(text.substring(index).trim()).toBe('{ abc }'); + + text = `{def: + {sub} + }`; + index = text.search(regex); + expect(index).not.toBe(-1); + expect(text.substring(index).startsWith('{def:')).toBe(true); + }); + it('can parse correct config', () => { + run_test(json); + }); + + it('can parse config even with noise', () => { + const with_noise = ` + > something + > more noise + ${json} + `; + run_test(with_noise); + }); }); }); diff --git a/packages/jest-editor-support/src/project_workspace.js b/packages/jest-editor-support/src/project_workspace.js index 10c5540f5c6f..a092ea6a668e 100644 --- a/packages/jest-editor-support/src/project_workspace.js +++ b/packages/jest-editor-support/src/project_workspace.js @@ -53,17 +53,26 @@ export default class ProjectWorkspace { */ collectCoverage: ?boolean; + /** + * if to output more information for debugging purpose. Default is false. + * + * @type {boolean} + */ + debug: ?boolean; + constructor( rootPath: string, pathToJest: string, pathToConfig: string, localJestMajorVersion: number, collectCoverage: ?boolean, + debug: ?boolean, ) { this.rootPath = rootPath; this.pathToJest = pathToJest; this.pathToConfig = pathToConfig; this.localJestMajorVersion = localJestMajorVersion; this.collectCoverage = collectCoverage; + this.debug = debug; } } diff --git a/packages/jest-editor-support/src/types.js b/packages/jest-editor-support/src/types.js index 74de73414479..fdabbdb65c0b 100644 --- a/packages/jest-editor-support/src/types.js +++ b/packages/jest-editor-support/src/types.js @@ -72,6 +72,7 @@ export type JestTotalResultsMeta = { export const messageTypes = { noTests: 1, + testResults: 3, unknown: 0, watchUsage: 2, };