Skip to content

Commit

Permalink
Cleanups for jest-editor-support (jestjs#2308)
Browse files Browse the repository at this point in the history
* Cleanups for jest-editor-support

* Disable a test on windows.
  • Loading branch information
cpojer authored Dec 13, 2016
1 parent b68c615 commit 8363dd4
Show file tree
Hide file tree
Showing 13 changed files with 255 additions and 132 deletions.
3 changes: 3 additions & 0 deletions integration_tests/__tests__/test-in-root-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

const path = require('path');
const runJest = require('../runJest');
const skipOnWindows = require('skipOnWindows');

skipOnWindows.suite();

it('runs tests in only test.js and spec.js', () => {
const result = runJest.json('test-in-root').json;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,32 +1,39 @@
// @flow
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/

'use strict';

const {ChildProcess, spawn} = require('child_process');
const ProjectWorkspace = require('./ProjectWorkspace');

/**
* Spawns and returns a Jest process with specific args
*
* Spawns and returns a Jest process with specific args
*
* @param {string[]} args
* @returns {ChildProcess}
*/
module.exports.jestChildProcessWithArgs = (
workspace: ProjectWorkspace, args: string[],
): ChildProcess => {
workspace: ProjectWorkspace, args: Array<string>,
): ChildProcess => {

// A command could look like `npm run test`, which we cannot use as a command
// as they can only be the first command, so take out the command, and add
// any other bits into the args
const runtimeExecutable = workspace.pathToJest;
const [command, ...initialArgs] = runtimeExecutable.split(' ');
const runtimeArgs = [...initialArgs, ...args];
// To use our own commands in create-react, we need to tell the command that

// To use our own commands in create-react, we need to tell the command that
// we're in a CI environment, or it will always append --watch
const env = process.env;
env['CI'] = 'true';

return spawn(command, runtimeArgs, {cwd: workspace.rootPath, env});
};


11 changes: 10 additions & 1 deletion packages/jest-editor-support/src/ProjectWorkspace.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
// @flow
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/

'use strict';

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,57 @@
// @flow
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/

'use strict';

const {ChildProcess} = require('child_process');
const {readFile} = require('fs');
const {tmpdir} = require('os');
const {EventEmitter} = require('events');
const ProjectWorkspace = require('./ProjectWorkspace');
const {jestChildProcessWithArgs} = require('./JestProcess');
const {jestChildProcessWithArgs} = require('./Process');

// This class represents the running process, and
// passes out events when it understands what data is being
// pass sent out of the process

module.exports = class JestRunner extends EventEmitter {
module.exports = class Runner extends EventEmitter {
debugprocess: ChildProcess;
workspace: ProjectWorkspace;
jsonFilePath: string;
outputPath: string;

constructor(workspace: ProjectWorkspace) {
super();
this.workspace = workspace;
this.jsonFilePath = tmpdir() + '/vscode-jest_runner.json';
this.outputPath = tmpdir() + '/jest_runner.json';
}

start() {
const args = [
'--json',
'--useStderr',
'--watch',
'--outputFile',
this.jsonFilePath,
'--json',
'--useStderr',
'--watch',
'--outputFile',
this.outputPath,
];

this.debugprocess = jestChildProcessWithArgs(this.workspace, args);
this.debugprocess.stdout.on('data', (data: Buffer) => {
// Make jest save to a file, otherwise we get chunked data
// 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().replace(/\n$/, '').trim();
if (stringValue.startsWith('Test results written to')) {
readFile(this.jsonFilePath, 'utf8', (err, data) => {
readFile(this.outputPath, 'utf8', (err, data) => {
if (err) {
const message = `JSON report not found at ${this.jsonFilePath}`;
this.emit('terminalError', message);
} else {
this.emit('executableJSON', JSON.parse(data));
const message = `JSON report not found at ${this.outputPath}`;
this.emit('terminalError', message);
} else {
this.emit('executableJSON', JSON.parse(data));
}
});
} else {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
// @flow
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/

'use strict';

import {ChildProcess} from 'child_process';
import EventEmitter from 'events';
import {EOL} from 'os';
import ProjectWorkspace from './ProjectWorkspace';
import {jestChildProcessWithArgs} from './JestProcess';
import {jestChildProcessWithArgs} from './Process';

// This class represents the the configuration of Jest's process
// we want to start with the defaults then override whatever they output
Expand All @@ -17,15 +26,15 @@ import {jestChildProcessWithArgs} from './JestProcess';
// for full deets

// For now, this is all we care about inside the config
export type JestConfigRepresentation = {
type ConfigRepresentation = {
testRegex: string,
}

module.exports = class JestSettings extends EventEmitter {
module.exports = class Settings extends EventEmitter {
debugprocess: ChildProcess;
workspace: ProjectWorkspace;

settings: JestConfigRepresentation;
settings: ConfigRepresentation;
jestVersionMajor: number | null;

constructor(workspace: ProjectWorkspace) {
Expand Down
98 changes: 60 additions & 38 deletions packages/jest-editor-support/src/TestReconciler.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
// @flow
/**
* Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @flow
*/

'use strict';

const path = require('path');

import type {
JestTotalResults,
JestAssertionResults,
JestTotalResults,
JestAssertionResults,
TestFileAssertionStatus,
TestAssertionStatus,
TestReconcilationState,
Expand All @@ -14,18 +23,18 @@ import type {
/**
* You have a Jest test runner watching for changes, and you have
* an extension that wants to know where to show errors after file parsing.
*
*
* This class represents the state between runs, keeping track of passes/fails
* at a file level, generating useful error messages and providing a nice API.
* at a file level, generating useful error messages and providing a nice API.
*/

module.exports = class TestReconciler {
fileStatuses: any;
fails: TestFileAssertionStatus[];
passes: TestFileAssertionStatus[];
fails: Array<TestFileAssertionStatus>;
passes: Array<TestFileAssertionStatus>;

constructor() {
this.fileStatuses = {};
this.fileStatuses = {};
}

updateFileWithJestStatus(results: JestTotalResults) {
Expand All @@ -36,42 +45,43 @@ module.exports = class TestReconciler {
results.testResults.forEach(file => {
// Did the file pass/fail?
const status = this.statusToReconcilationState(file.status);
// Create our own simpler representation
// Create our own simpler representation
const fileStatus: TestFileAssertionStatus = {
assertions: this.mapAssertions(file.name, file.assertionResults),
file: file.name,
message: file.message,
status,
};
this.fileStatuses[file.name] = fileStatus;
this.fileStatuses[file.name] = fileStatus;

if (status === 'KnownFail') {
this.fails.push(fileStatus);
} else if (status === 'KnownSuccess') {
} else if (status === 'KnownSuccess') {
this.passes.push(fileStatus);
}
});
}

failedStatuses(): TestFileAssertionStatus[] {
failedStatuses(): Array<TestFileAssertionStatus> {
return this.fails || [];
}

passedStatuses(): TestFileAssertionStatus[] {
passedStatuses(): Array<TestFileAssertionStatus> {
return this.passes || [];
}

// A failed test also contains the stack trace for an `expect`
// we don't get this as structured data, but what we get
// we don't get this as structured data, but what we get
// is useful enough to make it for ourselves

mapAssertions(
filename:string, assertions: JestAssertionResults[],
): TestAssertionStatus[] {
filename:string,
assertions: Array<JestAssertionResults>,
): Array<TestAssertionStatus> {
// Is it jest < 17? e.g. Before I added this to the JSON
if (!assertions) { return []; }
// Change all failing assertions into structured data

// Change all failing assertions into structured data
return assertions.map(assertion => {
// Failure messages seems to always be an array of one item
const message = assertion.failureMessages[0];
Expand All @@ -81,7 +91,7 @@ module.exports = class TestReconciler {
if (message) {
// Just the first line, with little whitespace
short = message.split(' at', 1)[0].trim();
// this will show inline, so we want to show very little
// this will show inline, so we want to show very little
terse = this.sanitizeShortErrorMessage(short);
line = this.lineOfError(message, filename);
}
Expand All @@ -96,15 +106,15 @@ module.exports = class TestReconciler {
});
}

// Do everything we can to try make a one-liner from the error report
// Do everything we can to try make a one-liner from the error report
sanitizeShortErrorMessage(string: string): string {
return string.split('\n')
.splice(2)
.join('')
.replace(' ', ' ')
.replace(/\[\d\dm/g, '')
.replace('Received:', ' Received:')
.replace('Difference:', ' Difference:');
.splice(2)
.join('')
.replace(' ', ' ')
.replace(/\[\d\dm/g, '')
.replace('Received:', ' Received:')
.replace('Difference:', ' Difference:');
}

// Pull the line out from the stack trace
Expand All @@ -116,23 +126,35 @@ module.exports = class TestReconciler {

statusToReconcilationState(status: string): TestReconcilationState {
switch (status) {
case 'passed': return 'KnownSuccess';
case 'failed': return 'KnownFail';
default: return 'Unknown';
case 'passed':
return 'KnownSuccess';
case 'failed':
return 'KnownFail';
default:
return 'Unknown';
}
}

stateForTestFile(file:string): TestReconcilationState {
const results: TestFileAssertionStatus = this.fileStatuses[file];
if (!results) { return 'Unknown'; }
return results.status;
stateForTestFile(file: string): TestReconcilationState {
const results = this.fileStatuses[file];
if (!results) {
return 'Unknown';
}
return results.status;
}

stateForTestAssertion(file:string, name:string): TestAssertionStatus | null {
const results: TestFileAssertionStatus = this.fileStatuses[file];
if (!results) { return null; }
stateForTestAssertion(
file: string,
name: string,
): TestAssertionStatus | null {
const results = this.fileStatuses[file];
if (!results) {
return null;
}
const assertion = results.assertions.find(a => a.title === name);
if (!assertion) { return null; }
return assertion;
if (!assertion) {
return null;
}
return assertion;
}
};
Loading

0 comments on commit 8363dd4

Please sign in to comment.