Skip to content

Commit

Permalink
Optional jasmine 2 support
Browse files Browse the repository at this point in the history
Summary: Good day.

This patch adds optional `jasmine 2.x` support to `jest`.

Benefits:

* bundled `jasmine` is not modified, it can be updated any time
* `done` callback support
* whatever you need from `jasmine-2.x`

TODOs:

- [ ] make jeffmo/jasmine-pit#12 work, so I can remove dependency on my fork of `jasmine-pit`
- [x] extract base code for both runners
- [x] update matchers as I haven't tested them after update
- [x] fix all TODO's
- [x] add note about `jasmine 1.x` and `jasmine 2.x` differences

Patches are welcome.
Closes #330

Reviewed By: zpao

Differential Revision: D2662964

fb-gh-sync-id: 13e3b9116a350e46a0271f6ceb26cf2af316f20e
  • Loading branch information
tomv564 authored and facebook-github-bot-2 committed Nov 18, 2015
1 parent 280560f commit 9c60191
Show file tree
Hide file tree
Showing 18 changed files with 3,988 additions and 154 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -65,6 +65,7 @@ Check out the [Getting Started](http://facebook.github.io/jest/docs/getting-star
- [`config.testPathDirs` [array<string>]](http://facebook.github.io/jest/docs/api.html#config-testpathdirs-array-string)
- [`config.testPathIgnorePatterns` [array<string>]](http://facebook.github.io/jest/docs/api.html#config-testpathignorepatterns-array-string)
- [`config.testPathPattern` [string]](http://facebook.github.io/jest/docs/api.html#config-testpathpattern-string)
- [`config.testRunner` [string]](http://facebook.github.io/jest/docs/api.html#config-testrunner-string)
- [`config.unmockedModulePathPatterns` [array<string>]](http://facebook.github.io/jest/docs/api.html#config-unmockedmodulepathpatterns-array-string)
- [`config.verbose` [boolean]](http://facebook.github.io/jest/docs/api.html#config-verbose-boolean)

Expand Down
6 changes: 6 additions & 0 deletions docs/API.md
Expand Up @@ -55,6 +55,7 @@ permalink: docs/api.html
- [`config.testPathDirs` [array<string>]](#config-testpathdirs-array-string)
- [`config.testPathIgnorePatterns` [array<string>]](#config-testpathignorepatterns-array-string)
- [`config.testPathPattern` [string]](http://facebook.github.io/jest/docs/api.html#config-testpathpattern-string)
- [`config.testRunner` [string]](http://facebook.github.io/jest/docs/api.html#config-testrunner-string)
- [`config.unmockedModulePathPatterns` [array<string>]](#config-unmockedmodulepathpatterns-array-string)
- [`config.verbose` [boolean]](#config-verbose-boolean)

Expand Down Expand Up @@ -414,6 +415,11 @@ A regexp pattern string that is matched against all test paths before executing

This is useful if you need to override the default. If you are testing one file at a time the default will be set to `/.*/`, however if you pass a blob rather than a single file the default will then be the absolute path of each test file. The override may be needed on windows machines where, for example, the test full path would be `C:/myproject/__tests__/mystest.jsx.jest` and the default pattern would be set as `/C:\myproject\__tests__\mystest.jsx.jest/`.

### `config.testRunner` [string]
(default: `./jasmineTestRunner/jasmineTestRunner`)

Allows overriding the default jasmine test runner with the bundled jasmine 2.2 test runner: `./jasmineTestRunner/jasmine2TestRunner`.

### `config.unmockedModulePathPatterns` [array<string>]
(default: `[]`)

Expand Down
1 change: 0 additions & 1 deletion package.json
Expand Up @@ -10,7 +10,6 @@
"istanbul": "^0.3.19",
"jsdom": "7.0.2",
"json-stable-stringify": "^1.0.0",
"lodash.template": "^3.6.2",
"mkdirp": "^0.5.1",
"node-haste": "^1.2.8",
"object-assign": "^4.0.1",
Expand Down
2 changes: 1 addition & 1 deletion src/DefaultTestReporter.js
Expand Up @@ -49,7 +49,7 @@ class DefaultTestReporter {
? (testResult.perfStats.end - testResult.perfStats.start) / 1000
: null;

let testDetail = [];
const testDetail = [];
if (testRunTime !== null) {
testDetail.push(
testRunTime > 2.5
Expand Down
Expand Up @@ -67,7 +67,7 @@ describe('HasteModuleLoader', function() {
return buildLoader().then(function(loader) {
expect(function() {
loader.requireModule(null, 'DoesntExist');
}).toThrow('Cannot find module \'DoesntExist\' from \'.\'');
}).toThrow(new Error('Cannot find module \'DoesntExist\' from \'.\''));
});
});

Expand Down Expand Up @@ -95,9 +95,9 @@ describe('HasteModuleLoader', function() {
return buildLoader().then(function(loader) {
expect(function() {
loader.requireModule(__filename, './DoesntExist');
}).toThrow(
}).toThrow(new Error(
'Cannot find module \'./DoesntExist\' from \'' + __filename + '\''
);
));
});
});

Expand Down
87 changes: 87 additions & 0 deletions src/jasmineTestRunner/Jasmine2Reporter.js
@@ -0,0 +1,87 @@
/**
* Copyright (c) 2014, 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.
*/
'use strict';

var jasmineRequire = require('../../vendor/jasmine/jasmine-2.3.4.js');
var jasmine = jasmineRequire.core(jasmineRequire);
var JasmineFormatter = require('./jasmineFormatter');

function Jasmine2Reporter(config) {
this._config = config || {};
this._formatter = new JasmineFormatter(jasmine, config);
this._resultsDeferred = Promise.defer();
this._testResults = [];
this._currentSuites = [];
}

Jasmine2Reporter.prototype.specDone = function(result) {
this._testResults.push(this._extractSpecResults(result,
this._currentSuites.slice(0)));
};

Jasmine2Reporter.prototype.suiteStarted = function(suite) {
this._currentSuites.push(suite.description);
};

Jasmine2Reporter.prototype.suiteDone = function() {
this._currentSuites.pop();
};

Jasmine2Reporter.prototype.jasmineDone = function() {
var numFailingTests = 0;
var numPassingTests = 0;

this._testResults.forEach(function(testResult) {
if (testResult.failureMessages.length > 0) {
numFailingTests++;
} else {
numPassingTests++;
}
});

this._resultsDeferred.resolve({
numFailingTests: numFailingTests,
numPassingTests: numPassingTests,
testResults: this._testResults,
});
};

Jasmine2Reporter.prototype.getResults = function() {
return this._resultsDeferred.promise;
};

Jasmine2Reporter.prototype._extractSpecResults =
function(specResult, currentSuites) {
var results = {
title: 'it ' + specResult.description,
ancestorTitles: currentSuites,
failureMessages: [],
logMessages: [], // Jasmine 2 does not have a logging interface
numPassingAsserts: 0, // Jasmine 2 only returns an array of failed asserts.
};

specResult.failedExpectations.forEach(failed => {
if (!failed.matcherName) { // exception

results.failureMessages.push(
this._formatter.formatException(failed.stack)
);

} else { // match failure

results.failureMessages.push(
this._formatter.formatMatchFailure(failed)
);

}
});

return results;
};

module.exports = Jasmine2Reporter;
151 changes: 13 additions & 138 deletions src/jasmineTestRunner/JasmineReporter.js
Expand Up @@ -7,22 +7,12 @@
*/
'use strict';

var colors = require('../lib/colors');
var diff = require('diff');
var formatMsg = require('../lib/utils').formatMsg;
var jasmine = require('../../vendor/jasmine/jasmine-1.3.0').jasmine;

var ERROR_TITLE_COLOR = colors.RED + colors.BOLD + colors.UNDERLINE;
var DIFFABLE_MATCHERS = {
toBe: true,
toNotBe: true,
toEqual: true,
toNotEqual: true,
};
var LINEBREAK_REGEX = /[\r\n]/;
var JasmineFormatter = require('./jasmineFormatter');

function JasmineReporter(config) {
jasmine.Reporter.call(this);
this._formatter = new JasmineFormatter(jasmine, config);
this._config = config || {};
this._logs = [];
this._resultsPromise = new Promise(resolve => this._resolve = resolve);
Expand Down Expand Up @@ -93,57 +83,22 @@ function(container, ancestorTitles, spec) {
switch (result.type) {
case 'expect':
if (result.passed()) {

results.numPassingAsserts++;

// Exception thrown
} else if (!result.matcherName && result.trace.stack) {
// jasmine doesn't give us access to the actual Error object, so we
// have to regexp out the message from the stack string in order to
// colorize the `message` value
result.trace.stack = result.trace.stack.replace(
/(^(.|\n)*?(?=\n\s*at\s))/,
this._formatMsg('$1', ERROR_TITLE_COLOR)
);

result.trace.stack = this._config.noStackTrace
? result.trace.stack.split('\n').slice(0, 2).join('\n')
: result.trace.stack;

results.failureMessages.push(result.trace.stack);

results.failureMessages.push(
this._formatter.formatException(result.trace.stack)
);

// Matcher failed
} else {
var message;
if (DIFFABLE_MATCHERS[result.matcherName]) {
var ppActual = this._prettyPrint(result.actual);
var ppExpected = this._prettyPrint(result.expected);
var colorDiff = this._highlightDifferences(ppActual, ppExpected);

var matcherName = (result.isNot ? 'NOT ' : '') + result.matcherName;

message =
this._formatMsg('Expected:', ERROR_TITLE_COLOR) +
' ' + colorDiff.a +
' ' + this._formatMsg(matcherName + ':', ERROR_TITLE_COLOR) +
' ' + colorDiff.b;
} else {
message = this._formatMsg(result.message, ERROR_TITLE_COLOR);
}

if (result.trace.stack) {
// Replace the error message with a colorized version of the error
message = result.trace.stack.replace(result.trace.message, message);

// Remove the 'Error: ' prefix from the stack trace
message = message.replace(/^.*Error:\s*/, '');

// Remove jasmine jonx from the stack trace
message = message.split('\n').filter(function(line) {
return !/vendor\/jasmine\//.test(line);
});
message = this._config.noStackTrace ? message.slice(0, 2) : message;
message = message.join('\n');
}

results.failureMessages.push(message);

results.failureMessages.push(
this._formatter.formatMatchFailure(result)
);
}
break;
default:
Expand All @@ -156,84 +111,4 @@ function(container, ancestorTitles, spec) {
container.push(results);
};

JasmineReporter.prototype._highlightDifferences = function(a, b) {
var differ;
if (a.match(LINEBREAK_REGEX) || b.match(LINEBREAK_REGEX)) {
// `diff` uses the Myers LCS diff algorithm which runs in O(n+d^2) time
// (where "d" is the edit distance) and can get very slow for large edit
// distances. Mitigate the cost by switching to a lower-resolution diff
// whenever linebreaks are involved.
differ = diff.diffLines;
} else {
differ = diff.diffChars;
}
var changes = differ(a, b);
var ret = {a: '', b: ''};
var change;
for (var i = 0, il = changes.length; i < il; i++) {
change = changes[i];
if (change.added) {
ret.b += this._formatMsg(change.value, colors.RED_BG);
} else if (change.removed) {
ret.a += this._formatMsg(change.value, colors.RED_BG);
} else {
ret.a += change.value;
ret.b += change.value;
}
}
return ret;
};

JasmineReporter.prototype._prettyPrint = function(obj, indent, cycleWeakMap) {
if (!indent) {
indent = '';
}

if (typeof obj === 'object' && obj !== null) {
if (jasmine.isDomNode(obj)) {
var attrStr = '';
Array.prototype.forEach.call(obj.attributes, function(attr) {
var attrName = attr.nodeName.trim();
var attrValue = attr.nodeValue.trim();
attrStr += ' ' + attrName + '="' + attrValue + '"';
});
return 'HTMLNode(' +
'<' + obj.tagName + attrStr + '>[...]</' + obj.tagName + '>' +
')';
}

if (!cycleWeakMap) {
cycleWeakMap = new WeakMap();
}

if (cycleWeakMap.get(obj) === true) {
return '<circular reference>';
}
cycleWeakMap.set(obj, true);

var orderedKeys = Object.keys(obj).sort();
var value;
var keysOutput = [];
var keyIndent = this._formatMsg('|', colors.GRAY) + ' ';
for (var i = 0; i < orderedKeys.length; i++) {
if (orderedKeys[i] === '__jstest_pp_cycle__') {
continue;
}
value = obj[orderedKeys[i]];
keysOutput.push(
indent + keyIndent + orderedKeys[i] + ': ' +
this._prettyPrint(value, indent + keyIndent, cycleWeakMap)
);
}
delete obj.__jstest_pp_cycle__;
return '{\n' + keysOutput.join(',\n') + '\n' + indent + '}';
} else {
return jasmine.pp(obj);
}
};

JasmineReporter.prototype._formatMsg = function(msg, color) {
return formatMsg(msg, color, this._config);
};

module.exports = JasmineReporter;

0 comments on commit 9c60191

Please sign in to comment.