Allow user to stop a specs execution when an expectation fails

[finish #1165916] #577
This commit is contained in:
Gregg Van Hove and Molly Trombley-McCann
2015-03-05 15:28:00 -08:00
parent 1a08d1e8c6
commit 7693a4c959
16 changed files with 461 additions and 47 deletions

View File

@@ -74,6 +74,9 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
var catchingExceptions = queryString.getParam("catch"); var catchingExceptions = queryString.getParam("catch");
env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions);
var throwingExpectationFailures = queryString.getParam("throwFailures");
env.throwOnExpectationFailure(throwingExpectationFailures);
/** /**
* ## Reporters * ## Reporters
* The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
@@ -81,6 +84,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
var htmlReporter = new jasmine.HtmlReporter({ var htmlReporter = new jasmine.HtmlReporter({
env: env, env: env,
onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); }, onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); },
onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); },
addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); },
getContainer: function() { return document.body; }, getContainer: function() { return document.body; },
createElement: function() { return document.createElement.apply(document, arguments); }, createElement: function() { return document.createElement.apply(document, arguments); },

View File

@@ -52,6 +52,9 @@
var catchingExceptions = queryString.getParam("catch"); var catchingExceptions = queryString.getParam("catch");
env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions);
var throwingExpectationFailures = queryString.getParam("throwFailures");
env.throwOnExpectationFailure(throwingExpectationFailures);
/** /**
* ## Reporters * ## Reporters
* The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any). * The `HtmlReporter` builds all of the HTML UI for the runner page. This reporter paints the dots, stars, and x's for specs, as well as all spec names and all failures (if any).
@@ -59,6 +62,7 @@
var htmlReporter = new jasmine.HtmlReporter({ var htmlReporter = new jasmine.HtmlReporter({
env: env, env: env,
onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); }, onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); },
onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); },
addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); },
getContainer: function() { return document.body; }, getContainer: function() { return document.body; },
createElement: function() { return document.createElement.apply(document, arguments); }, createElement: function() { return document.createElement.apply(document, arguments); },

View File

@@ -40,6 +40,7 @@ jasmineRequire.HtmlReporter = function(j$) {
createElement = options.createElement, createElement = options.createElement,
createTextNode = options.createTextNode, createTextNode = options.createTextNode,
onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
onThrowExpectationsClick = options.onThrowExpectationsClick || function() {},
addToExistingQueryString = options.addToExistingQueryString || defaultQueryString, addToExistingQueryString = options.addToExistingQueryString || defaultQueryString,
timer = options.timer || noopTimer, timer = options.timer || noopTimer,
results = [], results = [],
@@ -145,22 +146,51 @@ jasmineRequire.HtmlReporter = function(j$) {
this.jasmineDone = function() { this.jasmineDone = function() {
var banner = find('.banner'); var banner = find('.banner');
banner.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
var alert = find('.alert'); var alert = find('.alert');
alert.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
alert.appendChild(createDom('span', { className: 'exceptions' }, banner.appendChild(
createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions'), createDom('div', { className: 'run-options' },
createDom('input', { createDom('span', { className: 'trigger' }, 'Options'),
className: 'raise', createDom('div', { className: 'payload' },
id: 'raise-exceptions', createDom('div', { className: 'exceptions' },
type: 'checkbox' createDom('input', {
}) className: 'raise',
)); id: 'raise-exceptions',
var checkbox = find('#raise-exceptions'); type: 'checkbox'
}),
createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions')),
createDom('div', { className: 'throw-failures' },
createDom('input', {
className: 'throw',
id: 'throw-failures',
type: 'checkbox'
}),
createDom('label', { className: 'label', 'for': 'throw-failures' }, 'stop spec on expectation failure'))
)
));
checkbox.checked = !env.catchingExceptions(); var raiseCheckbox = find('#raise-exceptions');
checkbox.onclick = onRaiseExceptionsClick;
raiseCheckbox.checked = !env.catchingExceptions();
raiseCheckbox.onclick = onRaiseExceptionsClick;
var throwCheckbox = find('#throw-failures');
throwCheckbox.checked = env.throwingExpectationFailures();
throwCheckbox.onclick = onThrowExpectationsClick;
var optionsMenu = find('.run-options'),
optionsTrigger = optionsMenu.querySelector('.trigger'),
optionsPayload = optionsMenu.querySelector('.payload'),
isOpen = /\bopen\b/;
optionsTrigger.onclick = function() {
if (isOpen.test(optionsPayload.className)) {
optionsPayload.className = optionsPayload.className.replace(isOpen, '');
} else {
optionsPayload.className += ' open';
}
};
if (specsExecuted < totalSpecsDefined) { if (specsExecuted < totalSpecsDefined) {
var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all'; var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';

File diff suppressed because one or more lines are too long

View File

@@ -42,6 +42,7 @@ var getJasmineRequireObj = (function (jasmineGlobal) {
jRequire.base(j$, jasmineGlobal); jRequire.base(j$, jasmineGlobal);
j$.util = jRequire.util(); j$.util = jRequire.util();
j$.errors = jRequire.errors();
j$.Any = jRequire.Any(); j$.Any = jRequire.Any();
j$.Anything = jRequire.Anything(j$); j$.Anything = jRequire.Anything(j$);
j$.CallTracker = jRequire.CallTracker(); j$.CallTracker = jRequire.CallTracker();
@@ -303,6 +304,7 @@ getJasmineRequireObj().Spec = function(j$) {
this.expectationResultFactory = attrs.expectationResultFactory || function() { }; this.expectationResultFactory = attrs.expectationResultFactory || function() { };
this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; this.catchingExceptions = attrs.catchingExceptions || function() { return true; };
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
if (!this.queueableFn.fn) { if (!this.queueableFn.fn) {
this.pend(); this.pend();
@@ -324,6 +326,10 @@ getJasmineRequireObj().Spec = function(j$) {
this.result.passedExpectations.push(expectationResult); this.result.passedExpectations.push(expectationResult);
} else { } else {
this.result.failedExpectations.push(expectationResult); this.result.failedExpectations.push(expectationResult);
if(this.throwOnExpectationFailure){
throw new j$.errors.ExpectationFailed();
}
} }
}; };
@@ -367,6 +373,10 @@ getJasmineRequireObj().Spec = function(j$) {
return; return;
} }
if (e instanceof j$.errors.ExpectationFailed) {
return;
}
this.addExpectationResult(false, { this.addExpectationResult(false, {
matcherName: '', matcherName: '',
passed: false, passed: false,
@@ -458,6 +468,7 @@ getJasmineRequireObj().Env = function(j$) {
var currentSpec = null; var currentSpec = null;
var currentlyExecutingSuites = []; var currentlyExecutingSuites = [];
var currentDeclarationSuite = null; var currentDeclarationSuite = null;
var throwOnExpectationFailure = false;
var currentSuite = function() { var currentSuite = function() {
return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
@@ -599,6 +610,14 @@ getJasmineRequireObj().Env = function(j$) {
return j$.Spec.isPendingSpecException(e) || catchExceptions; return j$.Spec.isPendingSpecException(e) || catchExceptions;
}; };
this.throwOnExpectationFailure = function(value) {
throwOnExpectationFailure = !!value;
};
this.throwingExpectationFailures = function() {
return throwOnExpectationFailure;
};
var queueRunnerFactory = function(options) { var queueRunnerFactory = function(options) {
options.catchException = catchException; options.catchException = catchException;
options.clearStack = options.clearStack || clearStack; options.clearStack = options.clearStack || clearStack;
@@ -681,7 +700,8 @@ getJasmineRequireObj().Env = function(j$) {
description: description, description: description,
parentSuite: currentDeclarationSuite, parentSuite: currentDeclarationSuite,
expectationFactory: expectationFactory, expectationFactory: expectationFactory,
expectationResultFactory: expectationResultFactory expectationResultFactory: expectationResultFactory,
throwOnExpectationFailure: throwOnExpectationFailure
}); });
runnableLookupTable[suite.id] = suite; runnableLookupTable[suite.id] = suite;
@@ -775,7 +795,8 @@ getJasmineRequireObj().Env = function(j$) {
queueableFn: { queueableFn: {
fn: fn, fn: fn,
timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
} },
throwOnExpectationFailure: throwOnExpectationFailure
}); });
runnableLookupTable[spec.id] = spec; runnableLookupTable[spec.id] = spec;
@@ -1050,6 +1071,15 @@ getJasmineRequireObj().Clock = function() {
installed = false; installed = false;
}; };
self.withMock = function(closure) {
this.install();
try {
closure();
} finally {
this.uninstall();
}
};
self.mockDate = function(initialDate) { self.mockDate = function(initialDate) {
mockDate.install(initialDate); mockDate.install(initialDate);
}; };
@@ -1959,6 +1989,7 @@ getJasmineRequireObj().Suite = function() {
this.description = attrs.description; this.description = attrs.description;
this.expectationFactory = attrs.expectationFactory; this.expectationFactory = attrs.expectationFactory;
this.expectationResultFactory = attrs.expectationResultFactory; this.expectationResultFactory = attrs.expectationResultFactory;
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
this.beforeFns = []; this.beforeFns = [];
this.afterFns = []; this.afterFns = [];
@@ -2052,6 +2083,10 @@ getJasmineRequireObj().Suite = function() {
}; };
Suite.prototype.onException = function() { Suite.prototype.onException = function() {
if (arguments[0] instanceof j$.errors.ExpectationFailed) {
return;
}
if(isAfterAll(this.children)) { if(isAfterAll(this.children)) {
var data = { var data = {
matcherName: '', matcherName: '',
@@ -2073,10 +2108,17 @@ getJasmineRequireObj().Suite = function() {
if(isAfterAll(this.children) && isFailure(arguments)){ if(isAfterAll(this.children) && isFailure(arguments)){
var data = arguments[1]; var data = arguments[1];
this.result.failedExpectations.push(this.expectationResultFactory(data)); this.result.failedExpectations.push(this.expectationResultFactory(data));
if(this.throwOnExpectationFailure) {
throw new j$.errors.ExpectationFailed();
}
} else { } else {
for (var i = 0; i < this.children.length; i++) { for (var i = 0; i < this.children.length; i++) {
var child = this.children[i]; var child = this.children[i];
child.addExpectationResult.apply(child, arguments); try {
child.addExpectationResult.apply(child, arguments);
} catch(e) {
// keep going
}
} }
} }
}; };
@@ -2483,6 +2525,16 @@ getJasmineRequireObj().StringMatching = function(j$) {
return StringMatching; return StringMatching;
}; };
getJasmineRequireObj().errors = function() {
function ExpectationFailed() {}
ExpectationFailed.prototype = new Error();
ExpectationFailed.prototype.constructor = ExpectationFailed;
return {
ExpectationFailed: ExpectationFailed
};
};
getJasmineRequireObj().matchersUtil = function(j$) { getJasmineRequireObj().matchersUtil = function(j$) {
// TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter? // TODO: what to do about jasmine.pp not being inject? move to JSON.stringify? gut PrettyPrinter?

View File

@@ -25,4 +25,24 @@ describe("Env", function() {
expect(suite.description).toEqual('Jasmine__TopLevel__Suite'); expect(suite.description).toEqual('Jasmine__TopLevel__Suite');
}); });
}); });
it('can configure specs to throw errors on expectation failures', function() {
env.throwOnExpectationFailure(true);
spyOn(j$, 'Spec');
env.it('foo', function() {});
expect(j$.Spec).toHaveBeenCalledWith(jasmine.objectContaining({
throwOnExpectationFailure: true
}));
});
it('can configure suites to throw errors on expectation failures', function() {
env.throwOnExpectationFailure(true);
spyOn(j$, 'Suite');
env.describe('foo', function() {});
expect(j$.Suite).toHaveBeenCalledWith(jasmine.objectContaining({
throwOnExpectationFailure: true
}));
});
}); });

View File

@@ -255,6 +255,27 @@ describe("Spec", function() {
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual(['expectation2']); expect(resultCallback.calls.first().args[0].failedExpectations).toEqual(['expectation2']);
}); });
it("throws an ExpectationFailed error upon receiving a failed expectation when 'throwOnExpectationFailure' is set", function() {
var resultCallback = jasmine.createSpy('resultCallback'),
spec = new j$.Spec({
queueableFn: { fn: function() {} },
expectationResultFactory: function(data) { return data; },
queueRunnerFactory: function(attrs) { attrs.onComplete(); },
resultCallback: resultCallback,
throwOnExpectationFailure: true
});
spec.addExpectationResult(true, 'passed');
expect(function() {
spec.addExpectationResult(false, 'failed')
}).toThrowError(j$.errors.ExpectationFailed);
spec.execute();
expect(resultCallback.calls.first().args[0].passedExpectations).toEqual(['passed']);
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual(['failed']);
});
it("can return its full name", function() { it("can return its full name", function() {
var specNameSpy = jasmine.createSpy('specNameSpy').and.returnValue('expected val'); var specNameSpy = jasmine.createSpy('specNameSpy').and.returnValue('expected val');
@@ -303,6 +324,42 @@ describe("Spec", function() {
}); });
}); });
it("should log a failure when handling an exception", function() {
var resultCallback = jasmine.createSpy('resultCallback'),
spec = new j$.Spec({
queueableFn: { fn: function() {} },
expectationResultFactory: function(data) { return data; },
queueRunnerFactory: function(attrs) { attrs.onComplete(); },
resultCallback: resultCallback
});
spec.onException('foo');
spec.execute();
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([{
error: 'foo',
matcherName: '',
passed: false,
expected: '',
actual: ''
}]);
});
it("should not log an additional failure when handling an ExpectationFailed error", function() {
var resultCallback = jasmine.createSpy('resultCallback'),
spec = new j$.Spec({
queueableFn: { fn: function() {} },
expectationResultFactory: function(data) { return data; },
queueRunnerFactory: function(attrs) { attrs.onComplete(); },
resultCallback: resultCallback
});
spec.onException(new j$.errors.ExpectationFailed());
spec.execute();
expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([]);
});
it("retrieves a result with updated status", function() { it("retrieves a result with updated status", function() {
var spec = new j$.Spec({ queueableFn: { fn: function() {} } }); var spec = new j$.Spec({ queueableFn: { fn: function() {} } });

View File

@@ -83,7 +83,7 @@ describe("Suite", function() {
expect(suite.getResult().status).toBe('finished'); expect(suite.getResult().status).toBe('finished');
}); });
it("retrives a result with disabled status", function() { it("retrieves a result with disabled status", function() {
var suite = new j$.Suite({}); var suite = new j$.Suite({});
suite.disable(); suite.disable();
@@ -102,4 +102,44 @@ describe("Suite", function() {
expect(suite.isExecutable()).toBe(false); expect(suite.isExecutable()).toBe(false);
}); });
it("tells all children about expectation failures, even if one throws", function() {
var suite = new j$.Suite({}),
child1 = { addExpectationResult: jasmine.createSpy('child1#expectationResult'), result: {} },
child2 = { addExpectationResult: jasmine.createSpy('child2#expectationResult'), result: {} };
suite.addChild(child1);
suite.addChild(child2);
child1.addExpectationResult.and.throwError('foo');
suite.addExpectationResult('stuff');
expect(child1.addExpectationResult).toHaveBeenCalledWith('stuff');
expect(child2.addExpectationResult).toHaveBeenCalledWith('stuff');
});
it("throws an ExpectationFailed when receiving a failed expectation in an afterAll when throwOnExpectationFailure is set", function() {
var suite = new j$.Suite({
expectationResultFactory: function(data) { return data; },
throwOnExpectationFailure: true
});
suite.addChild({ result: { status: 'done' } });
expect(function() {
suite.addExpectationResult(false, 'failed');
}).toThrowError(j$.errors.ExpectationFailed);
expect(suite.status()).toBe('failed');
expect(suite.result.failedExpectations).toEqual(['failed']);
});
it("does not add an additional failure when an expectation fails in an afterAll", function(){
var suite = new j$.Suite({});
suite.addChild({ result: { status: 'done' } });
suite.onException(new j$.errors.ExpectationFailed());
expect(suite.getResult().failedExpectations).toEqual([]);
})
}); });

View File

@@ -266,7 +266,7 @@ describe("New HtmlReporter", function() {
timer.elapsed.and.returnValue(100); timer.elapsed.and.returnValue(100);
reporter.jasmineDone(); reporter.jasmineDone();
var duration = container.querySelector(".banner .duration"); var duration = container.querySelector(".alert .duration");
expect(duration.innerHTML).toMatch(/finished in 0.1s/); expect(duration.innerHTML).toMatch(/finished in 0.1s/);
}); });
@@ -364,6 +364,40 @@ describe("New HtmlReporter", function() {
// expect(specLink.getAttribute("title")).toEqual("A Suite with a spec"); // expect(specLink.getAttribute("title")).toEqual("A Suite with a spec");
}); });
it("has an options menu", function() {
var env = new j$.Env(),
container = document.createElement("div"),
getContainer = function() {
return container;
},
reporter = new j$.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
});
reporter.initialize();
reporter.jasmineDone({});
var trigger = container.querySelector('.run-options .trigger'),
payload = container.querySelector('.run-options .payload');
expect(payload.className).not.toContain('open');
trigger.click();
expect(payload.className).toContain('open');
trigger.click();
expect(payload.className).not.toContain('open');
});
describe("UI for raising/catching exceptions", function() { describe("UI for raising/catching exceptions", function() {
it("should be unchecked if the env is catching", function() { it("should be unchecked if the env is catching", function() {
var env = new j$.Env(), var env = new j$.Env(),
@@ -442,6 +476,86 @@ describe("New HtmlReporter", function() {
}); });
}); });
describe("UI for throwing errors on expectation failures", function() {
it("should be unchecked if not throwing", function() {
var env = new j$.Env(),
container = document.createElement("div"),
getContainer = function() {
return container;
},
reporter = new j$.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
});
reporter.initialize();
reporter.jasmineDone({});
var throwingExpectationsUI = container.querySelector(".throw");
expect(throwingExpectationsUI.checked).toBe(false);
});
it("should be checked if throwing", function() {
var env = new j$.Env(),
container = document.createElement("div"),
getContainer = function() {
return container;
},
reporter = new j$.HtmlReporter({
env: env,
getContainer: getContainer,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
});
env.throwOnExpectationFailure(true);
reporter.initialize();
reporter.jasmineDone({});
var throwingExpectationsUI = container.querySelector(".throw");
expect(throwingExpectationsUI.checked).toBe(true);
});
it("should affect the query param for throw expectation failures", function() {
var env = new j$.Env(),
container = document.createElement("div"),
throwingExceptionHandler = jasmine.createSpy('throwingExceptions'),
getContainer = function() {
return container;
},
reporter = new j$.HtmlReporter({
env: env,
getContainer: getContainer,
onThrowExpectationsClick: throwingExceptionHandler,
createElement: function() {
return document.createElement.apply(document, arguments);
},
createTextNode: function() {
return document.createTextNode.apply(document, arguments);
}
});
reporter.initialize();
reporter.jasmineDone({});
var throwingExpectationsUI = container.querySelector(".throw");
throwingExpectationsUI.click();
expect(throwingExceptionHandler).toHaveBeenCalled();
});
});
it("shows a message if no specs are run", function(){ it("shows a message if no specs are run", function(){
var env, container, reporter; var env, container, reporter;
env = new j$.Env(); env = new j$.Env();

View File

@@ -19,6 +19,7 @@ getJasmineRequireObj().Env = function(j$) {
var currentSpec = null; var currentSpec = null;
var currentlyExecutingSuites = []; var currentlyExecutingSuites = [];
var currentDeclarationSuite = null; var currentDeclarationSuite = null;
var throwOnExpectationFailure = false;
var currentSuite = function() { var currentSuite = function() {
return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; return currentlyExecutingSuites[currentlyExecutingSuites.length - 1];
@@ -160,6 +161,14 @@ getJasmineRequireObj().Env = function(j$) {
return j$.Spec.isPendingSpecException(e) || catchExceptions; return j$.Spec.isPendingSpecException(e) || catchExceptions;
}; };
this.throwOnExpectationFailure = function(value) {
throwOnExpectationFailure = !!value;
};
this.throwingExpectationFailures = function() {
return throwOnExpectationFailure;
};
var queueRunnerFactory = function(options) { var queueRunnerFactory = function(options) {
options.catchException = catchException; options.catchException = catchException;
options.clearStack = options.clearStack || clearStack; options.clearStack = options.clearStack || clearStack;
@@ -242,7 +251,8 @@ getJasmineRequireObj().Env = function(j$) {
description: description, description: description,
parentSuite: currentDeclarationSuite, parentSuite: currentDeclarationSuite,
expectationFactory: expectationFactory, expectationFactory: expectationFactory,
expectationResultFactory: expectationResultFactory expectationResultFactory: expectationResultFactory,
throwOnExpectationFailure: throwOnExpectationFailure
}); });
runnableLookupTable[suite.id] = suite; runnableLookupTable[suite.id] = suite;
@@ -336,7 +346,8 @@ getJasmineRequireObj().Env = function(j$) {
queueableFn: { queueableFn: {
fn: fn, fn: fn,
timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; } timeout: function() { return timeout || j$.DEFAULT_TIMEOUT_INTERVAL; }
} },
throwOnExpectationFailure: throwOnExpectationFailure
}); });
runnableLookupTable[spec.id] = spec; runnableLookupTable[spec.id] = spec;

View File

@@ -12,6 +12,7 @@ getJasmineRequireObj().Spec = function(j$) {
this.expectationResultFactory = attrs.expectationResultFactory || function() { }; this.expectationResultFactory = attrs.expectationResultFactory || function() { };
this.queueRunnerFactory = attrs.queueRunnerFactory || function() {}; this.queueRunnerFactory = attrs.queueRunnerFactory || function() {};
this.catchingExceptions = attrs.catchingExceptions || function() { return true; }; this.catchingExceptions = attrs.catchingExceptions || function() { return true; };
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
if (!this.queueableFn.fn) { if (!this.queueableFn.fn) {
this.pend(); this.pend();
@@ -33,6 +34,10 @@ getJasmineRequireObj().Spec = function(j$) {
this.result.passedExpectations.push(expectationResult); this.result.passedExpectations.push(expectationResult);
} else { } else {
this.result.failedExpectations.push(expectationResult); this.result.failedExpectations.push(expectationResult);
if(this.throwOnExpectationFailure){
throw new j$.errors.ExpectationFailed();
}
} }
}; };
@@ -76,6 +81,10 @@ getJasmineRequireObj().Spec = function(j$) {
return; return;
} }
if (e instanceof j$.errors.ExpectationFailed) {
return;
}
this.addExpectationResult(false, { this.addExpectationResult(false, {
matcherName: '', matcherName: '',
passed: false, passed: false,

View File

@@ -6,6 +6,7 @@ getJasmineRequireObj().Suite = function() {
this.description = attrs.description; this.description = attrs.description;
this.expectationFactory = attrs.expectationFactory; this.expectationFactory = attrs.expectationFactory;
this.expectationResultFactory = attrs.expectationResultFactory; this.expectationResultFactory = attrs.expectationResultFactory;
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
this.beforeFns = []; this.beforeFns = [];
this.afterFns = []; this.afterFns = [];
@@ -99,6 +100,10 @@ getJasmineRequireObj().Suite = function() {
}; };
Suite.prototype.onException = function() { Suite.prototype.onException = function() {
if (arguments[0] instanceof j$.errors.ExpectationFailed) {
return;
}
if(isAfterAll(this.children)) { if(isAfterAll(this.children)) {
var data = { var data = {
matcherName: '', matcherName: '',
@@ -120,10 +125,17 @@ getJasmineRequireObj().Suite = function() {
if(isAfterAll(this.children) && isFailure(arguments)){ if(isAfterAll(this.children) && isFailure(arguments)){
var data = arguments[1]; var data = arguments[1];
this.result.failedExpectations.push(this.expectationResultFactory(data)); this.result.failedExpectations.push(this.expectationResultFactory(data));
if(this.throwOnExpectationFailure) {
throw new j$.errors.ExpectationFailed();
}
} else { } else {
for (var i = 0; i < this.children.length; i++) { for (var i = 0; i < this.children.length; i++) {
var child = this.children[i]; var child = this.children[i];
child.addExpectationResult.apply(child, arguments); try {
child.addExpectationResult.apply(child, arguments);
} catch(e) {
// keep going
}
} }
} }
}; };

10
src/core/errors.js Normal file
View File

@@ -0,0 +1,10 @@
getJasmineRequireObj().errors = function() {
function ExpectationFailed() {}
ExpectationFailed.prototype = new Error();
ExpectationFailed.prototype.constructor = ExpectationFailed;
return {
ExpectationFailed: ExpectationFailed
};
};

View File

@@ -20,6 +20,7 @@ var getJasmineRequireObj = (function (jasmineGlobal) {
jRequire.base(j$, jasmineGlobal); jRequire.base(j$, jasmineGlobal);
j$.util = jRequire.util(); j$.util = jRequire.util();
j$.errors = jRequire.errors();
j$.Any = jRequire.Any(); j$.Any = jRequire.Any();
j$.Anything = jRequire.Anything(j$); j$.Anything = jRequire.Anything(j$);
j$.CallTracker = jRequire.CallTracker(); j$.CallTracker = jRequire.CallTracker();

View File

@@ -11,6 +11,7 @@ jasmineRequire.HtmlReporter = function(j$) {
createElement = options.createElement, createElement = options.createElement,
createTextNode = options.createTextNode, createTextNode = options.createTextNode,
onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {},
onThrowExpectationsClick = options.onThrowExpectationsClick || function() {},
addToExistingQueryString = options.addToExistingQueryString || defaultQueryString, addToExistingQueryString = options.addToExistingQueryString || defaultQueryString,
timer = options.timer || noopTimer, timer = options.timer || noopTimer,
results = [], results = [],
@@ -116,22 +117,51 @@ jasmineRequire.HtmlReporter = function(j$) {
this.jasmineDone = function() { this.jasmineDone = function() {
var banner = find('.banner'); var banner = find('.banner');
banner.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
var alert = find('.alert'); var alert = find('.alert');
alert.appendChild(createDom('span', {className: 'duration'}, 'finished in ' + timer.elapsed() / 1000 + 's'));
alert.appendChild(createDom('span', { className: 'exceptions' }, banner.appendChild(
createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions'), createDom('div', { className: 'run-options' },
createDom('input', { createDom('span', { className: 'trigger' }, 'Options'),
className: 'raise', createDom('div', { className: 'payload' },
id: 'raise-exceptions', createDom('div', { className: 'exceptions' },
type: 'checkbox' createDom('input', {
}) className: 'raise',
)); id: 'raise-exceptions',
var checkbox = find('#raise-exceptions'); type: 'checkbox'
}),
createDom('label', { className: 'label', 'for': 'raise-exceptions' }, 'raise exceptions')),
createDom('div', { className: 'throw-failures' },
createDom('input', {
className: 'throw',
id: 'throw-failures',
type: 'checkbox'
}),
createDom('label', { className: 'label', 'for': 'throw-failures' }, 'stop spec on expectation failure'))
)
));
checkbox.checked = !env.catchingExceptions(); var raiseCheckbox = find('#raise-exceptions');
checkbox.onclick = onRaiseExceptionsClick;
raiseCheckbox.checked = !env.catchingExceptions();
raiseCheckbox.onclick = onRaiseExceptionsClick;
var throwCheckbox = find('#throw-failures');
throwCheckbox.checked = env.throwingExpectationFailures();
throwCheckbox.onclick = onThrowExpectationsClick;
var optionsMenu = find('.run-options'),
optionsTrigger = optionsMenu.querySelector('.trigger'),
optionsPayload = optionsMenu.querySelector('.payload'),
isOpen = /\bopen\b/;
optionsTrigger.onclick = function() {
if (isOpen.test(optionsPayload.className)) {
optionsPayload.className = optionsPayload.className.replace(isOpen, '');
} else {
optionsPayload.className += ' open';
}
};
if (specsExecuted < totalSpecsDefined) { if (specsExecuted < totalSpecsDefined) {
var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all'; var skippedMessage = 'Ran ' + specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all';

View File

@@ -14,6 +14,7 @@ $failing-color: #ca3a11;
$pending-color: #ba9d37; $pending-color: #ba9d37;
$empty-color: #eff543; $empty-color: #eff543;
$neutral-color: #bababa; $neutral-color: #bababa;
$jasmine-color: #8a4182;
$font-size: 11px; $font-size: 11px;
$large-font-size: 14px; $large-font-size: 14px;
@@ -77,12 +78,6 @@ body {
top: 6px; top: 6px;
} }
.banner .duration {
position: absolute;
right: 14px;
top: 6px;
}
// This div is available for testing elements that must be added to the DOM. // This div is available for testing elements that must be added to the DOM.
// We position it out of view, so it doesn't obstruct the runner. // We position it out of view, so it doesn't obstruct the runner.
#jasmine_content { #jasmine_content {
@@ -101,8 +96,10 @@ body {
} }
.duration { .duration {
color: $faint-text-color; color: #fff;
float: right; float: right;
line-height: $line-height * 2;
padding-right: 9px;
} }
//--- Symbol summary ---// //--- Symbol summary ---//
@@ -166,11 +163,32 @@ body {
} }
} }
.exceptions { .run-options {
color: #fff;
float: right; float: right;
margin-top: 5px;
margin-right: 5px; margin-right: 5px;
border: 1px solid $jasmine-color;
color: $jasmine-color;
position: relative;
line-height: 20px;
.trigger {
cursor: pointer;
padding: 8px 16px;
}
.payload {
position: absolute;
display: none;
right: -1px;
border: 1px solid $jasmine-color;
background-color: $page-background-color;
white-space: nowrap;
padding: 4px 8px;
&.open {
display: block;
}
}
} }
//--- Alerts: status bars ---// //--- Alerts: status bars ---//