From e31db20ec787f7c95bbe5ba0c15f650605aaa492 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Fri, 27 Oct 2017 08:46:24 -0700 Subject: [PATCH 01/62] Default to running tests in random order [Finishes #109197518] --- lib/jasmine-core/boot.js | 5 ++++- lib/jasmine-core/boot/boot.js | 5 ++++- lib/jasmine-core/jasmine.js | 2 +- spec/core/integration/CustomMatchersSpec.js | 1 + spec/core/integration/EnvSpec.js | 10 ++++++++++ spec/core/integration/SpecRunningSpec.js | 5 +++-- spec/html/HtmlReporterSpec.js | 3 ++- src/core/Env.js | 2 +- 8 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/jasmine-core/boot.js b/lib/jasmine-core/boot.js index 85adb0d8..583ccd5f 100644 --- a/lib/jasmine-core/boot.js +++ b/lib/jasmine-core/boot.js @@ -80,7 +80,10 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. env.throwOnExpectationFailure(throwingExpectationFailures); var random = queryString.getParam("random"); - env.randomizeTests(random); + + if (random !== undefined) { + env.randomizeTests(random); + } var seed = queryString.getParam("seed"); if (seed) { diff --git a/lib/jasmine-core/boot/boot.js b/lib/jasmine-core/boot/boot.js index d9b5a80b..5923245a 100644 --- a/lib/jasmine-core/boot/boot.js +++ b/lib/jasmine-core/boot/boot.js @@ -58,7 +58,10 @@ env.throwOnExpectationFailure(throwingExpectationFailures); var random = queryString.getParam("random"); - env.randomizeTests(random); + + if (random !== undefined) { + env.randomizeTests(random); + } var seed = queryString.getParam("seed"); if (seed) { diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 9c65fcf7..cd744f21 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -707,7 +707,7 @@ getJasmineRequireObj().Env = function(j$) { var currentlyExecutingSuites = []; var currentDeclarationSuite = null; var throwOnExpectationFailure = false; - var random = false; + var random = true; var seed = null; var currentSuite = function() { diff --git a/spec/core/integration/CustomMatchersSpec.js b/spec/core/integration/CustomMatchersSpec.js index f9ec86ad..04f345ec 100644 --- a/spec/core/integration/CustomMatchersSpec.js +++ b/spec/core/integration/CustomMatchersSpec.js @@ -4,6 +4,7 @@ describe("Custom Matchers (Integration)", function() { beforeEach(function() { env = new jasmineUnderTest.Env(); + env.randomizeTests(false); }); it("allows adding more matchers local to a spec", function(done) { diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index f77b6265..b5bcef8a 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -54,6 +54,7 @@ describe("Env integration", function() { }; env.addReporter({ jasmineDone: assertions}); + env.randomizeTests(false); env.describe("A Suite", function() { env.it("with a spec", function() { @@ -82,6 +83,7 @@ describe("Env integration", function() { }; env.addReporter({ jasmineDone: assertions }); + env.randomizeTests(false); env.describe("Outer suite", function() { env.it("an outer spec", function() { @@ -116,6 +118,7 @@ describe("Env integration", function() { }; env.addReporter({ jasmineDone: assertions }); + env.randomizeTests(false); env.describe("Outer suite", function() { @@ -234,6 +237,7 @@ describe("Env integration", function() { var env = new jasmineUnderTest.Env(); env.addReporter({jasmineDone: done}); + env.randomizeTests(false); env.describe("tests", function() { var firstTimeThrough = true, firstSpecContext, secondSpecContext; @@ -909,6 +913,7 @@ describe("Env integration", function() { }; env.addReporter({ jasmineDone: assertions }); + env.randomizeTests(false); env.describe("tests", function() { env.it("test with mock clock", function() { @@ -1472,6 +1477,7 @@ describe("Env integration", function() { }); env.addReporter(reporter); + env.randomizeTests(true); env.execute(); }); @@ -1602,6 +1608,7 @@ describe("Env integration", function() { }); env.addReporter(reporter); + env.randomizeTests(false); env.describe("testing custom equality testers", function() { env.it("with a custom tester", function() { @@ -1637,6 +1644,7 @@ describe("Env integration", function() { }); env.addReporter(reporter); + env.randomizeTests(false); env.describe("testing custom equality testers", function() { env.beforeAll(function() { env.addCustomEqualityTester(function(a, b) { return true; }); }); @@ -1677,6 +1685,7 @@ describe("Env integration", function() { }); env.addReporter(reporter); + env.randomizeTests(false); env.describe("testing custom equality testers", function() { env.it("with a custom tester", function() { @@ -1729,6 +1738,7 @@ describe("Env integration", function() { }); env.addReporter(reporter); + env.randomizeTests(false); env.describe("testing custom equality testers", function() { env.beforeAll(function() { env.addCustomEqualityTester(function(a, b) { return true; })}); diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index 2028509a..85eef125 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -1,8 +1,9 @@ -describe("jasmine spec running", function () { +describe("spec running", function () { var env; beforeEach(function() { env = new jasmineUnderTest.Env(); + env.randomizeTests(false); }); it('should assign spec ids sequentially', function() { @@ -740,8 +741,8 @@ describe("jasmine spec running", function () { it("should run the tests in a consistent order when a seed is supplied", function(done) { var actions = []; - env.randomizeTests(true); env.seed('123456'); + env.randomizeTests(true); env.beforeEach(function () { actions.push('topSuite beforeEach'); diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 7047ea56..e84bbd0a 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -1,4 +1,4 @@ -describe("New HtmlReporter", function() { +describe("HtmlReporter", function() { it("builds the initial DOM elements, including the title banner", function() { var env = new jasmineUnderTest.Env(), container = document.createElement("div"), @@ -575,6 +575,7 @@ describe("New HtmlReporter", function() { } }); + env.randomizeTests(false); reporter.initialize(); reporter.jasmineDone({}); diff --git a/src/core/Env.js b/src/core/Env.js index 9655f15d..4cbed3d8 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -26,7 +26,7 @@ getJasmineRequireObj().Env = function(j$) { var currentlyExecutingSuites = []; var currentDeclarationSuite = null; var throwOnExpectationFailure = false; - var random = false; + var random = true; var seed = null; var currentSuite = function() { From 908583c49b3c2e6d0c74104aeb75bdcf82a3e637 Mon Sep 17 00:00:00 2001 From: sgravrock Date: Wed, 25 Oct 2017 11:28:47 -0700 Subject: [PATCH 02/62] Point CodeClimate at the right repo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 14951bde..01196ada 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [](http://jasmine.github.io) [![Build Status](https://travis-ci.org/jasmine/jasmine.svg?branch=master)](https://travis-ci.org/jasmine/jasmine) -[![Code Climate](https://codeclimate.com/github/pivotal/jasmine.svg)](https://codeclimate.com/github/pivotal/jasmine) +[![Code Climate](https://codeclimate.com/github/jasmine/jasmine.svg)](https://codeclimate.com/github/jasmine/jasmine) ======= From 9f7a6ef06158b4753327e17fbde0cbe5841d2975 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 28 Oct 2017 10:05:28 -0700 Subject: [PATCH 03/62] Check truthiness of toThrowError args, not arg count This enables some useful simplifications at the cost of making it impossible (for now) to expect a function to throw an error with a falsy message. [#20622765] --- lib/jasmine-core/jasmine.js | 14 +++++++------- src/core/matchers/toThrowError.js | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index cd744f21..1976a797 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -3755,18 +3755,18 @@ getJasmineRequireObj().toThrowError = function(j$) { var expected = null, errorType = null; - if (arguments.length == 2) { - expected = arguments[1]; - if (isAnErrorType(expected)) { - errorType = expected; - expected = null; - } - } else if (arguments.length > 2) { + if (arguments[2]) { errorType = arguments[1]; expected = arguments[2]; if (!isAnErrorType(errorType)) { throw new Error(getErrorMsg('Expected error type is not an Error.')); } + } else if (arguments[1]) { + expected = arguments[1]; + if (isAnErrorType(expected)) { + errorType = expected; + expected = null; + } } if (expected && !isStringOrRegExp(expected)) { diff --git a/src/core/matchers/toThrowError.js b/src/core/matchers/toThrowError.js index ef809e35..7cdc1d9f 100644 --- a/src/core/matchers/toThrowError.js +++ b/src/core/matchers/toThrowError.js @@ -71,18 +71,18 @@ getJasmineRequireObj().toThrowError = function(j$) { var expected = null, errorType = null; - if (arguments.length == 2) { - expected = arguments[1]; - if (isAnErrorType(expected)) { - errorType = expected; - expected = null; - } - } else if (arguments.length > 2) { + if (arguments[2]) { errorType = arguments[1]; expected = arguments[2]; if (!isAnErrorType(errorType)) { throw new Error(getErrorMsg('Expected error type is not an Error.')); } + } else if (arguments[1]) { + expected = arguments[1]; + if (isAnErrorType(expected)) { + errorType = expected; + expected = null; + } } if (expected && !isStringOrRegExp(expected)) { From 324ad0073eda749c75e0d6179f1b3bf66fa5e634 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 28 Oct 2017 10:14:02 -0700 Subject: [PATCH 04/62] Refactored toThrowMatching to facilitate adding more strategies * Extracted sub-matchers for the two major existing strategies (matching all errors, and matching by type and/or message) * Reduced the use of mutable state --- lib/jasmine-core/jasmine.js | 134 +++++++++++++++++------------- src/core/matchers/toThrowError.js | 134 +++++++++++++++++------------- 2 files changed, 148 insertions(+), 120 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 1976a797..d642adc5 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -3702,58 +3702,30 @@ getJasmineRequireObj().toThrowError = function(j$) { function toThrowError () { return { compare: function(actual) { - var threw = false, - pass = {pass: true}, - fail = {pass: false}, + var errorMatcher = getMatcher.apply(null, arguments), thrown; if (typeof actual != 'function') { throw new Error(getErrorMsg('Actual is not a Function')); } - var errorMatcher = getMatcher.apply(null, arguments); - try { actual(); + return fail('Expected function to throw an Error.'); } catch (e) { - threw = true; thrown = e; } - if (!threw) { - fail.message = 'Expected function to throw an Error.'; - return fail; - } - - // Get Error constructor of thrown if (!isErrorObject(thrown)) { - fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; - return fail; + return fail(function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }); } - if (errorMatcher.hasNoSpecifics()) { - pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.'; - return pass; - } - - if (errorMatcher.matches(thrown)) { - pass.message = function() { - return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.'; - }; - return pass; - } else { - fail.message = function() { - return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + - ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.'; - }; - return fail; - } + return errorMatcher.match(thrown); } }; function getMatcher() { - var expected = null, - errorType = null; + var expected, errorType; if (arguments[2]) { errorType = arguments[1]; @@ -3761,14 +3733,29 @@ getJasmineRequireObj().toThrowError = function(j$) { if (!isAnErrorType(errorType)) { throw new Error(getErrorMsg('Expected error type is not an Error.')); } + + return conditionalMatcher(expected, errorType); } else if (arguments[1]) { expected = arguments[1]; - if (isAnErrorType(expected)) { - errorType = expected; - expected = null; + if (isAnErrorType(arguments[1])) { + return conditionalMatcher(null, arguments[1]); + } else { + return conditionalMatcher(arguments[1], null); } + } else { + return anyMatcher(); } + } + function anyMatcher() { + return { + match: function(error) { + return pass('Expected function not to throw an Error, but it threw ' + j$.fnNameFor(error) + '.'); + } + }; + } + + function conditionalMatcher(expected, errorType) { if (expected && !isStringOrRegExp(expected)) { if (errorType) { throw new Error(getErrorMsg('Expected error message is not a string or RegExp.')); @@ -3785,33 +3772,46 @@ getJasmineRequireObj().toThrowError = function(j$) { } } + var errorTypeDescription = errorType ? j$.fnNameFor(errorType) : 'an exception'; + + function thrownDescription(thrown) { + var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', + thrownMessage = ''; + + if (expected) { + thrownMessage = ' with message ' + j$.pp(thrown.message); + } + + return thrownName + thrownMessage; + } + + function messageDescription() { + if (expected === null) { + return ''; + } else if (expected instanceof RegExp) { + return ' with a message matching ' + j$.pp(expected); + } else { + return ' with message ' + j$.pp(expected); + } + } + + function matches(error) { + return (errorType === null || error instanceof errorType) && + (expected === null || messageMatch(error.message)); + } + return { - errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception', - thrownDescription: function(thrown) { - var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', - thrownMessage = ''; - - if (expected) { - thrownMessage = ' with message ' + j$.pp(thrown.message); - } - - return thrownName + thrownMessage; - }, - messageDescription: function() { - if (expected === null) { - return ''; - } else if (expected instanceof RegExp) { - return ' with a message matching ' + j$.pp(expected); + match: function(thrown) { + if (matches(thrown)) { + return pass(function() { + return 'Expected function not to throw ' + errorTypeDescription + messageDescription() + '.'; + }); } else { - return ' with message ' + j$.pp(expected); + return fail(function() { + return 'Expected function to throw ' + errorTypeDescription + messageDescription() + + ', but it threw ' + thrownDescription(thrown) + '.'; + }); } - }, - hasNoSpecifics: function() { - return expected === null && errorType === null; - }, - matches: function(error) { - return (errorType === null || error instanceof errorType) && - (expected === null || messageMatch(error.message)); } }; } @@ -3842,6 +3842,20 @@ getJasmineRequireObj().toThrowError = function(j$) { } } + function pass(message) { + return { + pass: true, + message: message + }; + } + + function fail(message) { + return { + pass: false, + message: message + }; + } + return toThrowError; }; diff --git a/src/core/matchers/toThrowError.js b/src/core/matchers/toThrowError.js index 7cdc1d9f..d6e772a1 100644 --- a/src/core/matchers/toThrowError.js +++ b/src/core/matchers/toThrowError.js @@ -18,58 +18,30 @@ getJasmineRequireObj().toThrowError = function(j$) { function toThrowError () { return { compare: function(actual) { - var threw = false, - pass = {pass: true}, - fail = {pass: false}, + var errorMatcher = getMatcher.apply(null, arguments), thrown; if (typeof actual != 'function') { throw new Error(getErrorMsg('Actual is not a Function')); } - var errorMatcher = getMatcher.apply(null, arguments); - try { actual(); + return fail('Expected function to throw an Error.'); } catch (e) { - threw = true; thrown = e; } - if (!threw) { - fail.message = 'Expected function to throw an Error.'; - return fail; - } - - // Get Error constructor of thrown if (!isErrorObject(thrown)) { - fail.message = function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }; - return fail; + return fail(function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }); } - if (errorMatcher.hasNoSpecifics()) { - pass.message = 'Expected function not to throw an Error, but it threw ' + j$.fnNameFor(thrown) + '.'; - return pass; - } - - if (errorMatcher.matches(thrown)) { - pass.message = function() { - return 'Expected function not to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + '.'; - }; - return pass; - } else { - fail.message = function() { - return 'Expected function to throw ' + errorMatcher.errorTypeDescription + errorMatcher.messageDescription() + - ', but it threw ' + errorMatcher.thrownDescription(thrown) + '.'; - }; - return fail; - } + return errorMatcher.match(thrown); } }; function getMatcher() { - var expected = null, - errorType = null; + var expected, errorType; if (arguments[2]) { errorType = arguments[1]; @@ -77,14 +49,29 @@ getJasmineRequireObj().toThrowError = function(j$) { if (!isAnErrorType(errorType)) { throw new Error(getErrorMsg('Expected error type is not an Error.')); } + + return conditionalMatcher(expected, errorType); } else if (arguments[1]) { expected = arguments[1]; - if (isAnErrorType(expected)) { - errorType = expected; - expected = null; + if (isAnErrorType(arguments[1])) { + return conditionalMatcher(null, arguments[1]); + } else { + return conditionalMatcher(arguments[1], null); } + } else { + return anyMatcher(); } + } + function anyMatcher() { + return { + match: function(error) { + return pass('Expected function not to throw an Error, but it threw ' + j$.fnNameFor(error) + '.'); + } + }; + } + + function conditionalMatcher(expected, errorType) { if (expected && !isStringOrRegExp(expected)) { if (errorType) { throw new Error(getErrorMsg('Expected error message is not a string or RegExp.')); @@ -101,33 +88,46 @@ getJasmineRequireObj().toThrowError = function(j$) { } } + var errorTypeDescription = errorType ? j$.fnNameFor(errorType) : 'an exception'; + + function thrownDescription(thrown) { + var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', + thrownMessage = ''; + + if (expected) { + thrownMessage = ' with message ' + j$.pp(thrown.message); + } + + return thrownName + thrownMessage; + } + + function messageDescription() { + if (expected === null) { + return ''; + } else if (expected instanceof RegExp) { + return ' with a message matching ' + j$.pp(expected); + } else { + return ' with message ' + j$.pp(expected); + } + } + + function matches(error) { + return (errorType === null || error instanceof errorType) && + (expected === null || messageMatch(error.message)); + } + return { - errorTypeDescription: errorType ? j$.fnNameFor(errorType) : 'an exception', - thrownDescription: function(thrown) { - var thrownName = errorType ? j$.fnNameFor(thrown.constructor) : 'an exception', - thrownMessage = ''; - - if (expected) { - thrownMessage = ' with message ' + j$.pp(thrown.message); - } - - return thrownName + thrownMessage; - }, - messageDescription: function() { - if (expected === null) { - return ''; - } else if (expected instanceof RegExp) { - return ' with a message matching ' + j$.pp(expected); + match: function(thrown) { + if (matches(thrown)) { + return pass(function() { + return 'Expected function not to throw ' + errorTypeDescription + messageDescription() + '.'; + }); } else { - return ' with message ' + j$.pp(expected); + return fail(function() { + return 'Expected function to throw ' + errorTypeDescription + messageDescription() + + ', but it threw ' + thrownDescription(thrown) + '.'; + }); } - }, - hasNoSpecifics: function() { - return expected === null && errorType === null; - }, - matches: function(error) { - return (errorType === null || error instanceof errorType) && - (expected === null || messageMatch(error.message)); } }; } @@ -158,5 +158,19 @@ getJasmineRequireObj().toThrowError = function(j$) { } } + function pass(message) { + return { + pass: true, + message: message + }; + } + + function fail(message) { + return { + pass: false, + message: message + }; + } + return toThrowError; }; From 12ed3bfacdb393761bd31505f3760d617783accb Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Mon, 30 Oct 2017 08:45:30 -0700 Subject: [PATCH 05/62] Allow use of a predicate function to validate thrown exceptions [Finishes #20622765] --- lib/jasmine-core/jasmine.js | 31 +++++++++++++++++++++---- spec/core/matchers/toThrowErrorSpec.js | 32 ++++++++++++++++++++++++-- src/core/matchers/toThrowError.js | 31 +++++++++++++++++++++---- 3 files changed, 82 insertions(+), 12 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index d642adc5..cfd1d36a 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -3734,13 +3734,16 @@ getJasmineRequireObj().toThrowError = function(j$) { throw new Error(getErrorMsg('Expected error type is not an Error.')); } - return conditionalMatcher(expected, errorType); + return exactMatcher(expected, errorType); } else if (arguments[1]) { expected = arguments[1]; + if (isAnErrorType(arguments[1])) { - return conditionalMatcher(null, arguments[1]); + return exactMatcher(null, arguments[1]); + } else if (j$.isFunction_(arguments[1])) { + return predicateMatcher(arguments[1]); } else { - return conditionalMatcher(arguments[1], null); + return exactMatcher(arguments[1], null); } } else { return anyMatcher(); @@ -3755,12 +3758,30 @@ getJasmineRequireObj().toThrowError = function(j$) { }; } - function conditionalMatcher(expected, errorType) { + function predicateMatcher(predicate) { + return { + match: function(thrown) { + if (predicate(thrown)) { + return pass(function() { + return 'Expected function not to throw an exception matching a predicate.'; + }); + } else { + return fail(function() { + return 'Expected function to throw an exception matching a predicate, ' + + 'but it threw ' + j$.fnNameFor(thrown.constructor) + ' with message ' + + j$.pp(thrown.message) + '.'; + }); + } + } + }; + } + + function exactMatcher(expected, errorType) { if (expected && !isStringOrRegExp(expected)) { if (errorType) { throw new Error(getErrorMsg('Expected error message is not a string or RegExp.')); } else { - throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.')); + throw new Error(getErrorMsg('Expected is not an Error, string, RegExp, or Function.')); } } diff --git a/spec/core/matchers/toThrowErrorSpec.js b/spec/core/matchers/toThrowErrorSpec.js index 14726464..c7750e80 100644 --- a/spec/core/matchers/toThrowErrorSpec.js +++ b/spec/core/matchers/toThrowErrorSpec.js @@ -7,7 +7,7 @@ describe("toThrowError", function() { }).toThrowError(/Actual is not a Function/); }); - it("throws an error when the expected is not an Error, string, or RegExp", function() { + it("throws an error when the expected is not an Error, string, RegExp, or function", function() { var matcher = jasmineUnderTest.matchers.toThrowError(), fn = function() { throw new Error("foo"); @@ -15,7 +15,7 @@ describe("toThrowError", function() { expect(function() { matcher.compare(fn, 1); - }).toThrowError(/Expected is not an Error, string, or RegExp./); + }).toThrowError(/Expected is not an Error, string, RegExp, or Function./); }); it("throws an error when the expected error type is not an Error", function() { @@ -312,4 +312,32 @@ describe("toThrowError", function() { expect(result.pass).toBe(false); expect(result.message()).toEqual("Expected function to throw TypeError with a message matching /bar/, but it threw TypeError with message 'foo'."); }); + + it("passes if the argument is a function that returns true when called with the error", function() { + var matcher = jasmineUnderTest.matchers.toThrowError(), + predicate = function(e) { return e.message === "nope" }, + fn = function() { + throw new TypeError("nope"); + }, + result; + + result = matcher.compare(fn, predicate); + + expect(result.pass).toBe(true); + expect(result.message()).toEqual("Expected function not to throw an exception matching a predicate."); + }); + + it("fails if the argument is a function that returns false when called with the error", function() { + var matcher = jasmineUnderTest.matchers.toThrowError(), + predicate = function(e) { return e.message === "oh no" }, + fn = function() { + throw new TypeError("nope"); + }, + result; + + result = matcher.compare(fn, predicate); + + expect(result.pass).toBe(false); + expect(result.message()).toEqual("Expected function to throw an exception matching a predicate, but it threw TypeError with message 'nope'."); + }); }); diff --git a/src/core/matchers/toThrowError.js b/src/core/matchers/toThrowError.js index d6e772a1..ebbc4d3e 100644 --- a/src/core/matchers/toThrowError.js +++ b/src/core/matchers/toThrowError.js @@ -50,13 +50,16 @@ getJasmineRequireObj().toThrowError = function(j$) { throw new Error(getErrorMsg('Expected error type is not an Error.')); } - return conditionalMatcher(expected, errorType); + return exactMatcher(expected, errorType); } else if (arguments[1]) { expected = arguments[1]; + if (isAnErrorType(arguments[1])) { - return conditionalMatcher(null, arguments[1]); + return exactMatcher(null, arguments[1]); + } else if (j$.isFunction_(arguments[1])) { + return predicateMatcher(arguments[1]); } else { - return conditionalMatcher(arguments[1], null); + return exactMatcher(arguments[1], null); } } else { return anyMatcher(); @@ -71,12 +74,30 @@ getJasmineRequireObj().toThrowError = function(j$) { }; } - function conditionalMatcher(expected, errorType) { + function predicateMatcher(predicate) { + return { + match: function(thrown) { + if (predicate(thrown)) { + return pass(function() { + return 'Expected function not to throw an exception matching a predicate.'; + }); + } else { + return fail(function() { + return 'Expected function to throw an exception matching a predicate, ' + + 'but it threw ' + j$.fnNameFor(thrown.constructor) + ' with message ' + + j$.pp(thrown.message) + '.'; + }); + } + } + }; + } + + function exactMatcher(expected, errorType) { if (expected && !isStringOrRegExp(expected)) { if (errorType) { throw new Error(getErrorMsg('Expected error message is not a string or RegExp.')); } else { - throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.')); + throw new Error(getErrorMsg('Expected is not an Error, string, RegExp, or Function.')); } } From bd250f27c7660f7c30529547d732edaf9acee6bd Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 31 Oct 2017 19:22:02 -0700 Subject: [PATCH 06/62] Fail if error events (e.g. syntax errors) occur during loading [#24901981] --- lib/jasmine-core/jasmine.js | 20 ++++++++--- spec/core/integration/EnvSpec.js | 60 ++++++++++++++++++++++++++++++++ src/core/Env.js | 18 ++++++++-- src/core/GlobalErrors.js | 2 -- 4 files changed, 92 insertions(+), 8 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index cfd1d36a..023efe24 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -709,6 +709,7 @@ getJasmineRequireObj().Env = function(j$) { var throwOnExpectationFailure = false; var random = true; var seed = null; + var suppressLoadErrors = false; var currentSuite = function() { return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; @@ -772,6 +773,15 @@ getJasmineRequireObj().Env = function(j$) { ]); var globalErrors = new j$.GlobalErrors(); + globalErrors.install(); + globalErrors.pushListener(function(message) { + if (!suppressLoadErrors) { + topSuite.result.failedExpectations.push({ + passed: false, + message: message + }); + } + }); this.specFilter = function() { return true; @@ -915,6 +925,10 @@ getJasmineRequireObj().Env = function(j$) { return seed; }; + this.suppressLoadErrors = function() { + suppressLoadErrors = true; + }; + var queueRunnerFactory = function(options) { options.catchException = catchException; options.clearStack = options.clearStack || clearStack; @@ -941,6 +955,8 @@ getJasmineRequireObj().Env = function(j$) { }; this.execute = function(runnablesToRun) { + globalErrors.popListener(); + if(!runnablesToRun) { if (focusedRunnables.length) { runnablesToRun = focusedRunnables; @@ -996,11 +1012,9 @@ getJasmineRequireObj().Env = function(j$) { currentlyExecutingSuites.push(topSuite); - globalErrors.install(); processor.execute(function() { clearResourcesForRunnable(topSuite.id); currentlyExecutingSuites.pop(); - globalErrors.uninstall(); /** * Information passed to the {@link Reporter#jasmineDone} event. @@ -2398,8 +2412,6 @@ getJasmineRequireObj().GlobalErrors = function(j$) { } }; - this.uninstall = function noop() {}; - this.install = function install() { if (global.process && global.process.listeners && j$.isFunction_(global.process.on)) { var originalHandlers = global.process.listeners('uncaughtException'); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index b5bcef8a..df737be8 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1974,4 +1974,64 @@ describe("Env integration", function() { env.execute(); }); + + describe("In a browser", function() { + if (typeof document !== 'undefined') { + it('reports errors that occur during loading', function(done) { + var global = { + setTimeout: function(fn, delay) { setTimeout(fn, delay) }, + clearTimeout: function(fn, delay) { clearTimeout(fn, delay) }, + }; + spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); + + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.failedExpectations).toEqual([ + { + passed: false, + message: 'Uncaught SyntaxError: Unexpected end of input' + }, + { + passed: false, + message: 'Uncaught Error: ENOCHEESE' + } + ]); + + done(); + }); + + env.addReporter(reporter); + global.onerror('Uncaught SyntaxError: Unexpected end of input'); + global.onerror('Uncaught Error: ENOCHEESE'); + + env.execute(); + }); + } + + describe('If suppressLoadErrors was called', function() { + it('does not report errors that occur during loading', function(done) { + var global = { + setTimeout: function(fn, delay) { setTimeout(fn, delay) }, + clearTimeout: function(fn, delay) { clearTimeout(fn, delay) }, + }; + spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); + + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.failedExpectations).toEqual([]); + done(); + }); + + env.addReporter(reporter); + env.suppressLoadErrors(true); + global.onerror('Uncaught Error: ENOCHEESE'); + + env.execute(); + }); + }); + }); }); diff --git a/src/core/Env.js b/src/core/Env.js index 4cbed3d8..d13fdbb6 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -28,6 +28,7 @@ getJasmineRequireObj().Env = function(j$) { var throwOnExpectationFailure = false; var random = true; var seed = null; + var suppressLoadErrors = false; var currentSuite = function() { return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; @@ -91,6 +92,15 @@ getJasmineRequireObj().Env = function(j$) { ]); var globalErrors = new j$.GlobalErrors(); + globalErrors.install(); + globalErrors.pushListener(function(message) { + if (!suppressLoadErrors) { + topSuite.result.failedExpectations.push({ + passed: false, + message: message + }); + } + }); this.specFilter = function() { return true; @@ -234,6 +244,10 @@ getJasmineRequireObj().Env = function(j$) { return seed; }; + this.suppressLoadErrors = function() { + suppressLoadErrors = true; + }; + var queueRunnerFactory = function(options) { options.catchException = catchException; options.clearStack = options.clearStack || clearStack; @@ -260,6 +274,8 @@ getJasmineRequireObj().Env = function(j$) { }; this.execute = function(runnablesToRun) { + globalErrors.popListener(); + if(!runnablesToRun) { if (focusedRunnables.length) { runnablesToRun = focusedRunnables; @@ -315,11 +331,9 @@ getJasmineRequireObj().Env = function(j$) { currentlyExecutingSuites.push(topSuite); - globalErrors.install(); processor.execute(function() { clearResourcesForRunnable(topSuite.id); currentlyExecutingSuites.pop(); - globalErrors.uninstall(); /** * Information passed to the {@link Reporter#jasmineDone} event. diff --git a/src/core/GlobalErrors.js b/src/core/GlobalErrors.js index ced4a76a..3eaec399 100644 --- a/src/core/GlobalErrors.js +++ b/src/core/GlobalErrors.js @@ -13,8 +13,6 @@ getJasmineRequireObj().GlobalErrors = function(j$) { } }; - this.uninstall = function noop() {}; - this.install = function install() { if (global.process && global.process.listeners && j$.isFunction_(global.process.on)) { var originalHandlers = global.process.listeners('uncaughtException'); From 395b2b2d9784b8d628fb1866e9a6c3b21eac30b5 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 31 Oct 2017 21:39:54 -0700 Subject: [PATCH 07/62] Test against Edge --- .travis.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.travis.yml b/.travis.yml index b8663be6..0705366e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,6 +37,10 @@ matrix: - JASMINE_BROWSER="safari" - SAUCE_OS="OS X 10.9" - SAUCE_BROWSER_VERSION=7 + - env: + - JASMINE_BROWSER="MicrosoftEdge" + - SAUCE_OS="Windows 10" + - SAUCE_BROWSER_VERSION="15" - env: - JASMINE_BROWSER="internet explorer" - SAUCE_OS="Windows 8.1" From 7b8edcb401ff113ed32287ac5c55be04f87508ef Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 1 Nov 2017 11:44:58 -0700 Subject: [PATCH 08/62] HTML reporter reports overall failure if there are any global errors [#24901981] --- lib/jasmine-core/jasmine-html.js | 9 +++--- spec/html/HtmlReporterSpec.js | 52 ++++++++++++++++++++++++++++++++ src/html/HtmlReporter.js | 9 +++--- 3 files changed, 62 insertions(+), 8 deletions(-) diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index a33e6c17..97b83b2f 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -218,12 +218,14 @@ jasmineRequire.HtmlReporter = function(j$) { ); } var statusBarMessage = ''; - var statusBarClassName = 'jasmine-bar '; + var statusBarClassName = 'jasmine-overall-result jasmine-bar '; + var globalFailures = (doneResult && doneResult.failedExpectations) || []; + var failed = failureCount + globalFailures.length > 0; - if (totalSpecsDefined > 0) { + if (totalSpecsDefined > 0 || failed) { statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } - statusBarClassName += (failureCount > 0) ? 'jasmine-failed' : 'jasmine-passed'; + statusBarClassName += failed ? 'jasmine-failed' : 'jasmine-passed'; } else { statusBarClassName += 'jasmine-skipped'; statusBarMessage += 'No specs found'; @@ -249,7 +251,6 @@ jasmineRequire.HtmlReporter = function(j$) { } } - var globalFailures = (doneResult && doneResult.failedExpectations) || []; for(i = 0; i < globalFailures.length; i++) { var failure = globalFailures[i]; alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessagePrefix + failure.message)); diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index e84bbd0a..10f04fa5 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -733,6 +733,58 @@ describe("HtmlReporter", function() { expect(alertBars[0].innerHTML).toMatch(/No specs found/); }); + it("reports failure if there are global errors and no specs", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: function() { return container; }, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + reporter.initialize(); + reporter.jasmineStarted({ totalSpecsDefined: 0 }); + reporter.jasmineDone({ + failedExpectations: [{ + passed: false, + message: 'nope' + }] + }); + + var alertBar = container.querySelector(".jasmine-overall-result"); + expect(alertBar.getAttribute('class')).toMatch(/jasmine-failed/); + }); + + it("reports failure if there are global errors and some specs", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: function() { return container; }, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + reporter.initialize(); + reporter.jasmineStarted({ totalSpecsDefined: 0 }); + reporter.specDone({ + id: 123, + description: "with a spec", + fullName: "A Suite with a spec", + status: "passed", + passedExpectations: [{passed: true}], + failedExpectations: [] + }); + reporter.jasmineDone({ + failedExpectations: [{ + passed: false, + message: 'nope' + }] + }); + + var alertBar = container.querySelector(".jasmine-overall-result"); + expect(alertBar.getAttribute('class')).toMatch(/jasmine-failed/); + }); + describe("and all specs pass", function() { var env, container, reporter; beforeEach(function() { diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index 02a9e1ce..8ae8998c 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -189,12 +189,14 @@ jasmineRequire.HtmlReporter = function(j$) { ); } var statusBarMessage = ''; - var statusBarClassName = 'jasmine-bar '; + var statusBarClassName = 'jasmine-overall-result jasmine-bar '; + var globalFailures = (doneResult && doneResult.failedExpectations) || []; + var failed = failureCount + globalFailures.length > 0; - if (totalSpecsDefined > 0) { + if (totalSpecsDefined > 0 || failed) { statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } - statusBarClassName += (failureCount > 0) ? 'jasmine-failed' : 'jasmine-passed'; + statusBarClassName += failed ? 'jasmine-failed' : 'jasmine-passed'; } else { statusBarClassName += 'jasmine-skipped'; statusBarMessage += 'No specs found'; @@ -220,7 +222,6 @@ jasmineRequire.HtmlReporter = function(j$) { } } - var globalFailures = (doneResult && doneResult.failedExpectations) || []; for(i = 0; i < globalFailures.length; i++) { var failure = globalFailures[i]; alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessagePrefix + failure.message)); From 26a7bc6acf48805ba73f9e04366174b310bfdae4 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 1 Nov 2017 13:42:15 -0700 Subject: [PATCH 09/62] Report loading errors as loading errors, not afterAll errors [#24901981] --- lib/jasmine-core/jasmine-html.js | 7 ++--- lib/jasmine-core/jasmine.js | 9 ++++++- spec/core/integration/EnvSpec.js | 45 ++++++++++++++++++++++++++++++++ spec/html/HtmlReporterSpec.js | 13 ++++++--- src/core/Env.js | 1 + src/core/Suite.js | 8 +++++- src/html/HtmlReporter.js | 7 ++--- 7 files changed, 79 insertions(+), 11 deletions(-) diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 97b83b2f..6a871546 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -242,18 +242,19 @@ jasmineRequire.HtmlReporter = function(j$) { alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage, seedBar)); var errorBarClassName = 'jasmine-bar jasmine-errored'; - var errorBarMessagePrefix = 'AfterAll '; + var afterAllMessagePrefix = 'AfterAll '; for(var i = 0; i < failedSuites.length; i++) { var failedSuite = failedSuites[i]; for(var j = 0; j < failedSuite.failedExpectations.length; j++) { - alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessagePrefix + failedSuite.failedExpectations[j].message)); + alert.appendChild(createDom('span', {className: errorBarClassName}, afterAllMessagePrefix + failedSuite.failedExpectations[j].message)); } } for(i = 0; i < globalFailures.length; i++) { var failure = globalFailures[i]; - alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessagePrefix + failure.message)); + var prefix = failure.globalErrorType === 'load' ? 'Error during loading: ' : afterAllMessagePrefix; + alert.appendChild(createDom('span', {className: errorBarClassName}, prefix + failure.message)); } var results = find('.jasmine-results'); diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 023efe24..a0a4b525 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -778,6 +778,7 @@ getJasmineRequireObj().Env = function(j$) { if (!suppressLoadErrors) { topSuite.result.failedExpectations.push({ passed: false, + globalErrorType: 'load', message: message }); } @@ -5177,7 +5178,13 @@ getJasmineRequireObj().Suite = function(j$) { actual: '', error: arguments[0] }; - this.result.failedExpectations.push(this.expectationResultFactory(data)); + var failedExpectation = this.expectationResultFactory(data); + + if (!this.parentSuite) { + failedExpectation.globalErrorType = 'afterAll'; + } + + this.result.failedExpectations.push(failedExpectation); } else { for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index df737be8..50b249f5 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -422,6 +422,49 @@ describe("Env integration", function() { env.execute(); }); + it("tags top-level afterAll failures with a type", function(done) { + var env = new jasmineUnderTest.Env(); + + env.addReporter({jasmineDone: function(result) { + expect(result.failedExpectations[0].globalErrorType).toEqual('afterAll'); + done(); + }}); + + env.it('has a spec', function() {}); + + env.afterAll(function() { + debugger; + throw 'nope'; + }); + + env.execute(); + }); + + it("does not tag suite afterAll failures with a type", function(done) { + var env = new jasmineUnderTest.Env(), + reporter = { + jasmineDone: function() { + expect(reporter.suiteDone).toHaveBeenCalled(); + done(); + }, + suiteDone: jasmine.createSpy('suiteDone').and.callFake(function(result) { + expect(result.failedExpectations[0].globalErrorType).toBeFalsy(); + }) + } + + env.addReporter(reporter); + + env.describe('a suite', function() { + env.it('has a spec', function() {}); + + env.afterAll(function() { + throw 'nope'; + }); + }); + + env.execute(); + }); + it("fails all underlying specs when the beforeAll fails", function (done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]); @@ -1991,10 +2034,12 @@ describe("Env integration", function() { expect(e.failedExpectations).toEqual([ { passed: false, + globalErrorType: 'load', message: 'Uncaught SyntaxError: Unexpected end of input' }, { passed: false, + globalErrorType: 'load', message: 'Uncaught Error: ENOCHEESE' } ]); diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 10f04fa5..b88cafa7 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -195,16 +195,23 @@ describe("HtmlReporter", function() { reporter.jasmineStarted({}); reporter.suiteDone({ status: 'failed', failedExpectations: [{ message: 'My After All Exception' }] }); reporter.suiteDone({ status: 'failed', failedExpectations: [{ message: 'My Other Exception' }] }); - reporter.jasmineDone({ failedExpectations: [{ message: 'Global After All Failure' }, { message: 'Other Global' }] }); + reporter.jasmineDone({ failedExpectations: [ + { message: 'Global After All Failure', globalErrorType: 'afterAll' }, + { message: 'Other Global' }, + { message: 'Your JS is borken', globalErrorType: 'load' } + ] }); var alertBars = container.querySelectorAll(".jasmine-alert .jasmine-bar"); - expect(alertBars.length).toEqual(5); + expect(alertBars.length).toEqual(6); expect(alertBars[1].innerHTML).toMatch(/My After All Exception/); expect(alertBars[1].getAttribute("class")).toEqual('jasmine-bar jasmine-errored'); expect(alertBars[2].innerHTML).toMatch(/My Other Exception/); - expect(alertBars[3].innerHTML).toMatch(/Global After All Failure/); + expect(alertBars[3].innerHTML).toMatch(/AfterAll Global After All Failure/); + // TODO: What about this? expect(alertBars[4].innerHTML).toMatch(/Other Global/); + + expect(alertBars[5].innerHTML).toMatch(/Error during loading: Your JS is borken/); }); }); diff --git a/src/core/Env.js b/src/core/Env.js index d13fdbb6..4875623f 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -97,6 +97,7 @@ getJasmineRequireObj().Env = function(j$) { if (!suppressLoadErrors) { topSuite.result.failedExpectations.push({ passed: false, + globalErrorType: 'load', message: message }); } diff --git a/src/core/Suite.js b/src/core/Suite.js index e85ab04e..ed7ed3fb 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -119,7 +119,13 @@ getJasmineRequireObj().Suite = function(j$) { actual: '', error: arguments[0] }; - this.result.failedExpectations.push(this.expectationResultFactory(data)); + var failedExpectation = this.expectationResultFactory(data); + + if (!this.parentSuite) { + failedExpectation.globalErrorType = 'afterAll'; + } + + this.result.failedExpectations.push(failedExpectation); } else { for (var i = 0; i < this.children.length; i++) { var child = this.children[i]; diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index 8ae8998c..da5b7df2 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -213,18 +213,19 @@ jasmineRequire.HtmlReporter = function(j$) { alert.appendChild(createDom('span', {className: statusBarClassName}, statusBarMessage, seedBar)); var errorBarClassName = 'jasmine-bar jasmine-errored'; - var errorBarMessagePrefix = 'AfterAll '; + var afterAllMessagePrefix = 'AfterAll '; for(var i = 0; i < failedSuites.length; i++) { var failedSuite = failedSuites[i]; for(var j = 0; j < failedSuite.failedExpectations.length; j++) { - alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessagePrefix + failedSuite.failedExpectations[j].message)); + alert.appendChild(createDom('span', {className: errorBarClassName}, afterAllMessagePrefix + failedSuite.failedExpectations[j].message)); } } for(i = 0; i < globalFailures.length; i++) { var failure = globalFailures[i]; - alert.appendChild(createDom('span', {className: errorBarClassName}, errorBarMessagePrefix + failure.message)); + var prefix = failure.globalErrorType === 'load' ? 'Error during loading: ' : afterAllMessagePrefix; + alert.appendChild(createDom('span', {className: errorBarClassName}, prefix + failure.message)); } var results = find('.jasmine-results'); From ae9b95269c75706ca69ae1691635f8ad773292ed Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Thu, 2 Nov 2017 18:17:40 -0700 Subject: [PATCH 10/62] Load error handling doesn't depend on browser features, so test it everywher [#24901981] --- spec/core/integration/EnvSpec.js | 98 +++++++++++++++----------------- 1 file changed, 47 insertions(+), 51 deletions(-) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 50b249f5..cc5a44e5 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -2018,65 +2018,61 @@ describe("Env integration", function() { env.execute(); }); - describe("In a browser", function() { - if (typeof document !== 'undefined') { - it('reports errors that occur during loading', function(done) { - var global = { - setTimeout: function(fn, delay) { setTimeout(fn, delay) }, - clearTimeout: function(fn, delay) { clearTimeout(fn, delay) }, - }; - spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); + it('reports errors that occur during loading', function(done) { + var global = { + setTimeout: function(fn, delay) { setTimeout(fn, delay) }, + clearTimeout: function(fn, delay) { clearTimeout(fn, delay) }, + }; + spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); - var env = new jasmineUnderTest.Env(), - reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - reporter.jasmineDone.and.callFake(function(e) { - expect(e.failedExpectations).toEqual([ - { - passed: false, - globalErrorType: 'load', - message: 'Uncaught SyntaxError: Unexpected end of input' - }, - { - passed: false, - globalErrorType: 'load', - message: 'Uncaught Error: ENOCHEESE' - } - ]); + reporter.jasmineDone.and.callFake(function(e) { + expect(e.failedExpectations).toEqual([ + { + passed: false, + globalErrorType: 'load', + message: 'Uncaught SyntaxError: Unexpected end of input' + }, + { + passed: false, + globalErrorType: 'load', + message: 'Uncaught Error: ENOCHEESE' + } + ]); - done(); - }); + done(); + }); - env.addReporter(reporter); - global.onerror('Uncaught SyntaxError: Unexpected end of input'); - global.onerror('Uncaught Error: ENOCHEESE'); + env.addReporter(reporter); + global.onerror('Uncaught SyntaxError: Unexpected end of input'); + global.onerror('Uncaught Error: ENOCHEESE'); - env.execute(); + env.execute(); + }); + + describe('If suppressLoadErrors was called', function() { + it('does not report errors that occur during loading', function(done) { + var global = { + setTimeout: function(fn, delay) { setTimeout(fn, delay) }, + clearTimeout: function(fn, delay) { clearTimeout(fn, delay) }, + }; + spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); + + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.failedExpectations).toEqual([]); + done(); }); - } - describe('If suppressLoadErrors was called', function() { - it('does not report errors that occur during loading', function(done) { - var global = { - setTimeout: function(fn, delay) { setTimeout(fn, delay) }, - clearTimeout: function(fn, delay) { clearTimeout(fn, delay) }, - }; - spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); + env.addReporter(reporter); + env.suppressLoadErrors(true); + global.onerror('Uncaught Error: ENOCHEESE'); - var env = new jasmineUnderTest.Env(), - reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - - reporter.jasmineDone.and.callFake(function(e) { - expect(e.failedExpectations).toEqual([]); - done(); - }); - - env.addReporter(reporter); - env.suppressLoadErrors(true); - global.onerror('Uncaught Error: ENOCHEESE'); - - env.execute(); - }); + env.execute(); }); }); }); From 82eeed3c8553010434def262616f4e7f0e76df92 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Sat, 4 Nov 2017 10:28:42 -0700 Subject: [PATCH 11/62] Improved reporting of load errors and afterAll errors - Pass file and line number to reporters when present - Show file and line number in the HTML reporter when present - Visually separate adjacent errors in the HTML reporter [#24901981] --- lib/jasmine-core/jasmine-html.js | 18 +++++++++++++--- lib/jasmine-core/jasmine.css | 3 +-- lib/jasmine-core/jasmine.js | 6 ++++-- spec/core/integration/EnvSpec.js | 10 ++++++--- spec/html/HtmlReporterSpec.js | 36 +++++++++++++++++++++++++++----- src/core/Env.js | 6 ++++-- src/html/HtmlReporter.js | 18 +++++++++++++--- src/html/_HTMLReporter.scss | 7 ++----- 8 files changed, 79 insertions(+), 25 deletions(-) diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 6a871546..4f9722bd 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -252,9 +252,21 @@ jasmineRequire.HtmlReporter = function(j$) { } for(i = 0; i < globalFailures.length; i++) { - var failure = globalFailures[i]; - var prefix = failure.globalErrorType === 'load' ? 'Error during loading: ' : afterAllMessagePrefix; - alert.appendChild(createDom('span', {className: errorBarClassName}, prefix + failure.message)); + alert.appendChild(createDom('span', {className: errorBarClassName}, globalFailureMessage(globalFailures[i]))); + } + + function globalFailureMessage(failure) { + if (failure.globalErrorType === 'load') { + var prefix = 'Error during loading: ' + failure.message; + + if (failure.filename) { + return prefix + ' in ' + failure.filename + ' line ' + failure.lineno; + } else { + return prefix; + } + } else { + return afterAllMessagePrefix + failure.message; + } } var results = find('.jasmine-results'); diff --git a/lib/jasmine-core/jasmine.css b/lib/jasmine-core/jasmine.css index 63199827..02042214 100644 --- a/lib/jasmine-core/jasmine.css +++ b/lib/jasmine-core/jasmine.css @@ -29,10 +29,9 @@ body { overflow-y: scroll; } .jasmine_html-reporter .jasmine-run-options .jasmine-payload { position: absolute; display: none; right: -1px; border: 1px solid #8a4182; background-color: #eee; white-space: nowrap; padding: 4px 8px; } .jasmine_html-reporter .jasmine-run-options .jasmine-payload.jasmine-open { display: block; } .jasmine_html-reporter .jasmine-bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } -.jasmine_html-reporter .jasmine-bar.jasmine-failed { background-color: #ca3a11; } +.jasmine_html-reporter .jasmine-bar.jasmine-failed, .jasmine_html-reporter .jasmine-bar.jasmine-errored { background-color: #ca3a11; border-bottom: 1px solid #eee; } .jasmine_html-reporter .jasmine-bar.jasmine-passed { background-color: #007069; } .jasmine_html-reporter .jasmine-bar.jasmine-skipped { background-color: #bababa; } -.jasmine_html-reporter .jasmine-bar.jasmine-errored { background-color: #ca3a11; } .jasmine_html-reporter .jasmine-bar.jasmine-menu { background-color: #fff; color: #aaa; } .jasmine_html-reporter .jasmine-bar.jasmine-menu a { color: #333; } .jasmine_html-reporter .jasmine-bar a { color: white; } diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index a0a4b525..52304872 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -774,12 +774,14 @@ getJasmineRequireObj().Env = function(j$) { var globalErrors = new j$.GlobalErrors(); globalErrors.install(); - globalErrors.pushListener(function(message) { + globalErrors.pushListener(function(message, filename, lineno) { if (!suppressLoadErrors) { topSuite.result.failedExpectations.push({ passed: false, globalErrorType: 'load', - message: message + message: message, + filename: filename, + lineno: lineno }); } }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index cc5a44e5..8bbad5fe 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -2033,12 +2033,16 @@ describe("Env integration", function() { { passed: false, globalErrorType: 'load', - message: 'Uncaught SyntaxError: Unexpected end of input' + message: 'Uncaught SyntaxError: Unexpected end of input', + filename: 'borkenSpec.js', + lineno: 42 }, { passed: false, globalErrorType: 'load', - message: 'Uncaught Error: ENOCHEESE' + message: 'Uncaught Error: ENOCHEESE', + filename: undefined, + lineno: undefined } ]); @@ -2046,7 +2050,7 @@ describe("Env integration", function() { }); env.addReporter(reporter); - global.onerror('Uncaught SyntaxError: Unexpected end of input'); + global.onerror('Uncaught SyntaxError: Unexpected end of input', 'borkenSpec.js', 42); global.onerror('Uncaught Error: ENOCHEESE'); env.execute(); diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index b88cafa7..84bcd1a3 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -197,21 +197,47 @@ describe("HtmlReporter", function() { reporter.suiteDone({ status: 'failed', failedExpectations: [{ message: 'My Other Exception' }] }); reporter.jasmineDone({ failedExpectations: [ { message: 'Global After All Failure', globalErrorType: 'afterAll' }, - { message: 'Other Global' }, { message: 'Your JS is borken', globalErrorType: 'load' } ] }); var alertBars = container.querySelectorAll(".jasmine-alert .jasmine-bar"); - expect(alertBars.length).toEqual(6); + expect(alertBars.length).toEqual(5); expect(alertBars[1].innerHTML).toMatch(/My After All Exception/); expect(alertBars[1].getAttribute("class")).toEqual('jasmine-bar jasmine-errored'); expect(alertBars[2].innerHTML).toMatch(/My Other Exception/); expect(alertBars[3].innerHTML).toMatch(/AfterAll Global After All Failure/); - // TODO: What about this? - expect(alertBars[4].innerHTML).toMatch(/Other Global/); + expect(alertBars[4].innerHTML).toMatch(/Error during loading: Your JS is borken/); + expect(alertBars[4].innerHTML).not.toMatch(/line/); + }); - expect(alertBars[5].innerHTML).toMatch(/Error during loading: Your JS is borken/); + it("displays file and line information if available", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + getContainer = function() { return container; }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: getContainer, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + + reporter.initialize(); + + reporter.jasmineStarted({}); + reporter.jasmineDone({ failedExpectations: [ + { + message: 'Your JS is borken', + globalErrorType: 'load', + filename: 'some/file.js', + lineno: 42 + } + ] }); + + var alertBars = container.querySelectorAll(".jasmine-alert .jasmine-bar"); + + expect(alertBars.length).toEqual(2); + expect(alertBars[1].innerHTML).toMatch(/Error during loading: Your JS is borken in some\/file.js line 42/); }); }); diff --git a/src/core/Env.js b/src/core/Env.js index 4875623f..966a6065 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -93,12 +93,14 @@ getJasmineRequireObj().Env = function(j$) { var globalErrors = new j$.GlobalErrors(); globalErrors.install(); - globalErrors.pushListener(function(message) { + globalErrors.pushListener(function(message, filename, lineno) { if (!suppressLoadErrors) { topSuite.result.failedExpectations.push({ passed: false, globalErrorType: 'load', - message: message + message: message, + filename: filename, + lineno: lineno }); } }); diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index da5b7df2..cbcd41b8 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -223,9 +223,21 @@ jasmineRequire.HtmlReporter = function(j$) { } for(i = 0; i < globalFailures.length; i++) { - var failure = globalFailures[i]; - var prefix = failure.globalErrorType === 'load' ? 'Error during loading: ' : afterAllMessagePrefix; - alert.appendChild(createDom('span', {className: errorBarClassName}, prefix + failure.message)); + alert.appendChild(createDom('span', {className: errorBarClassName}, globalFailureMessage(globalFailures[i]))); + } + + function globalFailureMessage(failure) { + if (failure.globalErrorType === 'load') { + var prefix = 'Error during loading: ' + failure.message; + + if (failure.filename) { + return prefix + ' in ' + failure.filename + ' line ' + failure.lineno; + } else { + return prefix; + } + } else { + return afterAllMessagePrefix + failure.message; + } } var results = find('.jasmine-results'); diff --git a/src/html/_HTMLReporter.scss b/src/html/_HTMLReporter.scss index 093692b9..201ffdc1 100644 --- a/src/html/_HTMLReporter.scss +++ b/src/html/_HTMLReporter.scss @@ -200,8 +200,9 @@ body { display: block; color: #eee; - &.jasmine-failed { + &.jasmine-failed, &.jasmine-errored { background-color: $failing-color; + border-bottom: 1px solid $page-background-color; } &.jasmine-passed { @@ -212,10 +213,6 @@ body { background-color: $neutral-color; } - &.jasmine-errored { - background-color: $failing-color; - } - &.jasmine-menu { background-color: #fff; color: $faint-text-color; From 278ef9228bd06310da3cdb37ef2d8818dc9825d8 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 7 Nov 2017 12:24:44 -0800 Subject: [PATCH 12/62] Removed obsolete console reporter from core Anyone who was still using this should use the one in jasmine-npm instead. [Finishes #80410262] --- grunt/config/concat.js | 7 - lib/console/console.js | 168 ------------- lib/jasmine-core/boot/node_boot.js | 3 - lib/jasmine-core/node_boot.js | 3 - spec/console/ConsoleReporterSpec.js | 270 --------------------- spec/helpers/nodeDefineJasmineUnderTest.js | 5 +- src/console/ConsoleReporter.js | 155 ------------ src/console/requireConsole.js | 12 - 8 files changed, 1 insertion(+), 622 deletions(-) delete mode 100644 spec/console/ConsoleReporterSpec.js delete mode 100644 src/console/ConsoleReporter.js delete mode 100644 src/console/requireConsole.js diff --git a/grunt/config/concat.js b/grunt/config/concat.js index d8ab504e..6516e175 100644 --- a/grunt/config/concat.js +++ b/grunt/config/concat.js @@ -44,13 +44,6 @@ module.exports = { src: ['lib/jasmine-core/boot/node_boot.js'], dest: 'lib/jasmine-core/node_boot.js' }, - console: { - src: [ - 'src/console/requireConsole.js', - 'src/console/ConsoleReporter.js' - ], - dest: 'lib/console/console.js' - }, options: { banner: license(), process: { diff --git a/lib/console/console.js b/lib/console/console.js index 38ad952d..038ee41d 100644 --- a/lib/console/console.js +++ b/lib/console/console.js @@ -20,171 +20,3 @@ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -function getJasmineRequireObj() { - if (typeof module !== 'undefined' && module.exports) { - return exports; - } else { - window.jasmineRequire = window.jasmineRequire || {}; - return window.jasmineRequire; - } -} - -getJasmineRequireObj().console = function(jRequire, j$) { - j$.ConsoleReporter = jRequire.ConsoleReporter(); -}; - -getJasmineRequireObj().ConsoleReporter = function() { - - var noopTimer = { - start: function(){}, - elapsed: function(){ return 0; } - }; - - function ConsoleReporter(options) { - var print = options.print, - showColors = options.showColors || false, - onComplete = options.onComplete || function() {}, - timer = options.timer || noopTimer, - specCount, - failureCount, - failedSpecs = [], - pendingCount, - ansi = { - green: '\x1B[32m', - red: '\x1B[31m', - yellow: '\x1B[33m', - none: '\x1B[0m' - }, - failedSuites = []; - - print('ConsoleReporter is deprecated and will be removed in a future version.'); - - this.jasmineStarted = function() { - specCount = 0; - failureCount = 0; - pendingCount = 0; - print('Started'); - printNewline(); - timer.start(); - }; - - this.jasmineDone = function() { - printNewline(); - for (var i = 0; i < failedSpecs.length; i++) { - specFailureDetails(failedSpecs[i]); - } - - if(specCount > 0) { - printNewline(); - - var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' + - failureCount + ' ' + plural('failure', failureCount); - - if (pendingCount) { - specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount); - } - - print(specCounts); - } else { - print('No specs found'); - } - - printNewline(); - var seconds = timer.elapsed() / 1000; - print('Finished in ' + seconds + ' ' + plural('second', seconds)); - printNewline(); - - for(i = 0; i < failedSuites.length; i++) { - suiteFailureDetails(failedSuites[i]); - } - - onComplete(failureCount === 0); - }; - - this.specDone = function(result) { - specCount++; - - if (result.status == 'pending') { - pendingCount++; - print(colored('yellow', '*')); - return; - } - - if (result.status == 'passed') { - print(colored('green', '.')); - return; - } - - if (result.status == 'failed') { - failureCount++; - failedSpecs.push(result); - print(colored('red', 'F')); - } - }; - - this.suiteDone = function(result) { - if (result.failedExpectations && result.failedExpectations.length > 0) { - failureCount++; - failedSuites.push(result); - } - }; - - return this; - - function printNewline() { - print('\n'); - } - - function colored(color, str) { - return showColors ? (ansi[color] + str + ansi.none) : str; - } - - function plural(str, count) { - return count == 1 ? str : str + 's'; - } - - function repeat(thing, times) { - var arr = []; - for (var i = 0; i < times; i++) { - arr.push(thing); - } - return arr; - } - - function indent(str, spaces) { - var lines = (str || '').split('\n'); - var newArr = []; - for (var i = 0; i < lines.length; i++) { - newArr.push(repeat(' ', spaces).join('') + lines[i]); - } - return newArr.join('\n'); - } - - function specFailureDetails(result) { - printNewline(); - print(result.fullName); - - for (var i = 0; i < result.failedExpectations.length; i++) { - var failedExpectation = result.failedExpectations[i]; - printNewline(); - print(indent(failedExpectation.message, 2)); - print(indent(failedExpectation.stack, 2)); - } - - printNewline(); - } - - function suiteFailureDetails(result) { - for (var i = 0; i < result.failedExpectations.length; i++) { - printNewline(); - print(colored('red', 'An error was thrown in an afterAll')); - printNewline(); - print(colored('red', 'AfterAll ' + result.failedExpectations[i].message)); - - } - printNewline(); - } - } - - return ConsoleReporter; -}; diff --git a/lib/jasmine-core/boot/node_boot.js b/lib/jasmine-core/boot/node_boot.js index 6a4caf0d..304bcd12 100644 --- a/lib/jasmine-core/boot/node_boot.js +++ b/lib/jasmine-core/boot/node_boot.js @@ -1,9 +1,6 @@ module.exports = function(jasmineRequire) { var jasmine = jasmineRequire.core(jasmineRequire); - var consoleFns = require('../console/console.js'); - consoleFns.console(consoleFns, jasmine); - var env = jasmine.getEnv(); var jasmineInterface = jasmineRequire.interface(jasmine, env); diff --git a/lib/jasmine-core/node_boot.js b/lib/jasmine-core/node_boot.js index 7268ae9b..ce90a3ba 100644 --- a/lib/jasmine-core/node_boot.js +++ b/lib/jasmine-core/node_boot.js @@ -23,9 +23,6 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. module.exports = function(jasmineRequire) { var jasmine = jasmineRequire.core(jasmineRequire); - var consoleFns = require('../console/console.js'); - consoleFns.console(consoleFns, jasmine); - var env = jasmine.getEnv(); var jasmineInterface = jasmineRequire.interface(jasmine, env); diff --git a/spec/console/ConsoleReporterSpec.js b/spec/console/ConsoleReporterSpec.js deleted file mode 100644 index 7e0c1a4f..00000000 --- a/spec/console/ConsoleReporterSpec.js +++ /dev/null @@ -1,270 +0,0 @@ -describe("ConsoleReporter", function() { - var out; - - beforeEach(function() { - out = (function() { - var output = ""; - return { - print: function(str) { - output += str; - }, - getOutput: function() { - return output.replace('ConsoleReporter is deprecated and will be removed in a future version.', ''); - }, - clear: function() { - output = ""; - } - }; - }()); - }); - - it("reports that the suite has started to the console", function() { - var reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print - }); - - reporter.jasmineStarted(); - - expect(out.getOutput()).toEqual("Started\n"); - }); - - it("starts the provided timer when jasmine starts", function() { - var timerSpy = jasmine.createSpyObj('timer', ['start']), - reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print, - timer: timerSpy - }); - - reporter.jasmineStarted(); - - expect(timerSpy.start).toHaveBeenCalled(); - }); - - it("reports a passing spec as a dot", function() { - var reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print - }); - - reporter.specDone({status: "passed"}); - - expect(out.getOutput()).toEqual("."); - }); - - it("does not report a disabled spec", function() { - var reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print - }); - - reporter.specDone({status: "disabled"}); - - expect(out.getOutput()).toEqual(""); - }); - - it("reports a failing spec as an 'F'", function() { - var reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print - }); - - reporter.specDone({status: "failed"}); - - expect(out.getOutput()).toEqual("F"); - }); - - it("reports a pending spec as a '*'", function() { - var reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print - }); - - reporter.specDone({status: "pending"}); - - expect(out.getOutput()).toEqual("*"); - }); - - it("alerts user if there are no specs", function(){ - var reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print - }); - - reporter.jasmineStarted(); - out.clear(); - reporter.jasmineDone(); - - expect(out.getOutput()).toMatch(/No specs found/); - }); - - it("reports a summary when done (singular spec and time)", function() { - var timerSpy = jasmine.createSpyObj('timer', ['start', 'elapsed']), - reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print, - timer: timerSpy - }); - - reporter.jasmineStarted(); - reporter.specDone({status: "passed"}); - - timerSpy.elapsed.and.returnValue(1000); - - out.clear(); - reporter.jasmineDone(); - - expect(out.getOutput()).toMatch(/1 spec, 0 failures/); - expect(out.getOutput()).not.toMatch(/0 pending specs/); - expect(out.getOutput()).toMatch("Finished in 1 second\n"); - }); - - it("reports a summary when done (pluralized specs and seconds)", function() { - var timerSpy = jasmine.createSpyObj('timer', ['start', 'elapsed']), - reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print, - timer: timerSpy - }); - - reporter.jasmineStarted(); - reporter.specDone({status: "passed"}); - reporter.specDone({status: "pending"}); - reporter.specDone({ - status: "failed", - description: "with a failing spec", - fullName: "A suite with a failing spec", - failedExpectations: [ - { - passed: false, - message: "Expected true to be false.", - expected: false, - actual: true, - stack: "foo\nbar\nbaz" - } - ] - }); - - out.clear(); - - timerSpy.elapsed.and.returnValue(100); - - reporter.jasmineDone(); - - expect(out.getOutput()).toMatch(/3 specs, 1 failure, 1 pending spec/); - expect(out.getOutput()).toMatch("Finished in 0.1 seconds\n"); - }); - - it("reports a summary when done that includes stack traces for a failing suite", function() { - var reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print - }); - - reporter.jasmineStarted(); - reporter.specDone({status: "passed"}); - reporter.specDone({ - status: "failed", - description: "with a failing spec", - fullName: "A suite with a failing spec", - failedExpectations: [ - { - passed: false, - message: "Expected true to be false.", - expected: false, - actual: true, - stack: "foo bar baz" - } - ] - }); - - out.clear(); - - reporter.jasmineDone(); - - expect(out.getOutput()).toMatch(/true to be false/); - expect(out.getOutput()).toMatch(/foo bar baz/); - }); - - describe('onComplete callback', function(){ - var onComplete, reporter; - - beforeEach(function() { - onComplete = jasmine.createSpy('onComplete'); - reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print, - onComplete: onComplete - }); - reporter.jasmineStarted(); - }); - - it("is called when the suite is done", function() { - reporter.jasmineDone(); - expect(onComplete).toHaveBeenCalledWith(true); - }); - - it('calls it with false if there are spec failures', function() { - reporter.specDone({status: "failed", failedExpectations: []}); - reporter.jasmineDone(); - expect(onComplete).toHaveBeenCalledWith(false); - }); - - it('calls it with false if there are suite failures', function() { - reporter.specDone({status: "passed"}); - reporter.suiteDone({failedExpectations: [{ message: 'bananas' }] }); - reporter.jasmineDone(); - expect(onComplete).toHaveBeenCalledWith(false); - }); - }); - - describe("with color", function() { - it("reports that the suite has started to the console", function() { - var reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print, - showColors: true - }); - - reporter.jasmineStarted(); - - expect(out.getOutput()).toEqual("Started\n"); - }); - - it("reports a passing spec as a dot", function() { - var reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print, - showColors: true - }); - - reporter.specDone({status: "passed"}); - - expect(out.getOutput()).toEqual("\x1B[32m.\x1B[0m"); - }); - - it("does not report a disabled spec", function() { - var reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print, - showColors: true - }); - - reporter.specDone({status: 'disabled'}); - - expect(out.getOutput()).toEqual(""); - }); - - it("reports a failing spec as an 'F'", function() { - var reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print, - showColors: true - }); - - reporter.specDone({status: 'failed'}); - - expect(out.getOutput()).toEqual("\x1B[31mF\x1B[0m"); - }); - - it("displays all afterAll exceptions", function() { - var reporter = new jasmineUnderTest.ConsoleReporter({ - print: out.print, - showColors: true - }); - - reporter.suiteDone({ failedExpectations: [{ message: 'After All Exception' }] }); - reporter.suiteDone({ failedExpectations: [{ message: 'Some Other Exception' }] }); - reporter.jasmineDone(); - - expect(out.getOutput()).toMatch(/After All Exception/); - expect(out.getOutput()).toMatch(/Some Other Exception/); - }); - }); -}); diff --git a/spec/helpers/nodeDefineJasmineUnderTest.js b/spec/helpers/nodeDefineJasmineUnderTest.js index f1b4d97a..66ab129a 100644 --- a/spec/helpers/nodeDefineJasmineUnderTest.js +++ b/spec/helpers/nodeDefineJasmineUnderTest.js @@ -16,7 +16,7 @@ } function getSourceFiles() { - var src_files = ['core/**/*.js', 'console/**/*.js', 'version.js']; + var src_files = ['core/**/*.js', 'version.js']; src_files.forEach(function(file) { var filePath = path.join(__dirname, "../../", 'src/', file); glob.sync(filePath).forEach(function(resolvedFile) { @@ -25,9 +25,6 @@ }); } - extend(jasmineUnderTestRequire, require(path.join(__dirname,"../../src/console/requireConsole.js"))); getSourceFiles(); global.jasmineUnderTest = jasmineUnderTestRequire.core(jasmineUnderTestRequire); - - jasmineUnderTestRequire.console(jasmineUnderTestRequire, jasmineUnderTest); })(); diff --git a/src/console/ConsoleReporter.js b/src/console/ConsoleReporter.js deleted file mode 100644 index 583ad94e..00000000 --- a/src/console/ConsoleReporter.js +++ /dev/null @@ -1,155 +0,0 @@ -getJasmineRequireObj().ConsoleReporter = function() { - - var noopTimer = { - start: function(){}, - elapsed: function(){ return 0; } - }; - - function ConsoleReporter(options) { - var print = options.print, - showColors = options.showColors || false, - onComplete = options.onComplete || function() {}, - timer = options.timer || noopTimer, - specCount, - failureCount, - failedSpecs = [], - pendingCount, - ansi = { - green: '\x1B[32m', - red: '\x1B[31m', - yellow: '\x1B[33m', - none: '\x1B[0m' - }, - failedSuites = []; - - print('ConsoleReporter is deprecated and will be removed in a future version.'); - - this.jasmineStarted = function() { - specCount = 0; - failureCount = 0; - pendingCount = 0; - print('Started'); - printNewline(); - timer.start(); - }; - - this.jasmineDone = function() { - printNewline(); - for (var i = 0; i < failedSpecs.length; i++) { - specFailureDetails(failedSpecs[i]); - } - - if(specCount > 0) { - printNewline(); - - var specCounts = specCount + ' ' + plural('spec', specCount) + ', ' + - failureCount + ' ' + plural('failure', failureCount); - - if (pendingCount) { - specCounts += ', ' + pendingCount + ' pending ' + plural('spec', pendingCount); - } - - print(specCounts); - } else { - print('No specs found'); - } - - printNewline(); - var seconds = timer.elapsed() / 1000; - print('Finished in ' + seconds + ' ' + plural('second', seconds)); - printNewline(); - - for(i = 0; i < failedSuites.length; i++) { - suiteFailureDetails(failedSuites[i]); - } - - onComplete(failureCount === 0); - }; - - this.specDone = function(result) { - specCount++; - - if (result.status == 'pending') { - pendingCount++; - print(colored('yellow', '*')); - return; - } - - if (result.status == 'passed') { - print(colored('green', '.')); - return; - } - - if (result.status == 'failed') { - failureCount++; - failedSpecs.push(result); - print(colored('red', 'F')); - } - }; - - this.suiteDone = function(result) { - if (result.failedExpectations && result.failedExpectations.length > 0) { - failureCount++; - failedSuites.push(result); - } - }; - - return this; - - function printNewline() { - print('\n'); - } - - function colored(color, str) { - return showColors ? (ansi[color] + str + ansi.none) : str; - } - - function plural(str, count) { - return count == 1 ? str : str + 's'; - } - - function repeat(thing, times) { - var arr = []; - for (var i = 0; i < times; i++) { - arr.push(thing); - } - return arr; - } - - function indent(str, spaces) { - var lines = (str || '').split('\n'); - var newArr = []; - for (var i = 0; i < lines.length; i++) { - newArr.push(repeat(' ', spaces).join('') + lines[i]); - } - return newArr.join('\n'); - } - - function specFailureDetails(result) { - printNewline(); - print(result.fullName); - - for (var i = 0; i < result.failedExpectations.length; i++) { - var failedExpectation = result.failedExpectations[i]; - printNewline(); - print(indent(failedExpectation.message, 2)); - print(indent(failedExpectation.stack, 2)); - } - - printNewline(); - } - - function suiteFailureDetails(result) { - for (var i = 0; i < result.failedExpectations.length; i++) { - printNewline(); - print(colored('red', 'An error was thrown in an afterAll')); - printNewline(); - print(colored('red', 'AfterAll ' + result.failedExpectations[i].message)); - - } - printNewline(); - } - } - - return ConsoleReporter; -}; diff --git a/src/console/requireConsole.js b/src/console/requireConsole.js deleted file mode 100644 index a4515172..00000000 --- a/src/console/requireConsole.js +++ /dev/null @@ -1,12 +0,0 @@ -function getJasmineRequireObj() { - if (typeof module !== 'undefined' && module.exports) { - return exports; - } else { - window.jasmineRequire = window.jasmineRequire || {}; - return window.jasmineRequire; - } -} - -getJasmineRequireObj().console = function(jRequire, j$) { - j$.ConsoleReporter = jRequire.ConsoleReporter(); -}; From a3cb01097158bfc7fba2fe1da590cb10519e145d Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 7 Nov 2017 20:09:04 -0800 Subject: [PATCH 13/62] Removed remaining traces of the obsolete console reporter [#80410262] --- grunt/config/compress.js | 9 --------- spec/helpers/defineJasmineUnderTest.js | 1 - spec/support/jasmine.json | 1 - 3 files changed, 11 deletions(-) diff --git a/grunt/config/compress.js b/grunt/config/compress.js index ab994db8..af3d1a5a 100644 --- a/grunt/config/compress.js +++ b/grunt/config/compress.js @@ -2,7 +2,6 @@ var standaloneLibDir = "lib/jasmine-" + jasmineVersion; function root(path) { return "./" + path; } function libJasmineCore(path) { return root("lib/jasmine-core/" + path); } -function libConsole() { return "lib/console/" } function dist(path) { return root("dist/" + path); } module.exports = { @@ -29,14 +28,6 @@ module.exports = { expand: true, cwd: libJasmineCore("") }, - { - src: [ - "console.js" - ], - dest: standaloneLibDir, - expand: true, - cwd: libConsole() - }, { src: [ "boot.js" ], dest: standaloneLibDir, diff --git a/spec/helpers/defineJasmineUnderTest.js b/spec/helpers/defineJasmineUnderTest.js index b586c77a..4996387d 100644 --- a/spec/helpers/defineJasmineUnderTest.js +++ b/spec/helpers/defineJasmineUnderTest.js @@ -3,5 +3,4 @@ // to the Jasmine source files (and not jasmine.js). So re-require window.jasmineUnderTest = jasmineRequire.core(jasmineRequire); jasmineRequire.html(jasmineUnderTest); - jasmineRequire.console(jasmineRequire, jasmineUnderTest); })(); diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json index a6e286df..6f89d24a 100644 --- a/spec/support/jasmine.json +++ b/spec/support/jasmine.json @@ -2,7 +2,6 @@ "spec_dir": "spec", "spec_files": [ "core/**/*.js", - "console/**/*.js", "npmPackage/**/*.js" ], "helpers": [ From 1526d5e2a8c644d29966d763fa728463f8f444d9 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 7 Nov 2017 20:48:23 -0800 Subject: [PATCH 14/62] Treat afterAll errors at any level as failures [Finishes #152492514] --- lib/jasmine-core/jasmine-html.js | 2 +- spec/core/integration/EnvSpec.js | 1 - spec/html/HtmlReporterSpec.js | 21 +++++++++++++++++++++ src/html/HtmlReporter.js | 2 +- 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 4f9722bd..26248ed4 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -220,7 +220,7 @@ jasmineRequire.HtmlReporter = function(j$) { var statusBarMessage = ''; var statusBarClassName = 'jasmine-overall-result jasmine-bar '; var globalFailures = (doneResult && doneResult.failedExpectations) || []; - var failed = failureCount + globalFailures.length > 0; + var failed = failureCount + globalFailures.length + failedSuites.length > 0; if (totalSpecsDefined > 0 || failed) { statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 8bbad5fe..b5cfc29f 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -433,7 +433,6 @@ describe("Env integration", function() { env.it('has a spec', function() {}); env.afterAll(function() { - debugger; throw 'nope'; }); diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 84bcd1a3..87f7a093 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -179,6 +179,27 @@ describe("HtmlReporter", function() { }); describe("when there are suite failures", function () { + it("displays an overall result of failure even if no other failures occurred", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + getContainer = function() { return container; }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: getContainer, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + + reporter.initialize(); + + reporter.jasmineStarted({}); + reporter.suiteDone({ status: 'failed', failedExpectations: [{ message: 'My After All Exception' }] }); + reporter.jasmineDone({ failedExpectations: [] }); + + var alertBar = container.querySelector(".jasmine-overall-result"); + expect(alertBar.classList).toContain("jasmine-failed"); + }); + it("displays the exceptions in their own alert bars", function(){ var env = new jasmineUnderTest.Env(), container = document.createElement("div"), diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index cbcd41b8..e4ec91bf 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -191,7 +191,7 @@ jasmineRequire.HtmlReporter = function(j$) { var statusBarMessage = ''; var statusBarClassName = 'jasmine-overall-result jasmine-bar '; var globalFailures = (doneResult && doneResult.failedExpectations) || []; - var failed = failureCount + globalFailures.length > 0; + var failed = failureCount + globalFailures.length + failedSuites.length > 0; if (totalSpecsDefined > 0 || failed) { statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); From 419470e9df1ffc30f2aea0a8711f45e01b8c0a5c Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 7 Nov 2017 21:03:38 -0800 Subject: [PATCH 15/62] Removed support for IE <10 [#150527985] --- lib/jasmine-core/jasmine.js | 54 ++----------------- spec/core/ClockSpec.js | 31 ----------- spec/core/ExceptionFormatterSpec.js | 4 +- spec/core/PrettyPrintSpec.js | 14 +---- spec/core/SpyRegistrySpec.js | 18 ------- spec/core/UtilSpec.js | 3 -- .../ObjectContainingSpec.js | 3 -- spec/core/integration/EnvSpec.js | 9 ++-- spec/core/matchers/matchersUtilSpec.js | 13 ----- spec/core/matchers/toEqualSpec.js | 19 ------- spec/core/matchers/toThrowErrorSpec.js | 4 +- spec/helpers/BrowserFlags.js | 4 -- src/core/Clock.js | 17 ------ src/core/SpyRegistry.js | 14 +---- src/core/matchers/matchersUtil.js | 23 +------- 15 files changed, 15 insertions(+), 215 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 52304872..4d4bdb52 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -1948,22 +1948,10 @@ getJasmineRequireObj().Clock = function() { }; self.setTimeout = function(fn, delay, params) { - if (legacyIE()) { - if (arguments.length > 2) { - throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); - } - return timer.setTimeout(fn, delay); - } return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); }; self.setInterval = function(fn, delay, params) { - if (legacyIE()) { - if (arguments.length > 2) { - throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); - } - return timer.setInterval(fn, delay); - } return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); }; @@ -1998,11 +1986,6 @@ getJasmineRequireObj().Clock = function() { global.clearInterval === realTimingFunctions.clearInterval; } - function legacyIE() { - //if these methods are polyfilled, apply will be present - return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; - } - function replace(dest, source) { for (var prop in source) { dest[prop] = source[prop]; @@ -2667,28 +2650,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { var bIsDomNode = j$.isDomNode(b); if (aIsDomNode && bIsDomNode) { // At first try to use DOM3 method isEqualNode - if (a.isEqualNode) { - result = a.isEqualNode(b); - if (!result) { - diffBuilder.record(a, b); - } - return result; - } - // IE8 doesn't support isEqualNode, try to use outerHTML && innerText - var aIsElement = a instanceof Element; - var bIsElement = b instanceof Element; - if (aIsElement && bIsElement) { - result = a.outerHTML == b.outerHTML; - if (!result) { - diffBuilder.record(a, b); - } - return result; - } - if (aIsElement || bIsElement) { - diffBuilder.record(a, b); - return false; - } - result = a.innerText == b.innerText && a.textContent == b.textContent; + result = a.isEqualNode(b); if (!result) { diffBuilder.record(a, b); } @@ -4840,12 +4802,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { } } - var descriptor; - try { - descriptor = Object.getOwnPropertyDescriptor(obj, methodName); - } catch(e) { - // IE 8 doesn't support `definePropery` on non-DOM nodes - } + var descriptor = Object.getOwnPropertyDescriptor(obj, methodName); if (descriptor && !(descriptor.writable || descriptor.set)) { throw new Error(getErrorMsg(methodName + ' is not declared writable or has no setter')); @@ -4887,12 +4844,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { throw new Error('No property name supplied'); } - var descriptor; - try { - descriptor = j$.util.getPropertyDescriptor(obj, propertyName); - } catch(e) { - // IE 8 doesn't support `definePropery` on non-DOM nodes - } + var descriptor = j$.util.getPropertyDescriptor(obj, propertyName); if (!descriptor) { throw new Error(propertyName + ' property does not exist'); diff --git a/spec/core/ClockSpec.js b/spec/core/ClockSpec.js index 6b3ed1d0..e585149a 100644 --- a/spec/core/ClockSpec.js +++ b/spec/core/ClockSpec.js @@ -389,37 +389,6 @@ describe("Clock", function() { clock.tick(50); }).toThrow(); }); - - it("on IE < 9, fails if extra args are passed to fake clock", function() { - //fail, because this would break in IE9. - var fakeSetTimeout = jasmine.createSpy('setTimeout'), - fakeSetInterval = jasmine.createSpy('setInterval'), - delayedFunctionScheduler = jasmine.createSpyObj('delayedFunctionScheduler', ['scheduleFunction']), - fn = jasmine.createSpy('fn'), - fakeGlobal = { - setTimeout: fakeSetTimeout, - setInterval: fakeSetInterval - }, - mockDate = { install: function() {}, tick: function() {}, uninstall: function() {} }, - clock = new jasmineUnderTest.Clock(fakeGlobal, function () { return delayedFunctionScheduler; }, mockDate); - - fakeSetTimeout.apply = null; - fakeSetInterval.apply = null; - - clock.install(); - - clock.setTimeout(fn, 0); - expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(fn, 0, []); - expect(function() { - clock.setTimeout(fn, 0, 'extra'); - }).toThrow(); - - clock.setInterval(fn, 0); - expect(delayedFunctionScheduler.scheduleFunction).toHaveBeenCalledWith(fn, 0, [], true); - expect(function() { - clock.setInterval(fn, 0, 'extra'); - }).toThrow(); - }); }); describe("Clock (acceptance)", function() { diff --git a/spec/core/ExceptionFormatterSpec.js b/spec/core/ExceptionFormatterSpec.js index ef384dd7..6796ff62 100644 --- a/spec/core/ExceptionFormatterSpec.js +++ b/spec/core/ExceptionFormatterSpec.js @@ -47,8 +47,8 @@ describe("ExceptionFormatter", function() { }); describe("#stack", function() { - it("formats stack traces from Webkit, Firefox, node.js or IE10+", function() { - if (jasmine.getEnv().ieVersion < 10 || jasmine.getEnv().safariVersion < 6) { return; } + it("formats stack traces", function() { + if (jasmine.getEnv().safariVersion < 6) { return; } var error; try { throw new Error("an error") } catch(e) { error = e; } diff --git a/spec/core/PrettyPrintSpec.js b/spec/core/PrettyPrintSpec.js index d15aae10..931c2dba 100644 --- a/spec/core/PrettyPrintSpec.js +++ b/spec/core/PrettyPrintSpec.js @@ -250,11 +250,7 @@ describe("jasmineUnderTest.pp", function () { toString: function () { return Object.prototype.toString.call(this); } }; - if (jasmine.getEnv().ieVersion < 9) { - expect(jasmineUnderTest.pp(objFromOtherContext)).toEqual("Object({ foo: 'bar' })"); - } else { - expect(jasmineUnderTest.pp(objFromOtherContext)).toEqual("Object({ foo: 'bar', toString: Function })"); - } + expect(jasmineUnderTest.pp(objFromOtherContext)).toEqual("Object({ foo: 'bar', toString: Function })"); }); it("should stringify objects have have a toString that isn't a function", function() { @@ -262,11 +258,7 @@ describe("jasmineUnderTest.pp", function () { toString: "foo" }; - if (jasmine.getEnv().ieVersion < 9) { - expect(jasmineUnderTest.pp(obj)).toEqual("Object({ })"); - } else { - expect(jasmineUnderTest.pp(obj)).toEqual("Object({ toString: 'foo' })"); - } + expect(jasmineUnderTest.pp(obj)).toEqual("Object({ toString: 'foo' })"); }); it("should stringify objects from anonymous constructors with custom toString", function () { @@ -279,8 +271,6 @@ describe("jasmineUnderTest.pp", function () { }); it("should handle objects with null prototype", function() { - if (jasmine.getEnv().ieVersion < 9) { return; } - var obj = Object.create(null); obj.foo = 'bar'; diff --git a/spec/core/SpyRegistrySpec.js b/spec/core/SpyRegistrySpec.js index 4f09c932..b25dbaaa 100644 --- a/spec/core/SpyRegistrySpec.js +++ b/spec/core/SpyRegistrySpec.js @@ -54,9 +54,6 @@ describe("SpyRegistry", function() { }); it("checks if it can be spied upon", function() { - // IE 8 doesn't support `definePropery` on non-DOM nodes - if (jasmine.getEnv().ieVersion < 9) { return; } - var scope = {}; function myFunc() { @@ -94,9 +91,6 @@ describe("SpyRegistry", function() { }); describe("#spyOnProperty", function() { - // IE 8 doesn't support `definePropery` on non-DOM nodes - if (jasmine.getEnv().ieVersion < 9) { return; } - it("checks for the existence of the object", function() { var spyRegistry = new jasmineUnderTest.SpyRegistry(); expect(function() { @@ -246,9 +240,6 @@ describe("SpyRegistry", function() { }); it("does not add a property that the spied-upon object didn't originally have", function() { - // IE 8 doesn't support `Object.create` - if (jasmine.getEnv().ieVersion < 9) { return; } - var spies = [], spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), originalFunction = function() {}, @@ -266,9 +257,6 @@ describe("SpyRegistry", function() { }); it("restores the original function when it\'s inherited and cannot be deleted", function() { - // IE 8 doesn't support `Object.create` or `Object.defineProperty` - if (jasmine.getEnv().ieVersion < 9) { return; } - var spies = [], spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), originalFunction = function() {}, @@ -291,9 +279,6 @@ describe("SpyRegistry", function() { describe('spying on properties', function() { it("restores the original properties on the spied-upon objects", function() { - // IE 8 doesn't support `definePropery` on non-DOM nodes - if (jasmine.getEnv().ieVersion < 9) { return; } - var spies = [], spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), originalReturn = 1, @@ -311,9 +296,6 @@ describe("SpyRegistry", function() { }); it("does not add a property that the spied-upon object didn't originally have", function() { - // IE 8 doesn't support `Object.create` - if (jasmine.getEnv().ieVersion < 9) { return; } - var spies = [], spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), originalReturn = 1, diff --git a/spec/core/UtilSpec.js b/spec/core/UtilSpec.js index faaca2c2..84f08b8f 100644 --- a/spec/core/UtilSpec.js +++ b/spec/core/UtilSpec.js @@ -43,9 +43,6 @@ describe("jasmineUnderTest.util", function() { }); describe("getPropertyDescriptor", function() { - // IE 8 doesn't support `definePropery` on non-DOM nodes - if (jasmine.getEnv().ieVersion < 9) { return; } - it("get property descriptor from object", function() { var obj = {prop: 1}, actual = jasmineUnderTest.util.getPropertyDescriptor(obj, 'prop'), diff --git a/spec/core/asymmetric_equality/ObjectContainingSpec.js b/spec/core/asymmetric_equality/ObjectContainingSpec.js index fd2f5e66..e2741a9d 100644 --- a/spec/core/asymmetric_equality/ObjectContainingSpec.js +++ b/spec/core/asymmetric_equality/ObjectContainingSpec.js @@ -57,9 +57,6 @@ describe("ObjectContaining", function() { }); it("matches defined properties", function(){ - // IE 8 doesn't support `definePropery` on non-DOM nodes - if (jasmine.getEnv().ieVersion < 9) { return; } - var containing = new jasmineUnderTest.ObjectContaining({ foo: "fooVal" }); var definedPropertyObject = {}; diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index b5cfc29f..dee3d9be 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1164,12 +1164,9 @@ describe("Env integration", function() { env.describe('suite', function() { env.afterAll(function() { - if (jasmine.getEnv().ieVersion < 9) { - } else { - realSetTimeout(function() { - jasmine.clock().tick(10); - }, 100); - } + realSetTimeout(function() { + jasmine.clock().tick(10); + }, 100); }); env.describe('beforeAll', function() { env.beforeAll(function(innerDone) { diff --git a/spec/core/matchers/matchersUtilSpec.js b/spec/core/matchers/matchersUtilSpec.js index 98795db5..de2dae30 100644 --- a/spec/core/matchers/matchersUtilSpec.js +++ b/spec/core/matchers/matchersUtilSpec.js @@ -156,8 +156,6 @@ describe("matchersUtil", function() { }); it("passes for equivalent frozen objects (GitHub issue #266)", function() { - if (jasmine.getEnv().ieVersion < 9) { return; } - var a = { foo: 1 }, b = {foo: 1 }; @@ -191,10 +189,6 @@ describe("matchersUtil", function() { if (isNotRunningInBrowser()) { return; } - // iframe.contentWindow.eval isn't supported in ie8 - if (jasmine.getEnv().ieVersion < 9) { - return; - } var iframe = document.createElement('iframe'); document.body.appendChild(iframe); iframe.contentWindow.eval('window.testObject = {}'); @@ -356,8 +350,6 @@ describe("matchersUtil", function() { }); it("passes for null prototype objects with same properties", function () { - if (jasmine.getEnv().ieVersion < 9) { return; } - var objA = Object.create(null), objB = Object.create(null); @@ -368,8 +360,6 @@ describe("matchersUtil", function() { }); it("fails for null prototype objects with different properties", function () { - if (jasmine.getEnv().ieVersion < 9) { return; } - var objA = Object.create(null), objB = Object.create(null); @@ -449,9 +439,6 @@ describe("matchersUtil", function() { }); describe("when running in an environment with array polyfills", function() { - // IE 8 doesn't support `definePropery` on non-DOM nodes - if (jasmine.getEnv().ieVersion < 9) { return; } - var findIndexDescriptor = Object.getOwnPropertyDescriptor(Array.prototype, 'findIndex'); if (!findIndexDescriptor) { return; diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index 2c4916bb..c3115644 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -299,22 +299,7 @@ describe("toEqual", function() { expect(compareEquals(actual, expected).message).toEqual(message); }); - function constructorIsNotEnumerable() { - // in IE8, the constructor property is not enumerable, even if it is an - // own property of the object. - // Objects that differ only by an own `constructor` property are thus - // considered equal in IE8. - for (var key in {constructor: 1}) { - return false; - } - return true; - } - it("reports mismatches between objects with their own constructor property", function () { - if (constructorIsNotEnumerable()) { - return; - } - function Foo() {} function Bar() {} @@ -326,10 +311,6 @@ describe("toEqual", function() { }); it("reports mismatches between an object with a real constructor and one with its own constructor property", function () { - if (constructorIsNotEnumerable()) { - return; - } - function Foo() {} function Bar() {} diff --git a/spec/core/matchers/toThrowErrorSpec.js b/spec/core/matchers/toThrowErrorSpec.js index c7750e80..eaa9c137 100644 --- a/spec/core/matchers/toThrowErrorSpec.js +++ b/spec/core/matchers/toThrowErrorSpec.js @@ -79,7 +79,7 @@ describe("toThrowError", function() { }); it("passes if thrown is an instanceof Error regardless of global that contains its constructor", function() { - if (isNotRunningInBrowser() || jasmine.getEnv().phantomVersion < 2 || jasmine.getEnv().ieVersion < 10) { + if (isNotRunningInBrowser() || jasmine.getEnv().phantomVersion < 2) { return; } @@ -92,7 +92,7 @@ describe("toThrowError", function() { iframeDocument.body.appendChild(iframeDocument.createElement("script")) .textContent = "function method() { throw new Error('foo'); }"; } else { - // older IE + // IE 10 and older iframeDocument.write(""); } diff --git a/spec/helpers/BrowserFlags.js b/spec/helpers/BrowserFlags.js index 77d1a1e9..128d133e 100644 --- a/spec/helpers/BrowserFlags.js +++ b/spec/helpers/BrowserFlags.js @@ -8,10 +8,6 @@ return match ? parseFloat(match[1]) : void 0; } - env.ieVersion = browserVersion(function(userAgent) { - return /MSIE ([0-9]{1,}[\.0-9]{0,})/.exec(userAgent); - }); - env.safariVersion = browserVersion(function(userAgent) { return /Safari/.exec(userAgent) && /Version\/([0-9]{0,})/.exec(userAgent); }); diff --git a/src/core/Clock.js b/src/core/Clock.js index 52fafcfe..a0602c6a 100644 --- a/src/core/Clock.js +++ b/src/core/Clock.js @@ -83,22 +83,10 @@ getJasmineRequireObj().Clock = function() { }; self.setTimeout = function(fn, delay, params) { - if (legacyIE()) { - if (arguments.length > 2) { - throw new Error('IE < 9 cannot support extra params to setTimeout without a polyfill'); - } - return timer.setTimeout(fn, delay); - } return Function.prototype.apply.apply(timer.setTimeout, [global, arguments]); }; self.setInterval = function(fn, delay, params) { - if (legacyIE()) { - if (arguments.length > 2) { - throw new Error('IE < 9 cannot support extra params to setInterval without a polyfill'); - } - return timer.setInterval(fn, delay); - } return Function.prototype.apply.apply(timer.setInterval, [global, arguments]); }; @@ -133,11 +121,6 @@ getJasmineRequireObj().Clock = function() { global.clearInterval === realTimingFunctions.clearInterval; } - function legacyIE() { - //if these methods are polyfilled, apply will be present - return !(realTimingFunctions.setTimeout || realTimingFunctions.setInterval).apply; - } - function replace(dest, source) { for (var prop in source) { dest[prop] = source[prop]; diff --git a/src/core/SpyRegistry.js b/src/core/SpyRegistry.js index d8d4cf7f..139bfa3d 100644 --- a/src/core/SpyRegistry.js +++ b/src/core/SpyRegistry.js @@ -32,12 +32,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { } } - var descriptor; - try { - descriptor = Object.getOwnPropertyDescriptor(obj, methodName); - } catch(e) { - // IE 8 doesn't support `definePropery` on non-DOM nodes - } + var descriptor = Object.getOwnPropertyDescriptor(obj, methodName); if (descriptor && !(descriptor.writable || descriptor.set)) { throw new Error(getErrorMsg(methodName + ' is not declared writable or has no setter')); @@ -79,12 +74,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { throw new Error('No property name supplied'); } - var descriptor; - try { - descriptor = j$.util.getPropertyDescriptor(obj, propertyName); - } catch(e) { - // IE 8 doesn't support `definePropery` on non-DOM nodes - } + var descriptor = j$.util.getPropertyDescriptor(obj, propertyName); if (!descriptor) { throw new Error(propertyName + ' property does not exist'); diff --git a/src/core/matchers/matchersUtil.js b/src/core/matchers/matchersUtil.js index 71fc0047..c05f2a83 100644 --- a/src/core/matchers/matchersUtil.js +++ b/src/core/matchers/matchersUtil.js @@ -182,28 +182,7 @@ getJasmineRequireObj().matchersUtil = function(j$) { var bIsDomNode = j$.isDomNode(b); if (aIsDomNode && bIsDomNode) { // At first try to use DOM3 method isEqualNode - if (a.isEqualNode) { - result = a.isEqualNode(b); - if (!result) { - diffBuilder.record(a, b); - } - return result; - } - // IE8 doesn't support isEqualNode, try to use outerHTML && innerText - var aIsElement = a instanceof Element; - var bIsElement = b instanceof Element; - if (aIsElement && bIsElement) { - result = a.outerHTML == b.outerHTML; - if (!result) { - diffBuilder.record(a, b); - } - return result; - } - if (aIsElement || bIsElement) { - diffBuilder.record(a, b); - return false; - } - result = a.innerText == b.innerText && a.textContent == b.textContent; + result = a.isEqualNode(b); if (!result) { diffBuilder.record(a, b); } From aaf226b9e32477e585f42764278e33436b81b8b9 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 7 Nov 2017 21:07:09 -0800 Subject: [PATCH 16/62] Removed spec guards for versions of Safari we no longer support --- spec/core/ExceptionFormatterSpec.js | 2 -- spec/helpers/BrowserFlags.js | 4 ---- 2 files changed, 6 deletions(-) diff --git a/spec/core/ExceptionFormatterSpec.js b/spec/core/ExceptionFormatterSpec.js index 6796ff62..cdea7c8c 100644 --- a/spec/core/ExceptionFormatterSpec.js +++ b/spec/core/ExceptionFormatterSpec.js @@ -48,8 +48,6 @@ describe("ExceptionFormatter", function() { describe("#stack", function() { it("formats stack traces", function() { - if (jasmine.getEnv().safariVersion < 6) { return; } - var error; try { throw new Error("an error") } catch(e) { error = e; } diff --git a/spec/helpers/BrowserFlags.js b/spec/helpers/BrowserFlags.js index 128d133e..db3689e4 100644 --- a/spec/helpers/BrowserFlags.js +++ b/spec/helpers/BrowserFlags.js @@ -8,10 +8,6 @@ return match ? parseFloat(match[1]) : void 0; } - env.safariVersion = browserVersion(function(userAgent) { - return /Safari/.exec(userAgent) && /Version\/([0-9]{0,})/.exec(userAgent); - }); - env.firefoxVersion = browserVersion(function(userAgent) { return /Firefox\/([0-9]{0,})/.exec(userAgent); }); From 676689b0633f6b6fd2f47ce09c498bc05f207303 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 8 Nov 2017 09:11:41 -0800 Subject: [PATCH 17/62] Test against Node 4.x, not 0.10.x --- travis-node-script.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/travis-node-script.sh b/travis-node-script.sh index 6fa0ded0..65a2b073 100644 --- a/travis-node-script.sh +++ b/travis-node-script.sh @@ -4,7 +4,7 @@ rm -rf ~/.nvm git clone https://github.com/creationix/nvm.git ~/.nvm (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) source ~/.nvm/nvm.sh -nvm install v0.12.18 +nvm install v4.8.6 npm install npm test From d58f3dac5698505dda352ecdd4f06105688807d6 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 8 Nov 2017 09:17:55 -0800 Subject: [PATCH 18/62] Test against multiple Node versions on Travis --- .travis.yml | 9 +++++++++ travis-node-script.sh | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0705366e..41d697fa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,6 +24,15 @@ matrix: include: - env: - USE_SAUCE=false + - NODE_VERSION="4.8.6" + - TEST_COMMAND="bash travis-node-script.sh" + - env: + - USE_SAUCE=false + - NODE_VERSION="8.9.1" + - TEST_COMMAND="bash travis-node-script.sh" + - env: + - USE_SAUCE=false + - NODE_VERSION="9.1.0" - TEST_COMMAND="bash travis-node-script.sh" - env: - JASMINE_BROWSER="safari" diff --git a/travis-node-script.sh b/travis-node-script.sh index 65a2b073..a5897a16 100644 --- a/travis-node-script.sh +++ b/travis-node-script.sh @@ -4,7 +4,7 @@ rm -rf ~/.nvm git clone https://github.com/creationix/nvm.git ~/.nvm (cd ~/.nvm && git checkout `git describe --abbrev=0 --tags`) source ~/.nvm/nvm.sh -nvm install v4.8.6 +nvm install $NODE_VERSION npm install npm test From a42f28c7346618c216d92dde82c66e67ad5d9f36 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Thu, 9 Nov 2017 07:04:06 -0800 Subject: [PATCH 19/62] Removed checks for PhantomJS 1 v2 has been out for a long time, and v1 can no longer be easily installed. --- spec/core/matchers/toThrowErrorSpec.js | 2 +- spec/helpers/BrowserFlags.js | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/spec/core/matchers/toThrowErrorSpec.js b/spec/core/matchers/toThrowErrorSpec.js index eaa9c137..c88bbf57 100644 --- a/spec/core/matchers/toThrowErrorSpec.js +++ b/spec/core/matchers/toThrowErrorSpec.js @@ -79,7 +79,7 @@ describe("toThrowError", function() { }); it("passes if thrown is an instanceof Error regardless of global that contains its constructor", function() { - if (isNotRunningInBrowser() || jasmine.getEnv().phantomVersion < 2) { + if (isNotRunningInBrowser()) { return; } diff --git a/spec/helpers/BrowserFlags.js b/spec/helpers/BrowserFlags.js index db3689e4..9cf630e7 100644 --- a/spec/helpers/BrowserFlags.js +++ b/spec/helpers/BrowserFlags.js @@ -12,8 +12,4 @@ return /Firefox\/([0-9]{0,})/.exec(userAgent); }); - env.phantomVersion = browserVersion(function(userAgent) { - return /PhantomJS\/([0-9]{0,})/.exec(userAgent); - }); - })(jasmine.getEnv()); From 59ad2179545cdaede740036e8c0c1766f0dd9828 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Fri, 10 Nov 2017 10:47:08 -0800 Subject: [PATCH 20/62] Fixed bad travis.yml merge Re-removed IE 8, IE 9, and Node 0.12.x from the testing matrix --- .travis.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.travis.yml b/.travis.yml index 622273cd..8dc6d21e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -22,9 +22,6 @@ addons: matrix: include: - - env: - - USE_SAUCE=false - - TEST_COMMAND="bash travis-node-script.sh v0.12.18" - env: - USE_SAUCE=false - TEST_COMMAND="bash travis-node-script.sh v4" @@ -58,14 +55,6 @@ matrix: - JASMINE_BROWSER="internet explorer" - SAUCE_OS="Windows 8" - SAUCE_BROWSER_VERSION=10 - - env: - - JASMINE_BROWSER="internet explorer" - - SAUCE_OS="Windows 7" - - SAUCE_BROWSER_VERSION=9 - - env: - - JASMINE_BROWSER="internet explorer" - - SAUCE_OS="Windows 7" - - SAUCE_BROWSER_VERSION=8 - env: - JASMINE_BROWSER="chrome" - SAUCE_OS="Linux" From 5906a2c05c5628ca7cf06cf4ca1bada4a7c392b9 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 8 Nov 2017 22:44:29 -0800 Subject: [PATCH 21/62] Filter Jasmine frames from stack traces [Finishes #2644992] --- lib/jasmine-core/jasmine.js | 166 +++++++++++++++++++++++++++- spec/core/ExceptionFormatterSpec.js | 59 ++++++++++ spec/core/StackTraceSpec.js | 166 ++++++++++++++++++++++++++++ spec/core/UtilSpec.js | 11 +- src/core/ExceptionFormatter.js | 34 +++++- src/core/StackTrace.js | 80 ++++++++++++++ src/core/requireCore.js | 3 +- src/core/util.js | 48 ++++++++ 8 files changed, 557 insertions(+), 10 deletions(-) create mode 100644 spec/core/StackTraceSpec.js create mode 100644 src/core/StackTrace.js diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 19208da3..69698fdc 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -56,7 +56,8 @@ var getJasmineRequireObj = (function (jasmineGlobal) { j$.Clock = jRequire.Clock(); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); j$.Env = jRequire.Env(j$); - j$.ExceptionFormatter = jRequire.ExceptionFormatter(); + j$.StackTrace = jRequire.StackTrace(j$); + j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$); j$.Expectation = jRequire.Expectation(); j$.buildExpectationResult = jRequire.buildExpectationResult(); j$.JsApiReporter = jRequire.JsApiReporter(); @@ -461,6 +462,54 @@ getJasmineRequireObj().util = function(j$) { return Object.prototype.hasOwnProperty.call(obj, key); }; + function anyMatch(pattern, lines) { + var i; + + for (i = 0; i < lines.length; i++) { + if (lines[i].match(pattern)) { + return true; + } + } + + return false; + } + + function errorWithStack() { + // Don't throw and catch if we don't have to, because it makes it harder + // for users to debug their code with exception breakpoints. + var error = new Error(); + + if (error.stack) { + return error; + } + + // But some browsers (e.g. Phantom) only provide a stack trace if we throw. + try { + throw new Error(); + } catch (e) { + return e; + } + } + + function callerFile() { + var trace = new j$.StackTrace(errorWithStack().stack); + return trace.frames[2].file; + } + + util.jasmineFile = (function() { + var result; + + return function() { + var trace; + + if (!result) { + result = callerFile(); + } + + return result; + }; + }()); + return util; }; @@ -2186,8 +2235,10 @@ getJasmineRequireObj().errors = function() { ExpectationFailed: ExpectationFailed }; }; -getJasmineRequireObj().ExceptionFormatter = function() { - function ExceptionFormatter() { +getJasmineRequireObj().ExceptionFormatter = function(j$) { + + function ExceptionFormatter(options) { + var jasmineFile = (options && options.jasmineFile) || j$.util.jasmineFile(); this.message = function(error) { var message = ''; @@ -2209,8 +2260,34 @@ getJasmineRequireObj().ExceptionFormatter = function() { }; this.stack = function(error) { - return error ? error.stack : null; + if (!error || !error.stack) { + return null; + } + + var stackTrace = new j$.StackTrace(error.stack); + var lines = filterJasmine(stackTrace); + + if (stackTrace.message) { + lines.unshift(stackTrace.message); + } + + return lines.join('\n'); }; + + function filterJasmine(stackTrace) { + var result = [], + jasmineMarker = stackTrace.style === 'webkit' ? '' : ' at '; + + stackTrace.frames.forEach(function(frame) { + if (frame.file && frame.file !== jasmineFile) { + result.push(frame.raw); + } else if (result[result.length - 1] !== jasmineMarker) { + result.push(jasmineMarker); + } + }); + + return result; + } } return ExceptionFormatter; @@ -5033,6 +5110,87 @@ getJasmineRequireObj().SpyStrategy = function(j$) { return SpyStrategy; }; +getJasmineRequireObj().StackTrace = function(j$) { + function StackTrace(rawTrace) { + var lines = rawTrace + .split('\n') + .filter(function(line) { return line !== ''; }); + + if (lines[0].match(/^Error/)) { + this.message = lines.shift(); + } else { + this.message = undefined; + } + + var parseResult = tryParseFrames(lines); + this.frames = parseResult.frames; + this.style = parseResult.style; + } + + var framePatterns = [ + // PhantomJS on Linux, Node, Chrome, IE, Edge + // e.g. " at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)" + // Note that the "function name" can include a surprisingly large set of + // characters, including angle brackets and square brackets. + { re: /^\s*at ([^\)]+) \(([^\)]+)\)$/, fnIx: 1, fileLineColIx: 2, style: 'v8' }, + + // NodeJS alternate form, often mixed in with the Chrome style + // e.g. " at /some/path:4320:20 + { re: /\s*at (.+)$/, fileLineColIx: 1, style: 'v8' }, + + // PhantomJS on OS X, Safari, Firefox + // e.g. "run@http://localhost:8888/__jasmine__/jasmine.js:4320:27" + // or "http://localhost:8888/__jasmine__/jasmine.js:4320:27" + { re: /^(([^@\s]+)@)?([^\s]+)$/, fnIx: 2, fileLineColIx: 3, style: 'webkit' } + ]; + + // regexes should capture the function name (if any) as group 1 + // and the file, line, and column as group 2. + function tryParseFrames(lines) { + var style = null; + var frames = lines.map(function(line) { + var convertedLine = first(framePatterns, function(pattern) { + var overallMatch = line.match(pattern.re), + fileLineColMatch; + if (!overallMatch) { return null; } + + fileLineColMatch = overallMatch[pattern.fileLineColIx].match( + /^(.*):(\d+):\d+$/); + if (!fileLineColMatch) { return null; } + + style = style || pattern.style; + return { + raw: line, + file: fileLineColMatch[1], + line: parseInt(fileLineColMatch[2], 10), + func: overallMatch[pattern.fnIx] + }; + }); + + return convertedLine || { raw: line }; + }); + + return { + style: style, + frames: frames + }; + } + + function first(items, fn) { + var i, result; + + for (i = 0; i < items.length; i++) { + result = fn(items[i]); + + if (result) { + return result; + } + } + } + + return StackTrace; +}; + getJasmineRequireObj().Suite = function(j$) { function Suite(attrs) { this.env = attrs.env; diff --git a/spec/core/ExceptionFormatterSpec.js b/spec/core/ExceptionFormatterSpec.js index cdea7c8c..a7069d3e 100644 --- a/spec/core/ExceptionFormatterSpec.js +++ b/spec/core/ExceptionFormatterSpec.js @@ -54,6 +54,65 @@ describe("ExceptionFormatter", function() { expect(new jasmineUnderTest.ExceptionFormatter().stack(error)).toMatch(/ExceptionFormatterSpec\.js.*\d+/) }); + it("filters Jasmine stack frames from V8 style traces", function() { + var error = { + stack: 'Error: nope\n' + + ' at fn1 (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)\n' + + ' at fn2 (http://localhost:8888/__jasmine__/jasmine.js:4320:20)\n' + + ' at fn3 (http://localhost:8888/__jasmine__/jasmine.js:4320:20)\n' + + ' at fn4 (http://localhost:8888/__spec__/core/UtilSpec.js:110:19)\n' + }; + var subject = new jasmineUnderTest.ExceptionFormatter({ + jasmineFile: 'http://localhost:8888/__jasmine__/jasmine.js' + }); + var result = subject.stack(error); + expect(result).toEqual('Error: nope\n' + + ' at fn1 (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)\n' + + ' at \n' + + ' at fn4 (http://localhost:8888/__spec__/core/UtilSpec.js:110:19)' + ); + }); + + it("filters Jamine stack frames from Webkit style traces", function() { + var error = { + stack: 'http://localhost:8888/__spec__/core/UtilSpec.js:115:28\n' + + 'fn1@http://localhost:8888/__jasmine__/jasmine.js:4320:27\n' + + 'fn2@http://localhost:8888/__jasmine__/jasmine.js:4320:27\n' + + 'http://localhost:8888/__spec__/core/UtilSpec.js:115:28' + }; + var subject = new jasmineUnderTest.ExceptionFormatter({ + jasmineFile: 'http://localhost:8888/__jasmine__/jasmine.js' + }); + var result = subject.stack(error); + expect(result).toEqual( + 'http://localhost:8888/__spec__/core/UtilSpec.js:115:28\n' + + '\n' + + 'http://localhost:8888/__spec__/core/UtilSpec.js:115:28' + ); + }); + + it("filters Jasmine stack frames in this environment", function() { + var error, i; + try { throw new Error("an error"); } catch(e) { error = e; } + var subject = new jasmineUnderTest.ExceptionFormatter({ + jasmineFile: jasmine.util.jasmineFile() + }); + var result = subject.stack(error); + var lines = result.split('\n'); + + if (lines[0].match(/an error/)) { + lines.shift(); + } + + expect(lines[0]).toMatch(/ExceptionFormatterSpec.js/); + expect(lines[1]).toMatch(//); + + // Node has some number of additional frames below Jasmine. + for (i = 2; i < lines.length; i++) { + expect(lines[i]).not.toMatch(/jasmine.js/); + } + }); + it("returns null if no Error provided", function() { expect(new jasmineUnderTest.ExceptionFormatter().stack()).toBeNull(); }); diff --git a/spec/core/StackTraceSpec.js b/spec/core/StackTraceSpec.js new file mode 100644 index 00000000..e07b306c --- /dev/null +++ b/spec/core/StackTraceSpec.js @@ -0,0 +1,166 @@ +describe("StackTrace", function() { + it("understands Chrome/IE/Edge style traces", function() { + var raw = + 'Error: nope\n' + + ' at UserContext. (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)\n' + + ' at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)'; + + var result = new jasmineUnderTest.StackTrace(raw); + + expect(result.message).toEqual('Error: nope'); + expect(result.style).toEqual('v8'); + expect(result.frames).toEqual([ + { + raw: ' at UserContext. (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)', + func: 'UserContext.', + file: 'http://localhost:8888/__spec__/core/UtilSpec.js', + line: 115 + }, + { + raw: ' at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)', + func: 'QueueRunner.run', + file: 'http://localhost:8888/__jasmine__/jasmine.js', + line: 4320 + } + ]); + }); + + it("understands Node style traces", function() { + var raw = 'Error\n' + + ' at /somewhere/jasmine/lib/jasmine-core/jasmine.js:4255:9\n' + + ' at QueueRunner.complete [as onComplete] (/somewhere/jasmine/lib/jasmine-core/jasmine.js:579:9)\n' + + ' at Immediate. (/somewhere/jasmine/lib/jasmine-core/jasmine.js:4314:12)\n' + + ' at runCallback (timers.js:672:20)'; + var result = new jasmineUnderTest.StackTrace(raw); + + expect(result.message).toEqual('Error'); + expect(result.style).toEqual('v8'); + expect(result.frames).toEqual([ + { + raw: ' at /somewhere/jasmine/lib/jasmine-core/jasmine.js:4255:9', + func: undefined, + file: '/somewhere/jasmine/lib/jasmine-core/jasmine.js', + line: 4255 + }, + { + raw: ' at QueueRunner.complete [as onComplete] (/somewhere/jasmine/lib/jasmine-core/jasmine.js:579:9)', + func: 'QueueRunner.complete [as onComplete]', + file: '/somewhere/jasmine/lib/jasmine-core/jasmine.js', + line: 579 + }, + { + raw: ' at Immediate. (/somewhere/jasmine/lib/jasmine-core/jasmine.js:4314:12)', + func: 'Immediate.', + file: '/somewhere/jasmine/lib/jasmine-core/jasmine.js', + line: 4314 + }, + { + raw: ' at runCallback (timers.js:672:20)', + func: 'runCallback', + file: 'timers.js', + line: 672 + } + ]); + }); + + it("understands Safari/Firefox/Phantom-OS X style traces", function() { + var raw = + 'http://localhost:8888/__spec__/core/UtilSpec.js:115:28\n' + + 'run@http://localhost:8888/__jasmine__/jasmine.js:4320:27'; + var result = new jasmineUnderTest.StackTrace(raw); + + expect(result.message).toBeFalsy(); + expect(result.style).toEqual('webkit'); + expect(result.frames).toEqual([ + { + raw: 'http://localhost:8888/__spec__/core/UtilSpec.js:115:28', + func: undefined, + file: 'http://localhost:8888/__spec__/core/UtilSpec.js', + line: 115 + }, + { + raw: 'run@http://localhost:8888/__jasmine__/jasmine.js:4320:27', + func: 'run', + file: 'http://localhost:8888/__jasmine__/jasmine.js', + line: 4320 + } + ]); + }); + + it("does not mistake gibberish for Safari/Firefox/Phantom-OS X style traces", function() { + var raw = 'randomcharsnotincludingwhitespace'; + var result = new jasmineUnderTest.StackTrace(raw); + expect(result.style).toBeNull(); + expect(result.frames).toEqual([ + { raw: raw } + ]); + }); + + it("understands Phantom-Linux style traces", function() { + var raw = + ' at UserContext. (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)\n' + + ' at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)'; + + var result = new jasmineUnderTest.StackTrace(raw); + + expect(result.message).toBeFalsy(); + expect(result.style).toEqual('v8'); + expect(result.frames).toEqual([ + { + raw: ' at UserContext. (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)', + func: 'UserContext.', + file: 'http://localhost:8888/__spec__/core/UtilSpec.js', + line: 115 + }, + { + raw: ' at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)', + func: 'QueueRunner.run', + file: 'http://localhost:8888/__jasmine__/jasmine.js', + line: 4320 + } + ]); + }); + + it("ignores blank lines", function() { + var raw = + ' at UserContext. (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)\n'; + + var result = new jasmineUnderTest.StackTrace(raw); + + expect(result.frames).toEqual([ + { + raw: ' at UserContext. (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)', + func: 'UserContext.', + file: 'http://localhost:8888/__spec__/core/UtilSpec.js', + line: 115 + } + ]); + }); + + it("omits properties except 'raw' for frames that are not understood", function() { + var raw = + ' at UserContext. (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)\n' + + ' but this is quite unexpected\n' + + ' at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)'; + + var result = new jasmineUnderTest.StackTrace(raw); + expect(result.style).toEqual('v8'); + expect(result.frames).toEqual([ + { + raw: ' at UserContext. (http://localhost:8888/__spec__/core/UtilSpec.js:115:19)', + func: 'UserContext.', + file: 'http://localhost:8888/__spec__/core/UtilSpec.js', + line: 115 + }, + { + raw: ' but this is quite unexpected' + }, + { + raw: ' at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)', + func: 'QueueRunner.run', + file: 'http://localhost:8888/__jasmine__/jasmine.js', + line: 4320 + } + ]); + }); +}); diff --git a/spec/core/UtilSpec.js b/spec/core/UtilSpec.js index 84f08b8f..1cd616a2 100644 --- a/spec/core/UtilSpec.js +++ b/spec/core/UtilSpec.js @@ -91,6 +91,13 @@ describe("jasmineUnderTest.util", function() { expect(jasmineUnderTest.util.objectDifference(a, b)).toEqual({x: 1}); expect(jasmineUnderTest.util.objectDifference(b, a)).toEqual({y: 2}); - }) - }) + }); + }); + + describe("jasmineFile", function() { + it("returns the file containing jasmine.util", function() { + expect(jasmineUnderTest.util.jasmineFile()).toMatch(/util.js$/); + expect(jasmine.util.jasmineFile()).toMatch(/jasmine.js$/); + }); + }); }); diff --git a/src/core/ExceptionFormatter.js b/src/core/ExceptionFormatter.js index 74361a9a..e6808403 100644 --- a/src/core/ExceptionFormatter.js +++ b/src/core/ExceptionFormatter.js @@ -1,5 +1,7 @@ -getJasmineRequireObj().ExceptionFormatter = function() { - function ExceptionFormatter() { +getJasmineRequireObj().ExceptionFormatter = function(j$) { + + function ExceptionFormatter(options) { + var jasmineFile = (options && options.jasmineFile) || j$.util.jasmineFile(); this.message = function(error) { var message = ''; @@ -21,8 +23,34 @@ getJasmineRequireObj().ExceptionFormatter = function() { }; this.stack = function(error) { - return error ? error.stack : null; + if (!error || !error.stack) { + return null; + } + + var stackTrace = new j$.StackTrace(error.stack); + var lines = filterJasmine(stackTrace); + + if (stackTrace.message) { + lines.unshift(stackTrace.message); + } + + return lines.join('\n'); }; + + function filterJasmine(stackTrace) { + var result = [], + jasmineMarker = stackTrace.style === 'webkit' ? '' : ' at '; + + stackTrace.frames.forEach(function(frame) { + if (frame.file && frame.file !== jasmineFile) { + result.push(frame.raw); + } else if (result[result.length - 1] !== jasmineMarker) { + result.push(jasmineMarker); + } + }); + + return result; + } } return ExceptionFormatter; diff --git a/src/core/StackTrace.js b/src/core/StackTrace.js new file mode 100644 index 00000000..54ca89d6 --- /dev/null +++ b/src/core/StackTrace.js @@ -0,0 +1,80 @@ +getJasmineRequireObj().StackTrace = function(j$) { + function StackTrace(rawTrace) { + var lines = rawTrace + .split('\n') + .filter(function(line) { return line !== ''; }); + + if (lines[0].match(/^Error/)) { + this.message = lines.shift(); + } else { + this.message = undefined; + } + + var parseResult = tryParseFrames(lines); + this.frames = parseResult.frames; + this.style = parseResult.style; + } + + var framePatterns = [ + // PhantomJS on Linux, Node, Chrome, IE, Edge + // e.g. " at QueueRunner.run (http://localhost:8888/__jasmine__/jasmine.js:4320:20)" + // Note that the "function name" can include a surprisingly large set of + // characters, including angle brackets and square brackets. + { re: /^\s*at ([^\)]+) \(([^\)]+)\)$/, fnIx: 1, fileLineColIx: 2, style: 'v8' }, + + // NodeJS alternate form, often mixed in with the Chrome style + // e.g. " at /some/path:4320:20 + { re: /\s*at (.+)$/, fileLineColIx: 1, style: 'v8' }, + + // PhantomJS on OS X, Safari, Firefox + // e.g. "run@http://localhost:8888/__jasmine__/jasmine.js:4320:27" + // or "http://localhost:8888/__jasmine__/jasmine.js:4320:27" + { re: /^(([^@\s]+)@)?([^\s]+)$/, fnIx: 2, fileLineColIx: 3, style: 'webkit' } + ]; + + // regexes should capture the function name (if any) as group 1 + // and the file, line, and column as group 2. + function tryParseFrames(lines) { + var style = null; + var frames = lines.map(function(line) { + var convertedLine = first(framePatterns, function(pattern) { + var overallMatch = line.match(pattern.re), + fileLineColMatch; + if (!overallMatch) { return null; } + + fileLineColMatch = overallMatch[pattern.fileLineColIx].match( + /^(.*):(\d+):\d+$/); + if (!fileLineColMatch) { return null; } + + style = style || pattern.style; + return { + raw: line, + file: fileLineColMatch[1], + line: parseInt(fileLineColMatch[2], 10), + func: overallMatch[pattern.fnIx] + }; + }); + + return convertedLine || { raw: line }; + }); + + return { + style: style, + frames: frames + }; + } + + function first(items, fn) { + var i, result; + + for (i = 0; i < items.length; i++) { + result = fn(items[i]); + + if (result) { + return result; + } + } + } + + return StackTrace; +}; diff --git a/src/core/requireCore.js b/src/core/requireCore.js index 44e868e3..78fb343a 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -34,7 +34,8 @@ var getJasmineRequireObj = (function (jasmineGlobal) { j$.Clock = jRequire.Clock(); j$.DelayedFunctionScheduler = jRequire.DelayedFunctionScheduler(); j$.Env = jRequire.Env(j$); - j$.ExceptionFormatter = jRequire.ExceptionFormatter(); + j$.StackTrace = jRequire.StackTrace(j$); + j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$); j$.Expectation = jRequire.Expectation(); j$.buildExpectationResult = jRequire.buildExpectationResult(); j$.JsApiReporter = jRequire.JsApiReporter(); diff --git a/src/core/util.js b/src/core/util.js index 825e8ae6..c53c3aca 100644 --- a/src/core/util.js +++ b/src/core/util.js @@ -100,5 +100,53 @@ getJasmineRequireObj().util = function(j$) { return Object.prototype.hasOwnProperty.call(obj, key); }; + function anyMatch(pattern, lines) { + var i; + + for (i = 0; i < lines.length; i++) { + if (lines[i].match(pattern)) { + return true; + } + } + + return false; + } + + function errorWithStack() { + // Don't throw and catch if we don't have to, because it makes it harder + // for users to debug their code with exception breakpoints. + var error = new Error(); + + if (error.stack) { + return error; + } + + // But some browsers (e.g. Phantom) only provide a stack trace if we throw. + try { + throw new Error(); + } catch (e) { + return e; + } + } + + function callerFile() { + var trace = new j$.StackTrace(errorWithStack().stack); + return trace.frames[2].file; + } + + util.jasmineFile = (function() { + var result; + + return function() { + var trace; + + if (!result) { + result = callerFile(); + } + + return result; + }; + }()); + return util; }; From db615e4186e9110184b846a6afe40c988ac4a54a Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 14 Nov 2017 14:49:41 -0800 Subject: [PATCH 22/62] Determine overall status in core, not reporters [#92261606] [#78679648] --- lib/jasmine-core/jasmine-html.js | 11 +- lib/jasmine-core/jasmine.css | 1 + lib/jasmine-core/jasmine.js | 26 ++++ spec/core/integration/EnvSpec.js | 211 +++++++++++++++++++++++++++++++ spec/html/HtmlReporterSpec.js | 180 ++++++++++++-------------- src/core/Env.js | 26 ++++ src/html/HtmlReporter.js | 11 +- src/html/_HTMLReporter.scss | 5 + 8 files changed, 366 insertions(+), 105 deletions(-) diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 26248ed4..f77c75e1 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -225,10 +225,15 @@ jasmineRequire.HtmlReporter = function(j$) { if (totalSpecsDefined > 0 || failed) { statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } - statusBarClassName += failed ? 'jasmine-failed' : 'jasmine-passed'; + } + + if (doneResult.overallStatus === 'passed') { + statusBarClassName += ' jasmine-passed '; + } else if (doneResult.overallStatus === 'incomplete') { + statusBarClassName += ' jasmine-incomplete '; + statusBarMessage = 'Incomplete: ' + doneResult.incompleteReason + ', ' + statusBarMessage; } else { - statusBarClassName += 'jasmine-skipped'; - statusBarMessage += 'No specs found'; + statusBarClassName += ' jasmine-failed '; } var seedBar; diff --git a/lib/jasmine-core/jasmine.css b/lib/jasmine-core/jasmine.css index 02042214..002cb17b 100644 --- a/lib/jasmine-core/jasmine.css +++ b/lib/jasmine-core/jasmine.css @@ -31,6 +31,7 @@ body { overflow-y: scroll; } .jasmine_html-reporter .jasmine-bar { line-height: 28px; font-size: 14px; display: block; color: #eee; } .jasmine_html-reporter .jasmine-bar.jasmine-failed, .jasmine_html-reporter .jasmine-bar.jasmine-errored { background-color: #ca3a11; border-bottom: 1px solid #eee; } .jasmine_html-reporter .jasmine-bar.jasmine-passed { background-color: #007069; } +.jasmine_html-reporter .jasmine-bar.jasmine-incomplete { background-color: #bababa; } .jasmine_html-reporter .jasmine-bar.jasmine-skipped { background-color: #bababa; } .jasmine_html-reporter .jasmine-bar.jasmine-menu { background-color: #fff; color: #aaa; } .jasmine_html-reporter .jasmine-bar.jasmine-menu a { color: #333; } diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 69698fdc..324944c9 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -763,6 +763,7 @@ getJasmineRequireObj().Env = function(j$) { var random = true; var seed = null; var suppressLoadErrors = false; + var hasFailures = false; var currentSuite = function() { return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; @@ -1045,6 +1046,10 @@ getJasmineRequireObj().Env = function(j$) { } currentlyExecutingSuites.pop(); reporter.suiteDone(result); + + if (result.status === 'failed') { + hasFailures = true; + } }, orderChildren: function(node) { return order.sort(node.children); @@ -1071,14 +1076,31 @@ getJasmineRequireObj().Env = function(j$) { processor.execute(function() { clearResourcesForRunnable(topSuite.id); currentlyExecutingSuites.pop(); + var overallStatus, incompleteReason; + + if (hasFailures || topSuite.result.failedExpectations.length > 0) { + overallStatus = 'failed'; + } else if (focusedRunnables.length > 0) { + overallStatus = 'incomplete'; + incompleteReason = 'fit() or fdescribe() was found'; + } else if (totalSpecsDefined === 0) { + overallStatus = 'incomplete'; + incompleteReason = 'No specs found'; + } else { + overallStatus = 'passed'; + } /** * Information passed to the {@link Reporter#jasmineDone} event. * @typedef JasmineDoneInfo + * @property {OverallStatus} - The overall result of the sute: 'passed', 'failed', or 'incomplete'. + * @property {IncompleteReason} - Explanation of why the suite was incimplete. * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. */ reporter.jasmineDone({ + overallStatus: overallStatus, + incompleteReason: incompleteReason, order: order, failedExpectations: topSuite.result.failedExpectations }); @@ -1270,6 +1292,10 @@ getJasmineRequireObj().Env = function(j$) { clearResourcesForRunnable(spec.id); currentSpec = null; reporter.specDone(result); + + if (result.status === 'failed') { + hasFailures = true; + } } function specStarted(spec) { diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index dee3d9be..fa5b3dfa 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -2075,4 +2075,215 @@ describe("Env integration", function() { env.execute(); }); }); + + describe('Overall status in the jasmineDone event', function() { + describe('When everything passes', function() { + it('is "passed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('passed'); + done(); + }); + + env.addReporter(reporter); + env.it('passes', function() {}); + env.execute(); + }); + }); + + describe('When a spec fails', function() { + it('is "failed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('failed'); + done(); + }); + + env.addReporter(reporter); + env.it('fails', function() { + env.expect(true).toBe(false); + }); + env.execute(); + }); + }); + + describe('When a top-level beforeAll fails', function() { + it('is "failed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('failed'); + done(); + }); + + env.addReporter(reporter); + env.beforeAll(function() { + throw new Error('nope'); + }); + env.it('does not run', function() {}); + env.execute(); + }); + }); + + describe('When a suite beforeAll fails', function() { + it('is "failed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('failed'); + done(); + }); + + env.addReporter(reporter); + env.describe('something', function() { + env.beforeAll(function() { + throw new Error('nope'); + }); + env.it('does not run', function() {}); + }); + env.execute(); + }); + }); + + describe('When a top-level afterAll fails', function() { + it('is "failed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('failed'); + done(); + }); + + env.addReporter(reporter); + env.afterAll(function() { + throw new Error('nope'); + }); + env.it('does not run', function() {}); + env.execute(); + }); + }); + + describe('When a suite afterAll fails', function() { + it('is "failed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('failed'); + done(); + }); + + env.addReporter(reporter); + env.describe('something', function() { + env.afterAll(function() { + throw new Error('nope'); + }); + env.it('does not run', function() {}); + }); + env.execute(); + }); + }); + + describe("When there are load errors", function() { + it('is "failed"', function(done) { + var global = { + setTimeout: function(fn, delay) { setTimeout(fn, delay) }, + clearTimeout: function(fn, delay) { clearTimeout(fn, delay) }, + }; + spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); + + var env = new jasmineUnderTest.Env(); + var reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + debugger; + expect(e.overallStatus).toEqual('failed'); + done(); + }); + + env.addReporter(reporter); + env.it('passes', function() {}); + global.onerror('Uncaught Error: ENOCHEESE'); + env.execute(); + }); + }); + + describe('When there are no specs', function() { + it('is "incomplete"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('incomplete'); + expect(e.incompleteReason).toEqual('No specs found'); + done(); + }); + + env.addReporter(reporter); + env.execute(); + }); + }); + + describe('When a spec is focused', function() { + it('is "incomplete"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('incomplete'); + expect(e.incompleteReason).toEqual('fit() or fdescribe() was found'); + done(); + }); + + env.addReporter(reporter); + env.fit('is focused', function() {}); + env.execute(); + }); + }); + + describe('When a suite is focused', function() { + it('is "incomplete"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('incomplete'); + expect(e.incompleteReason).toEqual('fit() or fdescribe() was found'); + done(); + }); + + env.addReporter(reporter); + env.fdescribe('something focused', function() { + env.it('does a thing', function() {}); + }); + env.execute(); + }); + }); + + describe('When there are both failures and focused specs', function() { + it('is "failed"', function(done) { + var env = new jasmineUnderTest.Env(), + reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); + + reporter.jasmineDone.and.callFake(function(e) { + expect(e.overallStatus).toEqual('failed'); + expect(e.incompleteReason).toBeUndefined(); + done(); + }); + + env.addReporter(reporter); + env.fit('is focused', function() { + env.expect(true).toBe(false); + }); + env.execute(); + }); + }); + }); }); diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 87f7a093..c5154511 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -179,27 +179,6 @@ describe("HtmlReporter", function() { }); describe("when there are suite failures", function () { - it("displays an overall result of failure even if no other failures occurred", function() { - var env = new jasmineUnderTest.Env(), - container = document.createElement("div"), - getContainer = function() { return container; }, - reporter = new jasmineUnderTest.HtmlReporter({ - env: env, - getContainer: getContainer, - createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); } - }); - - reporter.initialize(); - - reporter.jasmineStarted({}); - reporter.suiteDone({ status: 'failed', failedExpectations: [{ message: 'My After All Exception' }] }); - reporter.jasmineDone({ failedExpectations: [] }); - - var alertBar = container.querySelector(".jasmine-overall-result"); - expect(alertBar.classList).toContain("jasmine-failed"); - }); - it("displays the exceptions in their own alert bars", function(){ var env = new jasmineUnderTest.Env(), container = document.createElement("div"), @@ -319,7 +298,7 @@ describe("HtmlReporter", function() { reporter.jasmineStarted({}); timer.elapsed.and.returnValue(100); - reporter.jasmineDone(); + reporter.jasmineDone({}); var duration = container.querySelector(".jasmine-alert .jasmine-duration"); expect(duration.innerHTML).toMatch(/finished in 0.1s/); @@ -740,7 +719,7 @@ describe("HtmlReporter", function() { }); reporter.initialize(); - reporter.jasmineDone(); + reporter.jasmineDone({}); var seedBar = container.querySelector(".jasmine-seed-bar"); expect(seedBar).toBeNull(); @@ -766,79 +745,6 @@ describe("HtmlReporter", function() { }); }); - it("shows a message if no specs are run", function(){ - var env, container, reporter; - env = new jasmineUnderTest.Env(); - container = document.createElement("div"); - var getContainer = function() { return container; }, - reporter = new jasmineUnderTest.HtmlReporter({ - env: env, - getContainer: getContainer, - createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); } - }); - reporter.initialize(); - - reporter.jasmineStarted({}); - reporter.jasmineDone({}); - - var alertBars = container.querySelectorAll(".jasmine-alert .jasmine-bar"); - expect(alertBars[0].getAttribute('class')).toMatch(/jasmine-skipped/); - expect(alertBars[0].innerHTML).toMatch(/No specs found/); - }); - - it("reports failure if there are global errors and no specs", function() { - var env = new jasmineUnderTest.Env(), - container = document.createElement("div"), - reporter = new jasmineUnderTest.HtmlReporter({ - env: env, - getContainer: function() { return container; }, - createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); } - }); - reporter.initialize(); - reporter.jasmineStarted({ totalSpecsDefined: 0 }); - reporter.jasmineDone({ - failedExpectations: [{ - passed: false, - message: 'nope' - }] - }); - - var alertBar = container.querySelector(".jasmine-overall-result"); - expect(alertBar.getAttribute('class')).toMatch(/jasmine-failed/); - }); - - it("reports failure if there are global errors and some specs", function() { - var env = new jasmineUnderTest.Env(), - container = document.createElement("div"), - reporter = new jasmineUnderTest.HtmlReporter({ - env: env, - getContainer: function() { return container; }, - createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); } - }); - reporter.initialize(); - reporter.jasmineStarted({ totalSpecsDefined: 0 }); - reporter.specDone({ - id: 123, - description: "with a spec", - fullName: "A Suite with a spec", - status: "passed", - passedExpectations: [{passed: true}], - failedExpectations: [] - }); - reporter.jasmineDone({ - failedExpectations: [{ - passed: false, - message: 'nope' - }] - }); - - var alertBar = container.querySelector(".jasmine-overall-result"); - expect(alertBar.getAttribute('class')).toMatch(/jasmine-failed/); - }); - describe("and all specs pass", function() { var env, container, reporter; beforeEach(function() { @@ -877,7 +783,6 @@ describe("HtmlReporter", function() { var alertBars = container.querySelectorAll(".jasmine-alert .jasmine-bar"); expect(alertBars.length).toEqual(1); - expect(alertBars[0].getAttribute('class')).toMatch(/jasmine-passed/); expect(alertBars[0].innerHTML).toMatch(/2 specs, 0 failures/); }); @@ -1042,8 +947,6 @@ describe("HtmlReporter", function() { it("reports the specs counts", function() { var alertBar = container.querySelector(".jasmine-alert .jasmine-bar"); - - expect(alertBar.getAttribute('class')).toMatch(/jasmine-failed/); expect(alertBar.innerHTML).toMatch(/2 specs, 1 failure/); }); @@ -1086,4 +989,83 @@ describe("HtmlReporter", function() { }); }); }); + + describe("The overall result bar", function() { + describe("When the jasmineDone event's overallStatus is 'passed'", function() { + it("has class jasmine-passed", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + getContainer = function() { return container; }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: getContainer, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + + reporter.initialize(); + + reporter.jasmineStarted({}); + reporter.jasmineDone({ + overallStatus: 'passed', + failedExpectations: [] + }); + + var alertBar = container.querySelector(".jasmine-overall-result"); + expect(alertBar.classList).toContain("jasmine-passed"); + }); + }); + + describe("When the jasmineDone event's overallStatus is 'failed'", function() { + it("has class jasmine-failed", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + getContainer = function() { return container; }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: getContainer, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + + reporter.initialize(); + + reporter.jasmineStarted({}); + reporter.jasmineDone({ + overallStatus: 'failed', + failedExpectations: [] + }); + + var alertBar = container.querySelector(".jasmine-overall-result"); + expect(alertBar.classList).toContain("jasmine-failed"); + }); + }); + + describe("When the jasmineDone event's overallStatus is 'failed'", function() { + it("has class jasmine-incomplete", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + getContainer = function() { return container; }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: getContainer, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + + reporter.initialize(); + + reporter.jasmineStarted({}); + reporter.jasmineDone({ + overallStatus: 'incomplete', + incompleteReason: 'because nope', + failedExpectations: [] + }); + + var alertBar = container.querySelector(".jasmine-overall-result"); + expect(alertBar.classList).toContain("jasmine-incomplete"); + expect(alertBar.textContent).toContain("Incomplete: because nope"); + }); + }); + }); }); diff --git a/src/core/Env.js b/src/core/Env.js index 966a6065..473cc1f8 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -29,6 +29,7 @@ getJasmineRequireObj().Env = function(j$) { var random = true; var seed = null; var suppressLoadErrors = false; + var hasFailures = false; var currentSuite = function() { return currentlyExecutingSuites[currentlyExecutingSuites.length - 1]; @@ -311,6 +312,10 @@ getJasmineRequireObj().Env = function(j$) { } currentlyExecutingSuites.pop(); reporter.suiteDone(result); + + if (result.status === 'failed') { + hasFailures = true; + } }, orderChildren: function(node) { return order.sort(node.children); @@ -337,14 +342,31 @@ getJasmineRequireObj().Env = function(j$) { processor.execute(function() { clearResourcesForRunnable(topSuite.id); currentlyExecutingSuites.pop(); + var overallStatus, incompleteReason; + + if (hasFailures || topSuite.result.failedExpectations.length > 0) { + overallStatus = 'failed'; + } else if (focusedRunnables.length > 0) { + overallStatus = 'incomplete'; + incompleteReason = 'fit() or fdescribe() was found'; + } else if (totalSpecsDefined === 0) { + overallStatus = 'incomplete'; + incompleteReason = 'No specs found'; + } else { + overallStatus = 'passed'; + } /** * Information passed to the {@link Reporter#jasmineDone} event. * @typedef JasmineDoneInfo + * @property {OverallStatus} - The overall result of the sute: 'passed', 'failed', or 'incomplete'. + * @property {IncompleteReason} - Explanation of why the suite was incimplete. * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. */ reporter.jasmineDone({ + overallStatus: overallStatus, + incompleteReason: incompleteReason, order: order, failedExpectations: topSuite.result.failedExpectations }); @@ -536,6 +558,10 @@ getJasmineRequireObj().Env = function(j$) { clearResourcesForRunnable(spec.id); currentSpec = null; reporter.specDone(result); + + if (result.status === 'failed') { + hasFailures = true; + } } function specStarted(spec) { diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index e4ec91bf..e089d536 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -196,10 +196,15 @@ jasmineRequire.HtmlReporter = function(j$) { if (totalSpecsDefined > 0 || failed) { statusBarMessage += pluralize('spec', specsExecuted) + ', ' + pluralize('failure', failureCount); if (pendingSpecCount) { statusBarMessage += ', ' + pluralize('pending spec', pendingSpecCount); } - statusBarClassName += failed ? 'jasmine-failed' : 'jasmine-passed'; + } + + if (doneResult.overallStatus === 'passed') { + statusBarClassName += ' jasmine-passed '; + } else if (doneResult.overallStatus === 'incomplete') { + statusBarClassName += ' jasmine-incomplete '; + statusBarMessage = 'Incomplete: ' + doneResult.incompleteReason + ', ' + statusBarMessage; } else { - statusBarClassName += 'jasmine-skipped'; - statusBarMessage += 'No specs found'; + statusBarClassName += ' jasmine-failed '; } var seedBar; diff --git a/src/html/_HTMLReporter.scss b/src/html/_HTMLReporter.scss index 201ffdc1..4031a777 100644 --- a/src/html/_HTMLReporter.scss +++ b/src/html/_HTMLReporter.scss @@ -209,6 +209,11 @@ body { background-color: $passing-color; } + &.jasmine-incomplete { + background-color: $neutral-color; + } + + &.jasmine-skipped { background-color: $neutral-color; } From d35b65cf7933a4d15f29e6f98ea595841fed313f Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Fri, 17 Nov 2017 14:28:17 -0800 Subject: [PATCH 23/62] Set version to 3.0.0-pre --- lib/jasmine-core/jasmine.js | 2 +- lib/jasmine-core/version.rb | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 324944c9..1b41666f 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -5637,5 +5637,5 @@ getJasmineRequireObj().UserContext = function(j$) { }; getJasmineRequireObj().version = function() { - return '2.8.0'; + return '3.0.0-pre'; }; diff --git a/lib/jasmine-core/version.rb b/lib/jasmine-core/version.rb index b91a9e70..0bbc361e 100644 --- a/lib/jasmine-core/version.rb +++ b/lib/jasmine-core/version.rb @@ -4,6 +4,6 @@ # module Jasmine module Core - VERSION = "2.8.0" + VERSION = "3.0.0.pre" end end diff --git a/package.json b/package.json index 8fb199c9..4c612f2c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jasmine-core", "license": "MIT", - "version": "2.8.0", + "version": "3.0.0-pre", "repository": { "type": "git", "url": "https://github.com/jasmine/jasmine.git" From e5ada1d010b5c6976e558d65c6f3fe37b525078e Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Mon, 20 Nov 2017 17:24:52 -0800 Subject: [PATCH 24/62] Add safari 10 and update readme to include edge --- .travis.yml | 4 ++++ README.md | 6 ++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.travis.yml b/.travis.yml index 8dc6d21e..2af9e8b0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,10 @@ matrix: - env: - USE_SAUCE=false - TEST_COMMAND="bash travis-node-script.sh v9" + - env: + - JASMINE_BROWSER="safari" + - SAUCE_OS="OS X 10.12" + - SAUCE_BROWSER_VERSION=10 - env: - JASMINE_BROWSER="safari" - SAUCE_OS="OS X 10.11" diff --git a/README.md b/README.md index 01196ada..5ea77347 100644 --- a/README.md +++ b/README.md @@ -3,9 +3,7 @@ [![Build Status](https://travis-ci.org/jasmine/jasmine.svg?branch=master)](https://travis-ci.org/jasmine/jasmine) [![Code Climate](https://codeclimate.com/github/jasmine/jasmine.svg)](https://codeclimate.com/github/jasmine/jasmine) -======= - -**A JavaScript Testing Framework** +# A JavaScript Testing Framework Jasmine is a Behavior Driven Development testing framework for JavaScript. It does not rely on browsers, DOM, or any JavaScript framework. Thus it's suited for websites, [Node.js](http://nodejs.org) projects, or anywhere that JavaScript can run. @@ -53,7 +51,7 @@ Add the following to your HTML file: ## Supported environments -Jasmine tests itself across many browsers (Safari, Chrome, Firefox, PhantomJS, and new Internet Explorer) as well as node. To see the exact version tests are run against look at our [.travis.yml](https://github.com/jasmine/jasmine/blob/master/.travis.yml) +Jasmine tests itself across many browsers (Safari, Chrome, Firefox, PhantomJS, Microsoft Edge, and new Internet Explorer) as well as nodejs. To see the exact version tests are run against look at our [.travis.yml](https://github.com/jasmine/jasmine/blob/master/.travis.yml) ## Support From b7e4c1e7794e39619870ecf636e252cbca2b4c2d Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Tue, 21 Nov 2017 17:27:21 -0800 Subject: [PATCH 25/62] Remove console.js altogether [#80410262] --- lib/console/console.js | 22 ---------------------- 1 file changed, 22 deletions(-) delete mode 100644 lib/console/console.js diff --git a/lib/console/console.js b/lib/console/console.js deleted file mode 100644 index 038ee41d..00000000 --- a/lib/console/console.js +++ /dev/null @@ -1,22 +0,0 @@ -/* -Copyright (c) 2008-2017 Pivotal Labs - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ From 9619acf91fae44dcaad3dac44ecd49e87a717600 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 28 Nov 2017 09:16:09 -0800 Subject: [PATCH 26/62] Extracted the predicate version of toThrowError into its own matcher This simplifies the signature of each matcher to something that jsdoc can actually handle. [Finishes #20622765] --- lib/jasmine-core/jasmine.js | 94 +++++++++++++++++------ spec/core/matchers/toThrowErrorSpec.js | 32 +------- spec/core/matchers/toThrowMatchingSpec.js | 73 ++++++++++++++++++ src/core/matchers/requireMatchers.js | 3 +- src/core/matchers/toThrowError.js | 22 +----- src/core/matchers/toThrowMatching.js | 68 ++++++++++++++++ 6 files changed, 218 insertions(+), 74 deletions(-) create mode 100644 spec/core/matchers/toThrowMatchingSpec.js create mode 100644 src/core/matchers/toThrowMatching.js diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 1b41666f..a13696be 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -117,7 +117,8 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { 'toHaveBeenCalledWith', 'toMatch', 'toThrow', - 'toThrowError' + 'toThrowError', + 'toThrowMatching', ], matchers = {}; @@ -3842,8 +3843,6 @@ getJasmineRequireObj().toThrowError = function(j$) { if (isAnErrorType(arguments[1])) { return exactMatcher(null, arguments[1]); - } else if (j$.isFunction_(arguments[1])) { - return predicateMatcher(arguments[1]); } else { return exactMatcher(arguments[1], null); } @@ -3860,30 +3859,12 @@ getJasmineRequireObj().toThrowError = function(j$) { }; } - function predicateMatcher(predicate) { - return { - match: function(thrown) { - if (predicate(thrown)) { - return pass(function() { - return 'Expected function not to throw an exception matching a predicate.'; - }); - } else { - return fail(function() { - return 'Expected function to throw an exception matching a predicate, ' + - 'but it threw ' + j$.fnNameFor(thrown.constructor) + ' with message ' + - j$.pp(thrown.message) + '.'; - }); - } - } - }; - } - function exactMatcher(expected, errorType) { if (expected && !isStringOrRegExp(expected)) { if (errorType) { throw new Error(getErrorMsg('Expected error message is not a string or RegExp.')); } else { - throw new Error(getErrorMsg('Expected is not an Error, string, RegExp, or Function.')); + throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.')); } } @@ -3982,6 +3963,75 @@ getJasmineRequireObj().toThrowError = function(j$) { return toThrowError; }; +getJasmineRequireObj().toThrowMatching = function(j$) { + var usageError = j$.formatErrorMsg('', 'expect(function() {}).toThrowMatching()'); + + /** + * {@link expect} a function to `throw` something matching a predicate. + * @function + * @name matchers#toThrowMatching + * @param {Function} predicate - A function that takes the thrown exception as its parameter and returns true if it matches. + * @example + * expect(function() { throw new Error('nope'); }).toThrowMatching(function(thrown) { return thrown.message === 'nope'; }); + */ + function toThrowMatching() { + return { + compare: function(actual, predicate) { + var thrown; + + if (typeof actual !== 'function') { + throw new Error(usageError('Actual is not a Function')); + } + + if (typeof predicate !== 'function') { + throw new Error(usageError('Predicate is not a Function')); + } + + try { + actual(); + return fail('Expected function to throw an exception.'); + } catch (e) { + thrown = e; + } + + if (predicate(thrown)) { + return pass('Expected function not to throw an exception matching a predicate.'); + } else { + return fail(function() { + return 'Expected function to throw an exception matching a predicate, ' + + 'but it threw ' + thrownDescription(thrown) + '.'; + }); + } + } + }; + } + + function thrownDescription(thrown) { + if (thrown && thrown.constructor) { + return j$.fnNameFor(thrown.constructor) + ' with message ' + + j$.pp(thrown.message); + } else { + return j$.pp(thrown); + } + } + + function pass(message) { + return { + pass: true, + message: message + }; + } + + function fail(message) { + return { + pass: false, + message: message + }; + } + + return toThrowMatching; +}; + getJasmineRequireObj().MockDate = function() { function MockDate(global) { var self = this; diff --git a/spec/core/matchers/toThrowErrorSpec.js b/spec/core/matchers/toThrowErrorSpec.js index c88bbf57..148997ce 100644 --- a/spec/core/matchers/toThrowErrorSpec.js +++ b/spec/core/matchers/toThrowErrorSpec.js @@ -7,7 +7,7 @@ describe("toThrowError", function() { }).toThrowError(/Actual is not a Function/); }); - it("throws an error when the expected is not an Error, string, RegExp, or function", function() { + it("throws an error when the expected is not an Error, string, or RegExp", function() { var matcher = jasmineUnderTest.matchers.toThrowError(), fn = function() { throw new Error("foo"); @@ -15,7 +15,7 @@ describe("toThrowError", function() { expect(function() { matcher.compare(fn, 1); - }).toThrowError(/Expected is not an Error, string, RegExp, or Function./); + }).toThrowError(/Expected is not an Error, string, or RegExp./); }); it("throws an error when the expected error type is not an Error", function() { @@ -312,32 +312,4 @@ describe("toThrowError", function() { expect(result.pass).toBe(false); expect(result.message()).toEqual("Expected function to throw TypeError with a message matching /bar/, but it threw TypeError with message 'foo'."); }); - - it("passes if the argument is a function that returns true when called with the error", function() { - var matcher = jasmineUnderTest.matchers.toThrowError(), - predicate = function(e) { return e.message === "nope" }, - fn = function() { - throw new TypeError("nope"); - }, - result; - - result = matcher.compare(fn, predicate); - - expect(result.pass).toBe(true); - expect(result.message()).toEqual("Expected function not to throw an exception matching a predicate."); - }); - - it("fails if the argument is a function that returns false when called with the error", function() { - var matcher = jasmineUnderTest.matchers.toThrowError(), - predicate = function(e) { return e.message === "oh no" }, - fn = function() { - throw new TypeError("nope"); - }, - result; - - result = matcher.compare(fn, predicate); - - expect(result.pass).toBe(false); - expect(result.message()).toEqual("Expected function to throw an exception matching a predicate, but it threw TypeError with message 'nope'."); - }); }); diff --git a/spec/core/matchers/toThrowMatchingSpec.js b/spec/core/matchers/toThrowMatchingSpec.js new file mode 100644 index 00000000..1987b73d --- /dev/null +++ b/spec/core/matchers/toThrowMatchingSpec.js @@ -0,0 +1,73 @@ +describe("toThrowMatching", function() { + it("throws an error when the actual is not a function", function() { + var matcher = jasmineUnderTest.matchers.toThrowMatching(); + + expect(function() { + matcher.compare({}, function() { return true; }); + }).toThrowError(/Actual is not a Function/); + }); + + it("throws an error when the expected is not a function", function() { + var matcher = jasmineUnderTest.matchers.toThrowMatching(), + fn = function() { + throw new Error("foo"); + }; + + expect(function() { + matcher.compare(fn, 1); + }).toThrowError(/Predicate is not a Function/); + }); + + it("fails if actual does not throw at all", function() { + var matcher = jasmineUnderTest.matchers.toThrowMatching(), + fn = function() { + return true; + }, + result; + + result = matcher.compare(fn, function() { return true; }); + + expect(result.pass).toBe(false); + expect(result.message).toEqual("Expected function to throw an exception."); + }); + + it("fails with the correct message if thrown is a falsy value", function() { + var matcher = jasmineUnderTest.matchers.toThrowMatching(), + fn = function() { + throw undefined; + }, + result; + + result = matcher.compare(fn, function() { return false; }); + expect(result.pass).toBe(false); + expect(result.message()).toEqual("Expected function to throw an exception matching a predicate, but it threw undefined."); + }); + + it("passes if the argument is a function that returns true when called with the error", function() { + var matcher = jasmineUnderTest.matchers.toThrowMatching(), + predicate = function(e) { return e.message === "nope" }, + fn = function() { + throw new TypeError("nope"); + }, + result; + + result = matcher.compare(fn, predicate); + + expect(result.pass).toBe(true); + expect(result.message).toEqual("Expected function not to throw an exception matching a predicate."); + }); + + it("fails if the argument is a function that returns false when called with the error", function() { + var matcher = jasmineUnderTest.matchers.toThrowMatching(), + predicate = function(e) { return e.message === "oh no" }, + fn = function() { + throw new TypeError("nope"); + }, + result; + + result = matcher.compare(fn, predicate); + + expect(result.pass).toBe(false); + expect(result.message()).toEqual("Expected function to throw an exception matching a predicate, but it threw TypeError with message 'nope'."); + }); +}); diff --git a/src/core/matchers/requireMatchers.js b/src/core/matchers/requireMatchers.js index 0d365617..2a97b5dc 100644 --- a/src/core/matchers/requireMatchers.js +++ b/src/core/matchers/requireMatchers.js @@ -23,7 +23,8 @@ getJasmineRequireObj().requireMatchers = function(jRequire, j$) { 'toHaveBeenCalledWith', 'toMatch', 'toThrow', - 'toThrowError' + 'toThrowError', + 'toThrowMatching', ], matchers = {}; diff --git a/src/core/matchers/toThrowError.js b/src/core/matchers/toThrowError.js index ebbc4d3e..2024d2a3 100644 --- a/src/core/matchers/toThrowError.js +++ b/src/core/matchers/toThrowError.js @@ -56,8 +56,6 @@ getJasmineRequireObj().toThrowError = function(j$) { if (isAnErrorType(arguments[1])) { return exactMatcher(null, arguments[1]); - } else if (j$.isFunction_(arguments[1])) { - return predicateMatcher(arguments[1]); } else { return exactMatcher(arguments[1], null); } @@ -74,30 +72,12 @@ getJasmineRequireObj().toThrowError = function(j$) { }; } - function predicateMatcher(predicate) { - return { - match: function(thrown) { - if (predicate(thrown)) { - return pass(function() { - return 'Expected function not to throw an exception matching a predicate.'; - }); - } else { - return fail(function() { - return 'Expected function to throw an exception matching a predicate, ' + - 'but it threw ' + j$.fnNameFor(thrown.constructor) + ' with message ' + - j$.pp(thrown.message) + '.'; - }); - } - } - }; - } - function exactMatcher(expected, errorType) { if (expected && !isStringOrRegExp(expected)) { if (errorType) { throw new Error(getErrorMsg('Expected error message is not a string or RegExp.')); } else { - throw new Error(getErrorMsg('Expected is not an Error, string, RegExp, or Function.')); + throw new Error(getErrorMsg('Expected is not an Error, string, or RegExp.')); } } diff --git a/src/core/matchers/toThrowMatching.js b/src/core/matchers/toThrowMatching.js new file mode 100644 index 00000000..bcc37166 --- /dev/null +++ b/src/core/matchers/toThrowMatching.js @@ -0,0 +1,68 @@ +getJasmineRequireObj().toThrowMatching = function(j$) { + var usageError = j$.formatErrorMsg('', 'expect(function() {}).toThrowMatching()'); + + /** + * {@link expect} a function to `throw` something matching a predicate. + * @function + * @name matchers#toThrowMatching + * @param {Function} predicate - A function that takes the thrown exception as its parameter and returns true if it matches. + * @example + * expect(function() { throw new Error('nope'); }).toThrowMatching(function(thrown) { return thrown.message === 'nope'; }); + */ + function toThrowMatching() { + return { + compare: function(actual, predicate) { + var thrown; + + if (typeof actual !== 'function') { + throw new Error(usageError('Actual is not a Function')); + } + + if (typeof predicate !== 'function') { + throw new Error(usageError('Predicate is not a Function')); + } + + try { + actual(); + return fail('Expected function to throw an exception.'); + } catch (e) { + thrown = e; + } + + if (predicate(thrown)) { + return pass('Expected function not to throw an exception matching a predicate.'); + } else { + return fail(function() { + return 'Expected function to throw an exception matching a predicate, ' + + 'but it threw ' + thrownDescription(thrown) + '.'; + }); + } + } + }; + } + + function thrownDescription(thrown) { + if (thrown && thrown.constructor) { + return j$.fnNameFor(thrown.constructor) + ' with message ' + + j$.pp(thrown.message); + } else { + return j$.pp(thrown); + } + } + + function pass(message) { + return { + pass: true, + message: message + }; + } + + function fail(message) { + return { + pass: false, + message: message + }; + } + + return toThrowMatching; +}; From a63172f53fe2f3429a4b09920c1273ca703c7d4a Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 29 Nov 2017 08:10:01 -0800 Subject: [PATCH 27/62] expect(null).toEqual(jasmine.any(Object)) no longer passes [Finishes #153181443] Fixes #1255. --- lib/jasmine-core/jasmine.js | 2 +- spec/core/asymmetric_equality/AnySpec.js | 7 +++++++ src/core/asymmetric_equality/Any.js | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index a13696be..7377fba4 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -1589,7 +1589,7 @@ getJasmineRequireObj().Any = function(j$) { } if (this.expectedObject == Object) { - return typeof other == 'object'; + return other !== null && typeof other == 'object'; } if (this.expectedObject == Boolean) { diff --git a/spec/core/asymmetric_equality/AnySpec.js b/spec/core/asymmetric_equality/AnySpec.js index 44ffe6b8..1739ae6f 100644 --- a/spec/core/asymmetric_equality/AnySpec.js +++ b/spec/core/asymmetric_equality/AnySpec.js @@ -68,10 +68,17 @@ describe("Any", function() { expect(any.asymmetricMatch(new Thing())).toBe(true); }); + it("does not treat null as an Object", function() { + var any = new jasmineUnderTest.Any(Object); + + expect(any.asymmetricMatch(null)).toBe(false); + }); + it("jasmineToString's itself", function() { var any = new jasmineUnderTest.Any(Number); expect(any.jasmineToString()).toEqual(''); + expect(any.jasmineToString()).toEqual(''); }); describe("when called without an argument", function() { diff --git a/src/core/asymmetric_equality/Any.js b/src/core/asymmetric_equality/Any.js index 3f447529..bc185518 100644 --- a/src/core/asymmetric_equality/Any.js +++ b/src/core/asymmetric_equality/Any.js @@ -24,7 +24,7 @@ getJasmineRequireObj().Any = function(j$) { } if (this.expectedObject == Object) { - return typeof other == 'object'; + return other !== null && typeof other == 'object'; } if (this.expectedObject == Boolean) { From 21655a82c9daf831ee0bf7dab27e758dd2fc0fb7 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Thu, 30 Nov 2017 17:30:20 -0800 Subject: [PATCH 28/62] Use prototype for spy strategy for better memory management - Also convert `identity` to a property from a method --- spec/core/SpySpec.js | 12 +- spec/core/SpyStrategySpec.js | 6 +- src/core/PrettyPrinter.js | 2 +- src/core/Spy.js | 2 +- src/core/SpyStrategy.js | 186 ++++++++++---------- src/core/matchers/toHaveBeenCalled.js | 4 +- src/core/matchers/toHaveBeenCalledBefore.js | 12 +- src/core/matchers/toHaveBeenCalledTimes.js | 4 +- src/core/matchers/toHaveBeenCalledWith.js | 6 +- 9 files changed, 115 insertions(+), 119 deletions(-) diff --git a/spec/core/SpySpec.js b/spec/core/SpySpec.js index 9bf55c3f..f3266ae5 100644 --- a/spec/core/SpySpec.js +++ b/spec/core/SpySpec.js @@ -83,10 +83,10 @@ describe('Spies', function () { var spyObj = jasmineUnderTest.createSpyObj('BaseName', {'method1': 42, 'method2': 'special sauce' }); expect(spyObj.method1()).toEqual(42); - expect(spyObj.method1.and.identity()).toEqual('BaseName.method1'); + expect(spyObj.method1.and.identity).toEqual('BaseName.method1'); expect(spyObj.method2()).toEqual('special sauce'); - expect(spyObj.method2.and.identity()).toEqual('BaseName.method2'); + expect(spyObj.method2.and.identity).toEqual('BaseName.method2'); }); @@ -94,16 +94,16 @@ describe('Spies', function () { var spyObj = jasmineUnderTest.createSpyObj('BaseName', ['method1', 'method2']); expect(spyObj).toEqual({ method1: jasmine.any(Function), method2: jasmine.any(Function)}); - expect(spyObj.method1.and.identity()).toEqual('BaseName.method1'); - expect(spyObj.method2.and.identity()).toEqual('BaseName.method2'); + expect(spyObj.method1.and.identity).toEqual('BaseName.method1'); + expect(spyObj.method2.and.identity).toEqual('BaseName.method2'); }); it("should allow you to omit the baseName", function() { var spyObj = jasmineUnderTest.createSpyObj(['method1', 'method2']); expect(spyObj).toEqual({ method1: jasmine.any(Function), method2: jasmine.any(Function)}); - expect(spyObj.method1.and.identity()).toEqual('unknown.method1'); - expect(spyObj.method2.and.identity()).toEqual('unknown.method2'); + expect(spyObj.method1.and.identity).toEqual('unknown.method1'); + expect(spyObj.method2.and.identity).toEqual('unknown.method2'); }); it("should throw if you do not pass an array or object argument", function() { diff --git a/spec/core/SpyStrategySpec.js b/spec/core/SpyStrategySpec.js index 16f1af18..6804f251 100644 --- a/spec/core/SpyStrategySpec.js +++ b/spec/core/SpyStrategySpec.js @@ -3,13 +3,13 @@ describe("SpyStrategy", function() { it("defaults its name to unknown", function() { var spyStrategy = new jasmineUnderTest.SpyStrategy(); - expect(spyStrategy.identity()).toEqual("unknown"); + expect(spyStrategy.identity).toEqual("unknown"); }); it("takes a name", function() { var spyStrategy = new jasmineUnderTest.SpyStrategy({name: "foo"}); - expect(spyStrategy.identity()).toEqual("foo"); + expect(spyStrategy.identity).toEqual("foo"); }); it("stubs an original function, if provided", function() { @@ -27,7 +27,7 @@ describe("SpyStrategy", function() { returnValue; spyStrategy.callThrough(); - returnValue = spyStrategy.exec("foo"); + returnValue = spyStrategy.exec(null, ["foo"]); expect(originalFn).toHaveBeenCalled(); expect(originalFn.calls.mostRecent().args).toEqual(["foo"]); diff --git a/src/core/PrettyPrinter.js b/src/core/PrettyPrinter.js index 16b66074..9adb4d55 100644 --- a/src/core/PrettyPrinter.js +++ b/src/core/PrettyPrinter.js @@ -27,7 +27,7 @@ getJasmineRequireObj().pp = function(j$) { } else if (typeof value === 'string') { this.emitString(value); } else if (j$.isSpy(value)) { - this.emitScalar('spy on ' + value.and.identity()); + this.emitScalar('spy on ' + value.and.identity); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { diff --git a/src/core/Spy.js b/src/core/Spy.js index 5fd010c7..97fb50ab 100644 --- a/src/core/Spy.js +++ b/src/core/Spy.js @@ -40,7 +40,7 @@ getJasmineRequireObj().Spy = function (j$) { }; callTracker.track(callData); - var returnValue = spyStrategy.exec.apply(this, arguments); + var returnValue = spyStrategy.exec(this, arguments); callData.returnValue = returnValue; return returnValue; diff --git a/src/core/SpyStrategy.js b/src/core/SpyStrategy.js index b0c716f7..87a934c7 100644 --- a/src/core/SpyStrategy.js +++ b/src/core/SpyStrategy.js @@ -6,105 +6,101 @@ getJasmineRequireObj().SpyStrategy = function(j$) { function SpyStrategy(options) { options = options || {}; - var identity = options.name || 'unknown', - originalFn = options.fn || function() {}, - getSpy = options.getSpy || function() {}, - plan = function() {}; - /** - * Return the identifying information for the spy. + * Get the identifying information for the spy. * @name Spy#and#identity - * @function - * @returns {String} + * @member + * @type {String} */ - this.identity = function() { - return identity; - }; - - /** - * Execute the current spy strategy. - * @name Spy#and#exec - * @function - */ - this.exec = function() { - return plan.apply(this, arguments); - }; - - /** - * Tell the spy to call through to the real implementation when invoked. - * @name Spy#and#callThrough - * @function - */ - this.callThrough = function() { - plan = originalFn; - return getSpy(); - }; - - /** - * Tell the spy to return the value when invoked. - * @name Spy#and#returnValue - * @function - * @param {*} value The value to return. - */ - this.returnValue = function(value) { - plan = function() { - return value; - }; - return getSpy(); - }; - - /** - * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked. - * @name Spy#and#returnValues - * @function - * @param {...*} values - Values to be returned on subsequent calls to the spy. - */ - this.returnValues = function() { - var values = Array.prototype.slice.call(arguments); - plan = function () { - return values.shift(); - }; - return getSpy(); - }; - - /** - * Tell the spy to throw an error when invoked. - * @name Spy#and#throwError - * @function - * @param {Error|String} something Thing to throw - */ - this.throwError = function(something) { - var error = (something instanceof Error) ? something : new Error(something); - plan = function() { - throw error; - }; - return getSpy(); - }; - - /** - * Tell the spy to call a fake implementation when invoked. - * @name Spy#and#callFake - * @function - * @param {Function} fn The function to invoke with the passed parameters. - */ - this.callFake = function(fn) { - if(!(j$.isFunction_(fn) || j$.isAsyncFunction_(fn))) { - throw new Error('Argument passed to callFake should be a function, got ' + fn); - } - plan = fn; - return getSpy(); - }; - - /** - * Tell the spy to do nothing when invoked. This is the default. - * @name Spy#and#stub - * @function - */ - this.stub = function(fn) { - plan = function() {}; - return getSpy(); - }; + this.identity = options.name || 'unknown', + this.originalFn = options.fn || function() {}, + this.getSpy = options.getSpy || function() {}, + this.plan = function() {}; } + /** + * Execute the current spy strategy. + * @name Spy#and#exec + * @function + */ + SpyStrategy.prototype.exec = function(context, args) { + return this.plan.apply(context, args); + }; + + /** + * Tell the spy to call through to the real implementation when invoked. + * @name Spy#and#callThrough + * @function + */ + SpyStrategy.prototype.callThrough = function() { + this.plan = this.originalFn; + return this.getSpy(); + }; + + /** + * Tell the spy to return the value when invoked. + * @name Spy#and#returnValue + * @function + * @param {*} value The value to return. + */ + SpyStrategy.prototype.returnValue = function(value) { + this.plan = function() { + return value; + }; + return this.getSpy(); + }; + + /** + * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked. + * @name Spy#and#returnValues + * @function + * @param {...*} values - Values to be returned on subsequent calls to the spy. + */ + SpyStrategy.prototype.returnValues = function() { + var values = Array.prototype.slice.call(arguments); + this.plan = function () { + return values.shift(); + }; + return this.getSpy(); + }; + + /** + * Tell the spy to throw an error when invoked. + * @name Spy#and#throwError + * @function + * @param {Error|String} something Thing to throw + */ + SpyStrategy.prototype.throwError = function(something) { + var error = (something instanceof Error) ? something : new Error(something); + this.plan = function() { + throw error; + }; + return this.getSpy(); + }; + + /** + * Tell the spy to call a fake implementation when invoked. + * @name Spy#and#callFake + * @function + * @param {Function} fn The function to invoke with the passed parameters. + */ + SpyStrategy.prototype.callFake = function(fn) { + if(!(j$.isFunction_(fn) || j$.isAsyncFunction_(fn))) { + throw new Error('Argument passed to callFake should be a function, got ' + fn); + } + this.plan = fn; + return this.getSpy(); + }; + + /** + * Tell the spy to do nothing when invoked. This is the default. + * @name Spy#and#stub + * @function + */ + SpyStrategy.prototype.stub = function(fn) { + this.plan = function() {}; + return this.getSpy(); + }; + return SpyStrategy; }; diff --git a/src/core/matchers/toHaveBeenCalled.js b/src/core/matchers/toHaveBeenCalled.js index 1612aa70..b6836f45 100644 --- a/src/core/matchers/toHaveBeenCalled.js +++ b/src/core/matchers/toHaveBeenCalled.js @@ -26,8 +26,8 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) { result.pass = actual.calls.any(); result.message = result.pass ? - 'Expected spy ' + actual.and.identity() + ' not to have been called.' : - 'Expected spy ' + actual.and.identity() + ' to have been called.'; + 'Expected spy ' + actual.and.identity + ' not to have been called.' : + 'Expected spy ' + actual.and.identity + ' to have been called.'; return result; } diff --git a/src/core/matchers/toHaveBeenCalledBefore.js b/src/core/matchers/toHaveBeenCalledBefore.js index e507f54c..eb98c07d 100644 --- a/src/core/matchers/toHaveBeenCalledBefore.js +++ b/src/core/matchers/toHaveBeenCalledBefore.js @@ -23,11 +23,11 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { var result = { pass: false }; if (!firstSpy.calls.count()) { - result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called.'; + result.message = 'Expected spy ' + firstSpy.and.identity + ' to have been called.'; return result; } if (!latterSpy.calls.count()) { - result.message = 'Expected spy ' + latterSpy.and.identity() + ' to have been called.'; + result.message = 'Expected spy ' + latterSpy.and.identity + ' to have been called.'; return result; } @@ -37,17 +37,17 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { result.pass = latest1stSpyCall < first2ndSpyCall; if (result.pass) { - result.message = 'Expected spy ' + firstSpy.and.identity() + ' to not have been called before spy ' + latterSpy.and.identity() + ', but it was'; + result.message = 'Expected spy ' + firstSpy.and.identity + ' to not have been called before spy ' + latterSpy.and.identity + ', but it was'; } else { var first1stSpyCall = firstSpy.calls.first().invocationOrder; var latest2ndSpyCall = latterSpy.calls.mostRecent().invocationOrder; if(first1stSpyCall < first2ndSpyCall) { - result.message = 'Expected latest call to spy ' + firstSpy.and.identity() + ' to have been called before first call to spy ' + latterSpy.and.identity() + ' (no interleaved calls)'; + result.message = 'Expected latest call to spy ' + firstSpy.and.identity + ' to have been called before first call to spy ' + latterSpy.and.identity + ' (no interleaved calls)'; } else if (latest2ndSpyCall > latest1stSpyCall) { - result.message = 'Expected first call to spy ' + latterSpy.and.identity() + ' to have been called after latest call to spy ' + firstSpy.and.identity() + ' (no interleaved calls)'; + result.message = 'Expected first call to spy ' + latterSpy.and.identity + ' to have been called after latest call to spy ' + firstSpy.and.identity + ' (no interleaved calls)'; } else { - result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called before spy ' + latterSpy.and.identity(); + result.message = 'Expected spy ' + firstSpy.and.identity + ' to have been called before spy ' + latterSpy.and.identity; } } diff --git a/src/core/matchers/toHaveBeenCalledTimes.js b/src/core/matchers/toHaveBeenCalledTimes.js index f23a84b3..786b9547 100644 --- a/src/core/matchers/toHaveBeenCalledTimes.js +++ b/src/core/matchers/toHaveBeenCalledTimes.js @@ -29,8 +29,8 @@ getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { var timesMessage = expected === 1 ? 'once' : expected + ' times'; result.pass = calls === expected; result.message = result.pass ? - 'Expected spy ' + actual.and.identity() + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' : - 'Expected spy ' + actual.and.identity() + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.'; + 'Expected spy ' + actual.and.identity + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' : + 'Expected spy ' + actual.and.identity + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.'; return result; } }; diff --git a/src/core/matchers/toHaveBeenCalledWith.js b/src/core/matchers/toHaveBeenCalledWith.js index c1399643..0cafcc69 100644 --- a/src/core/matchers/toHaveBeenCalledWith.js +++ b/src/core/matchers/toHaveBeenCalledWith.js @@ -23,15 +23,15 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { } if (!actual.calls.any()) { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; + result.message = function() { return 'Expected spy ' + actual.and.identity + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; return result; } if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { result.pass = true; - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; + result.message = function() { return 'Expected spy ' + actual.and.identity + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; } else { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; + result.message = function() { return 'Expected spy ' + actual.and.identity + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; } return result; From 98ead94c51d2f8918e5575e2913452d3d9f90e7f Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Fri, 1 Dec 2017 08:28:55 -0800 Subject: [PATCH 29/62] Fixed spec failure on Chrome --- spec/core/UtilSpec.js | 6 ++++-- spec/core/integration/EnvSpec.js | 5 ++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/spec/core/UtilSpec.js b/spec/core/UtilSpec.js index 1cd616a2..8c42fddb 100644 --- a/spec/core/UtilSpec.js +++ b/spec/core/UtilSpec.js @@ -96,8 +96,10 @@ describe("jasmineUnderTest.util", function() { describe("jasmineFile", function() { it("returns the file containing jasmine.util", function() { - expect(jasmineUnderTest.util.jasmineFile()).toMatch(/util.js$/); - expect(jasmine.util.jasmineFile()).toMatch(/jasmine.js$/); + // Chrome sometimes reports foo.js as foo.js/, so tolerate + // a trailing slash if present. + expect(jasmineUnderTest.util.jasmineFile()).toMatch(/util.js\/?$/); + expect(jasmine.util.jasmineFile()).toMatch(/jasmine.js\/?$/); }); }); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index fa5b3dfa..ca3a5f86 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -2201,13 +2201,12 @@ describe("Env integration", function() { var env = new jasmineUnderTest.Env(); var reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - + reporter.jasmineDone.and.callFake(function(e) { - debugger; expect(e.overallStatus).toEqual('failed'); done(); }); - + env.addReporter(reporter); env.it('passes', function() {}); global.onerror('Uncaught Error: ENOCHEESE'); From 2200dd084d6b2aa9cd9b8220c036d7153c6a0580 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Fri, 1 Dec 2017 08:34:12 -0800 Subject: [PATCH 30/62] Fixed build --- lib/jasmine-core/jasmine.js | 216 ++++++++++++++++++------------------ src/core/SpyStrategy.js | 6 +- 2 files changed, 109 insertions(+), 113 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 7377fba4..517b2aef 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -3538,8 +3538,8 @@ getJasmineRequireObj().toHaveBeenCalled = function(j$) { result.pass = actual.calls.any(); result.message = result.pass ? - 'Expected spy ' + actual.and.identity() + ' not to have been called.' : - 'Expected spy ' + actual.and.identity() + ' to have been called.'; + 'Expected spy ' + actual.and.identity + ' not to have been called.' : + 'Expected spy ' + actual.and.identity + ' to have been called.'; return result; } @@ -3574,11 +3574,11 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { var result = { pass: false }; if (!firstSpy.calls.count()) { - result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called.'; + result.message = 'Expected spy ' + firstSpy.and.identity + ' to have been called.'; return result; } if (!latterSpy.calls.count()) { - result.message = 'Expected spy ' + latterSpy.and.identity() + ' to have been called.'; + result.message = 'Expected spy ' + latterSpy.and.identity + ' to have been called.'; return result; } @@ -3588,17 +3588,17 @@ getJasmineRequireObj().toHaveBeenCalledBefore = function(j$) { result.pass = latest1stSpyCall < first2ndSpyCall; if (result.pass) { - result.message = 'Expected spy ' + firstSpy.and.identity() + ' to not have been called before spy ' + latterSpy.and.identity() + ', but it was'; + result.message = 'Expected spy ' + firstSpy.and.identity + ' to not have been called before spy ' + latterSpy.and.identity + ', but it was'; } else { var first1stSpyCall = firstSpy.calls.first().invocationOrder; var latest2ndSpyCall = latterSpy.calls.mostRecent().invocationOrder; if(first1stSpyCall < first2ndSpyCall) { - result.message = 'Expected latest call to spy ' + firstSpy.and.identity() + ' to have been called before first call to spy ' + latterSpy.and.identity() + ' (no interleaved calls)'; + result.message = 'Expected latest call to spy ' + firstSpy.and.identity + ' to have been called before first call to spy ' + latterSpy.and.identity + ' (no interleaved calls)'; } else if (latest2ndSpyCall > latest1stSpyCall) { - result.message = 'Expected first call to spy ' + latterSpy.and.identity() + ' to have been called after latest call to spy ' + firstSpy.and.identity() + ' (no interleaved calls)'; + result.message = 'Expected first call to spy ' + latterSpy.and.identity + ' to have been called after latest call to spy ' + firstSpy.and.identity + ' (no interleaved calls)'; } else { - result.message = 'Expected spy ' + firstSpy.and.identity() + ' to have been called before spy ' + latterSpy.and.identity(); + result.message = 'Expected spy ' + firstSpy.and.identity + ' to have been called before spy ' + latterSpy.and.identity; } } @@ -3641,8 +3641,8 @@ getJasmineRequireObj().toHaveBeenCalledTimes = function(j$) { var timesMessage = expected === 1 ? 'once' : expected + ' times'; result.pass = calls === expected; result.message = result.pass ? - 'Expected spy ' + actual.and.identity() + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' : - 'Expected spy ' + actual.and.identity() + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.'; + 'Expected spy ' + actual.and.identity + ' not to have been called ' + timesMessage + '. It was called ' + calls + ' times.' : + 'Expected spy ' + actual.and.identity + ' to have been called ' + timesMessage + '. It was called ' + calls + ' times.'; return result; } }; @@ -3676,15 +3676,15 @@ getJasmineRequireObj().toHaveBeenCalledWith = function(j$) { } if (!actual.calls.any()) { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; + result.message = function() { return 'Expected spy ' + actual.and.identity + ' to have been called with ' + j$.pp(expectedArgs) + ' but it was never called.'; }; return result; } if (util.contains(actual.calls.allArgs(), expectedArgs, customEqualityTesters)) { result.pass = true; - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; + result.message = function() { return 'Expected spy ' + actual.and.identity + ' not to have been called with ' + j$.pp(expectedArgs) + ' but it was.'; }; } else { - result.message = function() { return 'Expected spy ' + actual.and.identity() + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; + result.message = function() { return 'Expected spy ' + actual.and.identity + ' to have been called with ' + j$.pp(expectedArgs) + ' but actual calls were ' + j$.pp(actual.calls.allArgs()).replace(/^\[ | \]$/g, '') + '.'; }; } return result; @@ -4144,7 +4144,7 @@ getJasmineRequireObj().pp = function(j$) { } else if (typeof value === 'string') { this.emitString(value); } else if (j$.isSpy(value)) { - this.emitScalar('spy on ' + value.and.identity()); + this.emitScalar('spy on ' + value.and.identity); } else if (value instanceof RegExp) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { @@ -4905,7 +4905,7 @@ getJasmineRequireObj().Spy = function (j$) { }; callTracker.track(callData); - var returnValue = spyStrategy.exec.apply(this, arguments); + var returnValue = spyStrategy.exec(this, arguments); callData.returnValue = returnValue; return returnValue; @@ -5083,106 +5083,102 @@ getJasmineRequireObj().SpyStrategy = function(j$) { function SpyStrategy(options) { options = options || {}; - var identity = options.name || 'unknown', - originalFn = options.fn || function() {}, - getSpy = options.getSpy || function() {}, - plan = function() {}; - /** - * Return the identifying information for the spy. + * Get the identifying information for the spy. * @name Spy#and#identity - * @function - * @returns {String} + * @member + * @type {String} */ - this.identity = function() { - return identity; - }; - - /** - * Execute the current spy strategy. - * @name Spy#and#exec - * @function - */ - this.exec = function() { - return plan.apply(this, arguments); - }; - - /** - * Tell the spy to call through to the real implementation when invoked. - * @name Spy#and#callThrough - * @function - */ - this.callThrough = function() { - plan = originalFn; - return getSpy(); - }; - - /** - * Tell the spy to return the value when invoked. - * @name Spy#and#returnValue - * @function - * @param {*} value The value to return. - */ - this.returnValue = function(value) { - plan = function() { - return value; - }; - return getSpy(); - }; - - /** - * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked. - * @name Spy#and#returnValues - * @function - * @param {...*} values - Values to be returned on subsequent calls to the spy. - */ - this.returnValues = function() { - var values = Array.prototype.slice.call(arguments); - plan = function () { - return values.shift(); - }; - return getSpy(); - }; - - /** - * Tell the spy to throw an error when invoked. - * @name Spy#and#throwError - * @function - * @param {Error|String} something Thing to throw - */ - this.throwError = function(something) { - var error = (something instanceof Error) ? something : new Error(something); - plan = function() { - throw error; - }; - return getSpy(); - }; - - /** - * Tell the spy to call a fake implementation when invoked. - * @name Spy#and#callFake - * @function - * @param {Function} fn The function to invoke with the passed parameters. - */ - this.callFake = function(fn) { - if(!(j$.isFunction_(fn) || j$.isAsyncFunction_(fn))) { - throw new Error('Argument passed to callFake should be a function, got ' + fn); - } - plan = fn; - return getSpy(); - }; - - /** - * Tell the spy to do nothing when invoked. This is the default. - * @name Spy#and#stub - * @function - */ - this.stub = function(fn) { - plan = function() {}; - return getSpy(); - }; + this.identity = options.name || 'unknown'; + this.originalFn = options.fn || function() {}; + this.getSpy = options.getSpy || function() {}; + this.plan = function() {}; } + /** + * Execute the current spy strategy. + * @name Spy#and#exec + * @function + */ + SpyStrategy.prototype.exec = function(context, args) { + return this.plan.apply(context, args); + }; + + /** + * Tell the spy to call through to the real implementation when invoked. + * @name Spy#and#callThrough + * @function + */ + SpyStrategy.prototype.callThrough = function() { + this.plan = this.originalFn; + return this.getSpy(); + }; + + /** + * Tell the spy to return the value when invoked. + * @name Spy#and#returnValue + * @function + * @param {*} value The value to return. + */ + SpyStrategy.prototype.returnValue = function(value) { + this.plan = function() { + return value; + }; + return this.getSpy(); + }; + + /** + * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked. + * @name Spy#and#returnValues + * @function + * @param {...*} values - Values to be returned on subsequent calls to the spy. + */ + SpyStrategy.prototype.returnValues = function() { + var values = Array.prototype.slice.call(arguments); + this.plan = function () { + return values.shift(); + }; + return this.getSpy(); + }; + + /** + * Tell the spy to throw an error when invoked. + * @name Spy#and#throwError + * @function + * @param {Error|String} something Thing to throw + */ + SpyStrategy.prototype.throwError = function(something) { + var error = (something instanceof Error) ? something : new Error(something); + this.plan = function() { + throw error; + }; + return this.getSpy(); + }; + + /** + * Tell the spy to call a fake implementation when invoked. + * @name Spy#and#callFake + * @function + * @param {Function} fn The function to invoke with the passed parameters. + */ + SpyStrategy.prototype.callFake = function(fn) { + if(!(j$.isFunction_(fn) || j$.isAsyncFunction_(fn))) { + throw new Error('Argument passed to callFake should be a function, got ' + fn); + } + this.plan = fn; + return this.getSpy(); + }; + + /** + * Tell the spy to do nothing when invoked. This is the default. + * @name Spy#and#stub + * @function + */ + SpyStrategy.prototype.stub = function(fn) { + this.plan = function() {}; + return this.getSpy(); + }; + return SpyStrategy; }; diff --git a/src/core/SpyStrategy.js b/src/core/SpyStrategy.js index 87a934c7..773957e2 100644 --- a/src/core/SpyStrategy.js +++ b/src/core/SpyStrategy.js @@ -12,9 +12,9 @@ getJasmineRequireObj().SpyStrategy = function(j$) { * @member * @type {String} */ - this.identity = options.name || 'unknown', - this.originalFn = options.fn || function() {}, - this.getSpy = options.getSpy || function() {}, + this.identity = options.name || 'unknown'; + this.originalFn = options.fn || function() {}; + this.getSpy = options.getSpy || function() {}; this.plan = function() {}; } From d16aa550cbee8679e362cc79bc3ab0bff5652605 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Fri, 1 Dec 2017 08:45:58 -0800 Subject: [PATCH 31/62] Treat random= as a no-op rather than disabling randomization [#109197518] --- lib/jasmine-core/boot.js | 2 +- lib/jasmine-core/boot/boot.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/jasmine-core/boot.js b/lib/jasmine-core/boot.js index 583ccd5f..72f45b5e 100644 --- a/lib/jasmine-core/boot.js +++ b/lib/jasmine-core/boot.js @@ -81,7 +81,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. var random = queryString.getParam("random"); - if (random !== undefined) { + if (random !== undefined && random !== "") { env.randomizeTests(random); } diff --git a/lib/jasmine-core/boot/boot.js b/lib/jasmine-core/boot/boot.js index 5923245a..8e12623d 100644 --- a/lib/jasmine-core/boot/boot.js +++ b/lib/jasmine-core/boot/boot.js @@ -59,7 +59,7 @@ var random = queryString.getParam("random"); - if (random !== undefined) { + if (random !== undefined && random !== "") { env.randomizeTests(random); } From eb93d38294d269af73bdcffa6917ec3e3101d30c Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Fri, 8 Dec 2017 08:16:59 -0800 Subject: [PATCH 32/62] Allow jasmine-npm to handle its own load errors [Fixes #153466462] --- lib/jasmine-core/jasmine.js | 25 +++++++++++++------------ spec/core/integration/EnvSpec.js | 9 +++++++-- src/core/Env.js | 25 +++++++++++++------------ 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 517b2aef..6a5ee6cc 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -763,7 +763,7 @@ getJasmineRequireObj().Env = function(j$) { var throwOnExpectationFailure = false; var random = true; var seed = null; - var suppressLoadErrors = false; + var handlingLoadErrors = true; var hasFailures = false; var currentSuite = function() { @@ -830,15 +830,13 @@ getJasmineRequireObj().Env = function(j$) { var globalErrors = new j$.GlobalErrors(); globalErrors.install(); globalErrors.pushListener(function(message, filename, lineno) { - if (!suppressLoadErrors) { - topSuite.result.failedExpectations.push({ - passed: false, - globalErrorType: 'load', - message: message, - filename: filename, - lineno: lineno - }); - } + topSuite.result.failedExpectations.push({ + passed: false, + globalErrorType: 'load', + message: message, + filename: filename, + lineno: lineno + }); }); this.specFilter = function() { @@ -984,7 +982,10 @@ getJasmineRequireObj().Env = function(j$) { }; this.suppressLoadErrors = function() { - suppressLoadErrors = true; + if (handlingLoadErrors) { + globalErrors.popListener(); + } + handlingLoadErrors = false; }; var queueRunnerFactory = function(options) { @@ -1013,7 +1014,7 @@ getJasmineRequireObj().Env = function(j$) { }; this.execute = function(runnablesToRun) { - globalErrors.popListener(); + this.suppressLoadErrors(); if(!runnablesToRun) { if (focusedRunnables.length) { diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index ca3a5f86..d1a78de2 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -2053,18 +2053,23 @@ describe("Env integration", function() { }); describe('If suppressLoadErrors was called', function() { - it('does not report errors that occur during loading', function(done) { + it('does not report or handle errors that occur during loading', function(done) { var global = { setTimeout: function(fn, delay) { setTimeout(fn, delay) }, - clearTimeout: function(fn, delay) { clearTimeout(fn, delay) }, + clearTimeout: function(fn, delay) { clearTimeout(fn, delay) } }; spyOn(jasmineUnderTest, 'getGlobal').and.returnValue(global); + var globalErrors = new jasmineUnderTest.GlobalErrors(global); + var onerror = jasmine.createSpy('onerror'); + globalErrors.pushListener(onerror); + spyOn(jasmineUnderTest, 'GlobalErrors').and.returnValue(globalErrors); var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); reporter.jasmineDone.and.callFake(function(e) { expect(e.failedExpectations).toEqual([]); + expect(onerror).toHaveBeenCalledWith('Uncaught Error: ENOCHEESE'); done(); }); diff --git a/src/core/Env.js b/src/core/Env.js index 473cc1f8..003fd8fc 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -28,7 +28,7 @@ getJasmineRequireObj().Env = function(j$) { var throwOnExpectationFailure = false; var random = true; var seed = null; - var suppressLoadErrors = false; + var handlingLoadErrors = true; var hasFailures = false; var currentSuite = function() { @@ -95,15 +95,13 @@ getJasmineRequireObj().Env = function(j$) { var globalErrors = new j$.GlobalErrors(); globalErrors.install(); globalErrors.pushListener(function(message, filename, lineno) { - if (!suppressLoadErrors) { - topSuite.result.failedExpectations.push({ - passed: false, - globalErrorType: 'load', - message: message, - filename: filename, - lineno: lineno - }); - } + topSuite.result.failedExpectations.push({ + passed: false, + globalErrorType: 'load', + message: message, + filename: filename, + lineno: lineno + }); }); this.specFilter = function() { @@ -249,7 +247,10 @@ getJasmineRequireObj().Env = function(j$) { }; this.suppressLoadErrors = function() { - suppressLoadErrors = true; + if (handlingLoadErrors) { + globalErrors.popListener(); + } + handlingLoadErrors = false; }; var queueRunnerFactory = function(options) { @@ -278,7 +279,7 @@ getJasmineRequireObj().Env = function(j$) { }; this.execute = function(runnablesToRun) { - globalErrors.popListener(); + this.suppressLoadErrors(); if(!runnablesToRun) { if (focusedRunnables.length) { From ac5d8708b936291303aa0c03e9ead1cda00e4563 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Fri, 8 Dec 2017 18:53:11 -0800 Subject: [PATCH 33/62] More informative pretty-printing of DOM elements [Finishes ##153562618] --- lib/jasmine-core/jasmine.js | 15 +++++++++++++++ spec/core/matchers/toEqualSpec.js | 6 +++--- spec/html/PrettyPrintHtmlSpec.js | 18 +++++++++++++++--- src/core/PrettyPrinter.js | 15 +++++++++++++++ 4 files changed, 48 insertions(+), 6 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 6a5ee6cc..a7182895 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -4150,6 +4150,8 @@ getJasmineRequireObj().pp = function(j$) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { this.emitScalar('Function'); + } else if (value.nodeType === 1) { + this.emitDomElement(value); } else if (typeof value.nodeType === 'number') { this.emitScalar('HTMLNode'); } else if (value instanceof Date) { @@ -4206,6 +4208,7 @@ getJasmineRequireObj().pp = function(j$) { PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitDomElement = j$.unimplementedMethod_; function StringPrettyPrinter() { PrettyPrinter.call(this); @@ -4342,6 +4345,18 @@ getJasmineRequireObj().pp = function(j$) { this.append(constructorName + ' [ ' + itemsString + ' ]'); }; + StringPrettyPrinter.prototype.emitDomElement = function(el) { + var closingTag = ''; + + if (el.innerHTML === '') { + this.append(el.outerHTML.replace(closingTag, '')); + } else { + var tagEnd = el.outerHTML.indexOf(el.innerHTML); + this.append(el.outerHTML.substring(0, tagEnd)); + this.append('...' + closingTag); + } + }; + StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) { this.append(property); this.append(': '); diff --git a/spec/core/matchers/toEqualSpec.js b/spec/core/matchers/toEqualSpec.js index c3115644..456952ee 100644 --- a/spec/core/matchers/toEqualSpec.js +++ b/spec/core/matchers/toEqualSpec.js @@ -534,7 +534,7 @@ describe("toEqual", function() { var actual = {a: document.createElement('div')}, expected = {a: document.createElement('p')}, - message = 'Expected $.a = HTMLNode to equal HTMLNode.'; + message = 'Expected $.a =
to equal

.'; expect(compareEquals(actual, expected).message).toEqual(message); }); @@ -552,7 +552,7 @@ describe("toEqual", function() { var actual = {a: nodeA}, expected = {a: nodeB}, - message = 'Expected $.a = HTMLNode to equal HTMLNode.'; + message = 'Expected $.a =

...
to equal
...
.'; expect(compareEquals(actual, expected).message).toEqual(message); }) @@ -564,7 +564,7 @@ describe("toEqual", function() { var actual = {a: document.createElement('div')}, expected = {a: {}}, - message = 'Expected $.a = HTMLNode to equal Object({ }).'; + message = 'Expected $.a =
to equal Object({ }).'; expect(compareEquals(actual, expected).message).toEqual(message); }); diff --git a/spec/html/PrettyPrintHtmlSpec.js b/spec/html/PrettyPrintHtmlSpec.js index 336d85f0..caad7513 100644 --- a/spec/html/PrettyPrintHtmlSpec.js +++ b/spec/html/PrettyPrintHtmlSpec.js @@ -1,11 +1,23 @@ describe("jasmineUnderTest.pp (HTML Dependent)", function () { - it("should stringify HTML nodes properly", function() { - var sampleNode = document.createElement('div'); - sampleNode.innerHTML = 'foobar'; + it("should stringify non-element HTML nodes properly", function() { + var sampleNode = document.createTextNode(""); expect(jasmineUnderTest.pp(sampleNode)).toEqual("HTMLNode"); expect(jasmineUnderTest.pp({foo: sampleNode})).toEqual("Object({ foo: HTMLNode })"); }); + it("should stringify empty HTML elements as their opening tags", function () { + var simple = document.createElement('div'); + simple.className = 'foo'; + expect(jasmineUnderTest.pp(simple)).toEqual('
'); + }); + + it("should stringify non-empty HTML elements as tags with placeholders", function() { + var nonEmpty = document.createElement('div'); + nonEmpty.className = 'foo'; + nonEmpty.innerHTML = '

Irrelevant

'; + expect(jasmineUnderTest.pp(nonEmpty)).toEqual('
...
'); + }); + it("should print Firefox's wrapped native objects correctly", function() { if(jasmine.getEnv().firefoxVersion) { try { new CustomEvent(); } catch(e) { var err = e; }; diff --git a/src/core/PrettyPrinter.js b/src/core/PrettyPrinter.js index 9adb4d55..3d70f1a4 100644 --- a/src/core/PrettyPrinter.js +++ b/src/core/PrettyPrinter.js @@ -32,6 +32,8 @@ getJasmineRequireObj().pp = function(j$) { this.emitScalar(value.toString()); } else if (typeof value === 'function') { this.emitScalar('Function'); + } else if (value.nodeType === 1) { + this.emitDomElement(value); } else if (typeof value.nodeType === 'number') { this.emitScalar('HTMLNode'); } else if (value instanceof Date) { @@ -88,6 +90,7 @@ getJasmineRequireObj().pp = function(j$) { PrettyPrinter.prototype.emitObject = j$.unimplementedMethod_; PrettyPrinter.prototype.emitScalar = j$.unimplementedMethod_; PrettyPrinter.prototype.emitString = j$.unimplementedMethod_; + PrettyPrinter.prototype.emitDomElement = j$.unimplementedMethod_; function StringPrettyPrinter() { PrettyPrinter.call(this); @@ -224,6 +227,18 @@ getJasmineRequireObj().pp = function(j$) { this.append(constructorName + ' [ ' + itemsString + ' ]'); }; + StringPrettyPrinter.prototype.emitDomElement = function(el) { + var closingTag = ''; + + if (el.innerHTML === '') { + this.append(el.outerHTML.replace(closingTag, '')); + } else { + var tagEnd = el.outerHTML.indexOf(el.innerHTML); + this.append(el.outerHTML.substring(0, tagEnd)); + this.append('...' + closingTag); + } + }; + StringPrettyPrinter.prototype.formatProperty = function(obj, property, isGetter) { this.append(property); this.append(': '); From bb0992bf5fabec9b1fe6bd1c5f6b4d0e6d6b2158 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Mon, 11 Dec 2017 08:58:09 -0800 Subject: [PATCH 34/62] Added a toHaveClass matcher [#153567695] --- grunt/config/concat.js | 3 +- lib/jasmine-core/jasmine-html.js | 35 +++++++++++++++++++ spec/html/matchers/toHaveClassSpec.js | 48 +++++++++++++++++++++++++++ spec/support/jasmine.yml | 2 +- src/html/matchers/toHaveClass.js | 33 ++++++++++++++++++ src/html/requireHtml.js | 1 + 6 files changed, 120 insertions(+), 2 deletions(-) create mode 100644 spec/html/matchers/toHaveClassSpec.js create mode 100644 src/html/matchers/toHaveClass.js diff --git a/grunt/config/concat.js b/grunt/config/concat.js index 6516e175..07e4b615 100644 --- a/grunt/config/concat.js +++ b/grunt/config/concat.js @@ -15,7 +15,8 @@ module.exports = { 'src/html/HtmlReporter.js', 'src/html/HtmlSpecFilter.js', 'src/html/ResultsNode.js', - 'src/html/QueryString.js' + 'src/html/QueryString.js', + 'src/html/**/*.js' ], dest: 'lib/jasmine-core/jasmine-html.js' }, diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index f77c75e1..debec5e2 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -25,6 +25,7 @@ jasmineRequire.html = function(j$) { j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); j$.QueryString = jasmineRequire.QueryString(); j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); + j$.matchers.toHaveClass = jasmineRequire.toHaveClass(j$); }; jasmineRequire.HtmlReporter = function(j$) { @@ -516,3 +517,37 @@ jasmineRequire.QueryString = function() { return QueryString; }; + +jasmineRequire.toHaveClass = function(j$) { + /** + * {@link expect} the actual value to be a DOM element that has the expected class + * @function + * @name matchers#toHaveClass + * @param {Object} expected - The class name to test for + * @example + * var el = document.createElement('div'); + * el.className = 'foo bar baz'; + * expect(el).toHaveClass('bar'); + */ + function toHaveClass(util, customEqualityTesters) { + return { + compare: function(actual, expected) { + if (!isElement(actual)) { + throw new Error(j$.pp(actual) + ' is not a DOM element'); + } + + return { + pass: actual.classList.contains(expected) + }; + } + }; + } + + function isElement(maybeEl) { + return maybeEl && + maybeEl.classList && + j$.isFunction_(maybeEl.classList.contains); + } + + return toHaveClass; +}; diff --git a/spec/html/matchers/toHaveClassSpec.js b/spec/html/matchers/toHaveClassSpec.js new file mode 100644 index 00000000..f4d1b02b --- /dev/null +++ b/spec/html/matchers/toHaveClassSpec.js @@ -0,0 +1,48 @@ +describe('toHaveClass', function() { + it('fails for a DOM element that lacks the expected class', function() { + var matcher = jasmineUnderTest.matchers.toHaveClass(), + result = matcher.compare(document.createElement('div'), 'foo'); + + expect(result.pass).toBe(false); + }); + + it('passes for a DOM element that has the expected class', function() { + var matcher = jasmineUnderTest.matchers.toHaveClass(), + el = document.createElement('div'); + + el.className = 'foo bar baz'; + + expect(matcher.compare(el, 'foo').pass).toBe(true); + expect(matcher.compare(el, 'bar').pass).toBe(true); + expect(matcher.compare(el, 'bar').pass).toBe(true); + }); + + it('fails for a DOM element that only has other classes', function() { + var matcher = jasmineUnderTest.matchers.toHaveClass(), + el = document.createElement('div'); + + el.className = 'foo bar'; + + expect(matcher.compare(el, 'fo').pass).toBe(false); + }); + + it('throws an exception when actual is not a DOM element', function() { + var matcher = jasmineUnderTest.matchers.toHaveClass(); + + expect(function() { + matcher.compare('x', 'foo'); + }).toThrowError("'x' is not a DOM element"); + + expect(function() { + matcher.compare(undefined, 'foo'); + }).toThrowError('undefined is not a DOM element'); + + expect(function() { + matcher.compare(document.createTextNode(''), 'foo') + }).toThrowError('HTMLNode is not a DOM element'); + + expect(function() { + matcher.compare({classList: ''}, 'foo'); + }).toThrowError("Object({ classList: '' }) is not a DOM element"); + }); +}); diff --git a/spec/support/jasmine.yml b/spec/support/jasmine.yml index eb6a784d..e7e989d8 100644 --- a/spec/support/jasmine.yml +++ b/spec/support/jasmine.yml @@ -12,7 +12,7 @@ src_files: - 'core/PrettyPrinter.js' - 'core/Suite.js' - 'core/**/*.js' - - 'html/**.js' + - 'html/**/*.js' - '**/*.js' stylesheets: helpers: diff --git a/src/html/matchers/toHaveClass.js b/src/html/matchers/toHaveClass.js new file mode 100644 index 00000000..e90aa106 --- /dev/null +++ b/src/html/matchers/toHaveClass.js @@ -0,0 +1,33 @@ +jasmineRequire.toHaveClass = function(j$) { + /** + * {@link expect} the actual value to be a DOM element that has the expected class + * @function + * @name matchers#toHaveClass + * @param {Object} expected - The class name to test for + * @example + * var el = document.createElement('div'); + * el.className = 'foo bar baz'; + * expect(el).toHaveClass('bar'); + */ + function toHaveClass(util, customEqualityTesters) { + return { + compare: function(actual, expected) { + if (!isElement(actual)) { + throw new Error(j$.pp(actual) + ' is not a DOM element'); + } + + return { + pass: actual.classList.contains(expected) + }; + } + }; + } + + function isElement(maybeEl) { + return maybeEl && + maybeEl.classList && + j$.isFunction_(maybeEl.classList.contains); + } + + return toHaveClass; +}; diff --git a/src/html/requireHtml.js b/src/html/requireHtml.js index b60c3ba5..8d8b7b40 100644 --- a/src/html/requireHtml.js +++ b/src/html/requireHtml.js @@ -3,4 +3,5 @@ jasmineRequire.html = function(j$) { j$.HtmlReporter = jasmineRequire.HtmlReporter(j$); j$.QueryString = jasmineRequire.QueryString(); j$.HtmlSpecFilter = jasmineRequire.HtmlSpecFilter(); + j$.matchers.toHaveClass = jasmineRequire.toHaveClass(j$); }; From 2d33765cbf92db16075cf702622dc3ddf454e0fa Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Mon, 11 Dec 2017 09:16:24 -0800 Subject: [PATCH 35/62] Dogfood the toHaveClass matcher [#153567695] --- spec/html/HtmlReporterSpec.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index c5154511..27c2da49 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -421,15 +421,15 @@ describe("HtmlReporter", function() { var trigger = container.querySelector('.jasmine-run-options .jasmine-trigger'), payload = container.querySelector('.jasmine-run-options .jasmine-payload'); - expect(payload.className).not.toContain('jasmine-open'); + expect(payload).not.toHaveClass('jasmine-open'); trigger.onclick(); - expect(payload.className).toContain('jasmine-open'); + expect(payload).toHaveClass('jasmine-open'); trigger.onclick(); - expect(payload.className).not.toContain('jasmine-open'); + expect(payload).not.toHaveClass('jasmine-open'); }); describe("UI for raising/catching exceptions", function() { @@ -1002,17 +1002,17 @@ describe("HtmlReporter", function() { createElement: function() { return document.createElement.apply(document, arguments); }, createTextNode: function() { return document.createTextNode.apply(document, arguments); } }); - + reporter.initialize(); - + reporter.jasmineStarted({}); reporter.jasmineDone({ overallStatus: 'passed', failedExpectations: [] }); - + var alertBar = container.querySelector(".jasmine-overall-result"); - expect(alertBar.classList).toContain("jasmine-passed"); + expect(alertBar).toHaveClass("jasmine-passed"); }); }); @@ -1027,17 +1027,17 @@ describe("HtmlReporter", function() { createElement: function() { return document.createElement.apply(document, arguments); }, createTextNode: function() { return document.createTextNode.apply(document, arguments); } }); - + reporter.initialize(); - + reporter.jasmineStarted({}); reporter.jasmineDone({ overallStatus: 'failed', failedExpectations: [] }); - + var alertBar = container.querySelector(".jasmine-overall-result"); - expect(alertBar.classList).toContain("jasmine-failed"); + expect(alertBar).toHaveClass("jasmine-failed"); }); }); @@ -1052,18 +1052,18 @@ describe("HtmlReporter", function() { createElement: function() { return document.createElement.apply(document, arguments); }, createTextNode: function() { return document.createTextNode.apply(document, arguments); } }); - + reporter.initialize(); - + reporter.jasmineStarted({}); reporter.jasmineDone({ overallStatus: 'incomplete', incompleteReason: 'because nope', failedExpectations: [] }); - + var alertBar = container.querySelector(".jasmine-overall-result"); - expect(alertBar.classList).toContain("jasmine-incomplete"); + expect(alertBar).toHaveClass("jasmine-incomplete"); expect(alertBar.textContent).toContain("Incomplete: because nope"); }); }); From bdbaebc0458ed313676251fb890b37fa71ae6a72 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Mon, 11 Dec 2017 17:24:38 -0800 Subject: [PATCH 36/62] Update contributing for new naming of `jasmineUnderTest` --- .github/CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 8bd2f92a..c8d6ea55 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -41,9 +41,9 @@ Once you've pushed a feature branch to your forked repo, you're ready to open a ### Self-testing -Note that Jasmine tests itself. The files in `lib` are loaded first, defining the reference `jasmine`. Then the files in `src` are loaded, defining the reference `j$`. So there are two copies of the code loaded under test. +Note that Jasmine tests itself. The files in `lib` are loaded first, defining the reference `jasmine`. Then the files in `src` are loaded, defining the reference `jasmineUnderTest`. So there are two copies of the code loaded under test. -The tests should always use `j$` to refer to the objects and functions that are being tested. But the tests can use functions on `jasmine` as needed. _Be careful how you structure any new test code_. Copy the patterns you see in the existing code - this ensures that the code you're testing is not leaking into the `jasmine` reference and vice-versa. +The tests should always use `jasmineUnderTest` to refer to the objects and functions that are being tested. But the tests can use functions on `jasmine` as needed. _Be careful how you structure any new test code_. Copy the patterns you see in the existing code - this ensures that the code you're testing is not leaking into the `jasmine` reference and vice-versa. ### `boot.js` From d742ada71d2d78d8753367ac671035441e7221ba Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Mon, 18 Dec 2017 16:51:41 -0800 Subject: [PATCH 37/62] Added links to re-run the suites containing a failing spec [Finishes #25508053] --- lib/jasmine-core/jasmine-html.js | 32 +++++++++++++++++++++++++--- lib/jasmine-core/jasmine.css | 2 +- spec/html/HtmlReporterSpec.js | 36 ++++++++++++++++++++++++++------ src/html/HtmlReporter.js | 32 +++++++++++++++++++++++++--- src/html/_HTMLReporter.scss | 1 + 5 files changed, 90 insertions(+), 13 deletions(-) diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index debec5e2..50de0be7 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -128,9 +128,7 @@ jasmineRequire.HtmlReporter = function(j$) { var failure = createDom('div', {className: 'jasmine-spec-detail jasmine-failed'}, - createDom('div', {className: 'jasmine-description'}, - createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName) - ), + failureDescription(result, currentParent), createDom('div', {className: 'jasmine-messages'}) ); var messages = failure.childNodes[1]; @@ -349,6 +347,34 @@ jasmineRequire.HtmlReporter = function(j$) { return this; + function failureDescription(result, suite) { + var wrapper = createDom('div', {className: 'jasmine-description'}, + createDom('a', {title: result.description, href: specHref(result)}, result.description) + ); + var suiteLink; + + while (suite && suite.parent) { + wrapper.insertBefore(createTextNode(' > '), wrapper.firstChild); + suiteLink = createDom('a', {href: suiteHref(suite)}, suite.result.description); + wrapper.insertBefore(suiteLink, wrapper.firstChild); + + suite = suite.parent; + } + + return wrapper; + } + + function suiteHref(suite) { + var els = []; + + while (suite && suite.parent) { + els.unshift(suite.result.description); + suite = suite.parent; + } + + return addToExistingQueryString('spec', els.join(' ')); + } + function find(selector) { return getContainer().querySelector('.jasmine_html-reporter ' + selector); } diff --git a/lib/jasmine-core/jasmine.css b/lib/jasmine-core/jasmine.css index 002cb17b..a10d0838 100644 --- a/lib/jasmine-core/jasmine.css +++ b/lib/jasmine-core/jasmine.css @@ -51,7 +51,7 @@ body { overflow-y: scroll; } .jasmine_html-reporter .jasmine-suite { margin-top: 14px; } .jasmine_html-reporter .jasmine-suite a { color: #333; } .jasmine_html-reporter .jasmine-failures .jasmine-spec-detail { margin-bottom: 28px; } -.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description { background-color: #ca3a11; } +.jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description { background-color: #ca3a11; color: white; } .jasmine_html-reporter .jasmine-failures .jasmine-spec-detail .jasmine-description a { color: white; } .jasmine_html-reporter .jasmine-result-message { padding-top: 14px; color: #333; white-space: pre; } .jasmine_html-reporter .jasmine-result-message span.jasmine-result { display: block; } diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 27c2da49..ed93195c 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -395,7 +395,6 @@ describe("HtmlReporter", function() { var specLink = spec.childNodes[0]; expect(specLink.innerHTML).toEqual("with a spec"); expect(specLink.getAttribute("href")).toEqual("?foo=bar&spec=A Suite with a spec"); -// expect(specLink.getAttribute("title")).toEqual("A Suite with a spec"); }); it("has an options menu", function() { @@ -922,6 +921,14 @@ describe("HtmlReporter", function() { reporter.initialize(); reporter.jasmineStarted({ totalSpecsDefined: 1 }); + reporter.suiteStarted({ + id: 1, + description: "A suite" + }); + reporter.suiteStarted({ + id: 2, + description: "inner suite" + }); var passingResult = {id: 123, status: "passed", passedExpectations: [{passed: true}], failedExpectations: []}; reporter.specStarted(passingResult); @@ -931,7 +938,7 @@ describe("HtmlReporter", function() { id: 124, status: "failed", description: "a failing spec", - fullName: "a suite with a failing spec", + fullName: "a suite inner suite a failing spec", passedExpectations: [], failedExpectations: [ { @@ -942,6 +949,9 @@ describe("HtmlReporter", function() { }; reporter.specStarted(failingResult); reporter.specDone(failingResult); + reporter.suiteDone({}); + reporter.suiteDone({}); + reporter.suiteDone({}); reporter.jasmineDone({}); }); @@ -960,10 +970,6 @@ describe("HtmlReporter", function() { var specDiv = failure.childNodes[0]; expect(specDiv.getAttribute("class")).toEqual("jasmine-description"); - var specLink = specDiv.childNodes[0]; - expect(specLink.getAttribute("title")).toEqual("a suite with a failing spec"); - expect(specLink.getAttribute("href")).toEqual("?foo=bar&spec=a suite with a failing spec"); - var message = failure.childNodes[1].childNodes[0]; expect(message.getAttribute("class")).toEqual("jasmine-result-message"); expect(message.innerHTML).toEqual("a failure message"); @@ -973,6 +979,24 @@ describe("HtmlReporter", function() { expect(stackTrace.innerHTML).toEqual("a stack trace"); }); + it('provides links to focus on a failure and each containing suite', function() { + var description = container.querySelector('.jasmine-failures .jasmine-description'); + var links = description.querySelectorAll('a'); + + expect(description.textContent).toEqual('A suite > inner suite > a failing spec'); + + expect(links.length).toEqual(3); + expect(links[0].textContent).toEqual('A suite'); + + expect(links[0].getAttribute('href')).toMatch(/\?foo=bar&spec=A suite/); + + expect(links[1].textContent).toEqual('inner suite'); + expect(links[1].getAttribute('href')).toMatch(/\?foo=bar&spec=A suite inner suite/); + + expect(links[2].textContent).toEqual('a failing spec'); + expect(links[2].getAttribute('href')).toMatch(/\?foo=bar&spec=a suite inner suite a failing spec/); + }); + it("allows switching between failure details and the spec summary", function() { var menuBar = container.querySelectorAll(".jasmine-bar")[1]; diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index e089d536..181ee3a9 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -98,9 +98,7 @@ jasmineRequire.HtmlReporter = function(j$) { var failure = createDom('div', {className: 'jasmine-spec-detail jasmine-failed'}, - createDom('div', {className: 'jasmine-description'}, - createDom('a', {title: result.fullName, href: specHref(result)}, result.fullName) - ), + failureDescription(result, currentParent), createDom('div', {className: 'jasmine-messages'}) ); var messages = failure.childNodes[1]; @@ -319,6 +317,34 @@ jasmineRequire.HtmlReporter = function(j$) { return this; + function failureDescription(result, suite) { + var wrapper = createDom('div', {className: 'jasmine-description'}, + createDom('a', {title: result.description, href: specHref(result)}, result.description) + ); + var suiteLink; + + while (suite && suite.parent) { + wrapper.insertBefore(createTextNode(' > '), wrapper.firstChild); + suiteLink = createDom('a', {href: suiteHref(suite)}, suite.result.description); + wrapper.insertBefore(suiteLink, wrapper.firstChild); + + suite = suite.parent; + } + + return wrapper; + } + + function suiteHref(suite) { + var els = []; + + while (suite && suite.parent) { + els.unshift(suite.result.description); + suite = suite.parent; + } + + return addToExistingQueryString('spec', els.join(' ')); + } + function find(selector) { return getContainer().querySelector('.jasmine_html-reporter ' + selector); } diff --git a/src/html/_HTMLReporter.scss b/src/html/_HTMLReporter.scss index 4031a777..f7eace89 100644 --- a/src/html/_HTMLReporter.scss +++ b/src/html/_HTMLReporter.scss @@ -313,6 +313,7 @@ body { .jasmine-description { background-color: $failing-color; + color: white; a { color: white; From f38527ff15b342862f2ac780d39cf784cdb05fbe Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 19 Dec 2017 11:21:13 -0800 Subject: [PATCH 38/62] Removed IE 8 compatibility cruft from HtmlReporterSpec --- spec/html/HtmlReporterSpec.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index ed93195c..91497df0 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -26,9 +26,8 @@ describe("HtmlReporter", function() { expect(title.getAttribute('href')).toEqual('http://jasmine.github.io/'); expect(title.getAttribute('target')).toEqual('_blank'); - var version = banner.querySelector(".jasmine-version"), - versionText = 'textContent' in version ? version.textContent : version.innerText; - expect(versionText).toEqual(jasmineUnderTest.version); + var version = banner.querySelector(".jasmine-version"); + expect(version.textContent).toEqual(jasmineUnderTest.version); }); it("builds a single reporter even if initialized multiple times", function() { @@ -694,8 +693,7 @@ describe("HtmlReporter", function() { }); var seedBar = container.querySelector(".jasmine-seed-bar"); - var seedBarText = 'textContent' in seedBar ? seedBar.textContent : seedBar.innerText; - expect(seedBarText).toBe(', randomized with seed 424242'); + expect(seedBar.textContent).toBe(', randomized with seed 424242'); var seedLink = container.querySelector(".jasmine-seed-bar a"); expect(seedLink.getAttribute('href')).toBe('?seed=424242'); }); From 16e07a0e99475e0e4a8168dfc3c114321ac738e1 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 3 Jan 2018 08:49:34 -0800 Subject: [PATCH 39/62] Add the ability to specify the strategy to use for a spy based on which parameters are passed [Finishes #92260826] --- lib/jasmine-core/jasmine.js | 115 +++++++++++++++++++++++++++++++----- spec/core/SpySpec.js | 44 ++++++++++++++ src/core/Spy.js | 89 +++++++++++++++++++++++++++- src/core/SpyStrategy.js | 24 ++++---- 4 files changed, 245 insertions(+), 27 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 6d0f4c95..fbd9dd17 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2017 Pivotal Labs +Copyright (c) 2008-2018 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the @@ -4937,7 +4937,7 @@ getJasmineRequireObj().Spy = function (j$) { wrapper = makeFunc(numArgs, function () { return spy.apply(this, Array.prototype.slice.call(arguments)); }), - spyStrategy = new j$.SpyStrategy({ + strategyDispatcher = new SpyStrategyDispatcher({ name: name, fn: originalFn, getSpy: function () { @@ -4959,7 +4959,7 @@ getJasmineRequireObj().Spy = function (j$) { }; callTracker.track(callData); - var returnValue = spyStrategy.exec(this, arguments); + var returnValue = strategyDispatcher.exec(this, arguments); callData.returnValue = returnValue; return returnValue; @@ -4988,12 +4988,95 @@ getJasmineRequireObj().Spy = function (j$) { wrapper[prop] = originalFn[prop]; } - wrapper.and = spyStrategy; + /** + * @member {SpyStrategy} - Accesses the default strategy for the spy. This strategy will be used + * whenever the spy is called with arguments that don't match any strategy + * created with {@link Spy#withArgs}. + * @name Spy#and + * @example + * spyOn(someObj, 'func').and.returnValue(42); + */ + wrapper.and = strategyDispatcher.and; + /** + * Specifies a strategy to be used for calls to the spy that have the + * specified arguments. + * @name Spy#withArgs + * @function + * @param {...*} args - The arguments to match + * @type {SpyStrategy} + * @example + * spyOn(someObj, 'func').withArgs(1, 2, 3).and.returnValue(42); + * someObj.func(1, 2, 3); // returns 42 + */ + wrapper.withArgs = function() { + return strategyDispatcher.withArgs.apply(strategyDispatcher, arguments); + }; wrapper.calls = callTracker; return wrapper; } + + function SpyStrategyDispatcher(strategyArgs) { + var baseStrategy = new j$.SpyStrategy(strategyArgs); + var argsStrategies = new StrategyDict(function() { + return new j$.SpyStrategy(strategyArgs); + }); + + this.and = baseStrategy; + + this.exec = function(spy, args) { + var strategy = argsStrategies.get(args); + + if (!strategy) { + if (argsStrategies.any() && !baseStrategy.isConfigured()) { + throw new Error('Spy \'' + strategyArgs.name + '\' receieved a call with arguments ' + j$.pp(Array.prototype.slice.call(args)) + ' but all configured strategies specify other arguments.'); + } else { + strategy = baseStrategy; + } + } + + return strategy.exec(spy, args); + }; + + this.withArgs = function() { + return { and: argsStrategies.getOrCreate(arguments) }; + }; + } + + function StrategyDict(strategyFactory) { + this.strategies = []; + this.strategyFactory = strategyFactory; + } + + StrategyDict.prototype.any = function() { + return this.strategies.length > 0; + }; + + StrategyDict.prototype.getOrCreate = function(args) { + var strategy = this.get(args); + + if (!strategy) { + strategy = this.strategyFactory(); + this.strategies.push({ + args: args, + strategy: strategy + }); + } + + return strategy; + }; + + StrategyDict.prototype.get = function(args) { + var i; + + for (i = 0; i < this.strategies.length; i++) { + if (j$.matchersUtil.equals(args, this.strategies[i].args)) { + return this.strategies[i].strategy; + } + } + }; + return Spy; }; @@ -5132,26 +5215,26 @@ getJasmineRequireObj().SpyRegistry = function(j$) { getJasmineRequireObj().SpyStrategy = function(j$) { /** - * @namespace Spy#and + * @interface SpyStrategy */ function SpyStrategy(options) { options = options || {}; /** * Get the identifying information for the spy. - * @name Spy#and#identity + * @name SpyStrategy#identity * @member * @type {String} */ this.identity = options.name || 'unknown'; this.originalFn = options.fn || function() {}; this.getSpy = options.getSpy || function() {}; - this.plan = function() {}; + this.plan = this._defaultPlan = function() {}; } /** * Execute the current spy strategy. - * @name Spy#and#exec + * @name SpyStrategy#exec * @function */ SpyStrategy.prototype.exec = function(context, args) { @@ -5160,7 +5243,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** * Tell the spy to call through to the real implementation when invoked. - * @name Spy#and#callThrough + * @name SpyStrategy#callThrough * @function */ SpyStrategy.prototype.callThrough = function() { @@ -5170,7 +5253,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** * Tell the spy to return the value when invoked. - * @name Spy#and#returnValue + * @name SpyStrategy#returnValue * @function * @param {*} value The value to return. */ @@ -5183,7 +5266,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked. - * @name Spy#and#returnValues + * @name SpyStrategy#returnValues * @function * @param {...*} values - Values to be returned on subsequent calls to the spy. */ @@ -5197,7 +5280,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** * Tell the spy to throw an error when invoked. - * @name Spy#and#throwError + * @name SpyStrategy#throwError * @function * @param {Error|String} something Thing to throw */ @@ -5211,7 +5294,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** * Tell the spy to call a fake implementation when invoked. - * @name Spy#and#callFake + * @name SpyStrategy#callFake * @function * @param {Function} fn The function to invoke with the passed parameters. */ @@ -5225,7 +5308,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** * Tell the spy to do nothing when invoked. This is the default. - * @name Spy#and#stub + * @name SpyStrategy#stub * @function */ SpyStrategy.prototype.stub = function(fn) { @@ -5233,6 +5316,10 @@ getJasmineRequireObj().SpyStrategy = function(j$) { return this.getSpy(); }; + SpyStrategy.prototype.isConfigured = function() { + return this.plan !== this._defaultPlan; + }; + return SpyStrategy; }; diff --git a/spec/core/SpySpec.js b/spec/core/SpySpec.js index f3266ae5..562f684b 100644 --- a/spec/core/SpySpec.js +++ b/spec/core/SpySpec.js @@ -124,4 +124,48 @@ describe('Spies', function () { }).toThrow("createSpyObj requires a non-empty array or object of method names to create spies for"); }); }); + + it("can use different strategies for different arguments", function() { + var spy = jasmineUnderTest.createSpy('foo'); + spy.and.returnValue(42); + spy.withArgs('baz', 'grault').and.returnValue(-1); + spy.withArgs('thud').and.returnValue('bob'); + + expect(spy('foo')).toEqual(42); + expect(spy('baz', 'grault')).toEqual(-1); + expect(spy('thud')).toEqual('bob'); + expect(spy('baz', 'grault', 'waldo')).toEqual(42); + }); + + it("uses custom equality testers when selecting a strategy", function() { + var spy = jasmineUnderTest.createSpy('foo'); + spy.and.returnValue(42); + spy.withArgs(jasmineUnderTest.any(String)).and.returnValue(-1); + + expect(spy('foo')).toEqual(-1); + expect(spy({})).toEqual(42); + }); + + it("can reconfigure an argument-specific strategy", function() { + var spy = jasmineUnderTest.createSpy('foo'); + spy.withArgs('foo').and.returnValue(42); + spy.withArgs('foo').and.returnValue(17); + expect(spy('foo')).toEqual(17); + }); + + describe("When withArgs is used without a base strategy", function() { + it("uses the matching strategy", function() { + var spy = jasmineUnderTest.createSpy('foo'); + spy.withArgs('baz').and.returnValue(-1); + + expect(spy('baz')).toEqual(-1); + }); + + it("throws if the args don't match", function() { + var spy = jasmineUnderTest.createSpy('foo'); + spy.withArgs('bar').and.returnValue(-1); + + expect(function() { spy('baz', {qux: 42}); }).toThrowError('Spy \'foo\' receieved a call with arguments [ \'baz\', Object({ qux: 42 }) ] but all configured strategies specify other arguments.'); + }); + }); }); diff --git a/src/core/Spy.js b/src/core/Spy.js index 97fb50ab..c99ff191 100644 --- a/src/core/Spy.js +++ b/src/core/Spy.js @@ -18,7 +18,7 @@ getJasmineRequireObj().Spy = function (j$) { wrapper = makeFunc(numArgs, function () { return spy.apply(this, Array.prototype.slice.call(arguments)); }), - spyStrategy = new j$.SpyStrategy({ + strategyDispatcher = new SpyStrategyDispatcher({ name: name, fn: originalFn, getSpy: function () { @@ -40,7 +40,7 @@ getJasmineRequireObj().Spy = function (j$) { }; callTracker.track(callData); - var returnValue = spyStrategy.exec(this, arguments); + var returnValue = strategyDispatcher.exec(this, arguments); callData.returnValue = returnValue; return returnValue; @@ -69,11 +69,94 @@ getJasmineRequireObj().Spy = function (j$) { wrapper[prop] = originalFn[prop]; } - wrapper.and = spyStrategy; + /** + * @member {SpyStrategy} - Accesses the default strategy for the spy. This strategy will be used + * whenever the spy is called with arguments that don't match any strategy + * created with {@link Spy#withArgs}. + * @name Spy#and + * @example + * spyOn(someObj, 'func').and.returnValue(42); + */ + wrapper.and = strategyDispatcher.and; + /** + * Specifies a strategy to be used for calls to the spy that have the + * specified arguments. + * @name Spy#withArgs + * @function + * @param {...*} args - The arguments to match + * @type {SpyStrategy} + * @example + * spyOn(someObj, 'func').withArgs(1, 2, 3).and.returnValue(42); + * someObj.func(1, 2, 3); // returns 42 + */ + wrapper.withArgs = function() { + return strategyDispatcher.withArgs.apply(strategyDispatcher, arguments); + }; wrapper.calls = callTracker; return wrapper; } + + function SpyStrategyDispatcher(strategyArgs) { + var baseStrategy = new j$.SpyStrategy(strategyArgs); + var argsStrategies = new StrategyDict(function() { + return new j$.SpyStrategy(strategyArgs); + }); + + this.and = baseStrategy; + + this.exec = function(spy, args) { + var strategy = argsStrategies.get(args); + + if (!strategy) { + if (argsStrategies.any() && !baseStrategy.isConfigured()) { + throw new Error('Spy \'' + strategyArgs.name + '\' receieved a call with arguments ' + j$.pp(Array.prototype.slice.call(args)) + ' but all configured strategies specify other arguments.'); + } else { + strategy = baseStrategy; + } + } + + return strategy.exec(spy, args); + }; + + this.withArgs = function() { + return { and: argsStrategies.getOrCreate(arguments) }; + }; + } + + function StrategyDict(strategyFactory) { + this.strategies = []; + this.strategyFactory = strategyFactory; + } + + StrategyDict.prototype.any = function() { + return this.strategies.length > 0; + }; + + StrategyDict.prototype.getOrCreate = function(args) { + var strategy = this.get(args); + + if (!strategy) { + strategy = this.strategyFactory(); + this.strategies.push({ + args: args, + strategy: strategy + }); + } + + return strategy; + }; + + StrategyDict.prototype.get = function(args) { + var i; + + for (i = 0; i < this.strategies.length; i++) { + if (j$.matchersUtil.equals(args, this.strategies[i].args)) { + return this.strategies[i].strategy; + } + } + }; + return Spy; }; diff --git a/src/core/SpyStrategy.js b/src/core/SpyStrategy.js index 773957e2..643fe73f 100644 --- a/src/core/SpyStrategy.js +++ b/src/core/SpyStrategy.js @@ -1,26 +1,26 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** - * @namespace Spy#and + * @interface SpyStrategy */ function SpyStrategy(options) { options = options || {}; /** * Get the identifying information for the spy. - * @name Spy#and#identity + * @name SpyStrategy#identity * @member * @type {String} */ this.identity = options.name || 'unknown'; this.originalFn = options.fn || function() {}; this.getSpy = options.getSpy || function() {}; - this.plan = function() {}; + this.plan = this._defaultPlan = function() {}; } /** * Execute the current spy strategy. - * @name Spy#and#exec + * @name SpyStrategy#exec * @function */ SpyStrategy.prototype.exec = function(context, args) { @@ -29,7 +29,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** * Tell the spy to call through to the real implementation when invoked. - * @name Spy#and#callThrough + * @name SpyStrategy#callThrough * @function */ SpyStrategy.prototype.callThrough = function() { @@ -39,7 +39,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** * Tell the spy to return the value when invoked. - * @name Spy#and#returnValue + * @name SpyStrategy#returnValue * @function * @param {*} value The value to return. */ @@ -52,7 +52,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** * Tell the spy to return one of the specified values (sequentially) each time the spy is invoked. - * @name Spy#and#returnValues + * @name SpyStrategy#returnValues * @function * @param {...*} values - Values to be returned on subsequent calls to the spy. */ @@ -66,7 +66,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** * Tell the spy to throw an error when invoked. - * @name Spy#and#throwError + * @name SpyStrategy#throwError * @function * @param {Error|String} something Thing to throw */ @@ -80,7 +80,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** * Tell the spy to call a fake implementation when invoked. - * @name Spy#and#callFake + * @name SpyStrategy#callFake * @function * @param {Function} fn The function to invoke with the passed parameters. */ @@ -94,7 +94,7 @@ getJasmineRequireObj().SpyStrategy = function(j$) { /** * Tell the spy to do nothing when invoked. This is the default. - * @name Spy#and#stub + * @name SpyStrategy#stub * @function */ SpyStrategy.prototype.stub = function(fn) { @@ -102,5 +102,9 @@ getJasmineRequireObj().SpyStrategy = function(j$) { return this.getSpy(); }; + SpyStrategy.prototype.isConfigured = function() { + return this.plan !== this._defaultPlan; + }; + return SpyStrategy; }; From 298b5ba1277ff71411a304c4695bc54bb6e2f810 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 9 Jan 2018 09:53:45 -0800 Subject: [PATCH 40/62] Moved createSpyObj to env so it can be stateful --- lib/jasmine-core/jasmine.js | 86 +++++++++++++++++++----------------- spec/core/SpySpec.js | 18 +++++--- src/core/Env.js | 33 ++++++++++++++ src/core/base.js | 41 ----------------- src/core/requireInterface.js | 12 +++++ 5 files changed, 102 insertions(+), 88 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index fbd9dd17..6aca62cf 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -324,47 +324,6 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return putativeSpy.and instanceof j$.SpyStrategy && putativeSpy.calls instanceof j$.CallTracker; }; - - /** - * Create an object with multiple {@link Spy}s as its members. - * @name jasmine.createSpyObj - * @function - * @param {String} [baseName] - Base name for the spies in the object. - * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}. - * @return {Object} - */ - j$.createSpyObj = function(baseName, methodNames) { - var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); - - if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { - methodNames = baseName; - baseName = 'unknown'; - } - - var obj = {}; - var spiesWereSet = false; - - if (j$.isArray_(methodNames)) { - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); - spiesWereSet = true; - } - } else if (j$.isObject_(methodNames)) { - for (var key in methodNames) { - if (methodNames.hasOwnProperty(key)) { - obj[key] = j$.createSpy(baseName + '.' + key); - obj[key].and.returnValue(methodNames[key]); - spiesWereSet = true; - } - } - } - - if (!spiesWereSet) { - throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; - } - - return obj; - }; }; getJasmineRequireObj().util = function(j$) { @@ -1153,6 +1112,39 @@ getJasmineRequireObj().Env = function(j$) { return spyRegistry.spyOnProperty.apply(spyRegistry, arguments); }; + this.createSpyObj = function(baseName, methodNames) { + var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); + + if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { + methodNames = baseName; + baseName = 'unknown'; + } + + var obj = {}; + var spiesWereSet = false; + + if (j$.isArray_(methodNames)) { + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); + spiesWereSet = true; + } + } else if (j$.isObject_(methodNames)) { + for (var key in methodNames) { + if (methodNames.hasOwnProperty(key)) { + obj[key] = j$.createSpy(baseName + '.' + key); + obj[key].and.returnValue(methodNames[key]); + spiesWereSet = true; + } + } + } + + if (!spiesWereSet) { + throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; + } + + return obj; + }; + var ensureIsFunction = function(fn, caller) { if (!j$.isFunction_(fn)) { throw new Error(caller + ' expects a function argument; received ' + j$.getType_(fn)); @@ -4914,6 +4906,18 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.clock; }; + /** + * Create an object with multiple {@link Spy}s as its members. + * @name jasmine.createSpyObj + * @function + * @param {String} [baseName] - Base name for the spies in the object. + * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}. + * @return {Object} + */ + jasmine.createSpyObj = function(baseName, methodNames) { + return env.createSpyObj(baseName, methodNames); + }; + return jasmineInterface; }; diff --git a/spec/core/SpySpec.js b/spec/core/SpySpec.js index 562f684b..ac75ea47 100644 --- a/spec/core/SpySpec.js +++ b/spec/core/SpySpec.js @@ -1,4 +1,10 @@ describe('Spies', function () { + var env; + + beforeEach(function() { + env = new jasmineUnderTest.Env(); + }); + describe("createSpy", function() { var TestClass; @@ -80,7 +86,7 @@ describe('Spies', function () { describe("createSpyObj", function() { it("should create an object with spy methods and corresponding return values when you call jasmine.createSpyObj() with an object", function () { - var spyObj = jasmineUnderTest.createSpyObj('BaseName', {'method1': 42, 'method2': 'special sauce' }); + var spyObj = env.createSpyObj('BaseName', {'method1': 42, 'method2': 'special sauce' }); expect(spyObj.method1()).toEqual(42); expect(spyObj.method1.and.identity).toEqual('BaseName.method1'); @@ -91,7 +97,7 @@ describe('Spies', function () { it("should create an object with a bunch of spy methods when you call jasmine.createSpyObj()", function() { - var spyObj = jasmineUnderTest.createSpyObj('BaseName', ['method1', 'method2']); + var spyObj = env.createSpyObj('BaseName', ['method1', 'method2']); expect(spyObj).toEqual({ method1: jasmine.any(Function), method2: jasmine.any(Function)}); expect(spyObj.method1.and.identity).toEqual('BaseName.method1'); @@ -99,7 +105,7 @@ describe('Spies', function () { }); it("should allow you to omit the baseName", function() { - var spyObj = jasmineUnderTest.createSpyObj(['method1', 'method2']); + var spyObj = env.createSpyObj(['method1', 'method2']); expect(spyObj).toEqual({ method1: jasmine.any(Function), method2: jasmine.any(Function)}); expect(spyObj.method1.and.identity).toEqual('unknown.method1'); @@ -108,19 +114,19 @@ describe('Spies', function () { it("should throw if you do not pass an array or object argument", function() { expect(function() { - jasmineUnderTest.createSpyObj('BaseName'); + env.createSpyObj('BaseName'); }).toThrow("createSpyObj requires a non-empty array or object of method names to create spies for"); }); it("should throw if you pass an empty array argument", function() { expect(function() { - jasmineUnderTest.createSpyObj('BaseName', []); + env.createSpyObj('BaseName', []); }).toThrow("createSpyObj requires a non-empty array or object of method names to create spies for"); }); it("should throw if you pass an empty object argument", function() { expect(function() { - jasmineUnderTest.createSpyObj('BaseName', {}); + env.createSpyObj('BaseName', {}); }).toThrow("createSpyObj requires a non-empty array or object of method names to create spies for"); }); }); diff --git a/src/core/Env.js b/src/core/Env.js index 003fd8fc..3820d698 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -412,6 +412,39 @@ getJasmineRequireObj().Env = function(j$) { return spyRegistry.spyOnProperty.apply(spyRegistry, arguments); }; + this.createSpyObj = function(baseName, methodNames) { + var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); + + if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { + methodNames = baseName; + baseName = 'unknown'; + } + + var obj = {}; + var spiesWereSet = false; + + if (j$.isArray_(methodNames)) { + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); + spiesWereSet = true; + } + } else if (j$.isObject_(methodNames)) { + for (var key in methodNames) { + if (methodNames.hasOwnProperty(key)) { + obj[key] = j$.createSpy(baseName + '.' + key); + obj[key].and.returnValue(methodNames[key]); + spiesWereSet = true; + } + } + } + + if (!spiesWereSet) { + throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; + } + + return obj; + }; + var ensureIsFunction = function(fn, caller) { if (!j$.isFunction_(fn)) { throw new Error(caller + ' expects a function argument; received ' + j$.getType_(fn)); diff --git a/src/core/base.js b/src/core/base.js index da39f762..68817d95 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -192,45 +192,4 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return putativeSpy.and instanceof j$.SpyStrategy && putativeSpy.calls instanceof j$.CallTracker; }; - - /** - * Create an object with multiple {@link Spy}s as its members. - * @name jasmine.createSpyObj - * @function - * @param {String} [baseName] - Base name for the spies in the object. - * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}. - * @return {Object} - */ - j$.createSpyObj = function(baseName, methodNames) { - var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); - - if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { - methodNames = baseName; - baseName = 'unknown'; - } - - var obj = {}; - var spiesWereSet = false; - - if (j$.isArray_(methodNames)) { - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); - spiesWereSet = true; - } - } else if (j$.isObject_(methodNames)) { - for (var key in methodNames) { - if (methodNames.hasOwnProperty(key)) { - obj[key] = j$.createSpy(baseName + '.' + key); - obj[key].and.returnValue(methodNames[key]); - spiesWereSet = true; - } - } - } - - if (!spiesWereSet) { - throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; - } - - return obj; - }; }; diff --git a/src/core/requireInterface.js b/src/core/requireInterface.js index aa79d206..4e182648 100644 --- a/src/core/requireInterface.js +++ b/src/core/requireInterface.js @@ -256,5 +256,17 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.clock; }; + /** + * Create an object with multiple {@link Spy}s as its members. + * @name jasmine.createSpyObj + * @function + * @param {String} [baseName] - Base name for the spies in the object. + * @param {String[]|Object} methodNames - Array of method names to create spies for, or Object whose keys will be method names and values the {@link Spy#and#returnValue|returnValue}. + * @return {Object} + */ + jasmine.createSpyObj = function(baseName, methodNames) { + return env.createSpyObj(baseName, methodNames); + }; + return jasmineInterface; }; From 6f119c4e5a574346365c9cf1aee71ef2633546ca Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 9 Jan 2018 10:16:02 -0800 Subject: [PATCH 41/62] Moved createSpy to env so it can be stateful --- lib/jasmine-core/jasmine.js | 52 +++++++++++-------- spec/core/PrettyPrintSpec.js | 9 +++- spec/core/SpyRegistrySpec.js | 49 ++++++++++++----- spec/core/SpySpec.js | 24 ++++----- .../matchers/toHaveBeenCalledBeforeSpec.js | 28 +++++----- spec/core/matchers/toHaveBeenCalledSpec.js | 8 +-- .../matchers/toHaveBeenCalledTimesSpec.js | 14 ++--- .../core/matchers/toHaveBeenCalledWithSpec.js | 8 +-- src/core/Env.js | 23 +++++--- src/core/SpyRegistry.js | 5 +- src/core/base.js | 12 ----- src/core/requireInterface.js | 12 +++++ 12 files changed, 147 insertions(+), 97 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 6aca62cf..86a7df90 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -305,18 +305,6 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return new j$.ArrayWithExactContents(sample); }; - /** - * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it. - * @name jasmine.createSpy - * @function - * @param {String} [name] - Name to give the spy. This will be displayed in failure messages. - * @param {Function} [originalFn] - Function to act as the real implementation. - * @return {Spy} - */ - j$.createSpy = function(name, originalFn) { - return j$.Spy(name, originalFn); - }; - j$.isSpy = function(putativeSpy) { if (!putativeSpy) { return false; @@ -1093,12 +1081,17 @@ getJasmineRequireObj().Env = function(j$) { reporter.clearReporters(); }; - var spyRegistry = new j$.SpyRegistry({currentSpies: function() { - if(!currentRunnable()) { - throw new Error('Spies must be created in a before function or a spec'); + var spyRegistry = new j$.SpyRegistry({ + currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); + } + return runnableResources[currentRunnable().id].spies; + }, + createSpy: function(name, originalFn) { + return self.createSpy(name, originalFn); } - return runnableResources[currentRunnable().id].spies; - }}); + }); this.allowRespy = function(allow){ spyRegistry.allowRespy(allow); @@ -1112,6 +1105,10 @@ getJasmineRequireObj().Env = function(j$) { return spyRegistry.spyOnProperty.apply(spyRegistry, arguments); }; + this.createSpy = function(name, originalFn) { + return j$.Spy(name, originalFn); + }; + this.createSpyObj = function(baseName, methodNames) { var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); @@ -1125,13 +1122,13 @@ getJasmineRequireObj().Env = function(j$) { if (j$.isArray_(methodNames)) { for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); + obj[methodNames[i]] = self.createSpy(baseName + '.' + methodNames[i]); spiesWereSet = true; } } else if (j$.isObject_(methodNames)) { for (var key in methodNames) { if (methodNames.hasOwnProperty(key)) { - obj[key] = j$.createSpy(baseName + '.' + key); + obj[key] = self.createSpy(baseName + '.' + key); obj[key].and.returnValue(methodNames[key]); spiesWereSet = true; } @@ -4906,6 +4903,18 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.clock; }; + /** + * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it. + * @name jasmine.createSpy + * @function + * @param {String} [name] - Name to give the spy. This will be displayed in failure messages. + * @param {Function} [originalFn] - Function to act as the real implementation. + * @return {Spy} + */ + jasmine.createSpy = function(name, originalFn) { + return env.createSpy(name, originalFn); + }; + /** * Create an object with multiple {@link Spy}s as its members. * @name jasmine.createSpyObj @@ -5090,6 +5099,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { function SpyRegistry(options) { options = options || {}; + var createSpy = options.createSpy; var currentSpies = options.currentSpies || function() { return []; }; this.allowRespy = function(allow){ @@ -5125,7 +5135,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { } var originalMethod = obj[methodName], - spiedMethod = j$.createSpy(methodName, originalMethod), + spiedMethod = createSpy(methodName, originalMethod), restoreStrategy; if (Object.prototype.hasOwnProperty.call(obj, methodName)) { @@ -5180,7 +5190,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { } var originalDescriptor = j$.util.clone(descriptor), - spy = j$.createSpy(propertyName, descriptor[accessType]), + spy = createSpy(propertyName, descriptor[accessType]), restoreStrategy; if (Object.prototype.hasOwnProperty.call(obj, propertyName)) { diff --git a/spec/core/PrettyPrintSpec.js b/spec/core/PrettyPrintSpec.js index cfdeeb18..2277afbf 100644 --- a/spec/core/PrettyPrintSpec.js +++ b/spec/core/PrettyPrintSpec.js @@ -255,12 +255,17 @@ describe("jasmineUnderTest.pp", function () { }, env = new jasmineUnderTest.Env(); - var spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() {return [];}}); + var spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() {return [];}, + createSpy: function(name, originalFn) { + return jasmineUnderTest.Spy(name, originalFn); + } + }); spyRegistry.spyOn(TestObject, 'someFunction'); expect(jasmineUnderTest.pp(TestObject.someFunction)).toEqual("spy on someFunction"); - expect(jasmineUnderTest.pp(jasmineUnderTest.createSpy("something"))).toEqual("spy on something"); + expect(jasmineUnderTest.pp(env.createSpy("something"))).toEqual("spy on something"); }); it("should stringify objects that implement jasmineToString", function () { diff --git a/spec/core/SpyRegistrySpec.js b/spec/core/SpyRegistrySpec.js index b25dbaaa..d2c10e1b 100644 --- a/spec/core/SpyRegistrySpec.js +++ b/spec/core/SpyRegistrySpec.js @@ -1,7 +1,11 @@ describe("SpyRegistry", function() { + function createSpy(name, originalFn) { + return jasmineUnderTest.Spy(name, originalFn); + } + describe("#spyOn", function() { it("checks for the existence of the object", function() { - var spyRegistry = new jasmineUnderTest.SpyRegistry(); + var spyRegistry = new jasmineUnderTest.SpyRegistry({createSpy: createSpy}); expect(function() { spyRegistry.spyOn(void 0, 'pants'); }).toThrowError(/could not find an object/); @@ -43,7 +47,10 @@ describe("SpyRegistry", function() { it("checks if it has already been spied upon", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), subject = { spiedFunc: function() {} }; spyRegistry.spyOn(subject, 'spiedFunc'); @@ -81,7 +88,7 @@ describe("SpyRegistry", function() { it("overrides the method on the object and returns the spy", function() { var originalFunctionWasCalled = false, - spyRegistry = new jasmineUnderTest.SpyRegistry(), + spyRegistry = new jasmineUnderTest.SpyRegistry({createSpy: createSpy}), subject = { spiedFunc: function() { originalFunctionWasCalled = true; } }; var spy = spyRegistry.spyOn(subject, 'spiedFunc'); @@ -131,7 +138,7 @@ describe("SpyRegistry", function() { }); it("checks if it has already been spied upon", function() { - var spyRegistry = new jasmineUnderTest.SpyRegistry(), + var spyRegistry = new jasmineUnderTest.SpyRegistry({createSpy: createSpy}), subject = {}; Object.defineProperty(subject, 'spiedProp', { @@ -170,7 +177,7 @@ describe("SpyRegistry", function() { }); it("overrides the property getter on the object and returns the spy", function() { - var spyRegistry = new jasmineUnderTest.SpyRegistry(), + var spyRegistry = new jasmineUnderTest.SpyRegistry({createSpy: createSpy}), subject = {}, returnValue = 1; @@ -189,7 +196,7 @@ describe("SpyRegistry", function() { }); it("overrides the property setter on the object and returns the spy", function() { - var spyRegistry = new jasmineUnderTest.SpyRegistry(), + var spyRegistry = new jasmineUnderTest.SpyRegistry({createSpy: createSpy}), subject = {}, returnValue = 1; @@ -210,7 +217,10 @@ describe("SpyRegistry", function() { describe("#clearSpies", function() { it("restores the original functions on the spied-upon objects", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), originalFunction = function() {}, subject = { spiedFunc: originalFunction }; @@ -222,7 +232,10 @@ describe("SpyRegistry", function() { it("restores the original functions, even when that spy has been replace and re-spied upon", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), originalFunction = function() {}, subject = { spiedFunc: originalFunction }; @@ -241,7 +254,10 @@ describe("SpyRegistry", function() { it("does not add a property that the spied-upon object didn't originally have", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), originalFunction = function() {}, subjectParent = {spiedFunc: originalFunction}; @@ -258,7 +274,10 @@ describe("SpyRegistry", function() { it("restores the original function when it\'s inherited and cannot be deleted", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), originalFunction = function() {}, subjectParent = {spiedFunc: originalFunction}; @@ -280,7 +299,10 @@ describe("SpyRegistry", function() { describe('spying on properties', function() { it("restores the original properties on the spied-upon objects", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), originalReturn = 1, subject = {}; @@ -297,7 +319,10 @@ describe("SpyRegistry", function() { it("does not add a property that the spied-upon object didn't originally have", function() { var spies = [], - spyRegistry = new jasmineUnderTest.SpyRegistry({currentSpies: function() { return spies; }}), + spyRegistry = new jasmineUnderTest.SpyRegistry({ + currentSpies: function() { return spies; }, + createSpy: createSpy + }), originalReturn = 1, subjectParent = {}; diff --git a/spec/core/SpySpec.js b/spec/core/SpySpec.js index ac75ea47..fb90657f 100644 --- a/spec/core/SpySpec.js +++ b/spec/core/SpySpec.js @@ -15,7 +15,7 @@ describe('Spies', function () { }); it("preserves the properties of the spied function", function() { - var spy = jasmineUnderTest.createSpy(TestClass.prototype, TestClass.prototype.someFunction); + var spy = env.createSpy(TestClass.prototype, TestClass.prototype.someFunction); expect(spy.bob).toEqual("test"); }); @@ -24,19 +24,19 @@ describe('Spies', function () { TestClass.prototype.someFunction.and = "turkey"; expect(function() { - jasmineUnderTest.createSpy(TestClass.prototype, TestClass.prototype.someFunction); + env.createSpy(TestClass.prototype, TestClass.prototype.someFunction); }).toThrowError("Jasmine spies would overwrite the 'and' and 'calls' properties on the object being spied upon"); }); it("adds a spyStrategy and callTracker to the spy", function() { - var spy = jasmineUnderTest.createSpy(TestClass.prototype, TestClass.prototype.someFunction); + var spy = env.createSpy(TestClass.prototype, TestClass.prototype.someFunction); expect(spy.and).toEqual(jasmine.any(jasmineUnderTest.SpyStrategy)); expect(spy.calls).toEqual(jasmine.any(jasmineUnderTest.CallTracker)); }); it("tracks the argument of calls", function () { - var spy = jasmineUnderTest.createSpy(TestClass.prototype, TestClass.prototype.someFunction); + var spy = env.createSpy(TestClass.prototype, TestClass.prototype.someFunction); var trackSpy = spyOn(spy.calls, "track"); spy("arg"); @@ -45,7 +45,7 @@ describe('Spies', function () { }); it("tracks the context of calls", function () { - var spy = jasmineUnderTest.createSpy(TestClass.prototype, TestClass.prototype.someFunction); + var spy = env.createSpy(TestClass.prototype, TestClass.prototype.someFunction); var trackSpy = spyOn(spy.calls, "track"); var contextObject = { spyMethod: spy }; @@ -55,7 +55,7 @@ describe('Spies', function () { }); it("tracks the return value of calls", function () { - var spy = jasmineUnderTest.createSpy(TestClass.prototype, TestClass.prototype.someFunction); + var spy = env.createSpy(TestClass.prototype, TestClass.prototype.someFunction); var trackSpy = spyOn(spy.calls, "track"); spy.and.returnValue("return value"); @@ -77,7 +77,7 @@ describe('Spies', function () { for (var arity = 0; arity < functions.length; arity++) { var someFunction = functions[arity], - spy = jasmineUnderTest.createSpy(someFunction.name, someFunction); + spy = env.createSpy(someFunction.name, someFunction); expect(spy.length).toEqual(arity); } @@ -132,7 +132,7 @@ describe('Spies', function () { }); it("can use different strategies for different arguments", function() { - var spy = jasmineUnderTest.createSpy('foo'); + var spy = env.createSpy('foo'); spy.and.returnValue(42); spy.withArgs('baz', 'grault').and.returnValue(-1); spy.withArgs('thud').and.returnValue('bob'); @@ -144,7 +144,7 @@ describe('Spies', function () { }); it("uses custom equality testers when selecting a strategy", function() { - var spy = jasmineUnderTest.createSpy('foo'); + var spy = env.createSpy('foo'); spy.and.returnValue(42); spy.withArgs(jasmineUnderTest.any(String)).and.returnValue(-1); @@ -153,7 +153,7 @@ describe('Spies', function () { }); it("can reconfigure an argument-specific strategy", function() { - var spy = jasmineUnderTest.createSpy('foo'); + var spy = env.createSpy('foo'); spy.withArgs('foo').and.returnValue(42); spy.withArgs('foo').and.returnValue(17); expect(spy('foo')).toEqual(17); @@ -161,14 +161,14 @@ describe('Spies', function () { describe("When withArgs is used without a base strategy", function() { it("uses the matching strategy", function() { - var spy = jasmineUnderTest.createSpy('foo'); + var spy = env.createSpy('foo'); spy.withArgs('baz').and.returnValue(-1); expect(spy('baz')).toEqual(-1); }); it("throws if the args don't match", function() { - var spy = jasmineUnderTest.createSpy('foo'); + var spy = env.createSpy('foo'); spy.withArgs('bar').and.returnValue(-1); expect(function() { spy('baz', {qux: 42}); }).toThrowError('Spy \'foo\' receieved a call with arguments [ \'baz\', Object({ qux: 42 }) ] but all configured strategies specify other arguments.'); diff --git a/spec/core/matchers/toHaveBeenCalledBeforeSpec.js b/spec/core/matchers/toHaveBeenCalledBeforeSpec.js index 256ed1d2..f2e89c2f 100644 --- a/spec/core/matchers/toHaveBeenCalledBeforeSpec.js +++ b/spec/core/matchers/toHaveBeenCalledBeforeSpec.js @@ -2,14 +2,14 @@ describe("toHaveBeenCalledBefore", function() { it("throws an exception when the actual is not a spy", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), fn = function() {}, - secondSpy = jasmineUnderTest.createSpy('second spy'); + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'); expect(function() { matcher.compare(fn, secondSpy) }).toThrowError(Error, /Expected a spy, but got Function./); }); it("throws an exception when the expected is not a spy", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), fn = function() {}; expect(function() { matcher.compare(firstSpy, fn) }).toThrowError(Error, /Expected a spy, but got Function./); @@ -17,8 +17,8 @@ describe("toHaveBeenCalledBefore", function() { it("fails when the actual was not called", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), - secondSpy = jasmineUnderTest.createSpy('second spy'); + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'); secondSpy(); @@ -29,8 +29,8 @@ describe("toHaveBeenCalledBefore", function() { it("fails when the expected was not called", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), - secondSpy = jasmineUnderTest.createSpy('second spy'); + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'); firstSpy(); @@ -41,8 +41,8 @@ describe("toHaveBeenCalledBefore", function() { it("fails when the actual is called after the expected", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), - secondSpy = jasmineUnderTest.createSpy('second spy'), + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'), result; secondSpy(); @@ -55,8 +55,8 @@ describe("toHaveBeenCalledBefore", function() { it("fails when the actual is called before and after the expected", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), - secondSpy = jasmineUnderTest.createSpy('second spy'), + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'), result; firstSpy(); @@ -70,8 +70,8 @@ describe("toHaveBeenCalledBefore", function() { it("fails when the expected is called before and after the actual", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), - secondSpy = jasmineUnderTest.createSpy('second spy'), + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'), result; secondSpy(); @@ -85,8 +85,8 @@ describe("toHaveBeenCalledBefore", function() { it("passes when the actual is called before the expected", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledBefore(), - firstSpy = jasmineUnderTest.createSpy('first spy'), - secondSpy = jasmineUnderTest.createSpy('second spy'), + firstSpy = new jasmineUnderTest.Env().createSpy('first spy'), + secondSpy = new jasmineUnderTest.Env().createSpy('second spy'), result; firstSpy(); diff --git a/spec/core/matchers/toHaveBeenCalledSpec.js b/spec/core/matchers/toHaveBeenCalledSpec.js index e5597176..b14116f0 100644 --- a/spec/core/matchers/toHaveBeenCalledSpec.js +++ b/spec/core/matchers/toHaveBeenCalledSpec.js @@ -1,7 +1,7 @@ describe("toHaveBeenCalled", function() { it("passes when the actual was called, with a custom .not fail message", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalled(), - calledSpy = jasmineUnderTest.createSpy('called-spy'), + calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'), result; calledSpy(); @@ -13,7 +13,7 @@ describe("toHaveBeenCalled", function() { it("fails when the actual was not called", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalled(), - uncalledSpy = jasmineUnderTest.createSpy('uncalled spy'), + uncalledSpy = new jasmineUnderTest.Env().createSpy('uncalled spy'), result; result = matcher.compare(uncalledSpy); @@ -29,14 +29,14 @@ describe("toHaveBeenCalled", function() { it("throws an exception when invoked with any arguments", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalled(), - spy = jasmineUnderTest.createSpy('sample spy'); + spy = new jasmineUnderTest.Env().createSpy('sample spy'); expect(function() { matcher.compare(spy, 'foo') }).toThrowError(Error, /Does not take arguments, use toHaveBeenCalledWith/); }); it("has a custom message on failure", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalled(), - spy = jasmineUnderTest.createSpy('sample-spy'), + spy = new jasmineUnderTest.Env().createSpy('sample-spy'), result; result = matcher.compare(spy); diff --git a/spec/core/matchers/toHaveBeenCalledTimesSpec.js b/spec/core/matchers/toHaveBeenCalledTimesSpec.js index 6a12e779..62778fbd 100644 --- a/spec/core/matchers/toHaveBeenCalledTimesSpec.js +++ b/spec/core/matchers/toHaveBeenCalledTimesSpec.js @@ -1,14 +1,14 @@ describe("toHaveBeenCalledTimes", function() { it("passes when the actual 0 matches the expected 0 ", function () { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - calledSpy = jasmineUnderTest.createSpy('called-spy'), + calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'), result; result = matcher.compare(calledSpy, 0); expect(result.pass).toBeTruthy(); }); it("passes when the actual matches the expected", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - calledSpy = jasmineUnderTest.createSpy('called-spy'), + calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'), result; calledSpy(); @@ -18,7 +18,7 @@ describe("toHaveBeenCalledTimes", function() { it("fails when expected numbers is not supplied", function(){ var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - spy = jasmineUnderTest.createSpy('spy'), + spy = new jasmineUnderTest.Env().createSpy('spy'), result; spy(); @@ -29,7 +29,7 @@ describe("toHaveBeenCalledTimes", function() { it("fails when the actual was called less than the expected", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - uncalledSpy = jasmineUnderTest.createSpy('uncalled spy'), + uncalledSpy = new jasmineUnderTest.Env().createSpy('uncalled spy'), result; result = matcher.compare(uncalledSpy, 2); @@ -38,7 +38,7 @@ describe("toHaveBeenCalledTimes", function() { it("fails when the actual was called more than expected", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - uncalledSpy = jasmineUnderTest.createSpy('uncalled spy'), + uncalledSpy = new jasmineUnderTest.Env().createSpy('uncalled spy'), result; uncalledSpy(); @@ -59,7 +59,7 @@ describe("toHaveBeenCalledTimes", function() { it("has a custom message on failure that tells it was called only once", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - spy = jasmineUnderTest.createSpy('sample-spy'), + spy = new jasmineUnderTest.Env().createSpy('sample-spy'), result; spy(); spy(); @@ -72,7 +72,7 @@ describe("toHaveBeenCalledTimes", function() { it("has a custom message on failure that tells how many times it was called", function() { var matcher = jasmineUnderTest.matchers.toHaveBeenCalledTimes(), - spy = jasmineUnderTest.createSpy('sample-spy'), + spy = new jasmineUnderTest.Env().createSpy('sample-spy'), result; spy(); spy(); diff --git a/spec/core/matchers/toHaveBeenCalledWithSpec.js b/spec/core/matchers/toHaveBeenCalledWithSpec.js index fa39fc0a..110e3dc2 100644 --- a/spec/core/matchers/toHaveBeenCalledWithSpec.js +++ b/spec/core/matchers/toHaveBeenCalledWithSpec.js @@ -5,7 +5,7 @@ describe("toHaveBeenCalledWith", function() { contains: jasmine.createSpy('delegated-contains').and.returnValue(true) }, matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), - calledSpy = jasmineUnderTest.createSpy('called-spy'), + calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'), result; calledSpy('a', 'b'); @@ -21,7 +21,7 @@ describe("toHaveBeenCalledWith", function() { }, customEqualityTesters = [function() { return true; }], matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util, customEqualityTesters), - calledSpy = jasmineUnderTest.createSpy('called-spy'); + calledSpy = new jasmineUnderTest.Env().createSpy('called-spy'); calledSpy('a', 'b'); matcher.compare(calledSpy, 'a', 'b'); @@ -34,7 +34,7 @@ describe("toHaveBeenCalledWith", function() { contains: jasmine.createSpy('delegated-contains').and.returnValue(false) }, matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), - uncalledSpy = jasmineUnderTest.createSpy('uncalled spy'), + uncalledSpy = new jasmineUnderTest.Env().createSpy('uncalled spy'), result; result = matcher.compare(uncalledSpy); @@ -47,7 +47,7 @@ describe("toHaveBeenCalledWith", function() { contains: jasmine.createSpy('delegated-contains').and.returnValue(false) }, matcher = jasmineUnderTest.matchers.toHaveBeenCalledWith(util), - calledSpy = jasmineUnderTest.createSpy('called spy'), + calledSpy = new jasmineUnderTest.Env().createSpy('called spy'), result; calledSpy('a'); diff --git a/src/core/Env.js b/src/core/Env.js index 3820d698..b0a1ae17 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -393,12 +393,17 @@ getJasmineRequireObj().Env = function(j$) { reporter.clearReporters(); }; - var spyRegistry = new j$.SpyRegistry({currentSpies: function() { - if(!currentRunnable()) { - throw new Error('Spies must be created in a before function or a spec'); + var spyRegistry = new j$.SpyRegistry({ + currentSpies: function() { + if(!currentRunnable()) { + throw new Error('Spies must be created in a before function or a spec'); + } + return runnableResources[currentRunnable().id].spies; + }, + createSpy: function(name, originalFn) { + return self.createSpy(name, originalFn); } - return runnableResources[currentRunnable().id].spies; - }}); + }); this.allowRespy = function(allow){ spyRegistry.allowRespy(allow); @@ -412,6 +417,10 @@ getJasmineRequireObj().Env = function(j$) { return spyRegistry.spyOnProperty.apply(spyRegistry, arguments); }; + this.createSpy = function(name, originalFn) { + return j$.Spy(name, originalFn); + }; + this.createSpyObj = function(baseName, methodNames) { var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); @@ -425,13 +434,13 @@ getJasmineRequireObj().Env = function(j$) { if (j$.isArray_(methodNames)) { for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = j$.createSpy(baseName + '.' + methodNames[i]); + obj[methodNames[i]] = self.createSpy(baseName + '.' + methodNames[i]); spiesWereSet = true; } } else if (j$.isObject_(methodNames)) { for (var key in methodNames) { if (methodNames.hasOwnProperty(key)) { - obj[key] = j$.createSpy(baseName + '.' + key); + obj[key] = self.createSpy(baseName + '.' + key); obj[key].and.returnValue(methodNames[key]); spiesWereSet = true; } diff --git a/src/core/SpyRegistry.js b/src/core/SpyRegistry.js index 139bfa3d..1361cbac 100644 --- a/src/core/SpyRegistry.js +++ b/src/core/SpyRegistry.js @@ -4,6 +4,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { function SpyRegistry(options) { options = options || {}; + var createSpy = options.createSpy; var currentSpies = options.currentSpies || function() { return []; }; this.allowRespy = function(allow){ @@ -39,7 +40,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { } var originalMethod = obj[methodName], - spiedMethod = j$.createSpy(methodName, originalMethod), + spiedMethod = createSpy(methodName, originalMethod), restoreStrategy; if (Object.prototype.hasOwnProperty.call(obj, methodName)) { @@ -94,7 +95,7 @@ getJasmineRequireObj().SpyRegistry = function(j$) { } var originalDescriptor = j$.util.clone(descriptor), - spy = j$.createSpy(propertyName, descriptor[accessType]), + spy = createSpy(propertyName, descriptor[accessType]), restoreStrategy; if (Object.prototype.hasOwnProperty.call(obj, propertyName)) { diff --git a/src/core/base.js b/src/core/base.js index 68817d95..5f0d54aa 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -173,18 +173,6 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return new j$.ArrayWithExactContents(sample); }; - /** - * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it. - * @name jasmine.createSpy - * @function - * @param {String} [name] - Name to give the spy. This will be displayed in failure messages. - * @param {Function} [originalFn] - Function to act as the real implementation. - * @return {Spy} - */ - j$.createSpy = function(name, originalFn) { - return j$.Spy(name, originalFn); - }; - j$.isSpy = function(putativeSpy) { if (!putativeSpy) { return false; diff --git a/src/core/requireInterface.js b/src/core/requireInterface.js index 4e182648..ac0279d7 100644 --- a/src/core/requireInterface.js +++ b/src/core/requireInterface.js @@ -256,6 +256,18 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.clock; }; + /** + * Create a bare {@link Spy} object. This won't be installed anywhere and will not have any implementation behind it. + * @name jasmine.createSpy + * @function + * @param {String} [name] - Name to give the spy. This will be displayed in failure messages. + * @param {Function} [originalFn] - Function to act as the real implementation. + * @return {Spy} + */ + jasmine.createSpy = function(name, originalFn) { + return env.createSpy(name, originalFn); + }; + /** * Create an object with multiple {@link Spy}s as its members. * @name jasmine.createSpyObj From 1085914a76d4f9d5d2cc8d376481de9332fac082 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Tue, 9 Jan 2018 13:31:09 -0800 Subject: [PATCH 42/62] Extracted a SpyFactory to slim Env down a bit --- lib/jasmine-core/jasmine.js | 82 +++++++++++++++++++++++-------------- src/core/Env.js | 35 ++-------------- src/core/SpyFactory.js | 45 ++++++++++++++++++++ src/core/requireCore.js | 1 + 4 files changed, 101 insertions(+), 62 deletions(-) create mode 100644 src/core/SpyFactory.js diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 86a7df90..291bf3fc 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -70,6 +70,7 @@ var getJasmineRequireObj = (function (jasmineGlobal) { j$.ReportDispatcher = jRequire.ReportDispatcher(j$); j$.Spec = jRequire.Spec(j$); j$.Spy = jRequire.Spy(j$); + j$.SpyFactory = jRequire.SpyFactory(j$); j$.SpyRegistry = jRequire.SpyRegistry(j$); j$.SpyStrategy = jRequire.SpyStrategy(j$); j$.StringMatching = jRequire.StringMatching(j$); @@ -1081,6 +1082,8 @@ getJasmineRequireObj().Env = function(j$) { reporter.clearReporters(); }; + var spyFactory = new j$.SpyFactory(); + var spyRegistry = new j$.SpyRegistry({ currentSpies: function() { if(!currentRunnable()) { @@ -1106,40 +1109,11 @@ getJasmineRequireObj().Env = function(j$) { }; this.createSpy = function(name, originalFn) { - return j$.Spy(name, originalFn); + return spyFactory.createSpy(name, originalFn); }; this.createSpyObj = function(baseName, methodNames) { - var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); - - if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { - methodNames = baseName; - baseName = 'unknown'; - } - - var obj = {}; - var spiesWereSet = false; - - if (j$.isArray_(methodNames)) { - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = self.createSpy(baseName + '.' + methodNames[i]); - spiesWereSet = true; - } - } else if (j$.isObject_(methodNames)) { - for (var key in methodNames) { - if (methodNames.hasOwnProperty(key)) { - obj[key] = self.createSpy(baseName + '.' + key); - obj[key].and.returnValue(methodNames[key]); - spiesWereSet = true; - } - } - } - - if (!spiesWereSet) { - throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; - } - - return obj; + return spyFactory.createSpyObj(baseName, methodNames); }; var ensureIsFunction = function(fn, caller) { @@ -5093,6 +5067,52 @@ getJasmineRequireObj().Spy = function (j$) { return Spy; }; +getJasmineRequireObj().SpyFactory = function(j$) { + + function SpyFactory() { + var self = this; + + this.createSpy = function(name, originalFn) { + return j$.Spy(name, originalFn); + }; + + this.createSpyObj = function(baseName, methodNames) { + var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); + + if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { + methodNames = baseName; + baseName = 'unknown'; + } + + var obj = {}; + var spiesWereSet = false; + + if (j$.isArray_(methodNames)) { + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = self.createSpy(baseName + '.' + methodNames[i]); + spiesWereSet = true; + } + } else if (j$.isObject_(methodNames)) { + for (var key in methodNames) { + if (methodNames.hasOwnProperty(key)) { + obj[key] = self.createSpy(baseName + '.' + key); + obj[key].and.returnValue(methodNames[key]); + spiesWereSet = true; + } + } + } + + if (!spiesWereSet) { + throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; + } + + return obj; + }; + } + + return SpyFactory; +}; + getJasmineRequireObj().SpyRegistry = function(j$) { var getErrorMsg = j$.formatErrorMsg('', 'spyOn(, )'); diff --git a/src/core/Env.js b/src/core/Env.js index b0a1ae17..57f6868a 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -393,6 +393,8 @@ getJasmineRequireObj().Env = function(j$) { reporter.clearReporters(); }; + var spyFactory = new j$.SpyFactory(); + var spyRegistry = new j$.SpyRegistry({ currentSpies: function() { if(!currentRunnable()) { @@ -418,40 +420,11 @@ getJasmineRequireObj().Env = function(j$) { }; this.createSpy = function(name, originalFn) { - return j$.Spy(name, originalFn); + return spyFactory.createSpy(name, originalFn); }; this.createSpyObj = function(baseName, methodNames) { - var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); - - if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { - methodNames = baseName; - baseName = 'unknown'; - } - - var obj = {}; - var spiesWereSet = false; - - if (j$.isArray_(methodNames)) { - for (var i = 0; i < methodNames.length; i++) { - obj[methodNames[i]] = self.createSpy(baseName + '.' + methodNames[i]); - spiesWereSet = true; - } - } else if (j$.isObject_(methodNames)) { - for (var key in methodNames) { - if (methodNames.hasOwnProperty(key)) { - obj[key] = self.createSpy(baseName + '.' + key); - obj[key].and.returnValue(methodNames[key]); - spiesWereSet = true; - } - } - } - - if (!spiesWereSet) { - throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; - } - - return obj; + return spyFactory.createSpyObj(baseName, methodNames); }; var ensureIsFunction = function(fn, caller) { diff --git a/src/core/SpyFactory.js b/src/core/SpyFactory.js new file mode 100644 index 00000000..f36c299e --- /dev/null +++ b/src/core/SpyFactory.js @@ -0,0 +1,45 @@ +getJasmineRequireObj().SpyFactory = function(j$) { + + function SpyFactory() { + var self = this; + + this.createSpy = function(name, originalFn) { + return j$.Spy(name, originalFn); + }; + + this.createSpyObj = function(baseName, methodNames) { + var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); + + if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { + methodNames = baseName; + baseName = 'unknown'; + } + + var obj = {}; + var spiesWereSet = false; + + if (j$.isArray_(methodNames)) { + for (var i = 0; i < methodNames.length; i++) { + obj[methodNames[i]] = self.createSpy(baseName + '.' + methodNames[i]); + spiesWereSet = true; + } + } else if (j$.isObject_(methodNames)) { + for (var key in methodNames) { + if (methodNames.hasOwnProperty(key)) { + obj[key] = self.createSpy(baseName + '.' + key); + obj[key].and.returnValue(methodNames[key]); + spiesWereSet = true; + } + } + } + + if (!spiesWereSet) { + throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; + } + + return obj; + }; + } + + return SpyFactory; +}; diff --git a/src/core/requireCore.js b/src/core/requireCore.js index 78fb343a..358e7e8c 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -48,6 +48,7 @@ var getJasmineRequireObj = (function (jasmineGlobal) { j$.ReportDispatcher = jRequire.ReportDispatcher(j$); j$.Spec = jRequire.Spec(j$); j$.Spy = jRequire.Spy(j$); + j$.SpyFactory = jRequire.SpyFactory(j$); j$.SpyRegistry = jRequire.SpyRegistry(j$); j$.SpyStrategy = jRequire.SpyStrategy(j$); j$.StringMatching = jRequire.StringMatching(j$); From 4934e420b20bd4f6af3668ef06481c9dc7fecc33 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 10 Jan 2018 08:41:47 -0800 Subject: [PATCH 43/62] Allow adding custom spy strategies [#37288941] --- lib/jasmine-core/jasmine.js | 44 +++++++-- spec/core/SpyStrategySpec.js | 43 ++++++++ .../integration/CustomSpyStrategiesSpec.js | 98 +++++++++++++++++++ src/core/Env.js | 19 +++- src/core/Spy.js | 5 +- src/core/SpyFactory.js | 4 +- src/core/SpyStrategy.js | 16 +++ 7 files changed, 217 insertions(+), 12 deletions(-) create mode 100644 spec/core/integration/CustomSpyStrategiesSpec.js diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 291bf3fc..9b7290fa 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -797,6 +797,13 @@ getJasmineRequireObj().Env = function(j$) { return true; }; + this.addSpyStrategy = function(name, fn) { + if(!currentRunnable()) { + throw new Error('Custom spy strategies must be added in a before function or a spec'); + } + runnableResources[currentRunnable().id].customSpyStrategies[name] = fn; + }; + this.addCustomEqualityTester = function(tester) { if(!currentRunnable()) { throw new Error('Custom Equalities must be added in a before function or a spec'); @@ -841,7 +848,7 @@ getJasmineRequireObj().Env = function(j$) { }; var defaultResourcesForRunnable = function(id, parentRunnableId) { - var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}, customSpyStrategies: {}}; if(runnableResources[parentRunnableId]){ resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); @@ -1082,7 +1089,15 @@ getJasmineRequireObj().Env = function(j$) { reporter.clearReporters(); }; - var spyFactory = new j$.SpyFactory(); + var spyFactory = new j$.SpyFactory(function() { + var runnable = currentRunnable(); + + if (runnable) { + return runnableResources[runnable.id].customSpyStrategies; + } + + return {}; + }); var spyRegistry = new j$.SpyRegistry({ currentSpies: function() { @@ -4919,7 +4934,7 @@ getJasmineRequireObj().Spy = function (j$) { * @constructor * @name Spy */ - function Spy(name, originalFn) { + function Spy(name, originalFn, customStrategies) { var numArgs = (typeof originalFn === 'function' ? originalFn.length : 0), wrapper = makeFunc(numArgs, function () { return spy.apply(this, Array.prototype.slice.call(arguments)); @@ -4929,7 +4944,8 @@ getJasmineRequireObj().Spy = function (j$) { fn: originalFn, getSpy: function () { return wrapper; - } + }, + customStrategies: customStrategies }), callTracker = new j$.CallTracker(), spy = function () { @@ -5069,11 +5085,11 @@ getJasmineRequireObj().Spy = function (j$) { getJasmineRequireObj().SpyFactory = function(j$) { - function SpyFactory() { + function SpyFactory(getCustomStrategies) { var self = this; this.createSpy = function(name, originalFn) { - return j$.Spy(name, originalFn); + return j$.Spy(name, originalFn, getCustomStrategies()); }; this.createSpyObj = function(baseName, methodNames) { @@ -5264,6 +5280,22 @@ getJasmineRequireObj().SpyStrategy = function(j$) { this.originalFn = options.fn || function() {}; this.getSpy = options.getSpy || function() {}; this.plan = this._defaultPlan = function() {}; + + var k, cs = options.customStrategies || {}; + for (k in cs) { + if (j$.util.has(cs, k) && !this[k]) { + this[k] = function() { + var plan = cs[k].apply(null, arguments); + + if (!j$.isFunction_(plan)) { + throw new Error('Spy strategy must return a function'); + } + + this.plan = plan; + return this.getSpy(); + }; + } + } } /** diff --git a/spec/core/SpyStrategySpec.js b/spec/core/SpyStrategySpec.js index 6804f251..eed14801 100644 --- a/spec/core/SpyStrategySpec.js +++ b/spec/core/SpyStrategySpec.js @@ -110,6 +110,49 @@ describe("SpyStrategy", function() { }) }); + it("allows a custom strategy to be used", function() { + var customStrategy = jasmine.createSpy('custom strategy') + .and.returnValue('custom strategy result'), + factory = jasmine.createSpy('custom strategy factory') + .and.returnValue(customStrategy), + originalFn = jasmine.createSpy('original'), + spyStrategy = new jasmineUnderTest.SpyStrategy({ + fn: originalFn, + customStrategies: { + doSomething: factory + } + }); + + spyStrategy.doSomething(1, 2, 3); + expect(factory).toHaveBeenCalledWith(1, 2, 3); + expect(spyStrategy.exec(null, ['some', 'args'])) + .toEqual('custom strategy result'); + expect(customStrategy).toHaveBeenCalledWith('some', 'args'); + }); + + it("throws an error if a custom strategy doesn't return a function", function() { + var originalFn = jasmine.createSpy('original'), + spyStrategy = new jasmineUnderTest.SpyStrategy({ + fn: originalFn, + customStrategies: { + doSomething: function() { return 'not a function' } + } + }); + + expect(function() { spyStrategy.doSomething(1, 2, 3) }).toThrowError('Spy strategy must return a function'); + }); + + it("does not allow custom strategies to overwrite existing methods", function() { + var spyStrategy = new jasmineUnderTest.SpyStrategy({ + fn: function() {}, + customStrategies: { + exec: function() {} + } + }); + + expect(spyStrategy.exec).toBe(jasmineUnderTest.SpyStrategy.prototype.exec); + }); + it('throws an error when a non-function is passed to callFake strategy', function() { var originalFn = jasmine.createSpy('original'), spyStrategy = new jasmineUnderTest.SpyStrategy({fn: originalFn}), diff --git a/spec/core/integration/CustomSpyStrategiesSpec.js b/spec/core/integration/CustomSpyStrategiesSpec.js new file mode 100644 index 00000000..dbfb76ed --- /dev/null +++ b/spec/core/integration/CustomSpyStrategiesSpec.js @@ -0,0 +1,98 @@ +describe('Custom Spy Strategies (Integration)', function() { + var env; + + beforeEach(function() { + env = new jasmineUnderTest.Env(); + env.randomizeTests(false); + }); + + it('allows adding more strategies local to a suite', function(done) { + var strategyInstance = jasmine.createSpy('custom strategy instance') + .and.returnValue(42); + var strategyFactory = jasmine.createSpy('custom strategy factory') + .and.returnValue(strategyInstance); + + env.describe('suite defining a custom spy strategy', function() { + env.beforeEach(function() { + env.addSpyStrategy('frobnicate', strategyFactory); + }); + + env.it('spec in the suite', function() { + var spy = env.createSpy('something').and.frobnicate(17); + expect(spy(1, 2, 3)).toEqual(42); + expect(strategyFactory).toHaveBeenCalledWith(17); + expect(strategyInstance).toHaveBeenCalledWith(1, 2, 3); + }); + }); + + env.it('spec without custom strategy defined', function() { + expect(env.createSpy('something').and.frobnicate).toBeUndefined(); + }); + + function jasmineDone(result) { + expect(result.overallStatus).toEqual('passed'); + done(); + } + + env.addReporter({ jasmineDone: jasmineDone }); + env.execute(); + }); + + it('allows adding more strategies local to a spec', function(done) { + var strategyInstance = jasmine.createSpy('custom strategy instance') + .and.returnValue(42); + var strategyFactory = jasmine.createSpy('custom strategy factory') + .and.returnValue(strategyInstance); + + env.it('spec defining a custom spy strategy', function() { + env.addSpyStrategy('frobnicate', strategyFactory); + var spy = env.createSpy('something').and.frobnicate(17); + expect(spy(1, 2, 3)).toEqual(42); + expect(strategyFactory).toHaveBeenCalledWith(17); + expect(strategyInstance).toHaveBeenCalledWith(1, 2, 3); + }); + + env.it('spec without custom strategy defined', function() { + expect(env.createSpy('something').and.frobnicate).toBeUndefined(); + }); + + function jasmineDone(result) { + expect(result.overallStatus).toEqual('passed'); + done(); + } + + env.addReporter({ jasmineDone: jasmineDone }); + env.execute(); + }); + + it('allows using custom strategies on a per-argument basis', function(done) { + var strategyInstance = jasmine.createSpy('custom strategy instance') + .and.returnValue(42); + var strategyFactory = jasmine.createSpy('custom strategy factory') + .and.returnValue(strategyInstance); + + env.it('spec defining a custom spy strategy', function() { + env.addSpyStrategy('frobnicate', strategyFactory); + var spy = env.createSpy('something') + .and.returnValue('no args return') + .withArgs(1, 2, 3).and.frobnicate(17); + + expect(spy()).toEqual('no args return'); + expect(strategyInstance).not.toHaveBeenCalled(); + expect(spy(1, 2, 3)).toEqual(42); + expect(strategyInstance).toHaveBeenCalledWith(1, 2, 3); + }); + + env.it('spec without custom strategy defined', function() { + expect(env.createSpy('something').and.frobnicate).toBeUndefined(); + }); + + function jasmineDone(result) { + expect(result.overallStatus).toEqual('passed'); + done(); + } + + env.addReporter({ jasmineDone: jasmineDone }); + env.execute(); + }); +}); diff --git a/src/core/Env.js b/src/core/Env.js index 57f6868a..bb116567 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -108,6 +108,13 @@ getJasmineRequireObj().Env = function(j$) { return true; }; + this.addSpyStrategy = function(name, fn) { + if(!currentRunnable()) { + throw new Error('Custom spy strategies must be added in a before function or a spec'); + } + runnableResources[currentRunnable().id].customSpyStrategies[name] = fn; + }; + this.addCustomEqualityTester = function(tester) { if(!currentRunnable()) { throw new Error('Custom Equalities must be added in a before function or a spec'); @@ -152,7 +159,7 @@ getJasmineRequireObj().Env = function(j$) { }; var defaultResourcesForRunnable = function(id, parentRunnableId) { - var resources = {spies: [], customEqualityTesters: [], customMatchers: {}}; + var resources = {spies: [], customEqualityTesters: [], customMatchers: {}, customSpyStrategies: {}}; if(runnableResources[parentRunnableId]){ resources.customEqualityTesters = j$.util.clone(runnableResources[parentRunnableId].customEqualityTesters); @@ -393,7 +400,15 @@ getJasmineRequireObj().Env = function(j$) { reporter.clearReporters(); }; - var spyFactory = new j$.SpyFactory(); + var spyFactory = new j$.SpyFactory(function() { + var runnable = currentRunnable(); + + if (runnable) { + return runnableResources[runnable.id].customSpyStrategies; + } + + return {}; + }); var spyRegistry = new j$.SpyRegistry({ currentSpies: function() { diff --git a/src/core/Spy.js b/src/core/Spy.js index c99ff191..e6b40669 100644 --- a/src/core/Spy.js +++ b/src/core/Spy.js @@ -13,7 +13,7 @@ getJasmineRequireObj().Spy = function (j$) { * @constructor * @name Spy */ - function Spy(name, originalFn) { + function Spy(name, originalFn, customStrategies) { var numArgs = (typeof originalFn === 'function' ? originalFn.length : 0), wrapper = makeFunc(numArgs, function () { return spy.apply(this, Array.prototype.slice.call(arguments)); @@ -23,7 +23,8 @@ getJasmineRequireObj().Spy = function (j$) { fn: originalFn, getSpy: function () { return wrapper; - } + }, + customStrategies: customStrategies }), callTracker = new j$.CallTracker(), spy = function () { diff --git a/src/core/SpyFactory.js b/src/core/SpyFactory.js index f36c299e..5d54f9df 100644 --- a/src/core/SpyFactory.js +++ b/src/core/SpyFactory.js @@ -1,10 +1,10 @@ getJasmineRequireObj().SpyFactory = function(j$) { - function SpyFactory() { + function SpyFactory(getCustomStrategies) { var self = this; this.createSpy = function(name, originalFn) { - return j$.Spy(name, originalFn); + return j$.Spy(name, originalFn, getCustomStrategies()); }; this.createSpyObj = function(baseName, methodNames) { diff --git a/src/core/SpyStrategy.js b/src/core/SpyStrategy.js index 643fe73f..6563beb7 100644 --- a/src/core/SpyStrategy.js +++ b/src/core/SpyStrategy.js @@ -16,6 +16,22 @@ getJasmineRequireObj().SpyStrategy = function(j$) { this.originalFn = options.fn || function() {}; this.getSpy = options.getSpy || function() {}; this.plan = this._defaultPlan = function() {}; + + var k, cs = options.customStrategies || {}; + for (k in cs) { + if (j$.util.has(cs, k) && !this[k]) { + this[k] = function() { + var plan = cs[k].apply(null, arguments); + + if (!j$.isFunction_(plan)) { + throw new Error('Spy strategy must return a function'); + } + + this.plan = plan; + return this.getSpy(); + }; + } + } } /** From 170a6dce760f545a86df1667b10d8f42feae02e8 Mon Sep 17 00:00:00 2001 From: Steve Gravrock Date: Wed, 10 Jan 2018 09:00:09 -0800 Subject: [PATCH 44/62] Made naming somewhat less confusing --- spec/core/SpyStrategySpec.js | 12 +++---- .../integration/CustomSpyStrategiesSpec.js | 36 +++++++++---------- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/spec/core/SpyStrategySpec.js b/spec/core/SpyStrategySpec.js index eed14801..10e4f583 100644 --- a/spec/core/SpyStrategySpec.js +++ b/spec/core/SpyStrategySpec.js @@ -111,23 +111,23 @@ describe("SpyStrategy", function() { }); it("allows a custom strategy to be used", function() { - var customStrategy = jasmine.createSpy('custom strategy') + var plan = jasmine.createSpy('custom strategy') .and.returnValue('custom strategy result'), - factory = jasmine.createSpy('custom strategy factory') - .and.returnValue(customStrategy), + customStrategy = jasmine.createSpy('custom strategy') + .and.returnValue(plan), originalFn = jasmine.createSpy('original'), spyStrategy = new jasmineUnderTest.SpyStrategy({ fn: originalFn, customStrategies: { - doSomething: factory + doSomething: customStrategy } }); spyStrategy.doSomething(1, 2, 3); - expect(factory).toHaveBeenCalledWith(1, 2, 3); + expect(customStrategy).toHaveBeenCalledWith(1, 2, 3); expect(spyStrategy.exec(null, ['some', 'args'])) .toEqual('custom strategy result'); - expect(customStrategy).toHaveBeenCalledWith('some', 'args'); + expect(plan).toHaveBeenCalledWith('some', 'args'); }); it("throws an error if a custom strategy doesn't return a function", function() { diff --git a/spec/core/integration/CustomSpyStrategiesSpec.js b/spec/core/integration/CustomSpyStrategiesSpec.js index dbfb76ed..59e3b380 100644 --- a/spec/core/integration/CustomSpyStrategiesSpec.js +++ b/spec/core/integration/CustomSpyStrategiesSpec.js @@ -7,21 +7,21 @@ describe('Custom Spy Strategies (Integration)', function() { }); it('allows adding more strategies local to a suite', function(done) { - var strategyInstance = jasmine.createSpy('custom strategy instance') + var plan = jasmine.createSpy('custom strategy plan') .and.returnValue(42); - var strategyFactory = jasmine.createSpy('custom strategy factory') - .and.returnValue(strategyInstance); + var strategy = jasmine.createSpy('custom strategy') + .and.returnValue(plan); env.describe('suite defining a custom spy strategy', function() { env.beforeEach(function() { - env.addSpyStrategy('frobnicate', strategyFactory); + env.addSpyStrategy('frobnicate', strategy); }); env.it('spec in the suite', function() { var spy = env.createSpy('something').and.frobnicate(17); expect(spy(1, 2, 3)).toEqual(42); - expect(strategyFactory).toHaveBeenCalledWith(17); - expect(strategyInstance).toHaveBeenCalledWith(1, 2, 3); + expect(strategy).toHaveBeenCalledWith(17); + expect(plan).toHaveBeenCalledWith(1, 2, 3); }); }); @@ -39,17 +39,17 @@ describe('Custom Spy Strategies (Integration)', function() { }); it('allows adding more strategies local to a spec', function(done) { - var strategyInstance = jasmine.createSpy('custom strategy instance') + var plan = jasmine.createSpy('custom strategy plan') .and.returnValue(42); - var strategyFactory = jasmine.createSpy('custom strategy factory') - .and.returnValue(strategyInstance); + var strategy = jasmine.createSpy('custom strategy') + .and.returnValue(plan); env.it('spec defining a custom spy strategy', function() { - env.addSpyStrategy('frobnicate', strategyFactory); + env.addSpyStrategy('frobnicate', strategy); var spy = env.createSpy('something').and.frobnicate(17); expect(spy(1, 2, 3)).toEqual(42); - expect(strategyFactory).toHaveBeenCalledWith(17); - expect(strategyInstance).toHaveBeenCalledWith(1, 2, 3); + expect(strategy).toHaveBeenCalledWith(17); + expect(plan).toHaveBeenCalledWith(1, 2, 3); }); env.it('spec without custom strategy defined', function() { @@ -66,21 +66,21 @@ describe('Custom Spy Strategies (Integration)', function() { }); it('allows using custom strategies on a per-argument basis', function(done) { - var strategyInstance = jasmine.createSpy('custom strategy instance') + var plan = jasmine.createSpy('custom strategy plan') .and.returnValue(42); - var strategyFactory = jasmine.createSpy('custom strategy factory') - .and.returnValue(strategyInstance); + var strategy = jasmine.createSpy('custom strategy') + .and.returnValue(plan); env.it('spec defining a custom spy strategy', function() { - env.addSpyStrategy('frobnicate', strategyFactory); + env.addSpyStrategy('frobnicate', strategy); var spy = env.createSpy('something') .and.returnValue('no args return') .withArgs(1, 2, 3).and.frobnicate(17); expect(spy()).toEqual('no args return'); - expect(strategyInstance).not.toHaveBeenCalled(); + expect(plan).not.toHaveBeenCalled(); expect(spy(1, 2, 3)).toEqual(42); - expect(strategyInstance).toHaveBeenCalledWith(1, 2, 3); + expect(plan).toHaveBeenCalledWith(1, 2, 3); }); env.it('spec without custom strategy defined', function() { From e2a191b1161364b5d5fe61e9f9fdbc9dd68dc098 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Thu, 11 Jan 2018 17:19:26 -0800 Subject: [PATCH 45/62] Refactor QueueRunner and remove references to functions that Jasmine is done with [finishes #56030214] --- lib/jasmine-core/jasmine.js | 208 ++++++++++++++++++--------------- spec/core/TreeProcessorSpec.js | 1 + src/core/QueueRunner.js | 193 +++++++++++++++--------------- src/core/Spec.js | 1 + src/core/Suite.js | 13 +++ src/core/TreeProcessor.js | 1 + 6 files changed, 227 insertions(+), 190 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 2a270915..4a04699f 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -603,6 +603,7 @@ getJasmineRequireObj().Spec = function(j$) { this.queueRunnerFactory(runnerConfig); function complete(enabledAgain) { + self.queueableFn.fn = null; self.result.status = self.status(enabledAgain); self.resultCallback(self.result); @@ -4513,6 +4514,103 @@ getJasmineRequireObj().QueueRunner = function(j$) { } }; + QueueRunner.prototype.clearTimeout = function(timeoutId) { + Function.prototype.apply.apply(this.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); + }; + + QueueRunner.prototype.setTimeout = function(fn, timeout) { + return Function.prototype.apply.apply(this.timeout.setTimeout, [j$.getGlobal(), [fn, timeout]]); + }; + + QueueRunner.prototype.attempt = function attempt(iterativeIndex) { + var self = this, completedSynchronously = true, + handleError = function(error) { + onException(error); + next(); + }, + cleanup = once(function() { + self.clearTimeout(timeoutId); + self.globalErrors.popListener(handleError); + }), + next = once(function () { + cleanup(); + + function runNext() { + if (self.completeOnFirstError && errored) { + self.skipToCleanup(iterativeIndex); + } else { + self.run(iterativeIndex + 1); + } + } + + if (completedSynchronously) { + self.setTimeout(runNext); + } else { + runNext(); + } + }), + errored = false, + queueableFn = self.queueableFns[iterativeIndex], + timeoutId; + + next.fail = function() { + self.fail.apply(null, arguments); + errored = true; + next(); + }; + + self.globalErrors.pushListener(handleError); + + if (queueableFn.timeout) { + timeoutId = self.setTimeout(function() { + var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); + onException(error); + next(); + }, queueableFn.timeout()); + } + + try { + if (queueableFn.fn.length === 0) { + var maybeThenable = queueableFn.fn.call(self.userContext); + + if (maybeThenable && j$.isFunction_(maybeThenable.then)) { + maybeThenable.then(next, onPromiseRejection); + completedSynchronously = false; + return { completedSynchronously: false }; + } + } else { + queueableFn.fn.call(self.userContext, next); + completedSynchronously = false; + return { completedSynchronously: false }; + } + } catch (e) { + handleException(e, queueableFn); + errored = true; + } + + cleanup(); + return { completedSynchronously: true, errored: errored }; + + function onException(e) { + self.onException(e); + errored = true; + } + + function onPromiseRejection(e) { + onException(e); + next(); + } + + function handleException(e, queueableFn) { + onException(e); + if (!self.catchException(e)) { + //TODO: set a var when we catch an exception and + //use a finally block to close the loop in a nice way.. + throw e; + } + } + }; + QueueRunner.prototype.run = function(recursiveIndex) { var length = this.queueableFns.length, self = this, @@ -4520,7 +4618,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { - var result = attempt(iterativeIndex); + var result = this.attempt(iterativeIndex); if (!result.completedSynchronously) { return; @@ -4537,100 +4635,6 @@ getJasmineRequireObj().QueueRunner = function(j$) { self.onComplete(); }); - function attempt() { - var clearTimeout = function () { - Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); - }, - setTimeout = function(delayedFn, delay) { - return Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [delayedFn, delay]]); - }, - completedSynchronously = true, - handleError = function(error) { - onException(error); - next(); - }, - cleanup = once(function() { - clearTimeout(timeoutId); - self.globalErrors.popListener(handleError); - }), - next = once(function () { - cleanup(); - - function runNext() { - if (self.completeOnFirstError && errored) { - self.skipToCleanup(iterativeIndex); - } else { - self.run(iterativeIndex + 1); - } - } - - if (completedSynchronously) { - setTimeout(runNext); - } else { - runNext(); - } - }), - errored = false, - queueableFn = self.queueableFns[iterativeIndex], - timeoutId; - - next.fail = function() { - self.fail.apply(null, arguments); - errored = true; - next(); - }; - - self.globalErrors.pushListener(handleError); - - if (queueableFn.timeout) { - timeoutId = setTimeout(function() { - var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); - onException(error); - next(); - }, queueableFn.timeout()); - } - - try { - if (queueableFn.fn.length === 0) { - var maybeThenable = queueableFn.fn.call(self.userContext); - - if (maybeThenable && j$.isFunction_(maybeThenable.then)) { - maybeThenable.then(next, onPromiseRejection); - completedSynchronously = false; - return { completedSynchronously: false }; - } - } else { - queueableFn.fn.call(self.userContext, next); - completedSynchronously = false; - return { completedSynchronously: false }; - } - } catch (e) { - handleException(e, queueableFn); - errored = true; - } - - cleanup(); - return { completedSynchronously: true, errored: errored }; - - function onException(e) { - self.onException(e); - errored = true; - } - - function onPromiseRejection(e) { - onException(e); - next(); - } - - function handleException(e, queueableFn) { - onException(e); - if (!self.catchException(e)) { - //TODO: set a var when we catch an exception and - //use a finally block to close the loop in a nice way.. - throw e; - } - } - } }; return QueueRunner; @@ -5500,6 +5504,19 @@ getJasmineRequireObj().Suite = function(j$) { this.afterAllFns.unshift(fn); }; + function removeFns(queueableFns) { + for(var i = 0; i < queueableFns.length; i++) { + queueableFns[i].fn = null; + } + } + + Suite.prototype.cleanupBeforeAfter = function() { + removeFns(this.beforeAllFns); + removeFns(this.afterAllFns); + removeFns(this.beforeFns); + removeFns(this.afterFns); + }; + Suite.prototype.addChild = function(child) { this.children.push(child); }; @@ -5796,6 +5813,7 @@ getJasmineRequireObj().TreeProcessor = function() { queueRunnerFactory({ onComplete: function() { + node.cleanupBeforeAfter(); nodeComplete(node, node.getResult()); done(); }, diff --git a/spec/core/TreeProcessorSpec.js b/spec/core/TreeProcessorSpec.js index d1f2cd9b..c702d0cc 100644 --- a/spec/core/TreeProcessorSpec.js +++ b/spec/core/TreeProcessorSpec.js @@ -17,6 +17,7 @@ describe("TreeProcessor", function() { this.getResult = jasmine.createSpy(this.id + '#execute'); this.beforeAllFns = attrs.beforeAllFns || []; this.afterAllFns = attrs.afterAllFns || []; + this.cleanupBeforeAfter = function() { }; } function Leaf(attrs) { diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index f422b49d..c9b15a28 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -43,6 +43,103 @@ getJasmineRequireObj().QueueRunner = function(j$) { } }; + QueueRunner.prototype.clearTimeout = function(timeoutId) { + Function.prototype.apply.apply(this.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); + }; + + QueueRunner.prototype.setTimeout = function(fn, timeout) { + return Function.prototype.apply.apply(this.timeout.setTimeout, [j$.getGlobal(), [fn, timeout]]); + }; + + QueueRunner.prototype.attempt = function attempt(iterativeIndex) { + var self = this, completedSynchronously = true, + handleError = function(error) { + onException(error); + next(); + }, + cleanup = once(function() { + self.clearTimeout(timeoutId); + self.globalErrors.popListener(handleError); + }), + next = once(function () { + cleanup(); + + function runNext() { + if (self.completeOnFirstError && errored) { + self.skipToCleanup(iterativeIndex); + } else { + self.run(iterativeIndex + 1); + } + } + + if (completedSynchronously) { + self.setTimeout(runNext); + } else { + runNext(); + } + }), + errored = false, + queueableFn = self.queueableFns[iterativeIndex], + timeoutId; + + next.fail = function() { + self.fail.apply(null, arguments); + errored = true; + next(); + }; + + self.globalErrors.pushListener(handleError); + + if (queueableFn.timeout) { + timeoutId = self.setTimeout(function() { + var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); + onException(error); + next(); + }, queueableFn.timeout()); + } + + try { + if (queueableFn.fn.length === 0) { + var maybeThenable = queueableFn.fn.call(self.userContext); + + if (maybeThenable && j$.isFunction_(maybeThenable.then)) { + maybeThenable.then(next, onPromiseRejection); + completedSynchronously = false; + return { completedSynchronously: false }; + } + } else { + queueableFn.fn.call(self.userContext, next); + completedSynchronously = false; + return { completedSynchronously: false }; + } + } catch (e) { + handleException(e, queueableFn); + errored = true; + } + + cleanup(); + return { completedSynchronously: true, errored: errored }; + + function onException(e) { + self.onException(e); + errored = true; + } + + function onPromiseRejection(e) { + onException(e); + next(); + } + + function handleException(e, queueableFn) { + onException(e); + if (!self.catchException(e)) { + //TODO: set a var when we catch an exception and + //use a finally block to close the loop in a nice way.. + throw e; + } + } + }; + QueueRunner.prototype.run = function(recursiveIndex) { var length = this.queueableFns.length, self = this, @@ -50,7 +147,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { for(iterativeIndex = recursiveIndex; iterativeIndex < length; iterativeIndex++) { - var result = attempt(iterativeIndex); + var result = this.attempt(iterativeIndex); if (!result.completedSynchronously) { return; @@ -67,100 +164,6 @@ getJasmineRequireObj().QueueRunner = function(j$) { self.onComplete(); }); - function attempt() { - var clearTimeout = function () { - Function.prototype.apply.apply(self.timeout.clearTimeout, [j$.getGlobal(), [timeoutId]]); - }, - setTimeout = function(delayedFn, delay) { - return Function.prototype.apply.apply(self.timeout.setTimeout, [j$.getGlobal(), [delayedFn, delay]]); - }, - completedSynchronously = true, - handleError = function(error) { - onException(error); - next(); - }, - cleanup = once(function() { - clearTimeout(timeoutId); - self.globalErrors.popListener(handleError); - }), - next = once(function () { - cleanup(); - - function runNext() { - if (self.completeOnFirstError && errored) { - self.skipToCleanup(iterativeIndex); - } else { - self.run(iterativeIndex + 1); - } - } - - if (completedSynchronously) { - setTimeout(runNext); - } else { - runNext(); - } - }), - errored = false, - queueableFn = self.queueableFns[iterativeIndex], - timeoutId; - - next.fail = function() { - self.fail.apply(null, arguments); - errored = true; - next(); - }; - - self.globalErrors.pushListener(handleError); - - if (queueableFn.timeout) { - timeoutId = setTimeout(function() { - var error = new Error('Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.'); - onException(error); - next(); - }, queueableFn.timeout()); - } - - try { - if (queueableFn.fn.length === 0) { - var maybeThenable = queueableFn.fn.call(self.userContext); - - if (maybeThenable && j$.isFunction_(maybeThenable.then)) { - maybeThenable.then(next, onPromiseRejection); - completedSynchronously = false; - return { completedSynchronously: false }; - } - } else { - queueableFn.fn.call(self.userContext, next); - completedSynchronously = false; - return { completedSynchronously: false }; - } - } catch (e) { - handleException(e, queueableFn); - errored = true; - } - - cleanup(); - return { completedSynchronously: true, errored: errored }; - - function onException(e) { - self.onException(e); - errored = true; - } - - function onPromiseRejection(e) { - onException(e); - next(); - } - - function handleException(e, queueableFn) { - onException(e); - if (!self.catchException(e)) { - //TODO: set a var when we catch an exception and - //use a finally block to close the loop in a nice way.. - throw e; - } - } - } }; return QueueRunner; diff --git a/src/core/Spec.js b/src/core/Spec.js index c2ee8a58..7f2c1513 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -81,6 +81,7 @@ getJasmineRequireObj().Spec = function(j$) { this.queueRunnerFactory(runnerConfig); function complete(enabledAgain) { + self.queueableFn.fn = null; self.result.status = self.status(enabledAgain); self.resultCallback(self.result); diff --git a/src/core/Suite.js b/src/core/Suite.js index ed7ed3fb..c7783d59 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -65,6 +65,19 @@ getJasmineRequireObj().Suite = function(j$) { this.afterAllFns.unshift(fn); }; + function removeFns(queueableFns) { + for(var i = 0; i < queueableFns.length; i++) { + queueableFns[i].fn = null; + } + } + + Suite.prototype.cleanupBeforeAfter = function() { + removeFns(this.beforeAllFns); + removeFns(this.afterAllFns); + removeFns(this.beforeFns); + removeFns(this.afterFns); + }; + Suite.prototype.addChild = function(child) { this.children.push(child); }; diff --git a/src/core/TreeProcessor.js b/src/core/TreeProcessor.js index a1f5f4cb..137f23b4 100644 --- a/src/core/TreeProcessor.js +++ b/src/core/TreeProcessor.js @@ -168,6 +168,7 @@ getJasmineRequireObj().TreeProcessor = function() { queueRunnerFactory({ onComplete: function() { + node.cleanupBeforeAfter(); nodeComplete(node, node.getResult()); done(); }, From f20f78f82b6bbaac603ad121053b46df6a6fa63c Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Mon, 22 Jan 2018 12:10:24 -0800 Subject: [PATCH 46/62] Don't clobber previous custom plans with later plans [finishes #37288941] --- lib/jasmine-core/boot.js | 2 +- lib/jasmine-core/jasmine-html.js | 2 +- lib/jasmine-core/jasmine.js | 49 +++++++++++++------ lib/jasmine-core/node_boot.js | 2 +- .../integration/CustomSpyStrategiesSpec.js | 40 +++++++++++++++ src/core/SpyFactory.js | 12 ++--- src/core/SpyStrategy.js | 24 +++++---- src/core/requireInterface.js | 13 +++++ 8 files changed, 109 insertions(+), 35 deletions(-) diff --git a/lib/jasmine-core/boot.js b/lib/jasmine-core/boot.js index 72f45b5e..4655042c 100644 --- a/lib/jasmine-core/boot.js +++ b/lib/jasmine-core/boot.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2017 Pivotal Labs +Copyright (c) 2008-2018 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 50de0be7..f682bbdb 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2017 Pivotal Labs +Copyright (c) 2008-2018 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 9b7290fa..26de0a3f 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -4916,6 +4916,19 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.createSpyObj(baseName, methodNames); }; + /** + * Add a custom spy strategy for the current scope of specs. + * + * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. + * @name jasmine.addSpyStrategy + * @function + * @param {String} name - The name of the strategy (i.e. what you call from `and`) + * @param {Function} factory - Factory function that returns the plan to be executed. + */ + jasmine.addSpyStrategy = function(name, factory) { + return env.addSpyStrategy(identifier, factory); + }; + return jasmineInterface; }; @@ -5091,18 +5104,18 @@ getJasmineRequireObj().SpyFactory = function(j$) { this.createSpy = function(name, originalFn) { return j$.Spy(name, originalFn, getCustomStrategies()); }; - + this.createSpyObj = function(baseName, methodNames) { var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); - + if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { methodNames = baseName; baseName = 'unknown'; } - + var obj = {}; var spiesWereSet = false; - + if (j$.isArray_(methodNames)) { for (var i = 0; i < methodNames.length; i++) { obj[methodNames[i]] = self.createSpy(baseName + '.' + methodNames[i]); @@ -5117,11 +5130,11 @@ getJasmineRequireObj().SpyFactory = function(j$) { } } } - + if (!spiesWereSet) { throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; } - + return obj; }; } @@ -5284,20 +5297,24 @@ getJasmineRequireObj().SpyStrategy = function(j$) { var k, cs = options.customStrategies || {}; for (k in cs) { if (j$.util.has(cs, k) && !this[k]) { - this[k] = function() { - var plan = cs[k].apply(null, arguments); - - if (!j$.isFunction_(plan)) { - throw new Error('Spy strategy must return a function'); - } - - this.plan = plan; - return this.getSpy(); - }; + this[k] = createCustomPlan(cs[k]); } } } + function createCustomPlan(factory) { + return function() { + var plan = factory.apply(null, arguments); + + if (!j$.isFunction_(plan)) { + throw new Error('Spy strategy must return a function'); + } + + this.plan = plan; + return this.getSpy(); + }; + } + /** * Execute the current spy strategy. * @name SpyStrategy#exec diff --git a/lib/jasmine-core/node_boot.js b/lib/jasmine-core/node_boot.js index ce90a3ba..2b2d7adf 100644 --- a/lib/jasmine-core/node_boot.js +++ b/lib/jasmine-core/node_boot.js @@ -1,5 +1,5 @@ /* -Copyright (c) 2008-2017 Pivotal Labs +Copyright (c) 2008-2018 Pivotal Labs Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff --git a/spec/core/integration/CustomSpyStrategiesSpec.js b/spec/core/integration/CustomSpyStrategiesSpec.js index 59e3b380..ba82362b 100644 --- a/spec/core/integration/CustomSpyStrategiesSpec.js +++ b/spec/core/integration/CustomSpyStrategiesSpec.js @@ -95,4 +95,44 @@ describe('Custom Spy Strategies (Integration)', function() { env.addReporter({ jasmineDone: jasmineDone }); env.execute(); }); + + it('allows multiple custom strategies to be used', function(done) { + var plan1 = jasmine.createSpy('plan 1').and.returnValue(42), + strategy1 = jasmine.createSpy('strat 1').and.returnValue(plan1), + plan2 = jasmine.createSpy('plan 2').and.returnValue(24), + strategy2 = jasmine.createSpy('strat 2').and.returnValue(plan2), + specDone = jasmine.createSpy('specDone'); + + env.beforeEach(function() { + env.addSpyStrategy('frobnicate', strategy1); + env.addSpyStrategy('jiggle', strategy2); + }); + + env.it('frobnicates', function() { + plan1.calls.reset(); + plan2.calls.reset(); + var spy = env.createSpy('spy').and.frobnicate(); + expect(spy()).toEqual(42); + expect(plan1).toHaveBeenCalled(); + expect(plan2).not.toHaveBeenCalled(); + }); + + env.it('jiggles', function() { + plan1.calls.reset(); + plan2.calls.reset(); + var spy = env.createSpy('spy').and.jiggle(); + expect(spy()).toEqual(24); + expect(plan1).not.toHaveBeenCalled(); + expect(plan2).toHaveBeenCalled(); + }); + + function jasmineDone(result) { + expect(result.overallStatus).toEqual('passed'); + expect(specDone.calls.count()).toBe(2); + done(); + } + + env.addReporter({ jasmineDone: jasmineDone, specDone: specDone }); + env.execute(); + }); }); diff --git a/src/core/SpyFactory.js b/src/core/SpyFactory.js index 5d54f9df..d4821321 100644 --- a/src/core/SpyFactory.js +++ b/src/core/SpyFactory.js @@ -6,18 +6,18 @@ getJasmineRequireObj().SpyFactory = function(j$) { this.createSpy = function(name, originalFn) { return j$.Spy(name, originalFn, getCustomStrategies()); }; - + this.createSpyObj = function(baseName, methodNames) { var baseNameIsCollection = j$.isObject_(baseName) || j$.isArray_(baseName); - + if (baseNameIsCollection && j$.util.isUndefined(methodNames)) { methodNames = baseName; baseName = 'unknown'; } - + var obj = {}; var spiesWereSet = false; - + if (j$.isArray_(methodNames)) { for (var i = 0; i < methodNames.length; i++) { obj[methodNames[i]] = self.createSpy(baseName + '.' + methodNames[i]); @@ -32,11 +32,11 @@ getJasmineRequireObj().SpyFactory = function(j$) { } } } - + if (!spiesWereSet) { throw 'createSpyObj requires a non-empty array or object of method names to create spies for'; } - + return obj; }; } diff --git a/src/core/SpyStrategy.js b/src/core/SpyStrategy.js index 6563beb7..ae5d4451 100644 --- a/src/core/SpyStrategy.js +++ b/src/core/SpyStrategy.js @@ -20,20 +20,24 @@ getJasmineRequireObj().SpyStrategy = function(j$) { var k, cs = options.customStrategies || {}; for (k in cs) { if (j$.util.has(cs, k) && !this[k]) { - this[k] = function() { - var plan = cs[k].apply(null, arguments); - - if (!j$.isFunction_(plan)) { - throw new Error('Spy strategy must return a function'); - } - - this.plan = plan; - return this.getSpy(); - }; + this[k] = createCustomPlan(cs[k]); } } } + function createCustomPlan(factory) { + return function() { + var plan = factory.apply(null, arguments); + + if (!j$.isFunction_(plan)) { + throw new Error('Spy strategy must return a function'); + } + + this.plan = plan; + return this.getSpy(); + }; + } + /** * Execute the current spy strategy. * @name SpyStrategy#exec diff --git a/src/core/requireInterface.js b/src/core/requireInterface.js index ac0279d7..52505ecd 100644 --- a/src/core/requireInterface.js +++ b/src/core/requireInterface.js @@ -280,5 +280,18 @@ getJasmineRequireObj().interface = function(jasmine, env) { return env.createSpyObj(baseName, methodNames); }; + /** + * Add a custom spy strategy for the current scope of specs. + * + * _Note:_ This is only callable from within a {@link beforeEach}, {@link it}, or {@link beforeAll}. + * @name jasmine.addSpyStrategy + * @function + * @param {String} name - The name of the strategy (i.e. what you call from `and`) + * @param {Function} factory - Factory function that returns the plan to be executed. + */ + jasmine.addSpyStrategy = function(name, factory) { + return env.addSpyStrategy(identifier, factory); + }; + return jasmineInterface; }; From 414e03bdedba7a980db110612809be858bd8ea06 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Mon, 22 Jan 2018 14:21:52 -0800 Subject: [PATCH 47/62] Setup createSpy in all specs where needed and finish renaming params Signed-off-by: Elenore Bastian --- lib/jasmine-core/jasmine.js | 2 +- spec/core/SpyRegistrySpec.js | 1 + spec/html/SpyRegistryHtmlSpec.js | 11 ++++++++--- src/core/requireInterface.js | 2 +- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 51b31d90..b272a740 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -4958,7 +4958,7 @@ getJasmineRequireObj().interface = function(jasmine, env) { * @param {Function} factory - Factory function that returns the plan to be executed. */ jasmine.addSpyStrategy = function(name, factory) { - return env.addSpyStrategy(identifier, factory); + return env.addSpyStrategy(name, factory); }; return jasmineInterface; diff --git a/spec/core/SpyRegistrySpec.js b/spec/core/SpyRegistrySpec.js index 80eac687..92939f83 100644 --- a/spec/core/SpyRegistrySpec.js +++ b/spec/core/SpyRegistrySpec.js @@ -304,6 +304,7 @@ describe("SpyRegistry", function() { global = new FakeWindow(), spyRegistry = new jasmineUnderTest.SpyRegistry({ currentSpies: function() { return spies; }, + createSpy: createSpy, global: global }); diff --git a/spec/html/SpyRegistryHtmlSpec.js b/spec/html/SpyRegistryHtmlSpec.js index fd9a81fb..c51308b8 100644 --- a/spec/html/SpyRegistryHtmlSpec.js +++ b/spec/html/SpyRegistryHtmlSpec.js @@ -1,12 +1,17 @@ describe('Spy Registry browser-specific behavior', function() { + function createSpy(name, originalFn) { + return jasmineUnderTest.Spy(name, originalFn); + } + it('can spy on and unspy window.onerror', function() { requireWriteableOnerror(); var spies = [], spyRegistry = new jasmineUnderTest.SpyRegistry({ - currentSpies: function() { return spies; }, - global: window - }), + currentSpies: function() { return spies; }, + createSpy: createSpy, + global: window + }), originalHandler = window.onerror; try { diff --git a/src/core/requireInterface.js b/src/core/requireInterface.js index 52505ecd..62a5f073 100644 --- a/src/core/requireInterface.js +++ b/src/core/requireInterface.js @@ -290,7 +290,7 @@ getJasmineRequireObj().interface = function(jasmine, env) { * @param {Function} factory - Factory function that returns the plan to be executed. */ jasmine.addSpyStrategy = function(name, factory) { - return env.addSpyStrategy(identifier, factory); + return env.addSpyStrategy(name, factory); }; return jasmineInterface; From 12a47f05bf973158522537916bb954f868eab783 Mon Sep 17 00:00:00 2001 From: Elenore Bastian Date: Tue, 23 Jan 2018 10:15:28 -0800 Subject: [PATCH 48/62] Suite level errors all report the same way (on suiteDone) - For `beforeAll`, `afterAll`, and declaration errors [#150118881] #1409 Signed-off-by: Gregg Van Hove --- spec/core/SuiteSpec.js | 25 +-- spec/core/integration/EnvSpec.js | 189 +++++------------------ spec/core/integration/SpecRunningSpec.js | 15 +- spec/helpers/integrationMatchers.js | 43 ++++++ spec/support/jasmine.json | 1 + spec/support/jasmine.yml | 1 + src/core/Env.js | 4 +- src/core/Suite.js | 46 ++---- 8 files changed, 106 insertions(+), 218 deletions(-) create mode 100644 spec/helpers/integrationMatchers.js diff --git a/spec/core/SuiteSpec.js b/spec/core/SuiteSpec.js index ca45fe3d..78e78565 100644 --- a/spec/core/SuiteSpec.js +++ b/spec/core/SuiteSpec.js @@ -67,11 +67,10 @@ describe("Suite", function() { expect(suite.afterFns).toEqual([innerAfter, outerAfter]); }); - it('has a status of failed if any afterAll expectations have failed', function() { + it('has a status of failed if any expectations have failed', function() { var suite = new jasmineUnderTest.Suite({ expectationResultFactory: function() { return 'hi'; } }); - suite.addChild({ result: { status: 'done' } }); suite.addExpectationResult(false); expect(suite.status()).toBe('failed'); @@ -103,28 +102,11 @@ describe("Suite", function() { expect(suite.isExecutable()).toBe(false); }); - it("tells all children about expectation failures, even if one throws", function() { - var suite = new jasmineUnderTest.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() { + it("throws an ExpectationFailed when receiving a failed expectation when throwOnExpectationFailure is set", function() { var suite = new jasmineUnderTest.Suite({ expectationResultFactory: function(data) { return data; }, throwOnExpectationFailure: true }); - suite.addChild({ result: { status: 'done' } }); expect(function() { suite.addExpectationResult(false, 'failed'); @@ -134,9 +116,8 @@ describe("Suite", function() { expect(suite.result.failedExpectations).toEqual(['failed']); }); - it("does not add an additional failure when an expectation fails in an afterAll", function(){ + it("does not add an additional failure when an expectation fails", function(){ var suite = new jasmineUnderTest.Suite({}); - suite.addChild({ result: { status: 'done' } }); suite.onException(new jasmineUnderTest.errors.ExpectationFailed()); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index fc721d7d..44198f75 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1,43 +1,6 @@ describe("Env integration", function() { beforeEach(function() { - jasmine.addMatchers({ - toHaveFailedExpecationsForRunnable: function(util, customeEqualityTesters) { - return { - compare: function(actual, fullName, expectedFailures) { - var foundRunnable = false, expectations = true, foundFailures = []; - for (var i = 0; i < actual.calls.count(); i++) { - var args = actual.calls.argsFor(i)[0]; - - if (args.fullName === fullName) { - foundRunnable = true; - - for (var j = 0; j < args.failedExpectations.length; j++) { - foundFailures.push(args.failedExpectations[j].message); - } - - for (var j = 0; j < expectedFailures.length; j++) { - var failure = foundFailures[j]; - var expectedFailure = expectedFailures[j]; - - if (Object.prototype.toString.call(expectedFailure) === '[object RegExp]') { - expectations = expectations && expectedFailure.test(failure); - } else { - expectations = expectations && failure === expectedFailure; - } - } - break; - } - } - - return { - pass: foundRunnable && expectations, - message: !foundRunnable ? 'The runnable "' + fullName + '" never finished' : - 'Expected runnable "' + fullName + '" to have failures ' + jasmine.pp(expectedFailures) + ' but it had ' + jasmine.pp(foundFailures) - }; - } - }; - } - }); + jasmine.getEnv().registerIntegrationMatchers(); }); it("Suites execute as expected (no nesting)", function(done) { @@ -464,22 +427,16 @@ describe("Env integration", function() { env.execute(); }); - it("fails all underlying specs when the beforeAll fails", function (done) { + it("when the beforeAll fails, error at suite level", function (done) { var env = new jasmineUnderTest.Env(), - reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]); + reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "suiteDone", "jasmineDone" ]); reporter.jasmineDone.and.callFake(function() { expect(reporter.specDone.calls.count()).toEqual(2); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('A suite spec that will pass', []); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('A suite nesting another spec to pass', []); + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('A suite', ['Expected 1 to be 2.']); - expect(reporter.specDone.calls.argsFor(0)[0]) - .toEqual(jasmine.objectContaining({status: 'failed'})); - expect(reporter.specDone.calls.argsFor(0)[0].failedExpectations[0].message) - .toEqual("Expected 1 to be 2."); - - expect(reporter.specDone.calls.argsFor(1)[0]) - .toEqual(jasmine.objectContaining({status: 'failed'})); - expect(reporter.specDone.calls.argsFor(1)[0].failedExpectations[0].message) - .toEqual("Expected 1 to be 2."); done(); }); @@ -490,11 +447,11 @@ describe("Env integration", function() { env.expect(1).toBe(2); }); - env.it("spec that will be failed", function() { + env.it("spec that will pass", function() { }); env.describe("nesting", function() { - env.it("another spec to fail", function() { + env.it("another spec to pass", function() { }); }); }); @@ -512,8 +469,8 @@ describe("Env integration", function() { reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone", "suiteDone" ]); reporter.jasmineDone.and.callFake(function() { - expect(reporter.specDone).not.toHaveFailedExpecationsForRunnable('A suite fails', ['fail thrown']); - expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('A suite', ['fail thrown']); + expect(reporter.specDone).not.toHaveFailedExpectationsForRunnable('A suite fails', ['fail thrown']); + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('A suite', ['fail thrown']); done(); }); @@ -543,7 +500,7 @@ describe("Env integration", function() { reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('my suite', [ + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('my suite', [ 'Expected 1 to equal 2.', 'Expected 2 to equal 3.' ]); @@ -570,7 +527,7 @@ describe("Env integration", function() { reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('outer suite', [ + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('outer suite', [ 'Expected 1 to equal 2.', 'Expected 2 to equal 3.' ]); @@ -599,7 +556,7 @@ describe("Env integration", function() { reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('my suite', [ + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('my suite', [ (/^Error: After All Exception/) ]); done(); @@ -624,7 +581,7 @@ describe("Env integration", function() { reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('my suite', [ + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('my suite', [ 'Expected 1 to equal 2.' ]); done(); @@ -652,7 +609,7 @@ describe("Env integration", function() { reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('my suite', [ + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('my suite', [ (/^Error: After All Exception/) ]); done(); @@ -673,15 +630,13 @@ describe("Env integration", function() { }); }); - it('cascades expecatation failures in global beforeAll down to children', function(done) { + it('reports expectation failures in global beforeAll', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj(['specDone', 'jasmineDone']); reporter.jasmineDone.and.callFake(function(results) { - expect(results.failedExpectations).toEqual([]); - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('is a spec', [ - 'Expected 1 to be 0.' - ]); + expect(results.failedExpectations).toEqual([jasmine.objectContaining({ message: 'Expected 1 to be 0.' })]); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('is a spec', []); done(); }); @@ -1041,35 +996,6 @@ describe("Env integration", function() { env.execute(); }); - it("should wait a specified interval before failing beforeAll's and their associated specs that haven't called done", function(done) { - var env = new jasmineUnderTest.Env(), - reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]); - - reporter.jasmineDone.and.callFake(function() { - expect(reporter.specDone.calls.count()).toEqual(2); - expect(reporter.specDone.calls.argsFor(0)[0]).toEqual(jasmine.objectContaining({status: 'failed'})); - expect(reporter.specDone.calls.argsFor(1)[0]).toEqual(jasmine.objectContaining({status: 'failed'})); - done(); - }); - - env.addReporter(reporter); - jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 1290; - - env.beforeAll(function(innerDone) { - jasmine.clock().tick(1291); - }); - - env.it("spec that will be failed", function() { - }); - - env.describe("nesting", function() { - env.it("another spec to fail", function() { - }); - }); - - env.execute(); - }); - it("should not use the mock clock for asynchronous timeouts", function(done){ var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]), @@ -1102,59 +1028,18 @@ describe("Env integration", function() { env.execute(); }); - it("should wait the specified interval before reporting an afterAll that fails to call done", function(done) { - var env = new jasmineUnderTest.Env(), - reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone']); - - reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('my suite', [ - (/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./) - ]); - done(); - }); - - env.addReporter(reporter); - jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 3000; - - env.describe('my suite', function() { - env.it('my spec', function() { - }); - - env.afterAll(function(innerDone) { - jasmine.clock().tick(3001); - innerDone(); - }); - }); - - env.execute(); - jasmine.clock().tick(1); - }); - it('should wait a custom interval before reporting async functions that fail to call done', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone', 'suiteDone', 'specDone']), - realSetTimeout = this.realSetTimeout; + realSetTimeout = this.realSetTimeout, + timeoutFailure = (/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./); reporter.jasmineDone.and.callFake(function() { - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('suite beforeAll times out', [ - (/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./) - ]); - - expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('suite afterAll', [ - (/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./) - ]); - - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('suite beforeEach times out', [ - (/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./) - ]); - - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('suite afterEach times out', [ - (/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./) - ]); - - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('suite it times out', [ - (/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./) - ]); + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('suite beforeAll', [ timeoutFailure ]); + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('suite afterAll', [ timeoutFailure ]); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite beforeEach times out', [ timeoutFailure ]); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite afterEach times out', [ timeoutFailure ]); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite it times out', [ timeoutFailure ]); done(); }); @@ -1965,10 +1850,10 @@ describe("Env integration", function() { reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone','suiteDone','specDone']); reporter.jasmineDone.and.callFake(function() { - expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('async suite', [ + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('async suite', [ /^(((Uncaught )?Error: suite( thrown)?)|(suite thrown))$/ ]); - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('suite async spec', [ + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite async spec', [ /^(((Uncaught )?Error: spec( thrown)?)|(spec thrown))$/ ]); done(); @@ -2000,19 +1885,19 @@ describe("Env integration", function() { reporter.jasmineDone.and.callFake(function() { var msg = /\'.*\' should only be used in \'describe\' function/; - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('suite describe', [msg]); - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('suite xdescribe', [msg]); - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('suite fdescribe', [msg]); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite describe', [msg]); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite xdescribe', [msg]); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite fdescribe', [msg]); - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('spec it', [msg]); - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('spec xit', [msg]); - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('spec fit', [msg]); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('spec it', [msg]); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('spec xit', [msg]); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('spec fit', [msg]); - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('beforeAll spec', [msg]); - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('beforeEach spec', [msg]); + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('beforeAll', [msg]); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('beforeEach spec', [msg]); - expect(reporter.suiteDone).toHaveFailedExpecationsForRunnable('afterAll', [msg]); - expect(reporter.specDone).toHaveFailedExpecationsForRunnable('afterEach spec', [msg]); + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('afterAll', [msg]); + expect(reporter.specDone).toHaveFailedExpectationsForRunnable('afterEach spec', [msg]); done(); }); diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index 85eef125..464bab60 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -2,6 +2,7 @@ describe("spec running", function () { var env; beforeEach(function() { + jasmine.getEnv().registerIntegrationMatchers(); env = new jasmineUnderTest.Env(); env.randomizeTests(false); }); @@ -601,18 +602,16 @@ describe("spec running", function () { it("should recover gracefully when there are errors in describe functions", function(done) { var specs = [], - reporter = jasmine.createSpyObj(['specDone', 'jasmineDone']); + reporter = jasmine.createSpyObj(['specDone', 'suiteDone', 'jasmineDone']); reporter.specDone.and.callFake(function(result) { specs.push(result.fullName); }); reporter.jasmineDone.and.callFake(function() { - expect(specs).toContain('outer1 inner1 should thingy'); - expect(specs).toContain('outer1 inner1 encountered a declaration exception'); - expect(specs).toContain('outer1 inner2 should other thingy'); - expect(specs).toContain('outer1 encountered a declaration exception'); - expect(specs).toContain('outer2 should xxx'); + expect(specs).toEqual(['outer1 inner1 should thingy', 'outer1 inner2 should other thingy', 'outer2 should xxx']); + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('outer1 inner1', [/inner error/]); + expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('outer1', [/outer error/]); done(); }); @@ -623,7 +622,7 @@ describe("spec running", function () { this.expect(true).toEqual(true); }); - throw new Error("fake error"); + throw new Error("inner error"); }); env.describe("inner2", function() { @@ -632,7 +631,7 @@ describe("spec running", function () { }); }); - throw new Error("fake error"); + throw new Error("outer error"); }); }).not.toThrow(); diff --git a/spec/helpers/integrationMatchers.js b/spec/helpers/integrationMatchers.js new file mode 100644 index 00000000..8b524946 --- /dev/null +++ b/spec/helpers/integrationMatchers.js @@ -0,0 +1,43 @@ +(function(env) { + env.registerIntegrationMatchers = function() { + jasmine.addMatchers({ + toHaveFailedExpectationsForRunnable: function (util, customeEqualityTesters) { + return { + compare: function (actual, fullName, expectedFailures) { + var foundRunnable = false, expectations = true, foundFailures = []; + for (var i = 0; i < actual.calls.count(); i++) { + var args = actual.calls.argsFor(i)[0]; + + if (args.fullName === fullName) { + foundRunnable = true; + + for (var j = 0; j < args.failedExpectations.length; j++) { + foundFailures.push(args.failedExpectations[j].message); + } + + for (var j = 0; j < expectedFailures.length; j++) { + var failure = foundFailures[j]; + var expectedFailure = expectedFailures[j]; + + if (Object.prototype.toString.call(expectedFailure) === '[object RegExp]') { + expectations = expectations && expectedFailure.test(failure); + } else { + expectations = expectations && failure === expectedFailure; + } + } + break; + } + } + + return { + pass: foundRunnable && expectations, + message: !foundRunnable ? 'The runnable "' + fullName + '" never finished' : + 'Expected runnable "' + fullName + '" to have failures ' + jasmine.pp(expectedFailures) + ' but it had ' + jasmine.pp(foundFailures) + }; + } + }; + } + }); + }; +})(jasmine.getEnv()); + diff --git a/spec/support/jasmine.json b/spec/support/jasmine.json index 6f89d24a..b2c83485 100644 --- a/spec/support/jasmine.json +++ b/spec/support/jasmine.json @@ -10,6 +10,7 @@ "helpers/checkForSet.js", "helpers/checkForSymbol.js", "helpers/checkForTypedArrays.js", + "helpers/integrationMatchers.js", "helpers/nodeDefineJasmineUnderTest.js" ], "random": true diff --git a/spec/support/jasmine.yml b/spec/support/jasmine.yml index 36c29e91..88560b1a 100644 --- a/spec/support/jasmine.yml +++ b/spec/support/jasmine.yml @@ -23,6 +23,7 @@ helpers: - 'helpers/checkForSet.js' - 'helpers/checkForSymbol.js' - 'helpers/checkForTypedArrays.js' + - 'helpers/integrationMatchers.js' - 'helpers/defineJasmineUnderTest.js' spec_files: - '**/*[Ss]pec.js' diff --git a/src/core/Env.js b/src/core/Env.js index bb116567..28ab5edd 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -526,9 +526,7 @@ getJasmineRequireObj().Env = function(j$) { } if (declarationError) { - self.it('encountered a declaration exception', function() { - throw declarationError; - }); + suite.onException(declarationError); } currentDeclarationSuite = parentSuite; diff --git a/src/core/Suite.js b/src/core/Suite.js index c7783d59..9e2e0bb1 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -124,52 +124,32 @@ getJasmineRequireObj().Suite = function(j$) { return; } - if(isAfterAll(this.children)) { - var data = { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: arguments[0] - }; - var failedExpectation = this.expectationResultFactory(data); + var data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0] + }; + var failedExpectation = this.expectationResultFactory(data); - if (!this.parentSuite) { - failedExpectation.globalErrorType = 'afterAll'; - } - - this.result.failedExpectations.push(failedExpectation); - } else { - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - child.onException.apply(child, arguments); - } + if (!this.parentSuite) { + failedExpectation.globalErrorType = 'afterAll'; } + + this.result.failedExpectations.push(failedExpectation); }; Suite.prototype.addExpectationResult = function () { - if(isAfterAll(this.children) && isFailure(arguments)){ + if(isFailure(arguments)) { var data = arguments[1]; this.result.failedExpectations.push(this.expectationResultFactory(data)); if(this.throwOnExpectationFailure) { throw new j$.errors.ExpectationFailed(); } - } else { - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - try { - child.addExpectationResult.apply(child, arguments); - } catch(e) { - // keep going - } - } } }; - function isAfterAll(children) { - return children && children[0].result.status; - } - function isFailure(args) { return !args[0]; } From 9a96396f651226258f087dc8aed560b070bc4c03 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Tue, 23 Jan 2018 14:38:40 -0800 Subject: [PATCH 49/62] Generate jasmine.js for suite reporting changes Signed-off-by: Elenore Bastian --- lib/jasmine-core/jasmine.js | 50 +++++++++++-------------------------- 1 file changed, 14 insertions(+), 36 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index b272a740..017417b2 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -1216,9 +1216,7 @@ getJasmineRequireObj().Env = function(j$) { } if (declarationError) { - self.it('encountered a declaration exception', function() { - throw declarationError; - }); + suite.onException(declarationError); } currentDeclarationSuite = parentSuite; @@ -5646,52 +5644,32 @@ getJasmineRequireObj().Suite = function(j$) { return; } - if(isAfterAll(this.children)) { - var data = { - matcherName: '', - passed: false, - expected: '', - actual: '', - error: arguments[0] - }; - var failedExpectation = this.expectationResultFactory(data); + var data = { + matcherName: '', + passed: false, + expected: '', + actual: '', + error: arguments[0] + }; + var failedExpectation = this.expectationResultFactory(data); - if (!this.parentSuite) { - failedExpectation.globalErrorType = 'afterAll'; - } - - this.result.failedExpectations.push(failedExpectation); - } else { - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - child.onException.apply(child, arguments); - } + if (!this.parentSuite) { + failedExpectation.globalErrorType = 'afterAll'; } + + this.result.failedExpectations.push(failedExpectation); }; Suite.prototype.addExpectationResult = function () { - if(isAfterAll(this.children) && isFailure(arguments)){ + if(isFailure(arguments)) { var data = arguments[1]; this.result.failedExpectations.push(this.expectationResultFactory(data)); if(this.throwOnExpectationFailure) { throw new j$.errors.ExpectationFailed(); } - } else { - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - try { - child.addExpectationResult.apply(child, arguments); - } catch(e) { - // keep going - } - } } }; - function isAfterAll(children) { - return children && children[0].result.status; - } - function isFailure(args) { return !args[0]; } From 07996b567f83191e4258e131339c6ec0e8041b7b Mon Sep 17 00:00:00 2001 From: Elenore Bastian Date: Tue, 23 Jan 2018 15:39:11 -0800 Subject: [PATCH 50/62] Suite level errors are reported in failures list [#150118881] Signed-off-by: Gregg Van Hove --- lib/jasmine-core/jasmine-html.js | 256 ++++++++++++++++--------------- spec/html/HtmlReporterSpec.js | 201 ++++++++++++++---------- src/html/HtmlReporter.js | 252 +++++++++++++++--------------- src/html/ResultsNode.js | 4 + 4 files changed, 382 insertions(+), 331 deletions(-) diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 4e9558cd..3c54d040 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -49,9 +49,14 @@ jasmineRequire.HtmlReporter = function(j$) { }; ResultsStateBuilder.prototype.suiteDone = function(result) { + this.currentParent.updateResult(result); if (this.currentParent !== this.topResults) { this.currentParent = this.currentParent.parent; } + + if (result.status === 'failed') { + this.failureCount++; + } }; ResultsStateBuilder.prototype.specStarted = function(result) { @@ -86,10 +91,8 @@ jasmineRequire.HtmlReporter = function(j$) { addToExistingQueryString = options.addToExistingQueryString || defaultQueryString, filterSpecs = options.filterSpecs, timer = options.timer || noopTimer, - results = [], htmlReporterMain, - symbols, - failedSuites = []; + symbols; this.initialize = function() { clearPrior(); @@ -122,11 +125,11 @@ jasmineRequire.HtmlReporter = function(j$) { }; this.suiteDone = function(result) { - if (result.status == 'failed') { - failedSuites.push(result); - } - stateBuilder.suiteDone(result); + + if (result.status === 'failed') { + failures.push(failureDom(result)); + } }; this.specStarted = function(result) { @@ -152,21 +155,8 @@ jasmineRequire.HtmlReporter = function(j$) { } )); - if (result.status == 'failed') { - var failure = - createDom('div', {className: 'jasmine-spec-detail jasmine-failed'}, - failureDescription(result, stateBuilder.currentParent), - createDom('div', {className: 'jasmine-messages'}) - ); - var messages = failure.childNodes[1]; - - for (var i = 0; i < result.failedExpectations.length; i++) { - var expectation = result.failedExpectations[i]; - messages.appendChild(createDom('div', {className: 'jasmine-result-message'}, expectation.message)); - messages.appendChild(createDom('div', {className: 'jasmine-stack-trace'}, expectation.stack)); - } - - failures.push(failure); + if (result.status === 'failed') { + failures.push(failureDom(result)); } }; @@ -176,59 +166,7 @@ jasmineRequire.HtmlReporter = function(j$) { var order = doneResult && doneResult.order; alert.appendChild(createDom('span', {className: 'jasmine-duration'}, 'finished in ' + timer.elapsed() / 1000 + 's')); - banner.appendChild( - createDom('div', { className: 'jasmine-run-options' }, - createDom('span', { className: 'jasmine-trigger' }, 'Options'), - createDom('div', { className: 'jasmine-payload' }, - createDom('div', { className: 'jasmine-exceptions' }, - createDom('input', { - className: 'jasmine-raise', - id: 'jasmine-raise-exceptions', - type: 'checkbox' - }), - createDom('label', { className: 'jasmine-label', 'for': 'jasmine-raise-exceptions' }, 'raise exceptions')), - createDom('div', { className: 'jasmine-throw-failures' }, - createDom('input', { - className: 'jasmine-throw', - id: 'jasmine-throw-failures', - type: 'checkbox' - }), - createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')), - createDom('div', { className: 'jasmine-random-order' }, - createDom('input', { - className: 'jasmine-random', - id: 'jasmine-random-order', - type: 'checkbox' - }), - createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order')) - ) - )); - - var raiseCheckbox = find('#jasmine-raise-exceptions'); - - raiseCheckbox.checked = !env.catchingExceptions(); - raiseCheckbox.onclick = onRaiseExceptionsClick; - - var throwCheckbox = find('#jasmine-throw-failures'); - throwCheckbox.checked = env.throwingExpectationFailures(); - throwCheckbox.onclick = onThrowExpectationsClick; - - var randomCheckbox = find('#jasmine-random-order'); - randomCheckbox.checked = env.randomTests(); - randomCheckbox.onclick = onRandomClick; - - var optionsMenu = find('.jasmine-run-options'), - optionsTrigger = optionsMenu.querySelector('.jasmine-trigger'), - optionsPayload = optionsMenu.querySelector('.jasmine-payload'), - isOpen = /\bjasmine-open\b/; - - optionsTrigger.onclick = function() { - if (isOpen.test(optionsPayload.className)) { - optionsPayload.className = optionsPayload.className.replace(isOpen, ''); - } else { - optionsPayload.className += ' jasmine-open'; - } - }; + banner.appendChild(optionsMenu(env)); if (stateBuilder.specsExecuted < totalSpecsDefined) { var skippedMessage = 'Ran ' + stateBuilder.specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all'; @@ -242,7 +180,7 @@ jasmineRequire.HtmlReporter = function(j$) { var statusBarMessage = ''; var statusBarClassName = 'jasmine-overall-result jasmine-bar '; var globalFailures = (doneResult && doneResult.failedExpectations) || []; - var failed = stateBuilder.failureCount + globalFailures.length + failedSuites.length > 0; + var failed = stateBuilder.failureCount + globalFailures.length > 0; if (totalSpecsDefined > 0 || failed) { statusBarMessage += pluralize('spec', stateBuilder.specsExecuted) + ', ' + pluralize('failure', stateBuilder.failureCount); @@ -271,13 +209,6 @@ jasmineRequire.HtmlReporter = function(j$) { var errorBarClassName = 'jasmine-bar jasmine-errored'; var afterAllMessagePrefix = 'AfterAll '; - for(var i = 0; i < failedSuites.length; i++) { - var failedSuite = failedSuites[i]; - for(var j = 0; j < failedSuite.failedExpectations.length; j++) { - alert.appendChild(createDom('span', {className: errorBarClassName}, afterAllMessagePrefix + failedSuite.failedExpectations[j].message)); - } - } - for(i = 0; i < globalFailures.length; i++) { alert.appendChild(createDom('span', {className: errorBarClassName}, globalFailureMessage(globalFailures[i]))); } @@ -301,47 +232,6 @@ jasmineRequire.HtmlReporter = function(j$) { summaryList(stateBuilder.topResults, summary); - function summaryList(resultsTree, domParent) { - var specListNode; - for (var i = 0; i < resultsTree.children.length; i++) { - var resultNode = resultsTree.children[i]; - if (filterSpecs && !hasActiveSpec(resultNode)) { - continue; - } - if (resultNode.type == 'suite') { - var suiteListNode = createDom('ul', {className: 'jasmine-suite', id: 'suite-' + resultNode.result.id}, - createDom('li', {className: 'jasmine-suite-detail'}, - createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description) - ) - ); - - summaryList(resultNode, suiteListNode); - domParent.appendChild(suiteListNode); - } - if (resultNode.type == 'spec') { - if (domParent.getAttribute('class') != 'jasmine-specs') { - specListNode = createDom('ul', {className: 'jasmine-specs'}); - domParent.appendChild(specListNode); - } - var specDescription = resultNode.result.description; - if(noExpectations(resultNode.result)) { - specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; - } - if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') { - specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason; - } - specListNode.appendChild( - createDom('li', { - className: 'jasmine-' + resultNode.result.status, - id: 'spec-' + resultNode.result.id - }, - createDom('a', {href: specHref(resultNode.result)}, specDescription) - ) - ); - } - } - } - if (failures.length) { alert.appendChild( createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-spec-list'}, @@ -370,6 +260,120 @@ jasmineRequire.HtmlReporter = function(j$) { return this; + function failureDom(result) { + var failure = + createDom('div', {className: 'jasmine-spec-detail jasmine-failed'}, + failureDescription(result, stateBuilder.currentParent), + createDom('div', {className: 'jasmine-messages'}) + ); + var messages = failure.childNodes[1]; + + for (var i = 0; i < result.failedExpectations.length; i++) { + var expectation = result.failedExpectations[i]; + messages.appendChild(createDom('div', {className: 'jasmine-result-message'}, expectation.message)); + messages.appendChild(createDom('div', {className: 'jasmine-stack-trace'}, expectation.stack)); + } + + return failure; + } + + function summaryList(resultsTree, domParent) { + var specListNode; + for (var i = 0; i < resultsTree.children.length; i++) { + var resultNode = resultsTree.children[i]; + if (filterSpecs && !hasActiveSpec(resultNode)) { + continue; + } + if (resultNode.type === 'suite') { + var suiteListNode = createDom('ul', {className: 'jasmine-suite', id: 'suite-' + resultNode.result.id}, + createDom('li', {className: 'jasmine-suite-detail jasmine-' + resultNode.result.status}, + createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description) + ) + ); + + summaryList(resultNode, suiteListNode); + domParent.appendChild(suiteListNode); + } + if (resultNode.type === 'spec') { + if (domParent.getAttribute('class') !== 'jasmine-specs') { + specListNode = createDom('ul', {className: 'jasmine-specs'}); + domParent.appendChild(specListNode); + } + var specDescription = resultNode.result.description; + if(noExpectations(resultNode.result)) { + specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; + } + if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') { + specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason; + } + specListNode.appendChild( + createDom('li', { + className: 'jasmine-' + resultNode.result.status, + id: 'spec-' + resultNode.result.id + }, + createDom('a', {href: specHref(resultNode.result)}, specDescription) + ) + ); + } + } + } + + function optionsMenu(env) { + var optionsMenuDom = createDom('div', { className: 'jasmine-run-options' }, + createDom('span', { className: 'jasmine-trigger' }, 'Options'), + createDom('div', { className: 'jasmine-payload' }, + createDom('div', { className: 'jasmine-exceptions' }, + createDom('input', { + className: 'jasmine-raise', + id: 'jasmine-raise-exceptions', + type: 'checkbox' + }), + createDom('label', { className: 'jasmine-label', 'for': 'jasmine-raise-exceptions' }, 'raise exceptions')), + createDom('div', { className: 'jasmine-throw-failures' }, + createDom('input', { + className: 'jasmine-throw', + id: 'jasmine-throw-failures', + type: 'checkbox' + }), + createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')), + createDom('div', { className: 'jasmine-random-order' }, + createDom('input', { + className: 'jasmine-random', + id: 'jasmine-random-order', + type: 'checkbox' + }), + createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order')) + ) + ); + + var raiseCheckbox = optionsMenuDom.querySelector('#jasmine-raise-exceptions'); + + raiseCheckbox.checked = !env.catchingExceptions(); + raiseCheckbox.onclick = onRaiseExceptionsClick; + + var throwCheckbox = optionsMenuDom.querySelector('#jasmine-throw-failures'); + throwCheckbox.checked = env.throwingExpectationFailures(); + throwCheckbox.onclick = onThrowExpectationsClick; + + var randomCheckbox = optionsMenuDom.querySelector('#jasmine-random-order'); + randomCheckbox.checked = env.randomTests(); + randomCheckbox.onclick = onRandomClick; + + var optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'), + optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'), + isOpen = /\bjasmine-open\b/; + + optionsTrigger.onclick = function() { + if (isOpen.test(optionsPayload.className)) { + optionsPayload.className = optionsPayload.className.replace(isOpen, ''); + } else { + optionsPayload.className += ' jasmine-open'; + } + }; + + return optionsMenuDom; + } + function failureDescription(result, suite) { var wrapper = createDom('div', {className: 'jasmine-description'}, createDom('a', {title: result.description, href: specHref(result)}, result.description) @@ -510,6 +514,10 @@ jasmineRequire.ResultsNode = function() { this.last = function() { return this.children[this.children.length - 1]; }; + + this.updateResult = function(result) { + this.result = result; + }; } return ResultsNode; diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 795a5ed9..45e1667a 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -177,69 +177,6 @@ describe("HtmlReporter", function() { }); }); - describe("when there are suite failures", function () { - it("displays the exceptions in their own alert bars", function(){ - var env = new jasmineUnderTest.Env(), - container = document.createElement("div"), - getContainer = function() { return container; }, - reporter = new jasmineUnderTest.HtmlReporter({ - env: env, - getContainer: getContainer, - createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); } - }); - - reporter.initialize(); - - reporter.jasmineStarted({}); - reporter.suiteDone({ status: 'failed', failedExpectations: [{ message: 'My After All Exception' }] }); - reporter.suiteDone({ status: 'failed', failedExpectations: [{ message: 'My Other Exception' }] }); - reporter.jasmineDone({ failedExpectations: [ - { message: 'Global After All Failure', globalErrorType: 'afterAll' }, - { message: 'Your JS is borken', globalErrorType: 'load' } - ] }); - - var alertBars = container.querySelectorAll(".jasmine-alert .jasmine-bar"); - - expect(alertBars.length).toEqual(5); - expect(alertBars[1].innerHTML).toMatch(/My After All Exception/); - expect(alertBars[1].getAttribute("class")).toEqual('jasmine-bar jasmine-errored'); - expect(alertBars[2].innerHTML).toMatch(/My Other Exception/); - expect(alertBars[3].innerHTML).toMatch(/AfterAll Global After All Failure/); - expect(alertBars[4].innerHTML).toMatch(/Error during loading: Your JS is borken/); - expect(alertBars[4].innerHTML).not.toMatch(/line/); - }); - - it("displays file and line information if available", function() { - var env = new jasmineUnderTest.Env(), - container = document.createElement("div"), - getContainer = function() { return container; }, - reporter = new jasmineUnderTest.HtmlReporter({ - env: env, - getContainer: getContainer, - createElement: function() { return document.createElement.apply(document, arguments); }, - createTextNode: function() { return document.createTextNode.apply(document, arguments); } - }); - - reporter.initialize(); - - reporter.jasmineStarted({}); - reporter.jasmineDone({ failedExpectations: [ - { - message: 'Your JS is borken', - globalErrorType: 'load', - filename: 'some/file.js', - lineno: 42 - } - ] }); - - var alertBars = container.querySelectorAll(".jasmine-alert .jasmine-bar"); - - expect(alertBars.length).toEqual(2); - expect(alertBars[1].innerHTML).toMatch(/Error during loading: Your JS is borken in some\/file.js line 42/); - }); - }); - describe("when Jasmine is done", function() { it("adds a warning to the link title of specs that have no expectations", function() { if (!window.console) { @@ -351,7 +288,12 @@ describe("HtmlReporter", function() { reporter.specStarted(specResult); reporter.specDone(specResult); - reporter.suiteDone({id: 2}); + reporter.suiteDone({ + id: 2, + status: 'things', + description: "inner suite", + fullName: "A Suite inner suite" + }); specResult = { id: 209, @@ -364,7 +306,12 @@ describe("HtmlReporter", function() { reporter.specStarted(specResult); reporter.specDone(specResult); - reporter.suiteDone({id: 1}); + reporter.suiteDone({ + id: 1, + status: 'things', + description: "A Suite", + fullName: "A Suite" + }); reporter.jasmineDone({}); var summary = container.querySelector(".jasmine-summary"); @@ -379,7 +326,7 @@ describe("HtmlReporter", function() { var node = outerSuite.childNodes[i]; classes.push(node.getAttribute("class")); } - expect(classes).toEqual(["jasmine-suite-detail", "jasmine-specs", "jasmine-suite", "jasmine-specs"]); + expect(classes).toEqual(["jasmine-suite-detail jasmine-things", "jasmine-specs", "jasmine-suite", "jasmine-specs"]); var suiteDetail = outerSuite.childNodes[0]; var suiteLink = suiteDetail.childNodes[0]; @@ -430,6 +377,65 @@ describe("HtmlReporter", function() { expect(payload).not.toHaveClass('jasmine-open'); }); + describe("when there are global errors", function() { + it("displays the exceptions in their own alert bars", function(){ + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + getContainer = function() { return container; }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: getContainer, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + + reporter.initialize(); + + reporter.jasmineStarted({}); + reporter.jasmineDone({ failedExpectations: [ + { message: 'Global After All Failure', globalErrorType: 'afterAll' }, + { message: 'Your JS is borken', globalErrorType: 'load' } + ] }); + + var alertBars = container.querySelectorAll(".jasmine-alert .jasmine-bar"); + + expect(alertBars.length).toEqual(3); + expect(alertBars[1].getAttribute("class")).toEqual('jasmine-bar jasmine-errored'); + expect(alertBars[1].innerHTML).toMatch(/AfterAll Global After All Failure/); + expect(alertBars[2].innerHTML).toMatch(/Error during loading: Your JS is borken/); + expect(alertBars[2].innerHTML).not.toMatch(/line/); + }); + + it("displays file and line information if available", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + getContainer = function() { return container; }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: getContainer, + createElement: function() { return document.createElement.apply(document, arguments); }, + createTextNode: function() { return document.createTextNode.apply(document, arguments); } + }); + + reporter.initialize(); + + reporter.jasmineStarted({}); + reporter.jasmineDone({ failedExpectations: [ + { + message: 'Your JS is borken', + globalErrorType: 'load', + filename: 'some/file.js', + lineno: 42 + } + ] }); + + var alertBars = container.querySelectorAll(".jasmine-alert .jasmine-bar"); + + expect(alertBars.length).toEqual(2); + expect(alertBars[1].innerHTML).toMatch(/Error during loading: Your JS is borken in some\/file.js line 42/); + }); + }); + describe("UI for raising/catching exceptions", function() { it("should be unchecked if the env is catching", function() { var env = new jasmineUnderTest.Env(), @@ -928,11 +934,11 @@ describe("HtmlReporter", function() { description: "inner suite" }); - var passingResult = {id: 123, status: "passed", passedExpectations: [{passed: true}], failedExpectations: []}; - reporter.specStarted(passingResult); - reporter.specDone(passingResult); + var passingSpecResult = {id: 123, status: "passed", passedExpectations: [{passed: true}], failedExpectations: []}; + reporter.specStarted(passingSpecResult); + reporter.specDone(passingSpecResult); - var failingResult = { + var failingSpecResult = { id: 124, status: "failed", description: "a failing spec", @@ -945,36 +951,65 @@ describe("HtmlReporter", function() { } ] }; - reporter.specStarted(failingResult); - reporter.specDone(failingResult); - reporter.suiteDone({}); - reporter.suiteDone({}); - reporter.suiteDone({}); + + var passingSuiteResult = { + id: 1, + description: "A suite" + }; + var failingSuiteResult = { + id: 2, + description: 'a suite', + fullName: 'a suite', + status: 'failed', + failedExpectations: [{ message: 'My After All Exception' }] + }; + reporter.specStarted(failingSpecResult); + reporter.specDone(failingSpecResult); + reporter.suiteDone(passingSuiteResult); + reporter.suiteDone(failingSuiteResult); + reporter.suiteDone(passingSuiteResult); reporter.jasmineDone({}); }); it("reports the specs counts", function() { var alertBar = container.querySelector(".jasmine-alert .jasmine-bar"); - expect(alertBar.innerHTML).toMatch(/2 specs, 1 failure/); + expect(alertBar.innerHTML).toMatch(/2 specs, 2 failure/); }); it("reports failure messages and stack traces", function() { var specFailures = container.querySelector(".jasmine-failures"); - var failure = specFailures.childNodes[0]; - expect(failure.getAttribute("class")).toMatch(/jasmine-failed/); - expect(failure.getAttribute("class")).toMatch(/jasmine-spec-detail/); + expect(specFailures.childNodes.length).toEqual(2); - var specDiv = failure.childNodes[0]; + var specFailure = specFailures.childNodes[0]; + expect(specFailure.getAttribute("class")).toMatch(/jasmine-failed/); + expect(specFailure.getAttribute("class")).toMatch(/jasmine-spec-detail/); + + var specDiv = specFailure.childNodes[0]; expect(specDiv.getAttribute("class")).toEqual("jasmine-description"); - var message = failure.childNodes[1].childNodes[0]; + var message = specFailure.childNodes[1].childNodes[0]; expect(message.getAttribute("class")).toEqual("jasmine-result-message"); expect(message.innerHTML).toEqual("a failure message"); - var stackTrace = failure.childNodes[1].childNodes[1]; + var stackTrace = specFailure.childNodes[1].childNodes[1]; expect(stackTrace.getAttribute("class")).toEqual("jasmine-stack-trace"); expect(stackTrace.innerHTML).toEqual("a stack trace"); + + var suiteFailure = specFailures.childNodes[0]; + expect(suiteFailure.getAttribute("class")).toMatch(/jasmine-failed/); + expect(suiteFailure.getAttribute("class")).toMatch(/jasmine-spec-detail/); + + var suiteDiv = suiteFailure.childNodes[0]; + expect(suiteDiv.getAttribute("class")).toEqual("jasmine-description"); + + var suiteMessage = suiteFailure.childNodes[1].childNodes[0]; + expect(suiteMessage.getAttribute("class")).toEqual("jasmine-result-message"); + expect(suiteMessage.innerHTML).toEqual("a failure message"); + + var suiteStackTrace = suiteFailure.childNodes[1].childNodes[1]; + expect(suiteStackTrace.getAttribute("class")).toEqual("jasmine-stack-trace"); + expect(suiteStackTrace.innerHTML).toEqual("a stack trace"); }); it('provides links to focus on a failure and each containing suite', function() { @@ -1063,7 +1098,7 @@ describe("HtmlReporter", function() { }); }); - describe("When the jasmineDone event's overallStatus is 'failed'", function() { + describe("When the jasmineDone event's overallStatus is 'incomplete'", function() { it("has class jasmine-incomplete", function() { var env = new jasmineUnderTest.Env(), container = document.createElement("div"), diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index 72d4ee78..1dfde539 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -19,9 +19,14 @@ jasmineRequire.HtmlReporter = function(j$) { }; ResultsStateBuilder.prototype.suiteDone = function(result) { + this.currentParent.updateResult(result); if (this.currentParent !== this.topResults) { this.currentParent = this.currentParent.parent; } + + if (result.status === 'failed') { + this.failureCount++; + } }; ResultsStateBuilder.prototype.specStarted = function(result) { @@ -56,10 +61,8 @@ jasmineRequire.HtmlReporter = function(j$) { addToExistingQueryString = options.addToExistingQueryString || defaultQueryString, filterSpecs = options.filterSpecs, timer = options.timer || noopTimer, - results = [], htmlReporterMain, - symbols, - failedSuites = []; + symbols; this.initialize = function() { clearPrior(); @@ -92,11 +95,11 @@ jasmineRequire.HtmlReporter = function(j$) { }; this.suiteDone = function(result) { - if (result.status == 'failed') { - failedSuites.push(result); - } - stateBuilder.suiteDone(result); + + if (result.status === 'failed') { + failures.push(failureDom(result)); + } }; this.specStarted = function(result) { @@ -122,21 +125,8 @@ jasmineRequire.HtmlReporter = function(j$) { } )); - if (result.status == 'failed') { - var failure = - createDom('div', {className: 'jasmine-spec-detail jasmine-failed'}, - failureDescription(result, stateBuilder.currentParent), - createDom('div', {className: 'jasmine-messages'}) - ); - var messages = failure.childNodes[1]; - - for (var i = 0; i < result.failedExpectations.length; i++) { - var expectation = result.failedExpectations[i]; - messages.appendChild(createDom('div', {className: 'jasmine-result-message'}, expectation.message)); - messages.appendChild(createDom('div', {className: 'jasmine-stack-trace'}, expectation.stack)); - } - - failures.push(failure); + if (result.status === 'failed') { + failures.push(failureDom(result)); } }; @@ -146,59 +136,7 @@ jasmineRequire.HtmlReporter = function(j$) { var order = doneResult && doneResult.order; alert.appendChild(createDom('span', {className: 'jasmine-duration'}, 'finished in ' + timer.elapsed() / 1000 + 's')); - banner.appendChild( - createDom('div', { className: 'jasmine-run-options' }, - createDom('span', { className: 'jasmine-trigger' }, 'Options'), - createDom('div', { className: 'jasmine-payload' }, - createDom('div', { className: 'jasmine-exceptions' }, - createDom('input', { - className: 'jasmine-raise', - id: 'jasmine-raise-exceptions', - type: 'checkbox' - }), - createDom('label', { className: 'jasmine-label', 'for': 'jasmine-raise-exceptions' }, 'raise exceptions')), - createDom('div', { className: 'jasmine-throw-failures' }, - createDom('input', { - className: 'jasmine-throw', - id: 'jasmine-throw-failures', - type: 'checkbox' - }), - createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')), - createDom('div', { className: 'jasmine-random-order' }, - createDom('input', { - className: 'jasmine-random', - id: 'jasmine-random-order', - type: 'checkbox' - }), - createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order')) - ) - )); - - var raiseCheckbox = find('#jasmine-raise-exceptions'); - - raiseCheckbox.checked = !env.catchingExceptions(); - raiseCheckbox.onclick = onRaiseExceptionsClick; - - var throwCheckbox = find('#jasmine-throw-failures'); - throwCheckbox.checked = env.throwingExpectationFailures(); - throwCheckbox.onclick = onThrowExpectationsClick; - - var randomCheckbox = find('#jasmine-random-order'); - randomCheckbox.checked = env.randomTests(); - randomCheckbox.onclick = onRandomClick; - - var optionsMenu = find('.jasmine-run-options'), - optionsTrigger = optionsMenu.querySelector('.jasmine-trigger'), - optionsPayload = optionsMenu.querySelector('.jasmine-payload'), - isOpen = /\bjasmine-open\b/; - - optionsTrigger.onclick = function() { - if (isOpen.test(optionsPayload.className)) { - optionsPayload.className = optionsPayload.className.replace(isOpen, ''); - } else { - optionsPayload.className += ' jasmine-open'; - } - }; + banner.appendChild(optionsMenu(env)); if (stateBuilder.specsExecuted < totalSpecsDefined) { var skippedMessage = 'Ran ' + stateBuilder.specsExecuted + ' of ' + totalSpecsDefined + ' specs - run all'; @@ -212,7 +150,7 @@ jasmineRequire.HtmlReporter = function(j$) { var statusBarMessage = ''; var statusBarClassName = 'jasmine-overall-result jasmine-bar '; var globalFailures = (doneResult && doneResult.failedExpectations) || []; - var failed = stateBuilder.failureCount + globalFailures.length + failedSuites.length > 0; + var failed = stateBuilder.failureCount + globalFailures.length > 0; if (totalSpecsDefined > 0 || failed) { statusBarMessage += pluralize('spec', stateBuilder.specsExecuted) + ', ' + pluralize('failure', stateBuilder.failureCount); @@ -241,13 +179,6 @@ jasmineRequire.HtmlReporter = function(j$) { var errorBarClassName = 'jasmine-bar jasmine-errored'; var afterAllMessagePrefix = 'AfterAll '; - for(var i = 0; i < failedSuites.length; i++) { - var failedSuite = failedSuites[i]; - for(var j = 0; j < failedSuite.failedExpectations.length; j++) { - alert.appendChild(createDom('span', {className: errorBarClassName}, afterAllMessagePrefix + failedSuite.failedExpectations[j].message)); - } - } - for(i = 0; i < globalFailures.length; i++) { alert.appendChild(createDom('span', {className: errorBarClassName}, globalFailureMessage(globalFailures[i]))); } @@ -271,47 +202,6 @@ jasmineRequire.HtmlReporter = function(j$) { summaryList(stateBuilder.topResults, summary); - function summaryList(resultsTree, domParent) { - var specListNode; - for (var i = 0; i < resultsTree.children.length; i++) { - var resultNode = resultsTree.children[i]; - if (filterSpecs && !hasActiveSpec(resultNode)) { - continue; - } - if (resultNode.type == 'suite') { - var suiteListNode = createDom('ul', {className: 'jasmine-suite', id: 'suite-' + resultNode.result.id}, - createDom('li', {className: 'jasmine-suite-detail'}, - createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description) - ) - ); - - summaryList(resultNode, suiteListNode); - domParent.appendChild(suiteListNode); - } - if (resultNode.type == 'spec') { - if (domParent.getAttribute('class') != 'jasmine-specs') { - specListNode = createDom('ul', {className: 'jasmine-specs'}); - domParent.appendChild(specListNode); - } - var specDescription = resultNode.result.description; - if(noExpectations(resultNode.result)) { - specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; - } - if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') { - specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason; - } - specListNode.appendChild( - createDom('li', { - className: 'jasmine-' + resultNode.result.status, - id: 'spec-' + resultNode.result.id - }, - createDom('a', {href: specHref(resultNode.result)}, specDescription) - ) - ); - } - } - } - if (failures.length) { alert.appendChild( createDom('span', {className: 'jasmine-menu jasmine-bar jasmine-spec-list'}, @@ -340,6 +230,120 @@ jasmineRequire.HtmlReporter = function(j$) { return this; + function failureDom(result) { + var failure = + createDom('div', {className: 'jasmine-spec-detail jasmine-failed'}, + failureDescription(result, stateBuilder.currentParent), + createDom('div', {className: 'jasmine-messages'}) + ); + var messages = failure.childNodes[1]; + + for (var i = 0; i < result.failedExpectations.length; i++) { + var expectation = result.failedExpectations[i]; + messages.appendChild(createDom('div', {className: 'jasmine-result-message'}, expectation.message)); + messages.appendChild(createDom('div', {className: 'jasmine-stack-trace'}, expectation.stack)); + } + + return failure; + } + + function summaryList(resultsTree, domParent) { + var specListNode; + for (var i = 0; i < resultsTree.children.length; i++) { + var resultNode = resultsTree.children[i]; + if (filterSpecs && !hasActiveSpec(resultNode)) { + continue; + } + if (resultNode.type === 'suite') { + var suiteListNode = createDom('ul', {className: 'jasmine-suite', id: 'suite-' + resultNode.result.id}, + createDom('li', {className: 'jasmine-suite-detail jasmine-' + resultNode.result.status}, + createDom('a', {href: specHref(resultNode.result)}, resultNode.result.description) + ) + ); + + summaryList(resultNode, suiteListNode); + domParent.appendChild(suiteListNode); + } + if (resultNode.type === 'spec') { + if (domParent.getAttribute('class') !== 'jasmine-specs') { + specListNode = createDom('ul', {className: 'jasmine-specs'}); + domParent.appendChild(specListNode); + } + var specDescription = resultNode.result.description; + if(noExpectations(resultNode.result)) { + specDescription = 'SPEC HAS NO EXPECTATIONS ' + specDescription; + } + if(resultNode.result.status === 'pending' && resultNode.result.pendingReason !== '') { + specDescription = specDescription + ' PENDING WITH MESSAGE: ' + resultNode.result.pendingReason; + } + specListNode.appendChild( + createDom('li', { + className: 'jasmine-' + resultNode.result.status, + id: 'spec-' + resultNode.result.id + }, + createDom('a', {href: specHref(resultNode.result)}, specDescription) + ) + ); + } + } + } + + function optionsMenu(env) { + var optionsMenuDom = createDom('div', { className: 'jasmine-run-options' }, + createDom('span', { className: 'jasmine-trigger' }, 'Options'), + createDom('div', { className: 'jasmine-payload' }, + createDom('div', { className: 'jasmine-exceptions' }, + createDom('input', { + className: 'jasmine-raise', + id: 'jasmine-raise-exceptions', + type: 'checkbox' + }), + createDom('label', { className: 'jasmine-label', 'for': 'jasmine-raise-exceptions' }, 'raise exceptions')), + createDom('div', { className: 'jasmine-throw-failures' }, + createDom('input', { + className: 'jasmine-throw', + id: 'jasmine-throw-failures', + type: 'checkbox' + }), + createDom('label', { className: 'jasmine-label', 'for': 'jasmine-throw-failures' }, 'stop spec on expectation failure')), + createDom('div', { className: 'jasmine-random-order' }, + createDom('input', { + className: 'jasmine-random', + id: 'jasmine-random-order', + type: 'checkbox' + }), + createDom('label', { className: 'jasmine-label', 'for': 'jasmine-random-order' }, 'run tests in random order')) + ) + ); + + var raiseCheckbox = optionsMenuDom.querySelector('#jasmine-raise-exceptions'); + + raiseCheckbox.checked = !env.catchingExceptions(); + raiseCheckbox.onclick = onRaiseExceptionsClick; + + var throwCheckbox = optionsMenuDom.querySelector('#jasmine-throw-failures'); + throwCheckbox.checked = env.throwingExpectationFailures(); + throwCheckbox.onclick = onThrowExpectationsClick; + + var randomCheckbox = optionsMenuDom.querySelector('#jasmine-random-order'); + randomCheckbox.checked = env.randomTests(); + randomCheckbox.onclick = onRandomClick; + + var optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'), + optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'), + isOpen = /\bjasmine-open\b/; + + optionsTrigger.onclick = function() { + if (isOpen.test(optionsPayload.className)) { + optionsPayload.className = optionsPayload.className.replace(isOpen, ''); + } else { + optionsPayload.className += ' jasmine-open'; + } + }; + + return optionsMenuDom; + } + function failureDescription(result, suite) { var wrapper = createDom('div', {className: 'jasmine-description'}, createDom('a', {title: result.description, href: specHref(result)}, result.description) diff --git a/src/html/ResultsNode.js b/src/html/ResultsNode.js index 24cea254..bc3e0b4c 100644 --- a/src/html/ResultsNode.js +++ b/src/html/ResultsNode.js @@ -13,6 +13,10 @@ jasmineRequire.ResultsNode = function() { this.last = function() { return this.children[this.children.length - 1]; }; + + this.updateResult = function(result) { + this.result = result; + }; } return ResultsNode; From 6b156ca6d16e0d505287465e164ec8af120b98e1 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Thu, 25 Jan 2018 16:17:31 -0800 Subject: [PATCH 51/62] Unify status for xdescribe and xit - Ensure *All's only execute if at least one child will run - Specs will report a status of `excluded` instead of disabled [finishes #153967580] - #1418 Signed-off-by: Elenore Bastian --- lib/jasmine-core/jasmine.js | 65 ++++++--------- spec/core/JsApiReporterSpec.js | 2 +- spec/core/SpecSpec.js | 78 +----------------- spec/core/SuiteSpec.js | 15 +--- spec/core/TreeProcessorSpec.js | 133 +++++++++++++++++++------------ spec/core/integration/EnvSpec.js | 85 +++++++++++++++----- src/core/Env.js | 12 ++- src/core/Spec.js | 23 ++---- src/core/Suite.js | 6 +- src/core/TreeProcessor.js | 24 +++--- 10 files changed, 204 insertions(+), 239 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 017417b2..d7154461 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -525,7 +525,7 @@ getJasmineRequireObj().Spec = function(j$) { return this.expectationFactory(actual, this); }; - Spec.prototype.execute = function(onComplete, enabled) { + Spec.prototype.execute = function(onComplete, excluded) { var self = this; this.onStart(this); @@ -542,17 +542,16 @@ getJasmineRequireObj().Spec = function(j$) { userContext: this.userContext() }; - if (!this.isExecutable() || this.markedPending || enabled === false) { + if (this.markedPending || excluded === true) { runnerConfig.queueableFns = []; runnerConfig.cleanupFns = []; - runnerConfig.onComplete = function() { complete(enabled); }; } this.queueRunnerFactory(runnerConfig); - function complete(enabledAgain) { + function complete() { self.queueableFn.fn = null; - self.result.status = self.status(enabledAgain); + self.result.status = self.status(excluded); self.resultCallback(self.result); if (onComplete) { @@ -580,10 +579,6 @@ getJasmineRequireObj().Spec = function(j$) { }, true); }; - Spec.prototype.disable = function() { - this.disabled = true; - }; - Spec.prototype.pend = function(message) { this.markedPending = true; if (message) { @@ -596,9 +591,9 @@ getJasmineRequireObj().Spec = function(j$) { return this.result; }; - Spec.prototype.status = function(enabled) { - if (this.disabled || enabled === false) { - return 'disabled'; + Spec.prototype.status = function(excluded) { + if (excluded === true) { + return 'excluded'; } if (this.markedPending) { @@ -612,10 +607,6 @@ getJasmineRequireObj().Spec = function(j$) { } }; - Spec.prototype.isExecutable = function() { - return !this.disabled; - }; - Spec.prototype.getFullName = function() { return this.getSpecName(this); }; @@ -976,6 +967,7 @@ getJasmineRequireObj().Env = function(j$) { }; this.execute = function(runnablesToRun) { + var self = this; this.suppressLoadErrors(); if(!runnablesToRun) { @@ -1005,9 +997,7 @@ getJasmineRequireObj().Env = function(j$) { throw new Error('Tried to complete the wrong suite'); } - if (!suite.markedPending) { - clearResourcesForRunnable(suite.id); - } + clearResourcesForRunnable(suite.id); currentlyExecutingSuites.pop(); reporter.suiteDone(result); @@ -1017,6 +1007,9 @@ getJasmineRequireObj().Env = function(j$) { }, orderChildren: function(node) { return order.sort(node.children); + }, + excludeNode: function(spec) { + return !self.specFilter(spec); } }); @@ -1267,10 +1260,6 @@ getJasmineRequireObj().Env = function(j$) { throwOnExpectationFailure: throwOnExpectationFailure }); - if (!self.specFilter(spec)) { - spec.disable(); - } - return spec; function specResultCallback(result) { @@ -5610,14 +5599,10 @@ getJasmineRequireObj().Suite = function(j$) { if (this.result.failedExpectations.length > 0) { return 'failed'; } else { - return 'finished'; + return 'passed'; } }; - Suite.prototype.isExecutable = function() { - return !this.markedPending; - }; - Suite.prototype.canBeReentered = function() { return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; }; @@ -5712,13 +5697,14 @@ getJasmineRequireObj().TreeProcessor = function() { nodeStart = attrs.nodeStart || function() {}, nodeComplete = attrs.nodeComplete || function() {}, orderChildren = attrs.orderChildren || function(node) { return node.children; }, + excludeNode = attrs.excludeNode || function(node) { return false; }, stats = { valid: true }, processed = false, defaultMin = Infinity, defaultMax = 1 - Infinity; this.processTree = function() { - processNode(tree, false); + processNode(tree, true); processed = true; return stats; }; @@ -5752,18 +5738,18 @@ getJasmineRequireObj().TreeProcessor = function() { } } - function processNode(node, parentEnabled) { + function processNode(node, parentExcluded) { var executableIndex = runnableIndex(node.id); if (executableIndex !== undefined) { - parentEnabled = true; + parentExcluded = false; } - parentEnabled = parentEnabled && node.isExecutable(); - if (!node.children) { + var excluded = parentExcluded || excludeNode(node); stats[node.id] = { - executable: parentEnabled && node.isExecutable(), + excluded: excluded, + willExecute: !excluded && !node.markedPending, segments: [{ index: 0, owner: node, @@ -5780,7 +5766,7 @@ getJasmineRequireObj().TreeProcessor = function() { for (var i = 0; i < orderedChildren.length; i++) { var child = orderedChildren[i]; - processNode(child, parentEnabled); + processNode(child, parentExcluded); if (!stats.valid) { return; @@ -5788,11 +5774,12 @@ getJasmineRequireObj().TreeProcessor = function() { var childStats = stats[child.id]; - hasExecutableChild = hasExecutableChild || childStats.executable; + hasExecutableChild = hasExecutableChild || childStats.willExecute; } stats[node.id] = { - executable: hasExecutableChild + excluded: parentExcluded, + willExecute: hasExecutableChild }; segmentChildren(node, orderedChildren, stats[node.id], executableIndex); @@ -5888,7 +5875,7 @@ getJasmineRequireObj().TreeProcessor = function() { }; } else { return { - fn: function(done) { node.execute(done, stats[node.id].executable); } + fn: function(done) { node.execute(done, stats[node.id].excluded); } }; } } @@ -5901,7 +5888,7 @@ getJasmineRequireObj().TreeProcessor = function() { result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index)); } - if (!stats[node.id].executable) { + if (!stats[node.id].willExecute) { return result; } diff --git a/spec/core/JsApiReporterSpec.js b/spec/core/JsApiReporterSpec.js index 8e6564d8..f7fe8141 100644 --- a/spec/core/JsApiReporterSpec.js +++ b/spec/core/JsApiReporterSpec.js @@ -192,7 +192,7 @@ describe("JsApiReporter", function() { }; suiteResult2 = { id: 2, - status: 'finished' + status: 'passed' }; reporter.suiteStarted(suiteStarted1); diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index 8bc538da..9a32101e 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -140,7 +140,7 @@ describe("Spec", function() { expect(spec.status()).toBe('pending'); }); - it("can be disabled, but still calls callbacks", function() { + it("can be excluded at execution time by a parent", function() { var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner') .and.callFake(function(attrs) { attrs.onComplete(); }), startCallback = jasmine.createSpy('startCallback'), @@ -153,35 +153,9 @@ describe("Spec", function() { queueRunnerFactory: fakeQueueRunner }); - spec.disable(); + spec.execute(undefined, true); - expect(spec.status()).toBe('disabled'); - - spec.execute(); - - expect(fakeQueueRunner).toHaveBeenCalled(); - expect(specBody).not.toHaveBeenCalled(); - - expect(startCallback).toHaveBeenCalled(); - expect(resultCallback).toHaveBeenCalled(); - }); - - it("can be disabled at execution time by a parent", function() { - var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner') - .and.callFake(function(attrs) { attrs.onComplete(); }), - startCallback = jasmine.createSpy('startCallback'), - specBody = jasmine.createSpy('specBody'), - resultCallback = jasmine.createSpy('resultCallback'), - spec = new jasmineUnderTest.Spec({ - onStart:startCallback, - queueableFn: { fn: specBody }, - resultCallback: resultCallback, - queueRunnerFactory: fakeQueueRunner - }); - - spec.execute(undefined, false); - - expect(spec.result.status).toBe('disabled'); + expect(spec.result.status).toBe('excluded'); expect(fakeQueueRunner).toHaveBeenCalled(); expect(specBody).not.toHaveBeenCalled(); @@ -392,50 +366,4 @@ describe("Spec", function() { expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([]); }); - - it("retrieves a result with updated status", function() { - var spec = new jasmineUnderTest.Spec({ queueableFn: { fn: function() {} } }); - - expect(spec.getResult().status).toBe('passed'); - }); - - it("retrives a result with disabled status", function() { - var spec = new jasmineUnderTest.Spec({ queueableFn: { fn: function() {} } }); - spec.disable(); - - expect(spec.getResult().status).toBe('disabled'); - }); - - it("retrives a result with pending status", function() { - var spec = new jasmineUnderTest.Spec({ queueableFn: { fn: function() {} } }); - spec.pend(); - - expect(spec.getResult().status).toBe('pending'); - }); - - it("should not be executable when disabled", function() { - var spec = new jasmineUnderTest.Spec({ - queueableFn: { fn: function() {} } - }); - spec.disable(); - - expect(spec.isExecutable()).toBe(false); - }); - - it("should be executable when pending", function() { - var spec = new jasmineUnderTest.Spec({ - queueableFn: { fn: function() {} } - }); - spec.pend(); - - expect(spec.isExecutable()).toBe(true); - }); - - it("should be executable when not disabled or pending", function() { - var spec = new jasmineUnderTest.Spec({ - queueableFn: { fn: function() {} } - }); - - expect(spec.isExecutable()).toBe(true); - }); }); diff --git a/spec/core/SuiteSpec.js b/spec/core/SuiteSpec.js index 78e78565..fd9144d9 100644 --- a/spec/core/SuiteSpec.js +++ b/spec/core/SuiteSpec.js @@ -79,7 +79,7 @@ describe("Suite", function() { it("retrieves a result with updated status", function() { var suite = new jasmineUnderTest.Suite({}); - expect(suite.getResult().status).toBe('finished'); + expect(suite.getResult().status).toBe('passed'); }); it("retrieves a result with pending status", function() { @@ -89,19 +89,6 @@ describe("Suite", function() { expect(suite.getResult().status).toBe('pending'); }); - it("is executable if not pending", function() { - var suite = new jasmineUnderTest.Suite({}); - - expect(suite.isExecutable()).toBe(true); - }); - - it("is not executable if pending", function() { - var suite = new jasmineUnderTest.Suite({}); - suite.pend(); - - expect(suite.isExecutable()).toBe(false); - }); - it("throws an ExpectationFailed when receiving a failed expectation when throwOnExpectationFailure is set", function() { var suite = new jasmineUnderTest.Suite({ expectationResultFactory: function(data) { return data; }, diff --git a/spec/core/TreeProcessorSpec.js b/spec/core/TreeProcessorSpec.js index c702d0cc..d8426144 100644 --- a/spec/core/TreeProcessorSpec.js +++ b/spec/core/TreeProcessorSpec.js @@ -8,9 +8,7 @@ describe("TreeProcessor", function() { this.canBeReentered = function() { return !attrs.noReenter; }; - this.isExecutable = function() { - return attrs.executable !== false; - }; + this.markedPending = attrs.markedPending || false; this.sharedUserContext = function() { return attrs.userContext || {}; }; @@ -23,13 +21,11 @@ describe("TreeProcessor", function() { function Leaf(attrs) { attrs = attrs || {}; this.id = 'leaf' + leafNumber++; - this.isExecutable = function() { - return attrs.executable !== false; - }; + this.markedPending = attrs.markedPending || false; this.execute = jasmine.createSpy(this.id + '#execute'); } - it("processes a single executable leaf", function() { + it("processes a single leaf", function() { var leaf = new Leaf(), processor = new jasmineUnderTest.TreeProcessor({ tree: leaf, runnableIds: [leaf.id] }), result = processor.processTree(); @@ -37,20 +33,22 @@ describe("TreeProcessor", function() { expect(result.valid).toBe(true); expect(result[leaf.id]).toEqual({ - executable: true, + excluded: false, + willExecute: true, segments: jasmine.any(Array) }); }); - it("processes a single non-executable leaf", function() { - var leaf = new Leaf({ executable: false }), + it("processes a single pending leaf", function() { + var leaf = new Leaf({ markedPending: true }), processor = new jasmineUnderTest.TreeProcessor({ tree: leaf, runnableIds: [leaf.id] }), result = processor.processTree(); expect(result.valid).toBe(true); expect(result[leaf.id]).toEqual({ - executable: false, + excluded: false, + willExecute: false, segments: jasmine.any(Array) }); }); @@ -63,7 +61,26 @@ describe("TreeProcessor", function() { expect(result.valid).toBe(true); expect(result[leaf.id]).toEqual({ - executable: false, + excluded: true, + willExecute: false, + segments: jasmine.any(Array) + }); + }); + + it("processes a single excluded leaf", function() { + var leaf = new Leaf(), + processor = new jasmineUnderTest.TreeProcessor({ + tree: leaf, + runnableIds: [leaf.id], + excludeNode: function(node) { return true; } + }), + result = processor.processTree(); + + expect(result.valid).toBe(true); + + expect(result[leaf.id]).toEqual({ + excluded: true, + willExecute: false, segments: jasmine.any(Array) }); }); @@ -77,18 +94,20 @@ describe("TreeProcessor", function() { expect(result.valid).toBe(true); expect(result[parent.id]).toEqual({ - executable: true, + excluded: false, + willExecute: true, segments: jasmine.any(Array) }); expect(result[leaf.id]).toEqual({ - executable: true, + excluded: false, + willExecute: true, segments: jasmine.any(Array) }); }); - it("processes a tree with a single non-executable leaf, with the root specified", function() { - var leaf = new Leaf({ executable: false }), + it("processes a tree with a single pending leaf, with the root specified", function() { + var leaf = new Leaf({ markedPending: true }), parent = new Node({ children: [leaf] }), processor = new jasmineUnderTest.TreeProcessor({ tree: parent, runnableIds: [parent.id] }), result = processor.processTree(); @@ -96,61 +115,77 @@ describe("TreeProcessor", function() { expect(result.valid).toBe(true); expect(result[parent.id]).toEqual({ - executable: false, + excluded: false, + willExecute: false, segments: jasmine.any(Array) }); expect(result[leaf.id]).toEqual({ - executable: false, + excluded: false, + willExecute: false, segments: jasmine.any(Array) }); }); it("processes a complicated tree with the root specified", function() { - var nonExecutable = new Leaf({ executable: false }), - executable = new Leaf({ executable: true }), - parent = new Node({ children: [nonExecutable, executable] }), + var pendingLeaf = new Leaf({ markedPending: true }), + executableLeaf = new Leaf({ markedPending: false }), + parent = new Node({ children: [pendingLeaf, executableLeaf] }), childless = new Node(), - childOfDisabled = new Leaf({ executable: true }), - disabledNode = new Node({ executable: false, children: [childOfDisabled] }), - root = new Node({ children: [parent, childless, disabledNode] }), + childOfPending = new Leaf({ markedPending: true }), + pendingNode = new Node({ markedPending: true, children: [childOfPending] }), + parentOfPendings = new Node({ markedPending: false, children: [childless, pendingNode] }), + root = new Node({ children: [parent, parentOfPendings] }), processor = new jasmineUnderTest.TreeProcessor({ tree: root, runnableIds: [root.id] }), result = processor.processTree(); expect(result.valid).toBe(true); expect(result[root.id]).toEqual({ - executable: true, + excluded: false, + willExecute: true, + segments: jasmine.any(Array) + }); + + expect(result[parentOfPendings.id]).toEqual({ + excluded: false, + willExecute: false, segments: jasmine.any(Array) }); expect(result[childless.id]).toEqual({ - executable: false, + excluded: false, + willExecute: false, segments: jasmine.any(Array) }); - expect(result[nonExecutable.id]).toEqual({ - executable: false, + expect(result[pendingLeaf.id]).toEqual({ + excluded: false, + willExecute: false, segments: jasmine.any(Array) }); - expect(result[executable.id]).toEqual({ - executable: true, + expect(result[executableLeaf.id]).toEqual({ + excluded: false, + willExecute: true, segments: jasmine.any(Array) }); expect(result[parent.id]).toEqual({ - executable: true, + excluded: false, + willExecute: true, segments: jasmine.any(Array) }); - expect(result[disabledNode.id]).toEqual({ - executable: false, + expect(result[pendingNode.id]).toEqual({ + excluded: false, + willExecute: false, segments: jasmine.any(Array) }); - expect(result[childOfDisabled.id]).toEqual({ - executable: false, + expect(result[childOfPending.id]).toEqual({ + excluded: false, + willExecute: false, segments: jasmine.any(Array) }); }); @@ -219,7 +254,7 @@ describe("TreeProcessor", function() { queueRunner.calls.mostRecent().args[0].queueableFns[0].fn('foo'); - expect(leaf.execute).toHaveBeenCalledWith('foo', true); + expect(leaf.execute).toHaveBeenCalledWith('foo', false); }); it("runs a node with no children", function() { @@ -286,22 +321,22 @@ describe("TreeProcessor", function() { expect(queueableFns.length).toBe(2); queueableFns[0].fn('foo'); - expect(leaf1.execute).toHaveBeenCalledWith('foo', true); + expect(leaf1.execute).toHaveBeenCalledWith('foo', false); queueableFns[1].fn('bar'); - expect(leaf2.execute).toHaveBeenCalledWith('bar', true); + expect(leaf2.execute).toHaveBeenCalledWith('bar', false); }); - it("runs a disabled node", function() { + it("runs an excluded node with leaf", function() { var leaf1 = new Leaf(), - node = new Node({ children: [leaf1], executable: false }), + node = new Node({ children: [leaf1] }), root = new Node({ children: [node] }), queueRunner = jasmine.createSpy('queueRunner'), nodeStart = jasmine.createSpy('nodeStart'), nodeComplete = jasmine.createSpy('nodeComplete'), processor = new jasmineUnderTest.TreeProcessor({ tree: root, - runnableIds: [node.id], + runnableIds: [], queueRunnerFactory: queueRunner, nodeStart: nodeStart, nodeComplete: nodeComplete @@ -319,7 +354,7 @@ describe("TreeProcessor", function() { expect(queueableFns.length).toBe(1); queueableFns[0].fn('foo'); - expect(leaf1.execute).toHaveBeenCalledWith('foo', false); + expect(leaf1.execute).toHaveBeenCalledWith('foo', true); node.getResult.and.returnValue({ im: 'disabled' }); @@ -401,13 +436,13 @@ describe("TreeProcessor", function() { expect(queueableFns).toEqual([]); }); - it("does not run beforeAlls or afterAlls for a disabled node", function() { - var leaf = new Leaf(), + it("does not run beforeAlls or afterAlls for a node with only pending children", function() { + var leaf = new Leaf({ markedPending: true }), node = new Node({ children: [leaf], beforeAllFns: ['before'], afterAllFns: ['after'], - executable: false + markedPending: false }), root = new Node({ children: [node] }), queueRunner = jasmine.createSpy('queueRunner'), @@ -452,7 +487,7 @@ describe("TreeProcessor", function() { expect(leaf1.execute).toHaveBeenCalled(); }); - it("runs specified leaves before non-specified leaves", function() { + it("runs specified leaves before non-specified leaves within a parent node", function() { var specified = new Leaf(), nonSpecified = new Leaf(), root = new Node({ children: [nonSpecified, specified] }), @@ -469,11 +504,11 @@ describe("TreeProcessor", function() { queueableFns[0].fn(); expect(nonSpecified.execute).not.toHaveBeenCalled(); - expect(specified.execute).toHaveBeenCalledWith(undefined, true); + expect(specified.execute).toHaveBeenCalledWith(undefined, false); queueableFns[1].fn(); - expect(nonSpecified.execute).toHaveBeenCalledWith(undefined, false); + expect(nonSpecified.execute).toHaveBeenCalledWith(undefined, true); }); it("runs nodes and leaves with a specified order", function() { @@ -692,7 +727,7 @@ describe("TreeProcessor", function() { expect(leaf11.execute).toHaveBeenCalled(); }); - it("runs nodes in a custom order when orderChildren is overrided", function() { + it("runs nodes in a custom order when orderChildren is overridden", function() { var leaf1 = new Leaf(), leaf2 = new Leaf(), leaf3 = new Leaf(), diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 44198f75..6e71d3c7 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -418,7 +418,7 @@ describe("Env integration", function() { env.describe('a suite', function() { env.it('has a spec', function() {}); - + env.afterAll(function() { throw 'nope'; }); @@ -747,6 +747,47 @@ describe("Env integration", function() { env.execute([first_spec.id, second_spec.id]); }); + it("Allows filtering out specs and suites to run programmatically", function(done) { + var env = new jasmineUnderTest.Env(), + calls = [], + suiteCallback = jasmine.createSpy('suite callback'), + firstSpec, + secondSuite; + + var assertions = function() { + expect(calls.length).toEqual(2); + expect(calls).toEqual(jasmine.arrayContaining([ + 'first spec', + 'second spec' + ])); + expect(suiteCallback).toHaveBeenCalled(); + done(); + }; + + env.addReporter({jasmineDone: assertions, suiteDone: suiteCallback}); + + env.describe("first suite", function() { + env.it("first spec", function() { + calls.push('first spec'); + }); + env.it("second spec", function() { + calls.push('second spec'); + }); + }); + + secondSuite = env.describe("second suite", function() { + env.it("third spec", function() { + calls.push('third spec'); + }); + }); + + env.specFilter = function(spec) { + return /^first suite/.test(spec.getFullName()); + }; + + env.execute(); + }); + it("Functions can be spied on and have their calls tracked", function (done) { var env = new jasmineUnderTest.Env(); @@ -1508,7 +1549,7 @@ describe("Env integration", function() { order: jasmine.any(jasmineUnderTest.Order) }); - expect(reporter.specDone).toHaveBeenCalledWith(jasmine.objectContaining({ status: 'disabled' })); + expect(reporter.specDone).toHaveBeenCalledWith(jasmine.objectContaining({ status: 'pending' })); expect(reporter.suiteDone).toHaveBeenCalledWith(jasmine.objectContaining({ description: 'xd out', status: 'pending' })); expect(reporter.suiteDone.calls.count()).toBe(4); @@ -2011,12 +2052,12 @@ describe("Env integration", function() { it('is "passed"', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - + reporter.jasmineDone.and.callFake(function(e) { expect(e.overallStatus).toEqual('passed'); done(); }); - + env.addReporter(reporter); env.it('passes', function() {}); env.execute(); @@ -2027,12 +2068,12 @@ describe("Env integration", function() { it('is "failed"', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - + reporter.jasmineDone.and.callFake(function(e) { expect(e.overallStatus).toEqual('failed'); done(); }); - + env.addReporter(reporter); env.it('fails', function() { env.expect(true).toBe(false); @@ -2045,12 +2086,12 @@ describe("Env integration", function() { it('is "failed"', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - + reporter.jasmineDone.and.callFake(function(e) { expect(e.overallStatus).toEqual('failed'); done(); }); - + env.addReporter(reporter); env.beforeAll(function() { throw new Error('nope'); @@ -2064,12 +2105,12 @@ describe("Env integration", function() { it('is "failed"', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - + reporter.jasmineDone.and.callFake(function(e) { expect(e.overallStatus).toEqual('failed'); done(); }); - + env.addReporter(reporter); env.describe('something', function() { env.beforeAll(function() { @@ -2085,12 +2126,12 @@ describe("Env integration", function() { it('is "failed"', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - + reporter.jasmineDone.and.callFake(function(e) { expect(e.overallStatus).toEqual('failed'); done(); }); - + env.addReporter(reporter); env.afterAll(function() { throw new Error('nope'); @@ -2104,12 +2145,12 @@ describe("Env integration", function() { it('is "failed"', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - + reporter.jasmineDone.and.callFake(function(e) { expect(e.overallStatus).toEqual('failed'); done(); }); - + env.addReporter(reporter); env.describe('something', function() { env.afterAll(function() { @@ -2148,13 +2189,13 @@ describe("Env integration", function() { it('is "incomplete"', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - + reporter.jasmineDone.and.callFake(function(e) { expect(e.overallStatus).toEqual('incomplete'); expect(e.incompleteReason).toEqual('No specs found'); done(); }); - + env.addReporter(reporter); env.execute(); }); @@ -2164,13 +2205,13 @@ describe("Env integration", function() { it('is "incomplete"', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - + reporter.jasmineDone.and.callFake(function(e) { expect(e.overallStatus).toEqual('incomplete'); expect(e.incompleteReason).toEqual('fit() or fdescribe() was found'); done(); }); - + env.addReporter(reporter); env.fit('is focused', function() {}); env.execute(); @@ -2181,13 +2222,13 @@ describe("Env integration", function() { it('is "incomplete"', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - + reporter.jasmineDone.and.callFake(function(e) { expect(e.overallStatus).toEqual('incomplete'); expect(e.incompleteReason).toEqual('fit() or fdescribe() was found'); done(); }); - + env.addReporter(reporter); env.fdescribe('something focused', function() { env.it('does a thing', function() {}); @@ -2200,13 +2241,13 @@ describe("Env integration", function() { it('is "failed"', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('reporter', ['jasmineDone', 'suiteDone', 'specDone']); - + reporter.jasmineDone.and.callFake(function(e) { expect(e.overallStatus).toEqual('failed'); expect(e.incompleteReason).toBeUndefined(); done(); }); - + env.addReporter(reporter); env.fit('is focused', function() { env.expect(true).toBe(false); diff --git a/src/core/Env.js b/src/core/Env.js index 28ab5edd..a3ce0dd3 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -286,6 +286,7 @@ getJasmineRequireObj().Env = function(j$) { }; this.execute = function(runnablesToRun) { + var self = this; this.suppressLoadErrors(); if(!runnablesToRun) { @@ -315,9 +316,7 @@ getJasmineRequireObj().Env = function(j$) { throw new Error('Tried to complete the wrong suite'); } - if (!suite.markedPending) { - clearResourcesForRunnable(suite.id); - } + clearResourcesForRunnable(suite.id); currentlyExecutingSuites.pop(); reporter.suiteDone(result); @@ -327,6 +326,9 @@ getJasmineRequireObj().Env = function(j$) { }, orderChildren: function(node) { return order.sort(node.children); + }, + excludeNode: function(spec) { + return !self.specFilter(spec); } }); @@ -577,10 +579,6 @@ getJasmineRequireObj().Env = function(j$) { throwOnExpectationFailure: throwOnExpectationFailure }); - if (!self.specFilter(spec)) { - spec.disable(); - } - return spec; function specResultCallback(result) { diff --git a/src/core/Spec.js b/src/core/Spec.js index 7f2c1513..7f918067 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -55,7 +55,7 @@ getJasmineRequireObj().Spec = function(j$) { return this.expectationFactory(actual, this); }; - Spec.prototype.execute = function(onComplete, enabled) { + Spec.prototype.execute = function(onComplete, excluded) { var self = this; this.onStart(this); @@ -72,17 +72,16 @@ getJasmineRequireObj().Spec = function(j$) { userContext: this.userContext() }; - if (!this.isExecutable() || this.markedPending || enabled === false) { + if (this.markedPending || excluded === true) { runnerConfig.queueableFns = []; runnerConfig.cleanupFns = []; - runnerConfig.onComplete = function() { complete(enabled); }; } this.queueRunnerFactory(runnerConfig); - function complete(enabledAgain) { + function complete() { self.queueableFn.fn = null; - self.result.status = self.status(enabledAgain); + self.result.status = self.status(excluded); self.resultCallback(self.result); if (onComplete) { @@ -110,10 +109,6 @@ getJasmineRequireObj().Spec = function(j$) { }, true); }; - Spec.prototype.disable = function() { - this.disabled = true; - }; - Spec.prototype.pend = function(message) { this.markedPending = true; if (message) { @@ -126,9 +121,9 @@ getJasmineRequireObj().Spec = function(j$) { return this.result; }; - Spec.prototype.status = function(enabled) { - if (this.disabled || enabled === false) { - return 'disabled'; + Spec.prototype.status = function(excluded) { + if (excluded === true) { + return 'excluded'; } if (this.markedPending) { @@ -142,10 +137,6 @@ getJasmineRequireObj().Spec = function(j$) { } }; - Spec.prototype.isExecutable = function() { - return !this.disabled; - }; - Spec.prototype.getFullName = function() { return this.getSpecName(this); }; diff --git a/src/core/Suite.js b/src/core/Suite.js index 9e2e0bb1..ca7906a8 100644 --- a/src/core/Suite.js +++ b/src/core/Suite.js @@ -90,14 +90,10 @@ getJasmineRequireObj().Suite = function(j$) { if (this.result.failedExpectations.length > 0) { return 'failed'; } else { - return 'finished'; + return 'passed'; } }; - Suite.prototype.isExecutable = function() { - return !this.markedPending; - }; - Suite.prototype.canBeReentered = function() { return this.beforeAllFns.length === 0 && this.afterAllFns.length === 0; }; diff --git a/src/core/TreeProcessor.js b/src/core/TreeProcessor.js index 137f23b4..f633094c 100644 --- a/src/core/TreeProcessor.js +++ b/src/core/TreeProcessor.js @@ -6,13 +6,14 @@ getJasmineRequireObj().TreeProcessor = function() { nodeStart = attrs.nodeStart || function() {}, nodeComplete = attrs.nodeComplete || function() {}, orderChildren = attrs.orderChildren || function(node) { return node.children; }, + excludeNode = attrs.excludeNode || function(node) { return false; }, stats = { valid: true }, processed = false, defaultMin = Infinity, defaultMax = 1 - Infinity; this.processTree = function() { - processNode(tree, false); + processNode(tree, true); processed = true; return stats; }; @@ -46,18 +47,18 @@ getJasmineRequireObj().TreeProcessor = function() { } } - function processNode(node, parentEnabled) { + function processNode(node, parentExcluded) { var executableIndex = runnableIndex(node.id); if (executableIndex !== undefined) { - parentEnabled = true; + parentExcluded = false; } - parentEnabled = parentEnabled && node.isExecutable(); - if (!node.children) { + var excluded = parentExcluded || excludeNode(node); stats[node.id] = { - executable: parentEnabled && node.isExecutable(), + excluded: excluded, + willExecute: !excluded && !node.markedPending, segments: [{ index: 0, owner: node, @@ -74,7 +75,7 @@ getJasmineRequireObj().TreeProcessor = function() { for (var i = 0; i < orderedChildren.length; i++) { var child = orderedChildren[i]; - processNode(child, parentEnabled); + processNode(child, parentExcluded); if (!stats.valid) { return; @@ -82,11 +83,12 @@ getJasmineRequireObj().TreeProcessor = function() { var childStats = stats[child.id]; - hasExecutableChild = hasExecutableChild || childStats.executable; + hasExecutableChild = hasExecutableChild || childStats.willExecute; } stats[node.id] = { - executable: hasExecutableChild + excluded: parentExcluded, + willExecute: hasExecutableChild }; segmentChildren(node, orderedChildren, stats[node.id], executableIndex); @@ -182,7 +184,7 @@ getJasmineRequireObj().TreeProcessor = function() { }; } else { return { - fn: function(done) { node.execute(done, stats[node.id].executable); } + fn: function(done) { node.execute(done, stats[node.id].excluded); } }; } } @@ -195,7 +197,7 @@ getJasmineRequireObj().TreeProcessor = function() { result.push(executeNode(segmentChildren[i].owner, segmentChildren[i].index)); } - if (!stats[node.id].executable) { + if (!stats[node.id].willExecute) { return result; } From 3df9cc26d4b6ab525e6905bca063d0d2e7339f08 Mon Sep 17 00:00:00 2001 From: Elenore Bastian Date: Fri, 26 Jan 2018 15:27:35 -0800 Subject: [PATCH 52/62] Update css for new reporter status Signed-off-by: Gregg Van Hove --- lib/jasmine-core/jasmine.css | 6 +++--- src/html/_HTMLReporter.scss | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/jasmine-core/jasmine.css b/lib/jasmine-core/jasmine.css index a10d0838..941ecc50 100644 --- a/lib/jasmine-core/jasmine.css +++ b/lib/jasmine-core/jasmine.css @@ -18,8 +18,8 @@ body { overflow-y: scroll; } .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-passed:before { color: #007069; content: "\02022"; } .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed { line-height: 9px; } .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-failed:before { color: #ca3a11; content: "\d7"; font-weight: bold; margin-left: -1px; } -.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-disabled { font-size: 14px; } -.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-disabled:before { color: #bababa; content: "\02022"; } +.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-excluded { font-size: 14px; } +.jasmine_html-reporter .jasmine-symbol-summary li.jasmine-excluded:before { color: #bababa; content: "\02022"; } .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending { line-height: 17px; } .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-pending:before { color: #ba9d37; content: "*"; } .jasmine_html-reporter .jasmine-symbol-summary li.jasmine-empty { font-size: 14px; } @@ -46,7 +46,7 @@ body { overflow-y: scroll; } .jasmine_html-reporter .jasmine-summary li.jasmine-failed a { color: #ca3a11; } .jasmine_html-reporter .jasmine-summary li.jasmine-empty a { color: #ba9d37; } .jasmine_html-reporter .jasmine-summary li.jasmine-pending a { color: #ba9d37; } -.jasmine_html-reporter .jasmine-summary li.jasmine-disabled a { color: #bababa; } +.jasmine_html-reporter .jasmine-summary li.jasmine-excluded a { color: #bababa; } .jasmine_html-reporter .jasmine-description + .jasmine-suite { margin-top: 0; } .jasmine_html-reporter .jasmine-suite { margin-top: 14px; } .jasmine_html-reporter .jasmine-suite a { color: #333; } diff --git a/src/html/_HTMLReporter.scss b/src/html/_HTMLReporter.scss index f7eace89..f74ed7ff 100644 --- a/src/html/_HTMLReporter.scss +++ b/src/html/_HTMLReporter.scss @@ -135,7 +135,7 @@ body { } } - &.jasmine-disabled { + &.jasmine-excluded { font-size: 14px; &:before { @@ -287,7 +287,7 @@ body { color: $pending-color; } - &.jasmine-disabled a { + &.jasmine-excluded a { color: $neutral-color; } } From 341c6df6ea66d9577e9386585cec01937e2bb929 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Mon, 29 Jan 2018 10:00:15 -0800 Subject: [PATCH 53/62] Fix HTML reporter display for excluded specs --- lib/jasmine-core/jasmine-html.js | 4 ++-- spec/html/HtmlReporterSpec.js | 20 ++++++++++---------- src/html/HtmlReporter.js | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 3c54d040..4585a6f9 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -65,7 +65,7 @@ jasmineRequire.HtmlReporter = function(j$) { ResultsStateBuilder.prototype.specDone = function(result) { this.currentParent.addChild(result, 'spec'); - if (result.status !== 'disabled') { + if (result.status !== 'excluded') { this.specsExecuted++; } @@ -469,7 +469,7 @@ jasmineRequire.HtmlReporter = function(j$) { } function hasActiveSpec(resultNode) { - if (resultNode.type == 'spec' && resultNode.result.status != 'disabled') { + if (resultNode.type == 'spec' && resultNode.result.status != 'excluded') { return true; } diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 45e1667a..f35985d7 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -92,7 +92,7 @@ describe("HtmlReporter", function() { expect(specEl.getAttribute("class")).toEqual("jasmine-empty"); }); - it("reports the status symbol of a disabled spec", function() { + it("reports the status symbol of a excluded spec", function() { var env = new jasmineUnderTest.Env(), container = document.createElement("div"), getContainer = function() { return container; }, @@ -104,10 +104,10 @@ describe("HtmlReporter", function() { }); reporter.initialize(); - reporter.specDone({id: 789, status: "disabled", fullName: "symbols should have titles", passedExpectations: [], failedExpectations: []}); + reporter.specDone({id: 789, status: "excluded", fullName: "symbols should have titles", passedExpectations: [], failedExpectations: []}); var specEl = container.querySelector('.jasmine-symbol-summary li'); - expect(specEl.getAttribute("class")).toEqual("jasmine-disabled"); + expect(specEl.getAttribute("class")).toEqual("jasmine-excluded"); expect(specEl.getAttribute("id")).toEqual("spec_789"); expect(specEl.getAttribute("title")).toEqual("symbols should have titles"); }); @@ -802,7 +802,7 @@ describe("HtmlReporter", function() { }); }); - describe("and there are disabled specs", function() { + describe("and there are excluded specs", function() { var env, container, reporter, reporterConfig, specStatus; beforeEach(function() { env = new jasmineUnderTest.Env(); @@ -815,9 +815,9 @@ describe("HtmlReporter", function() { }; specStatus = { id: 123, - description: "with a disabled spec", - fullName: "A Suite with a disabled spec", - status: "disabled", + description: "with a excluded spec", + fullName: "A Suite with a excluded spec", + status: "excluded", passedExpectations: [], failedExpectations: [] }; @@ -834,10 +834,10 @@ describe("HtmlReporter", function() { reporter.jasmineDone({}); }); - it("shows the disabled spec in the spec list", function() { + it("shows the excluded spec in the spec list", function() { var specList = container.querySelector(".jasmine-summary"); - expect(specList.innerHTML).toContain('with a disabled spec'); + expect(specList.innerHTML).toContain('with a excluded spec'); }); }); @@ -852,7 +852,7 @@ describe("HtmlReporter", function() { reporter.jasmineDone({}); }); - it("doesn't show the disabled spec in the spec list", function() { + it("doesn't show the excluded spec in the spec list", function() { var specList = container.querySelector(".jasmine-summary"); expect(specList.innerHTML).toEqual(''); diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index 1dfde539..5ca871b2 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -35,7 +35,7 @@ jasmineRequire.HtmlReporter = function(j$) { ResultsStateBuilder.prototype.specDone = function(result) { this.currentParent.addChild(result, 'spec'); - if (result.status !== 'disabled') { + if (result.status !== 'excluded') { this.specsExecuted++; } @@ -439,7 +439,7 @@ jasmineRequire.HtmlReporter = function(j$) { } function hasActiveSpec(resultNode) { - if (resultNode.type == 'spec' && resultNode.result.status != 'disabled') { + if (resultNode.type == 'spec' && resultNode.result.status != 'excluded') { return true; } From 50cd6fdd6812c96c2c646f2c5e51d9dacc5bfe06 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Fri, 26 Jan 2018 18:00:46 -0800 Subject: [PATCH 54/62] Allow reporter callbacks to be asynchronous [finishes #154673961] - Fixes #842 Signed-off-by: Elenore Bastian --- spec/core/ExceptionsSpec.js | 76 +++++------- spec/core/ReportDispatcherSpec.js | 95 +++++++++++---- spec/core/SpecSpec.js | 68 +++++++---- spec/core/TreeProcessorSpec.js | 72 +++++------ spec/core/integration/EnvSpec.js | 106 ++++++++++++---- src/core/Env.js | 193 +++++++++++++++--------------- src/core/QueueRunner.js | 12 +- src/core/ReportDispatcher.js | 36 +++++- src/core/Spec.js | 33 +++-- src/core/TreeProcessor.js | 15 ++- 10 files changed, 434 insertions(+), 272 deletions(-) diff --git a/spec/core/ExceptionsSpec.js b/spec/core/ExceptionsSpec.js index ba770457..e0a141a6 100644 --- a/spec/core/ExceptionsSpec.js +++ b/spec/core/ExceptionsSpec.js @@ -5,64 +5,42 @@ describe('Exceptions:', function() { env = new jasmineUnderTest.Env(); }); - describe('with break on exception', function() { - it('should not catch the exception', function() { - env.catchExceptions(false); - env.describe('suite for break on exceptions', function() { - env.it('should break when an exception is thrown', function() { - throw new Error('I should hit a breakpoint!'); - }); + it('should handle exceptions thrown, but continue', function(done) { + var secondTest = jasmine.createSpy('second test'); + env.describe('Suite for handles exceptions', function () { + env.it('should be a test that fails because it throws an exception', function() { + throw new Error(); }); - var spy = jasmine.createSpy('spy'); - - try { - env.execute(); - spy(); - } - catch (e) {} - - expect(spy).not.toHaveBeenCalled(); + env.it('should be a passing test that runs after exceptions are thrown from a async test', secondTest); }); + + var expectations = function() { + expect(secondTest).toHaveBeenCalled(); + done(); + }; + + env.addReporter({ jasmineDone: expectations }); + env.execute(); }); - describe("with catch on exception", function() { - it('should handle exceptions thrown, but continue', function(done) { - var secondTest = jasmine.createSpy('second test'); - env.describe('Suite for handles exceptions', function () { - env.it('should be a test that fails because it throws an exception', function() { - throw new Error(); - }); - env.it('should be a passing test that runs after exceptions are thrown from a async test', secondTest); + it("should handle exceptions thrown directly in top-level describe blocks and continue", function(done) { + var secondDescribe = jasmine.createSpy("second describe"); + env.describe("a suite that throws an exception", function () { + env.it("is a test that should pass", function () { + this.expect(true).toEqual(true); }); - var expectations = function() { - expect(secondTest).toHaveBeenCalled(); - done(); - }; - - env.addReporter({ jasmineDone: expectations }); - env.execute(); + throw new Error("top level error"); }); + env.describe("a suite that doesn't throw an exception", secondDescribe); - it("should handle exceptions thrown directly in top-level describe blocks and continue", function(done) { - var secondDescribe = jasmine.createSpy("second describe"); - env.describe("a suite that throws an exception", function () { - env.it("is a test that should pass", function () { - this.expect(true).toEqual(true); - }); + var expectations = function() { + expect(secondDescribe).toHaveBeenCalled(); + done(); + }; - throw new Error("top level error"); - }); - env.describe("a suite that doesn't throw an exception", secondDescribe); - - var expectations = function() { - expect(secondDescribe).toHaveBeenCalled(); - done(); - }; - - env.addReporter({ jasmineDone: expectations }); - env.execute(); - }); + env.addReporter({ jasmineDone: expectations }); + env.execute(); }); }); diff --git a/spec/core/ReportDispatcherSpec.js b/spec/core/ReportDispatcherSpec.js index cb7a540a..43db5ffa 100644 --- a/spec/core/ReportDispatcherSpec.js +++ b/spec/core/ReportDispatcherSpec.js @@ -9,70 +9,125 @@ describe("ReportDispatcher", function() { }); it("dispatches requested methods to added reporters", function() { - var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar']), + var queueRunnerFactory = jasmine.createSpy('queueRunner'), + dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar'], queueRunnerFactory), reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']), - anotherReporter = jasmine.createSpyObj('reporter', ['foo', 'bar']); + anotherReporter = jasmine.createSpyObj('reporter', ['foo', 'bar']), + completeCallback = jasmine.createSpy('complete'); dispatcher.addReporter(reporter); dispatcher.addReporter(anotherReporter); - dispatcher.foo(123, 456); + dispatcher.foo(123, 456, completeCallback); + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [{fn: jasmine.any(Function)}, {fn: jasmine.any(Function)}] + })); + + var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; + fns[0].fn(); expect(reporter.foo).toHaveBeenCalledWith(123, 456); + expect(reporter.foo.calls.mostRecent().object).toBe(reporter); + + fns[1].fn(); expect(anotherReporter.foo).toHaveBeenCalledWith(123, 456); + expect(anotherReporter.foo.calls.mostRecent().object).toBe(anotherReporter); - dispatcher.bar('a', 'b'); + queueRunnerFactory.calls.reset(); + dispatcher.bar('a', 'b', completeCallback); + + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [{fn: jasmine.any(Function)}, {fn: jasmine.any(Function)}] + })); + + fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; + fns[0].fn(); expect(reporter.bar).toHaveBeenCalledWith('a', 'b'); + + fns[1].fn(); expect(anotherReporter.bar).toHaveBeenCalledWith('a', 'b'); }); it("does not dispatch to a reporter if the reporter doesn't accept the method", function() { - var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo']), + var queueRunnerFactory = jasmine.createSpy('queueRunner'), + dispatcher = new jasmineUnderTest.ReportDispatcher(['foo'], queueRunnerFactory), reporter = jasmine.createSpyObj('reporter', ['baz']); dispatcher.addReporter(reporter); - expect(function() { - dispatcher.foo(123, 456); - }).not.toThrow(); + dispatcher.foo(123, 456); + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [] + })); }); - it("allows providing a fallback reporter in case there's no other report", function() { - var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar']), - reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']); + it("allows providing a fallback reporter in case there's no other reporter", function() { + var queueRunnerFactory = jasmine.createSpy('queueRunner'), + dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar'], queueRunnerFactory), + reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']), + completeCallback = jasmine.createSpy('complete'); dispatcher.provideFallbackReporter(reporter); - dispatcher.foo(123, 456); + dispatcher.foo(123, 456, completeCallback); + + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [{fn: jasmine.any(Function)}] + })); + + var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; + fns[0].fn(); expect(reporter.foo).toHaveBeenCalledWith(123, 456); }); - it("does not call fallback reporting methods when another report is provided", function() { - var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar']), + it("does not call fallback reporting methods when another reporter is provided", function() { + var queueRunnerFactory = jasmine.createSpy('queueRunner'), + dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar'], queueRunnerFactory), reporter = jasmine.createSpyObj('reporter', ['foo', 'bar']), - fallbackReporter = jasmine.createSpyObj('otherReporter', ['foo', 'bar']); + fallbackReporter = jasmine.createSpyObj('otherReporter', ['foo', 'bar']), + completeCallback = jasmine.createSpy('complete'); dispatcher.provideFallbackReporter(fallbackReporter); dispatcher.addReporter(reporter); - dispatcher.foo(123, 456); + dispatcher.foo(123, 456, completeCallback); + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [{fn: jasmine.any(Function)}] + })); + + var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; + fns[0].fn(); expect(reporter.foo).toHaveBeenCalledWith(123, 456); expect(fallbackReporter.foo).not.toHaveBeenCalledWith(123, 456); }); it("allows registered reporters to be cleared", function() { - var dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar']), + var queueRunnerFactory = jasmine.createSpy('queueRunner'), + dispatcher = new jasmineUnderTest.ReportDispatcher(['foo', 'bar'], queueRunnerFactory), reporter1 = jasmine.createSpyObj('reporter1', ['foo', 'bar']), - reporter2 = jasmine.createSpyObj('reporter2', ['foo', 'bar']); + reporter2 = jasmine.createSpyObj('reporter2', ['foo', 'bar']), + completeCallback = jasmine.createSpy('complete'); dispatcher.addReporter(reporter1); - dispatcher.foo(123); + dispatcher.foo(123, completeCallback); + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [{fn: jasmine.any(Function)}] + })); + + var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; + fns[0].fn(); expect(reporter1.foo).toHaveBeenCalledWith(123); dispatcher.clearReporters(); dispatcher.addReporter(reporter2); - dispatcher.bar(456); + dispatcher.bar(456, completeCallback); + expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ + queueableFns: [{fn: jasmine.any(Function)}] + })); + + fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; + fns[0].fn(); expect(reporter1.bar).not.toHaveBeenCalled(); expect(reporter2.bar).toHaveBeenCalledWith(456); }); diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index 9a32101e..d0b8d169 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -55,6 +55,7 @@ describe("Spec", function() { spec.execute(); + fakeQueueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); // TODO: due to some issue with the Pretty Printer, this line fails, but the other two pass. // This means toHaveBeenCalledWith on IE8 will always be broken. @@ -82,6 +83,7 @@ describe("Spec", function() { spec.execute(); + fakeQueueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); expect(startCallback).toHaveBeenCalled(); }); @@ -104,8 +106,8 @@ describe("Spec", function() { spec.execute(); var options = fakeQueueRunner.calls.mostRecent().args[0]; - expect(options.queueableFns).toEqual([before, queueableFn]); - expect(options.cleanupFns).toEqual([after]); + expect(options.queueableFns).toEqual([{fn: jasmine.any(Function)}, before, queueableFn]); + expect(options.cleanupFns).toEqual([after, {fn: jasmine.any(Function)}]); }); it("tells the queue runner that it's a leaf node", function() { @@ -141,8 +143,7 @@ describe("Spec", function() { }); it("can be excluded at execution time by a parent", function() { - var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner') - .and.callFake(function(attrs) { attrs.onComplete(); }), + var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'), startCallback = jasmine.createSpy('startCallback'), specBody = jasmine.createSpy('specBody'), resultCallback = jasmine.createSpy('resultCallback'), @@ -153,20 +154,26 @@ describe("Spec", function() { queueRunnerFactory: fakeQueueRunner }); - spec.execute(undefined, true); + spec.execute('cally-back', true); - expect(spec.result.status).toBe('excluded'); - - expect(fakeQueueRunner).toHaveBeenCalled(); + expect(fakeQueueRunner).toHaveBeenCalledWith(jasmine.objectContaining({ + onComplete: 'cally-back', + queueableFns: [{fn: jasmine.any(Function)}], + cleanupFns: [{fn: jasmine.any(Function)}] + })); expect(specBody).not.toHaveBeenCalled(); + var args = fakeQueueRunner.calls.mostRecent().args[0]; + args.queueableFns[0].fn(); expect(startCallback).toHaveBeenCalled(); + args.cleanupFns[0].fn(); expect(resultCallback).toHaveBeenCalled(); + + expect(spec.result.status).toBe('excluded'); }); it("can be marked pending, but still calls callbacks when executed", function() { - var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner') - .and.callFake(function(attrs) { attrs.onComplete(); }), + var fakeQueueRunner = jasmine.createSpy('fakeQueueRunner'), startCallback = jasmine.createSpy('startCallback'), resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ @@ -188,7 +195,10 @@ describe("Spec", function() { expect(fakeQueueRunner).toHaveBeenCalled(); + var args = fakeQueueRunner.calls.mostRecent().args[0]; + args.queueableFns[0].fn(); expect(startCallback).toHaveBeenCalled(); + args.cleanupFns[0].fn('things'); expect(resultCallback).toHaveBeenCalledWith({ id: spec.id, status: 'pending', @@ -197,7 +207,7 @@ describe("Spec", function() { failedExpectations: [], passedExpectations: [], pendingReason: '' - }); + }, 'things'); }); it("should call the done callback on execution complete", function() { @@ -233,11 +243,12 @@ describe("Spec", function() { }); it("keeps track of passed and failed expectations", function() { - var resultCallback = jasmine.createSpy('resultCallback'), + var fakeQueueRunner = jasmine.createSpy('queueRunner'), + resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ queueableFn: { fn: jasmine.createSpy("spec body") }, expectationResultFactory: function (data) { return data; }, - queueRunnerFactory: function(attrs) { attrs.onComplete(); }, + queueRunnerFactory: fakeQueueRunner, resultCallback: resultCallback }); spec.addExpectationResult(true, 'expectation1'); @@ -245,19 +256,21 @@ describe("Spec", function() { spec.execute(); + fakeQueueRunner.calls.mostRecent().args[0].cleanupFns[0].fn(); expect(resultCallback.calls.first().args[0].passedExpectations).toEqual(['expectation1']); 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'), + var fakeQueueRunner = jasmine.createSpy('queueRunner'), + resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ - queueableFn: { fn: function() {} }, - expectationResultFactory: function(data) { return data; }, - queueRunnerFactory: function(attrs) { attrs.onComplete(); }, - resultCallback: resultCallback, - throwOnExpectationFailure: true - }); + queueableFn: { fn: function() {} }, + expectationResultFactory: function(data) { return data; }, + queueRunnerFactory: fakeQueueRunner, + resultCallback: resultCallback, + throwOnExpectationFailure: true + }); spec.addExpectationResult(true, 'passed'); expect(function() { @@ -266,6 +279,7 @@ describe("Spec", function() { spec.execute(); + fakeQueueRunner.calls.mostRecent().args[0].cleanupFns[0].fn(); expect(resultCallback.calls.first().args[0].passedExpectations).toEqual(['passed']); expect(resultCallback.calls.first().args[0].failedExpectations).toEqual(['failed']); }); @@ -332,17 +346,20 @@ describe("Spec", function() { }); it("should log a failure when handling an exception", function() { - var resultCallback = jasmine.createSpy('resultCallback'), + var fakeQueueRunner = jasmine.createSpy('queueRunner'), + resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ queueableFn: { fn: function() {} }, expectationResultFactory: function(data) { return data; }, - queueRunnerFactory: function(attrs) { attrs.onComplete(); }, + queueRunnerFactory: fakeQueueRunner, resultCallback: resultCallback }); spec.onException('foo'); spec.execute(); + var args = fakeQueueRunner.calls.mostRecent().args[0]; + args.cleanupFns[0].fn(); expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([{ error: 'foo', matcherName: '', @@ -353,17 +370,20 @@ describe("Spec", function() { }); it("should not log an additional failure when handling an ExpectationFailed error", function() { - var resultCallback = jasmine.createSpy('resultCallback'), + var fakeQueueRunner = jasmine.createSpy('queueRunner'), + resultCallback = jasmine.createSpy('resultCallback'), spec = new jasmineUnderTest.Spec({ queueableFn: { fn: function() {} }, expectationResultFactory: function(data) { return data; }, - queueRunnerFactory: function(attrs) { attrs.onComplete(); }, + queueRunnerFactory: fakeQueueRunner, resultCallback: resultCallback }); spec.onException(new jasmineUnderTest.errors.ExpectationFailed()); spec.execute(); + var args = fakeQueueRunner.calls.mostRecent().args[0]; + args.cleanupFns[0].fn(); expect(resultCallback.calls.first().args[0].failedExpectations).toEqual([]); }); }); diff --git a/spec/core/TreeProcessorSpec.js b/spec/core/TreeProcessorSpec.js index d8426144..6ab3a15e 100644 --- a/spec/core/TreeProcessorSpec.js +++ b/spec/core/TreeProcessorSpec.js @@ -284,19 +284,20 @@ describe("TreeProcessor", function() { queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(nodeDone); - expect(nodeStart).toHaveBeenCalledWith(node); expect(queueRunner).toHaveBeenCalledWith({ onComplete: jasmine.any(Function), - queueableFns: [], + queueableFns: [{ fn: jasmine.any(Function) }], userContext: { node: 'context' }, onException: jasmine.any(Function) }); + queueRunner.calls.mostRecent().args[0].queueableFns[0].fn('foo'); + expect(nodeStart).toHaveBeenCalledWith(node, 'foo'); + node.getResult.and.returnValue({ my: 'result' }); queueRunner.calls.mostRecent().args[0].onComplete(); - expect(nodeComplete).toHaveBeenCalledWith(node, { my: 'result' }); - expect(nodeDone).toHaveBeenCalled(); + expect(nodeComplete).toHaveBeenCalledWith(node, { my: 'result' }, nodeDone); }); it("runs a node with children", function() { @@ -318,12 +319,12 @@ describe("TreeProcessor", function() { queueableFns[0].fn(nodeDone); queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(queueableFns.length).toBe(2); + expect(queueableFns.length).toBe(3); - queueableFns[0].fn('foo'); + queueableFns[1].fn('foo'); expect(leaf1.execute).toHaveBeenCalledWith('foo', false); - queueableFns[1].fn('bar'); + queueableFns[2].fn('bar'); expect(leaf2.execute).toHaveBeenCalledWith('bar', false); }); @@ -348,18 +349,19 @@ describe("TreeProcessor", function() { var queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; queueableFns[0].fn(nodeDone); - expect(nodeStart).toHaveBeenCalledWith(node); - queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(queueableFns.length).toBe(1); + expect(queueableFns.length).toBe(2); - queueableFns[0].fn('foo'); + queueableFns[0].fn('bar'); + expect(nodeStart).toHaveBeenCalledWith(node, 'bar'); + + queueableFns[1].fn('foo'); expect(leaf1.execute).toHaveBeenCalledWith('foo', true); node.getResult.and.returnValue({ im: 'disabled' }); queueRunner.calls.mostRecent().args[0].onComplete(); - expect(nodeComplete).toHaveBeenCalledWith(node, { im: 'disabled' }); + expect(nodeComplete).toHaveBeenCalledWith(node, { im: 'disabled' }, nodeDone); }); it("runs beforeAlls for a node with children", function() { @@ -384,7 +386,7 @@ describe("TreeProcessor", function() { queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(queueableFns).toEqual(['beforeAll1', 'beforeAll2', { fn: jasmine.any(Function) }]); + expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, 'beforeAll1', 'beforeAll2', { fn: jasmine.any(Function) }]); }); it("runs afterAlls for a node with children", function() { @@ -409,7 +411,7 @@ describe("TreeProcessor", function() { queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, 'afterAll1', 'afterAll2']); + expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, { fn: jasmine.any(Function) }, 'afterAll1', 'afterAll2']); }); it("does not run beforeAlls or afterAlls for a node with no children", function() { @@ -433,7 +435,7 @@ describe("TreeProcessor", function() { queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(queueableFns).toEqual([]); + expect(queueableFns).toEqual([{fn: jasmine.any(Function)}]); }); it("does not run beforeAlls or afterAlls for a node with only pending children", function() { @@ -460,7 +462,7 @@ describe("TreeProcessor", function() { queueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }]); + expect(queueableFns).toEqual([{ fn: jasmine.any(Function) }, { fn: jasmine.any(Function) }]); }); it("runs leaves in the order specified", function() { @@ -529,7 +531,7 @@ describe("TreeProcessor", function() { expect(specifiedLeaf.execute).not.toHaveBeenCalled(); var nodeQueueableFns = queueRunner.calls.mostRecent().args[0].queueableFns; - nodeQueueableFns[0].fn(); + nodeQueueableFns[1].fn(); expect(childLeaf.execute).toHaveBeenCalled(); @@ -558,8 +560,8 @@ describe("TreeProcessor", function() { expect(queueableFns.length).toBe(5); queueableFns[0].fn(); - expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(leaf1.execute).toHaveBeenCalled(); queueableFns[1].fn(); @@ -567,8 +569,8 @@ describe("TreeProcessor", function() { queueableFns[2].fn(); expect(queueRunner.calls.count()).toBe(3); - expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(leaf2.execute).toHaveBeenCalled(); queueableFns[3].fn(); @@ -576,8 +578,8 @@ describe("TreeProcessor", function() { queueableFns[4].fn(); expect(queueRunner.calls.count()).toBe(4); - expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(leaf3.execute).toHaveBeenCalled(); }); @@ -603,12 +605,12 @@ describe("TreeProcessor", function() { queueableFns[0].fn(); expect(queueRunner.calls.count()).toBe(2); - expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(queueRunner.calls.count()).toBe(3); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(leaf1.execute).toHaveBeenCalled(); queueableFns[1].fn(); @@ -616,12 +618,12 @@ describe("TreeProcessor", function() { queueableFns[2].fn(); expect(queueRunner.calls.count()).toBe(4); - expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(queueRunner.calls.count()).toBe(5); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(leaf2.execute).toHaveBeenCalled(); queueableFns[3].fn(); @@ -629,12 +631,12 @@ describe("TreeProcessor", function() { queueableFns[4].fn(); expect(queueRunner.calls.count()).toBe(6); - expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(1); + expect(queueRunner.calls.mostRecent().args[0].queueableFns.length).toBe(2); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(queueRunner.calls.count()).toBe(7); - queueRunner.calls.mostRecent().args[0].queueableFns[0].fn(); + queueRunner.calls.mostRecent().args[0].queueableFns[1].fn(); expect(leaf3.execute).toHaveBeenCalled(); }); @@ -661,11 +663,11 @@ describe("TreeProcessor", function() { queueableFns[1].fn(); var childFns = queueRunner.calls.mostRecent().args[0].queueableFns; - expect(childFns.length).toBe(2); - childFns[0].fn(); + expect(childFns.length).toBe(3); + childFns[1].fn(); expect(leaf2.execute).toHaveBeenCalled(); - childFns[1].fn(); + childFns[2].fn(); expect(leaf3.execute).toHaveBeenCalled(); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 6e71d3c7..ac3852a7 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -412,7 +412,7 @@ describe("Env integration", function() { suiteDone: jasmine.createSpy('suiteDone').and.callFake(function(result) { expect(result.failedExpectations[0].globalErrorType).toBeFalsy(); }) - } + }; env.addReporter(reporter); @@ -480,9 +480,11 @@ describe("Env integration", function() { env.it('fails', function(specDone) { setTimeout(function() { specDone(); + setTimeout(function() { setTimeout(function() { global.onerror('fail'); }); + }); }); }); }); @@ -1002,28 +1004,35 @@ describe("Env integration", function() { }); describe("with a mock clock", function() { + var realSetTimeout; beforeEach(function() { this.originalTimeout = jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL; - this.realSetTimeout = setTimeout; + realSetTimeout = setTimeout; jasmine.clock().install(); }); afterEach(function() { + jasmine.clock().tick(1); + jasmine.clock().tick(1); jasmine.clock().uninstall(); jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = this.originalTimeout; }); - it("should wait a specified interval before failing specs haven't called done yet", function(done) { + it("should wait a default interval before failing specs that haven't called done yet", function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]); - reporter.specDone.and.callFake(function() { - expect(reporter.specDone).toHaveBeenCalledWith(jasmine.objectContaining({status: 'failed'})); + reporter.specDone.and.callFake(function(result) { + expect(result).toEqual(jasmine.objectContaining({status: 'failed'})); + realSetTimeout(function() { + jasmine.clock().tick(1); + }, 0); }); reporter.jasmineDone.and.callFake(function() { - expect(reporter.jasmineDone.calls.count()).toEqual(1); - done(); + expect(reporter.specDone.calls.count()).toEqual(1); + jasmine.clock().tick(1); + realSetTimeout(done); }); env.addReporter(reporter); @@ -1032,6 +1041,7 @@ describe("Env integration", function() { env.it("async spec that doesn't call done", function(underTestCallback) { env.expect(true).toBeTruthy(); jasmine.clock().tick(8416); + jasmine.clock().tick(1); }); env.execute(); @@ -1042,10 +1052,17 @@ describe("Env integration", function() { reporter = jasmine.createSpyObj('fakeReporter', [ "specDone", "jasmineDone" ]), clock = env.clock; + reporter.specDone.and.callFake(function() { + realSetTimeout(function() { + jasmine.clock().tick(1); + }, 0); + }); + reporter.jasmineDone.and.callFake(function() { - expect(reporter.specDone.calls.count()).toEqual(1); + expect(reporter.specDone).toHaveBeenCalledTimes(1); expect(reporter.specDone.calls.argsFor(0)[0]).toEqual(jasmine.objectContaining({status: 'passed'})); - done(); + jasmine.clock().tick(1); + realSetTimeout(done); }); env.addReporter(reporter); @@ -1062,8 +1079,7 @@ describe("Env integration", function() { env.it("spec that should not time out", function(innerDone) { clock.tick(6); expect(true).toEqual(true); - innerDone(); - jasmine.clock().tick(1); + realSetTimeout(innerDone); }); env.execute(); @@ -1072,9 +1088,20 @@ describe("Env integration", function() { it('should wait a custom interval before reporting async functions that fail to call done', function(done) { var env = new jasmineUnderTest.Env(), reporter = jasmine.createSpyObj('fakeReport', ['jasmineDone', 'suiteDone', 'specDone']), - realSetTimeout = this.realSetTimeout, timeoutFailure = (/^Error: Timeout - Async callback was not invoked within timeout specified by jasmine\.DEFAULT_TIMEOUT_INTERVAL\./); + reporter.specDone.and.callFake(function(r) { + realSetTimeout(function() { + jasmine.clock().tick(1); + }, 0); + }); + + reporter.suiteDone.and.callFake(function(r) { + realSetTimeout(function() { + jasmine.clock().tick(1); + }, 0); + }); + reporter.jasmineDone.and.callFake(function() { expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('suite beforeAll', [ timeoutFailure ]); expect(reporter.suiteDone).toHaveFailedExpectationsForRunnable('suite afterAll', [ timeoutFailure ]); @@ -1082,18 +1109,19 @@ describe("Env integration", function() { expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite afterEach times out', [ timeoutFailure ]); expect(reporter.specDone).toHaveFailedExpectationsForRunnable('suite it times out', [ timeoutFailure ]); - done(); + jasmine.clock().tick(1); + realSetTimeout(done); }); env.addReporter(reporter); jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 10000; env.describe('suite', function() { - env.afterAll(function() { - realSetTimeout(function() { - jasmine.clock().tick(10); - }, 100); - }); + // env.afterAll(function() { + // realSetTimeout(function() { + // jasmine.clock().tick(10); + // }, 100); + // }); env.describe('beforeAll', function() { env.beforeAll(function(innerDone) { realSetTimeout(function() { @@ -1101,7 +1129,12 @@ describe("Env integration", function() { }, 0); }, 5000); - env.it('times out', function() {}); + env.it('times out', function(innerDone) { + realSetTimeout(function() { + jasmine.clock().tick(1); + innerDone(); + }, 0); + }); }); env.describe('afterAll', function() { @@ -1111,7 +1144,12 @@ describe("Env integration", function() { }, 0); }, 2000); - env.it('times out', function() {}); + env.it('times out', function(innerDone) { + realSetTimeout(function() { + jasmine.clock().tick(1); + innerDone(); + }, 0); + }); }); env.describe('beforeEach', function() { @@ -1121,7 +1159,12 @@ describe("Env integration", function() { }, 0); }, 1000); - env.it('times out', function() {}); + env.it('times out', function(innerDone) { + realSetTimeout(function() { + jasmine.clock().tick(1); + innerDone(); + }, 0); + }); }); env.describe('afterEach', function() { @@ -1131,7 +1174,12 @@ describe("Env integration", function() { }, 0); }, 4000); - env.it('times out', function() {}); + env.it('times out', function(innerDone) { + realSetTimeout(function() { + jasmine.clock().tick(1); + innerDone(); + }, 0); + }); }); env.it('it times out', function(innerDone) { @@ -1148,6 +1196,12 @@ describe("Env integration", function() { var env = new jasmineUnderTest.Env(), specDone = jasmine.createSpy('specDone'); + specDone.and.callFake(function() { + realSetTimeout(function() { + jasmine.clock().tick(1); + }, 0); + }); + env.addReporter({ specDone: specDone, jasmineDone: function() { @@ -1175,7 +1229,9 @@ describe("Env integration", function() { message: 'Failed: error message' })] })); - done(); + + jasmine.clock().tick(1); + realSetTimeout(done); } }); @@ -1187,6 +1243,7 @@ describe("Env integration", function() { }, 1); jasmine.clock().tick(1); jasmine.clock().tick(1); + jasmine.clock().tick(1); }); env.it('specifies a message', function(innerDone) { @@ -1196,6 +1253,7 @@ describe("Env integration", function() { }, 1); jasmine.clock().tick(1); jasmine.clock().tick(1); + jasmine.clock().tick(1); }); env.it('fails via the done callback', function(innerDone) { @@ -1204,6 +1262,7 @@ describe("Env integration", function() { }, 1); jasmine.clock().tick(1); jasmine.clock().tick(1); + jasmine.clock().tick(1); }); env.it('has a message from an Error', function(innerDone) { @@ -1213,6 +1272,7 @@ describe("Env integration", function() { }, 1); jasmine.clock().tick(1); jasmine.clock().tick(1); + jasmine.clock().tick(1); }); }); diff --git a/src/core/Env.js b/src/core/Env.js index a3ce0dd3..21f7d760 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -39,59 +39,6 @@ getJasmineRequireObj().Env = function(j$) { return currentSpec || currentSuite(); }; - /** - * This represents the available reporter callback for an object passed to {@link Env#addReporter}. - * @interface Reporter - */ - var reporter = new j$.ReportDispatcher([ - /** - * `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts. - * @function - * @name Reporter#jasmineStarted - * @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run - */ - 'jasmineStarted', - /** - * When the entire suite has finished execution `jasmineDone` is called - * @function - * @name Reporter#jasmineDone - * @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running. - */ - 'jasmineDone', - /** - * `suiteStarted` is invoked when a `describe` starts to run - * @function - * @name Reporter#suiteStarted - * @param {SuiteResult} result Information about the individual {@link describe} being run - */ - 'suiteStarted', - /** - * `suiteDone` is invoked when all of the child specs and suites for a given suite have been run - * - * While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`. - * @function - * @name Reporter#suiteDone - * @param {SuiteResult} result - */ - 'suiteDone', - /** - * `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions) - * @function - * @name Reporter#specStarted - * @param {SpecResult} result Information about the individual {@link it} being run - */ - 'specStarted', - /** - * `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run. - * - * While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed. - * @function - * @name Reporter#specDone - * @param {SpecResult} result - */ - 'specDone' - ]); - var globalErrors = new j$.GlobalErrors(); globalErrors.install(); globalErrors.pushListener(function(message, filename, lineno) { @@ -260,15 +207,18 @@ getJasmineRequireObj().Env = function(j$) { handlingLoadErrors = false; }; - var queueRunnerFactory = function(options) { + var queueRunnerFactory = function(options, args) { options.catchException = catchException; options.clearStack = options.clearStack || clearStack; options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; options.fail = self.fail; options.globalErrors = globalErrors; options.completeOnFirstError = throwOnExpectationFailure && options.isLeaf; + options.onException = options.onException || function(e) { + (currentRunnable() || topSuite).onException(e); + }; - new j$.QueueRunner(options).execute(); + new j$.QueueRunner(options).execute(args); }; var topSuite = new j$.Suite({ @@ -285,6 +235,59 @@ getJasmineRequireObj().Env = function(j$) { return topSuite; }; + /** + * This represents the available reporter callback for an object passed to {@link Env#addReporter}. + * @interface Reporter + */ + var reporter = new j$.ReportDispatcher([ + /** + * `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts. + * @function + * @name Reporter#jasmineStarted + * @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run + */ + 'jasmineStarted', + /** + * When the entire suite has finished execution `jasmineDone` is called + * @function + * @name Reporter#jasmineDone + * @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running. + */ + 'jasmineDone', + /** + * `suiteStarted` is invoked when a `describe` starts to run + * @function + * @name Reporter#suiteStarted + * @param {SuiteResult} result Information about the individual {@link describe} being run + */ + 'suiteStarted', + /** + * `suiteDone` is invoked when all of the child specs and suites for a given suite have been run + * + * While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`. + * @function + * @name Reporter#suiteDone + * @param {SuiteResult} result + */ + 'suiteDone', + /** + * `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions) + * @function + * @name Reporter#specStarted + * @param {SpecResult} result Information about the individual {@link it} being run + */ + 'specStarted', + /** + * `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run. + * + * While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed. + * @function + * @name Reporter#specDone + * @param {SpecResult} result + */ + 'specDone' + ], queueRunnerFactory); + this.execute = function(runnablesToRun) { var self = this; this.suppressLoadErrors(); @@ -306,23 +309,24 @@ getJasmineRequireObj().Env = function(j$) { tree: topSuite, runnableIds: runnablesToRun, queueRunnerFactory: queueRunnerFactory, - nodeStart: function(suite) { + nodeStart: function(suite, next) { currentlyExecutingSuites.push(suite); defaultResourcesForRunnable(suite.id, suite.parentSuite.id); - reporter.suiteStarted(suite.result); + reporter.suiteStarted(suite.result, next); }, - nodeComplete: function(suite, result) { + nodeComplete: function(suite, result, next) { if (suite !== currentSuite()) { throw new Error('Tried to complete the wrong suite'); } clearResourcesForRunnable(suite.id); currentlyExecutingSuites.pop(); - reporter.suiteDone(result); if (result.status === 'failed') { hasFailures = true; } + + reporter.suiteDone(result, next); }, orderChildren: function(node) { return order.sort(node.children); @@ -345,40 +349,40 @@ getJasmineRequireObj().Env = function(j$) { reporter.jasmineStarted({ totalSpecsDefined: totalSpecsDefined, order: order - }); + }, function() { + currentlyExecutingSuites.push(topSuite); - currentlyExecutingSuites.push(topSuite); + processor.execute(function () { + clearResourcesForRunnable(topSuite.id); + currentlyExecutingSuites.pop(); + var overallStatus, incompleteReason; - processor.execute(function() { - clearResourcesForRunnable(topSuite.id); - currentlyExecutingSuites.pop(); - var overallStatus, incompleteReason; + if (hasFailures || topSuite.result.failedExpectations.length > 0) { + overallStatus = 'failed'; + } else if (focusedRunnables.length > 0) { + overallStatus = 'incomplete'; + incompleteReason = 'fit() or fdescribe() was found'; + } else if (totalSpecsDefined === 0) { + overallStatus = 'incomplete'; + incompleteReason = 'No specs found'; + } else { + overallStatus = 'passed'; + } - if (hasFailures || topSuite.result.failedExpectations.length > 0) { - overallStatus = 'failed'; - } else if (focusedRunnables.length > 0) { - overallStatus = 'incomplete'; - incompleteReason = 'fit() or fdescribe() was found'; - } else if (totalSpecsDefined === 0) { - overallStatus = 'incomplete'; - incompleteReason = 'No specs found'; - } else { - overallStatus = 'passed'; - } - - /** - * Information passed to the {@link Reporter#jasmineDone} event. - * @typedef JasmineDoneInfo - * @property {OverallStatus} - The overall result of the sute: 'passed', 'failed', or 'incomplete'. - * @property {IncompleteReason} - Explanation of why the suite was incimplete. - * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. - * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. - */ - reporter.jasmineDone({ - overallStatus: overallStatus, - incompleteReason: incompleteReason, - order: order, - failedExpectations: topSuite.result.failedExpectations + /** + * Information passed to the {@link Reporter#jasmineDone} event. + * @typedef JasmineDoneInfo + * @property {OverallStatus} - The overall result of the sute: 'passed', 'failed', or 'incomplete'. + * @property {IncompleteReason} - Explanation of why the suite was incimplete. + * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. + * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. + */ + reporter.jasmineDone({ + overallStatus: overallStatus, + incompleteReason: incompleteReason, + order: order, + failedExpectations: topSuite.result.failedExpectations + }, function() {}); }); }); }; @@ -581,20 +585,21 @@ getJasmineRequireObj().Env = function(j$) { return spec; - function specResultCallback(result) { + function specResultCallback(result, next) { clearResourcesForRunnable(spec.id); currentSpec = null; - reporter.specDone(result); if (result.status === 'failed') { hasFailures = true; } + + reporter.specDone(result, next); } - function specStarted(spec) { + function specStarted(spec, next) { currentSpec = spec; defaultResourcesForRunnable(spec.id, suite.id); - reporter.specStarted(spec.result); + reporter.specStarted(spec.result, next); } }; diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index c9b15a28..cc8dc1e0 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -24,6 +24,10 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.fail = attrs.fail || function() {}; this.globalErrors = attrs.globalErrors || { pushListener: function() {}, popListener: function() {} }; this.completeOnFirstError = !!attrs.completeOnFirstError; + + if (typeof(this.onComplete) !== 'function') { + throw new Error('invalid onComplete ' + JSON.stringify(this.onComplete)); + } } QueueRunner.prototype.execute = function() { @@ -53,15 +57,15 @@ getJasmineRequireObj().QueueRunner = function(j$) { QueueRunner.prototype.attempt = function attempt(iterativeIndex) { var self = this, completedSynchronously = true, - handleError = function(error) { + handleError = function handleError(error) { onException(error); next(); }, - cleanup = once(function() { + cleanup = once(function cleanup() { self.clearTimeout(timeoutId); self.globalErrors.popListener(handleError); }), - next = once(function () { + next = once(function next() { cleanup(); function runNext() { @@ -82,7 +86,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { queueableFn = self.queueableFns[iterativeIndex], timeoutId; - next.fail = function() { + next.fail = function nextFail() { self.fail.apply(null, arguments); errored = true; next(); diff --git a/src/core/ReportDispatcher.js b/src/core/ReportDispatcher.js index 4e8b29ea..a8d0d576 100644 --- a/src/core/ReportDispatcher.js +++ b/src/core/ReportDispatcher.js @@ -1,5 +1,5 @@ getJasmineRequireObj().ReportDispatcher = function(j$) { - function ReportDispatcher(methods) { + function ReportDispatcher(methods, queueRunnerFactory) { var dispatchedMethods = methods || []; @@ -33,11 +33,39 @@ getJasmineRequireObj().ReportDispatcher = function(j$) { if (reporters.length === 0 && fallbackReporter !== null) { reporters.push(fallbackReporter); } + var onComplete = args[args.length - 1]; + args = j$.util.argsToArray(args).splice(0, args.length - 1); + var fns = []; for (var i = 0; i < reporters.length; i++) { var reporter = reporters[i]; - if (reporter[method]) { - reporter[method].apply(reporter, j$.util.cloneArgs(args)); - } + addFn(fns, reporter, method, args); + } + + queueRunnerFactory({ + queueableFns: fns, + onComplete: onComplete + }); + } + + function addFn(fns, reporter, method, args) { + var fn = reporter[method]; + if (!fn) { + return; + } + + var thisArgs = j$.util.cloneArgs(args); + if (fn.length <= 1) { + fns.push({ + fn: function () { + return fn.apply(reporter, thisArgs); + } + }); + } else { + fns.push({ + fn: function (done) { + return fn.apply(reporter, thisArgs.concat([done])); + } + }); } } } diff --git a/src/core/Spec.js b/src/core/Spec.js index 7f918067..b9c9b7e8 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -58,7 +58,19 @@ getJasmineRequireObj().Spec = function(j$) { Spec.prototype.execute = function(onComplete, excluded) { var self = this; - this.onStart(this); + var onStart = { + fn: function(done) { + self.onStart(self, done); + } + }; + + var complete = { + fn: function(done) { + self.queueableFn.fn = null; + self.result.status = self.status(excluded); + self.resultCallback(self.result, done); + } + }; var fns = this.beforeAndAfterFns(); var regularFns = fns.befores.concat(this.queueableFn); @@ -67,8 +79,10 @@ getJasmineRequireObj().Spec = function(j$) { isLeaf: true, queueableFns: regularFns, cleanupFns: fns.afters, - onException: function() { self.onException.apply(self, arguments); }, - onComplete: complete, + onException: function () { + self.onException.apply(self, arguments); + }, + onComplete: onComplete, userContext: this.userContext() }; @@ -77,17 +91,10 @@ getJasmineRequireObj().Spec = function(j$) { runnerConfig.cleanupFns = []; } + runnerConfig.queueableFns.unshift(onStart); + runnerConfig.cleanupFns.push(complete); + this.queueRunnerFactory(runnerConfig); - - function complete() { - self.queueableFn.fn = null; - self.result.status = self.status(excluded); - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } }; Spec.prototype.onException = function onException(e) { diff --git a/src/core/TreeProcessor.js b/src/core/TreeProcessor.js index f633094c..43ca9c5b 100644 --- a/src/core/TreeProcessor.js +++ b/src/core/TreeProcessor.js @@ -166,17 +166,20 @@ getJasmineRequireObj().TreeProcessor = function() { if (node.children) { return { fn: function(done) { - nodeStart(node); + var onStart = { + fn: function(next) { + nodeStart(node, next); + } + }; queueRunnerFactory({ - onComplete: function() { + onComplete: function () { node.cleanupBeforeAfter(); - nodeComplete(node, node.getResult()); - done(); + nodeComplete(node, node.getResult(), done); }, - queueableFns: wrapChildren(node, segmentNumber), + queueableFns: [onStart].concat(wrapChildren(node, segmentNumber)), userContext: node.sharedUserContext(), - onException: function() { + onException: function () { node.onException.apply(node, arguments); } }); From 262a2fe674f9fc7250eb361585d8ddb3e2c0b780 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Mon, 29 Jan 2018 14:02:02 -0800 Subject: [PATCH 55/62] Generate compiled jasmine.js for async reporters work --- lib/jasmine-core/jasmine.js | 289 +++++++++++++++++++++--------------- 1 file changed, 168 insertions(+), 121 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index d7154461..f8a9f028 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -528,7 +528,19 @@ getJasmineRequireObj().Spec = function(j$) { Spec.prototype.execute = function(onComplete, excluded) { var self = this; - this.onStart(this); + var onStart = { + fn: function(done) { + self.onStart(self, done); + } + }; + + var complete = { + fn: function(done) { + self.queueableFn.fn = null; + self.result.status = self.status(excluded); + self.resultCallback(self.result, done); + } + }; var fns = this.beforeAndAfterFns(); var regularFns = fns.befores.concat(this.queueableFn); @@ -537,8 +549,10 @@ getJasmineRequireObj().Spec = function(j$) { isLeaf: true, queueableFns: regularFns, cleanupFns: fns.afters, - onException: function() { self.onException.apply(self, arguments); }, - onComplete: complete, + onException: function () { + self.onException.apply(self, arguments); + }, + onComplete: onComplete, userContext: this.userContext() }; @@ -547,17 +561,10 @@ getJasmineRequireObj().Spec = function(j$) { runnerConfig.cleanupFns = []; } + runnerConfig.queueableFns.unshift(onStart); + runnerConfig.cleanupFns.push(complete); + this.queueRunnerFactory(runnerConfig); - - function complete() { - self.queueableFn.fn = null; - self.result.status = self.status(excluded); - self.resultCallback(self.result); - - if (onComplete) { - onComplete(); - } - } }; Spec.prototype.onException = function onException(e) { @@ -720,59 +727,6 @@ getJasmineRequireObj().Env = function(j$) { return currentSpec || currentSuite(); }; - /** - * This represents the available reporter callback for an object passed to {@link Env#addReporter}. - * @interface Reporter - */ - var reporter = new j$.ReportDispatcher([ - /** - * `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts. - * @function - * @name Reporter#jasmineStarted - * @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run - */ - 'jasmineStarted', - /** - * When the entire suite has finished execution `jasmineDone` is called - * @function - * @name Reporter#jasmineDone - * @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running. - */ - 'jasmineDone', - /** - * `suiteStarted` is invoked when a `describe` starts to run - * @function - * @name Reporter#suiteStarted - * @param {SuiteResult} result Information about the individual {@link describe} being run - */ - 'suiteStarted', - /** - * `suiteDone` is invoked when all of the child specs and suites for a given suite have been run - * - * While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`. - * @function - * @name Reporter#suiteDone - * @param {SuiteResult} result - */ - 'suiteDone', - /** - * `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions) - * @function - * @name Reporter#specStarted - * @param {SpecResult} result Information about the individual {@link it} being run - */ - 'specStarted', - /** - * `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run. - * - * While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed. - * @function - * @name Reporter#specDone - * @param {SpecResult} result - */ - 'specDone' - ]); - var globalErrors = new j$.GlobalErrors(); globalErrors.install(); globalErrors.pushListener(function(message, filename, lineno) { @@ -941,15 +895,18 @@ getJasmineRequireObj().Env = function(j$) { handlingLoadErrors = false; }; - var queueRunnerFactory = function(options) { + var queueRunnerFactory = function(options, args) { options.catchException = catchException; options.clearStack = options.clearStack || clearStack; options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; options.fail = self.fail; options.globalErrors = globalErrors; options.completeOnFirstError = throwOnExpectationFailure && options.isLeaf; + options.onException = options.onException || function(e) { + (currentRunnable() || topSuite).onException(e); + }; - new j$.QueueRunner(options).execute(); + new j$.QueueRunner(options).execute(args); }; var topSuite = new j$.Suite({ @@ -966,6 +923,59 @@ getJasmineRequireObj().Env = function(j$) { return topSuite; }; + /** + * This represents the available reporter callback for an object passed to {@link Env#addReporter}. + * @interface Reporter + */ + var reporter = new j$.ReportDispatcher([ + /** + * `jasmineStarted` is called after all of the specs have been loaded, but just before execution starts. + * @function + * @name Reporter#jasmineStarted + * @param {JasmineStartedInfo} suiteInfo Information about the full Jasmine suite that is being run + */ + 'jasmineStarted', + /** + * When the entire suite has finished execution `jasmineDone` is called + * @function + * @name Reporter#jasmineDone + * @param {JasmineDoneInfo} suiteInfo Information about the full Jasmine suite that just finished running. + */ + 'jasmineDone', + /** + * `suiteStarted` is invoked when a `describe` starts to run + * @function + * @name Reporter#suiteStarted + * @param {SuiteResult} result Information about the individual {@link describe} being run + */ + 'suiteStarted', + /** + * `suiteDone` is invoked when all of the child specs and suites for a given suite have been run + * + * While jasmine doesn't require any specific functions, not defining a `suiteDone` will make it impossible for a reporter to know when a suite has failures in an `afterAll`. + * @function + * @name Reporter#suiteDone + * @param {SuiteResult} result + */ + 'suiteDone', + /** + * `specStarted` is invoked when an `it` starts to run (including associated `beforeEach` functions) + * @function + * @name Reporter#specStarted + * @param {SpecResult} result Information about the individual {@link it} being run + */ + 'specStarted', + /** + * `specDone` is invoked when an `it` and its associated `beforeEach` and `afterEach` functions have been run. + * + * While jasmine doesn't require any specific functions, not defining a `specDone` will make it impossible for a reporter to know when a spec has failed. + * @function + * @name Reporter#specDone + * @param {SpecResult} result + */ + 'specDone' + ], queueRunnerFactory); + this.execute = function(runnablesToRun) { var self = this; this.suppressLoadErrors(); @@ -987,23 +997,24 @@ getJasmineRequireObj().Env = function(j$) { tree: topSuite, runnableIds: runnablesToRun, queueRunnerFactory: queueRunnerFactory, - nodeStart: function(suite) { + nodeStart: function(suite, next) { currentlyExecutingSuites.push(suite); defaultResourcesForRunnable(suite.id, suite.parentSuite.id); - reporter.suiteStarted(suite.result); + reporter.suiteStarted(suite.result, next); }, - nodeComplete: function(suite, result) { + nodeComplete: function(suite, result, next) { if (suite !== currentSuite()) { throw new Error('Tried to complete the wrong suite'); } clearResourcesForRunnable(suite.id); currentlyExecutingSuites.pop(); - reporter.suiteDone(result); if (result.status === 'failed') { hasFailures = true; } + + reporter.suiteDone(result, next); }, orderChildren: function(node) { return order.sort(node.children); @@ -1026,40 +1037,40 @@ getJasmineRequireObj().Env = function(j$) { reporter.jasmineStarted({ totalSpecsDefined: totalSpecsDefined, order: order - }); + }, function() { + currentlyExecutingSuites.push(topSuite); - currentlyExecutingSuites.push(topSuite); + processor.execute(function () { + clearResourcesForRunnable(topSuite.id); + currentlyExecutingSuites.pop(); + var overallStatus, incompleteReason; - processor.execute(function() { - clearResourcesForRunnable(topSuite.id); - currentlyExecutingSuites.pop(); - var overallStatus, incompleteReason; + if (hasFailures || topSuite.result.failedExpectations.length > 0) { + overallStatus = 'failed'; + } else if (focusedRunnables.length > 0) { + overallStatus = 'incomplete'; + incompleteReason = 'fit() or fdescribe() was found'; + } else if (totalSpecsDefined === 0) { + overallStatus = 'incomplete'; + incompleteReason = 'No specs found'; + } else { + overallStatus = 'passed'; + } - if (hasFailures || topSuite.result.failedExpectations.length > 0) { - overallStatus = 'failed'; - } else if (focusedRunnables.length > 0) { - overallStatus = 'incomplete'; - incompleteReason = 'fit() or fdescribe() was found'; - } else if (totalSpecsDefined === 0) { - overallStatus = 'incomplete'; - incompleteReason = 'No specs found'; - } else { - overallStatus = 'passed'; - } - - /** - * Information passed to the {@link Reporter#jasmineDone} event. - * @typedef JasmineDoneInfo - * @property {OverallStatus} - The overall result of the sute: 'passed', 'failed', or 'incomplete'. - * @property {IncompleteReason} - Explanation of why the suite was incimplete. - * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. - * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. - */ - reporter.jasmineDone({ - overallStatus: overallStatus, - incompleteReason: incompleteReason, - order: order, - failedExpectations: topSuite.result.failedExpectations + /** + * Information passed to the {@link Reporter#jasmineDone} event. + * @typedef JasmineDoneInfo + * @property {OverallStatus} - The overall result of the sute: 'passed', 'failed', or 'incomplete'. + * @property {IncompleteReason} - Explanation of why the suite was incimplete. + * @property {Order} order - Information about the ordering (random or not) of this execution of the suite. + * @property {Expectation[]} failedExpectations - List of expectations that failed in an {@link afterAll} at the global level. + */ + reporter.jasmineDone({ + overallStatus: overallStatus, + incompleteReason: incompleteReason, + order: order, + failedExpectations: topSuite.result.failedExpectations + }, function() {}); }); }); }; @@ -1262,20 +1273,21 @@ getJasmineRequireObj().Env = function(j$) { return spec; - function specResultCallback(result) { + function specResultCallback(result, next) { clearResourcesForRunnable(spec.id); currentSpec = null; - reporter.specDone(result); if (result.status === 'failed') { hasFailures = true; } + + reporter.specDone(result, next); } - function specStarted(spec) { + function specStarted(spec, next) { currentSpec = spec; defaultResourcesForRunnable(spec.id, suite.id); - reporter.specStarted(spec.result); + reporter.specStarted(spec.result, next); } }; @@ -4460,6 +4472,10 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.fail = attrs.fail || function() {}; this.globalErrors = attrs.globalErrors || { pushListener: function() {}, popListener: function() {} }; this.completeOnFirstError = !!attrs.completeOnFirstError; + + if (typeof(this.onComplete) !== 'function') { + throw new Error('invalid onComplete ' + JSON.stringify(this.onComplete)); + } } QueueRunner.prototype.execute = function() { @@ -4489,15 +4505,15 @@ getJasmineRequireObj().QueueRunner = function(j$) { QueueRunner.prototype.attempt = function attempt(iterativeIndex) { var self = this, completedSynchronously = true, - handleError = function(error) { + handleError = function handleError(error) { onException(error); next(); }, - cleanup = once(function() { + cleanup = once(function cleanup() { self.clearTimeout(timeoutId); self.globalErrors.popListener(handleError); }), - next = once(function () { + next = once(function next() { cleanup(); function runNext() { @@ -4518,7 +4534,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { queueableFn = self.queueableFns[iterativeIndex], timeoutId; - next.fail = function() { + next.fail = function nextFail() { self.fail.apply(null, arguments); errored = true; next(); @@ -4606,7 +4622,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { }; getJasmineRequireObj().ReportDispatcher = function(j$) { - function ReportDispatcher(methods) { + function ReportDispatcher(methods, queueRunnerFactory) { var dispatchedMethods = methods || []; @@ -4640,11 +4656,39 @@ getJasmineRequireObj().ReportDispatcher = function(j$) { if (reporters.length === 0 && fallbackReporter !== null) { reporters.push(fallbackReporter); } + var onComplete = args[args.length - 1]; + args = j$.util.argsToArray(args).splice(0, args.length - 1); + var fns = []; for (var i = 0; i < reporters.length; i++) { var reporter = reporters[i]; - if (reporter[method]) { - reporter[method].apply(reporter, j$.util.cloneArgs(args)); - } + addFn(fns, reporter, method, args); + } + + queueRunnerFactory({ + queueableFns: fns, + onComplete: onComplete + }); + } + + function addFn(fns, reporter, method, args) { + var fn = reporter[method]; + if (!fn) { + return; + } + + var thisArgs = j$.util.cloneArgs(args); + if (fn.length <= 1) { + fns.push({ + fn: function () { + return fn.apply(reporter, thisArgs); + } + }); + } else { + fns.push({ + fn: function (done) { + return fn.apply(reporter, thisArgs.concat([done])); + } + }); } } } @@ -5857,17 +5901,20 @@ getJasmineRequireObj().TreeProcessor = function() { if (node.children) { return { fn: function(done) { - nodeStart(node); + var onStart = { + fn: function(next) { + nodeStart(node, next); + } + }; queueRunnerFactory({ - onComplete: function() { + onComplete: function () { node.cleanupBeforeAfter(); - nodeComplete(node, node.getResult()); - done(); + nodeComplete(node, node.getResult(), done); }, - queueableFns: wrapChildren(node, segmentNumber), + queueableFns: [onStart].concat(wrapChildren(node, segmentNumber)), userContext: node.sharedUserContext(), - onException: function() { + onException: function () { node.onException.apply(node, arguments); } }); From 46cc48ccfa27ebfdc92fcdf865d81bf1af1f37aa Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Mon, 29 Jan 2018 16:46:30 -0800 Subject: [PATCH 56/62] Detect an Error passed to `done` and add an expectation failure - See #567 --- lib/jasmine-core/jasmine.js | 37 +++++++++++++----------- spec/core/QueueRunnerSpec.js | 41 ++++++++++++++++++++++++++ spec/core/integration/EnvSpec.js | 48 +++++++++++++++---------------- src/core/QueueRunner.js | 11 +++++-- src/core/base.js | 11 +++++++ src/core/matchers/toThrowError.js | 15 ++-------- 6 files changed, 107 insertions(+), 56 deletions(-) diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index f8a9f028..e90947ab 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -218,6 +218,17 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return j$.getType_(value) === '[object ' + typeName + ']'; }; + j$.isError_ = function(value) { + if (value instanceof Error) { + return true; + } + if (value && value.constructor && value.constructor.constructor && + (value instanceof (value.constructor.constructor('return this')()).Error)) { + return true; + } + return false; + }; + j$.getType_ = function(value) { return Object.prototype.toString.apply(value); }; @@ -3846,7 +3857,7 @@ getJasmineRequireObj().toThrowError = function(j$) { thrown = e; } - if (!isErrorObject(thrown)) { + if (!j$.isError_(thrown)) { return fail(function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }); } @@ -3958,18 +3969,7 @@ getJasmineRequireObj().toThrowError = function(j$) { var Surrogate = function() {}; Surrogate.prototype = type.prototype; - return isErrorObject(new Surrogate()); - } - - function isErrorObject(thrown) { - if (thrown instanceof Error) { - return true; - } - if (thrown && thrown.constructor && thrown.constructor.constructor && - (thrown instanceof (thrown.constructor.constructor('return this')()).Error)) { - return true; - } - return false; + return j$.isError_(new Surrogate()); } } @@ -4453,7 +4453,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { return function() { if (!called) { called = true; - fn(); + fn.apply(null, arguments); } return null; }; @@ -4507,15 +4507,20 @@ getJasmineRequireObj().QueueRunner = function(j$) { var self = this, completedSynchronously = true, handleError = function handleError(error) { onException(error); - next(); + next(error); }, cleanup = once(function cleanup() { self.clearTimeout(timeoutId); self.globalErrors.popListener(handleError); }), - next = once(function next() { + next = once(function next(err) { cleanup(); + if (j$.isError_(err)) { + self.fail(err); + errored = true; + } + function runNext() { if (self.completeOnFirstError && errored) { self.skipToCleanup(iterativeIndex); diff --git a/spec/core/QueueRunnerSpec.js b/spec/core/QueueRunnerSpec.js index 3059b50b..15483cfe 100644 --- a/spec/core/QueueRunnerSpec.js +++ b/spec/core/QueueRunnerSpec.js @@ -135,6 +135,29 @@ describe("QueueRunner", function() { expect(queueableFn2.fn).toHaveBeenCalled(); }); + it("explicitly fails an async function when next is called with an Error and moves to the next function", function() { + var err = new Error('foo'), + queueableFn1 = { fn: function(done) { + setTimeout(function() { done(err); }, 100); + } }, + queueableFn2 = { fn: jasmine.createSpy('fn2') }, + failFn = jasmine.createSpy('fail'), + queueRunner = new jasmineUnderTest.QueueRunner({ + queueableFns: [queueableFn1, queueableFn2], + fail: failFn + }); + + queueRunner.execute(); + + expect(failFn).not.toHaveBeenCalled(); + expect(queueableFn2.fn).not.toHaveBeenCalled(); + + jasmine.clock().tick(100); + + expect(failFn).toHaveBeenCalledWith(err); + expect(queueableFn2.fn).toHaveBeenCalled(); + }); + it("sets a timeout if requested for asynchronous functions so they don't go on forever", function() { var timeout = 3, beforeFn = { fn: function(done) { }, type: 'before', timeout: function() { return timeout; } }, @@ -522,6 +545,24 @@ describe("QueueRunner", function() { expect(nextQueueableFn.fn).not.toHaveBeenCalled(); expect(cleanupFn.fn).toHaveBeenCalled(); }); + + it("skips to cleanup functions when next is called with an Error", function() { + var queueableFn = { fn: function(done) { + done(new Error('nope')); + } }, + nextQueueableFn = { fn: jasmine.createSpy('nextFunction') }, + cleanupFn = { fn: jasmine.createSpy('cleanup') }, + queueRunner = new jasmineUnderTest.QueueRunner({ + queueableFns: [queueableFn, nextQueueableFn], + cleanupFns: [cleanupFn], + completeOnFirstError: true, + }); + + queueRunner.execute(); + jasmine.clock().tick(); + expect(nextQueueableFn.fn).not.toHaveBeenCalled(); + expect(cleanupFn.fn).toHaveBeenCalled(); + }); }); }); diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index ac3852a7..4fae2454 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1205,30 +1205,21 @@ describe("Env integration", function() { env.addReporter({ specDone: specDone, jasmineDone: function() { - expect(specDone).toHaveBeenCalledWith(jasmine.objectContaining({ - description: 'has a default message', - failedExpectations: [jasmine.objectContaining({ - message: 'Failed' - })] - })); - expect(specDone).toHaveBeenCalledWith(jasmine.objectContaining({ - description: 'specifies a message', - failedExpectations: [jasmine.objectContaining({ - message: 'Failed: messy message' - })] - })); - expect(specDone).toHaveBeenCalledWith(jasmine.objectContaining({ - description: 'fails via the done callback', - failedExpectations: [jasmine.objectContaining({ - message: 'Failed: done failed' - })] - })); - expect(specDone).toHaveBeenCalledWith(jasmine.objectContaining({ - description: 'has a message from an Error', - failedExpectations: [jasmine.objectContaining({ - message: 'Failed: error message' - })] - })); + expect(specDone).toHaveFailedExpectationsForRunnable('failing has a default message', + ['Failed'] + ); + expect(specDone).toHaveFailedExpectationsForRunnable('failing specifies a message', + ['Failed: messy message'] + ); + expect(specDone).toHaveFailedExpectationsForRunnable('failing fails via the done callback', + ['Failed: done failed'] + ); + expect(specDone).toHaveFailedExpectationsForRunnable('failing has a message from an Error', + ['Failed: error message'] + ); + expect(specDone).toHaveFailedExpectationsForRunnable('failing has a message from an Error to done', + ['Failed: done error'] + ); jasmine.clock().tick(1); realSetTimeout(done); @@ -1274,6 +1265,15 @@ describe("Env integration", function() { jasmine.clock().tick(1); jasmine.clock().tick(1); }); + + env.it('has a message from an Error to done', function(innerDone) { + setTimeout(function() { + innerDone(new Error('done error')); + }, 1); + jasmine.clock().tick(1); + jasmine.clock().tick(1); + jasmine.clock().tick(1); + }); }); env.execute(); diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index cc8dc1e0..10a88f10 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -5,7 +5,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { return function() { if (!called) { called = true; - fn(); + fn.apply(null, arguments); } return null; }; @@ -59,15 +59,20 @@ getJasmineRequireObj().QueueRunner = function(j$) { var self = this, completedSynchronously = true, handleError = function handleError(error) { onException(error); - next(); + next(error); }, cleanup = once(function cleanup() { self.clearTimeout(timeoutId); self.globalErrors.popListener(handleError); }), - next = once(function next() { + next = once(function next(err) { cleanup(); + if (j$.isError_(err)) { + self.fail(err); + errored = true; + } + function runNext() { if (self.completeOnFirstError && errored) { self.skipToCleanup(iterativeIndex); diff --git a/src/core/base.js b/src/core/base.js index 5f0d54aa..799208c6 100644 --- a/src/core/base.js +++ b/src/core/base.js @@ -85,6 +85,17 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) { return j$.getType_(value) === '[object ' + typeName + ']'; }; + j$.isError_ = function(value) { + if (value instanceof Error) { + return true; + } + if (value && value.constructor && value.constructor.constructor && + (value instanceof (value.constructor.constructor('return this')()).Error)) { + return true; + } + return false; + }; + j$.getType_ = function(value) { return Object.prototype.toString.apply(value); }; diff --git a/src/core/matchers/toThrowError.js b/src/core/matchers/toThrowError.js index 2024d2a3..ca3bfc5e 100644 --- a/src/core/matchers/toThrowError.js +++ b/src/core/matchers/toThrowError.js @@ -32,7 +32,7 @@ getJasmineRequireObj().toThrowError = function(j$) { thrown = e; } - if (!isErrorObject(thrown)) { + if (!j$.isError_(thrown)) { return fail(function() { return 'Expected function to throw an Error, but it threw ' + j$.pp(thrown) + '.'; }); } @@ -144,18 +144,7 @@ getJasmineRequireObj().toThrowError = function(j$) { var Surrogate = function() {}; Surrogate.prototype = type.prototype; - return isErrorObject(new Surrogate()); - } - - function isErrorObject(thrown) { - if (thrown instanceof Error) { - return true; - } - if (thrown && thrown.constructor && thrown.constructor.constructor && - (thrown instanceof (thrown.constructor.constructor('return this')()).Error)) { - return true; - } - return false; + return j$.isError_(new Surrogate()); } } From e908b67b19e8877d3dd3924f304817a02b81485d Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Mon, 29 Jan 2018 17:05:54 -0800 Subject: [PATCH 57/62] Move explicit fail spec out of mock clock context and re-add the afterAll for IEs --- spec/core/integration/EnvSpec.js | 156 ++++++++++++++----------------- 1 file changed, 69 insertions(+), 87 deletions(-) diff --git a/spec/core/integration/EnvSpec.js b/spec/core/integration/EnvSpec.js index 4fae2454..e8c6b13d 100644 --- a/spec/core/integration/EnvSpec.js +++ b/spec/core/integration/EnvSpec.js @@ -1117,11 +1117,15 @@ describe("Env integration", function() { jasmineUnderTest.DEFAULT_TIMEOUT_INTERVAL = 10000; env.describe('suite', function() { - // env.afterAll(function() { - // realSetTimeout(function() { - // jasmine.clock().tick(10); - // }, 100); - // }); + env.afterAll(function() { + realSetTimeout(function() { + try { + jasmine.clock().tick(10); + } catch(e) { + // don't worry if the clock is already uninstalled + } + }, 100); + }); env.describe('beforeAll', function() { env.beforeAll(function(innerDone) { realSetTimeout(function() { @@ -1191,93 +1195,71 @@ describe("Env integration", function() { env.execute(); }); + }); - it('explicitly fails an async spec', function(done) { - var env = new jasmineUnderTest.Env(), + it('explicitly fails an async spec', function(done) { + var env = new jasmineUnderTest.Env(), specDone = jasmine.createSpy('specDone'); - specDone.and.callFake(function() { - realSetTimeout(function() { - jasmine.clock().tick(1); - }, 0); - }); + env.addReporter({ + specDone: specDone, + jasmineDone: function() { + expect(specDone).toHaveFailedExpectationsForRunnable('failing has a default message', + ['Failed'] + ); + expect(specDone).toHaveFailedExpectationsForRunnable('failing specifies a message', + ['Failed: messy message'] + ); + expect(specDone).toHaveFailedExpectationsForRunnable('failing fails via the done callback', + ['Failed: done failed'] + ); + expect(specDone).toHaveFailedExpectationsForRunnable('failing has a message from an Error', + ['Failed: error message'] + ); + expect(specDone).toHaveFailedExpectationsForRunnable('failing has a message from an Error to done', + ['Failed: done error'] + ); - env.addReporter({ - specDone: specDone, - jasmineDone: function() { - expect(specDone).toHaveFailedExpectationsForRunnable('failing has a default message', - ['Failed'] - ); - expect(specDone).toHaveFailedExpectationsForRunnable('failing specifies a message', - ['Failed: messy message'] - ); - expect(specDone).toHaveFailedExpectationsForRunnable('failing fails via the done callback', - ['Failed: done failed'] - ); - expect(specDone).toHaveFailedExpectationsForRunnable('failing has a message from an Error', - ['Failed: error message'] - ); - expect(specDone).toHaveFailedExpectationsForRunnable('failing has a message from an Error to done', - ['Failed: done error'] - ); - - jasmine.clock().tick(1); - realSetTimeout(done); - } - }); - - env.describe('failing', function() { - env.it('has a default message', function(innerDone) { - setTimeout(function() { - env.fail(); - innerDone(); - }, 1); - jasmine.clock().tick(1); - jasmine.clock().tick(1); - jasmine.clock().tick(1); - }); - - env.it('specifies a message', function(innerDone) { - setTimeout(function() { - env.fail('messy message'); - innerDone(); - }, 1); - jasmine.clock().tick(1); - jasmine.clock().tick(1); - jasmine.clock().tick(1); - }); - - env.it('fails via the done callback', function(innerDone) { - setTimeout(function() { - innerDone.fail('done failed'); - }, 1); - jasmine.clock().tick(1); - jasmine.clock().tick(1); - jasmine.clock().tick(1); - }); - - env.it('has a message from an Error', function(innerDone) { - setTimeout(function() { - env.fail(new Error('error message')); - innerDone(); - }, 1); - jasmine.clock().tick(1); - jasmine.clock().tick(1); - jasmine.clock().tick(1); - }); - - env.it('has a message from an Error to done', function(innerDone) { - setTimeout(function() { - innerDone(new Error('done error')); - }, 1); - jasmine.clock().tick(1); - jasmine.clock().tick(1); - jasmine.clock().tick(1); - }); - }); - - env.execute(); + setTimeout(done); + } }); + + env.describe('failing', function() { + env.it('has a default message', function(innerDone) { + setTimeout(function() { + env.fail(); + innerDone(); + }, 1); + }); + + env.it('specifies a message', function(innerDone) { + setTimeout(function() { + env.fail('messy message'); + innerDone(); + }, 1); + }); + + env.it('fails via the done callback', function(innerDone) { + setTimeout(function() { + innerDone.fail('done failed'); + }, 1); + }); + + env.it('has a message from an Error', function(innerDone) { + setTimeout(function() { + env.fail(new Error('error message')); + innerDone(); + }, 1); + }); + + env.it('has a message from an Error to done', function(innerDone) { + setTimeout(function() { + innerDone(new Error('done error')); + }, 1); + }); + }); + + env.execute(); }); describe('focused tests', function() { From e15f273f06c6b8c7902050fe500486c056802ed1 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Tue, 30 Jan 2018 11:36:56 -0800 Subject: [PATCH 58/62] Replace old "catch exceptions" logic with proper fail fast with error reporting - Option is called stopOnSpecFailure [#85966014] - See #414 - See jasmine/jasmine-npm#16 --- lib/jasmine-core/boot.js | 6 +- lib/jasmine-core/boot/boot.js | 6 +- lib/jasmine-core/jasmine-html.js | 17 +++--- lib/jasmine-core/jasmine.js | 73 ++++++++++++------------ spec/core/QueueRunnerSpec.js | 37 +++++++----- spec/core/ReportDispatcherSpec.js | 18 ++++-- spec/core/SpecSpec.js | 19 +++++- spec/core/integration/SpecRunningSpec.js | 26 +++++++++ spec/html/HtmlReporterSpec.js | 32 ++++++----- src/core/Env.js | 34 ++++++----- src/core/QueueRunner.js | 32 +++++------ src/core/ReportDispatcher.js | 3 +- src/core/Spec.js | 4 +- src/html/HtmlReporter.js | 17 +++--- 14 files changed, 191 insertions(+), 133 deletions(-) diff --git a/lib/jasmine-core/boot.js b/lib/jasmine-core/boot.js index 4655042c..74dfd70d 100644 --- a/lib/jasmine-core/boot.js +++ b/lib/jasmine-core/boot.js @@ -73,8 +73,8 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. var filterSpecs = !!queryString.getParam("spec"); - var catchingExceptions = queryString.getParam("catch"); - env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); + var stoppingOnSpecFailure = queryString.getParam("failFast"); + env.stopOnSpecFailure(typeof stoppingOnSpecFailure === "undefined" ? true : stoppingOnSpecFailure); var throwingExpectationFailures = queryString.getParam("throwFailures"); env.throwOnExpectationFailure(throwingExpectationFailures); @@ -96,7 +96,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var htmlReporter = new jasmine.HtmlReporter({ env: env, - onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); }, + onStopExecutionClick: function() { queryString.navigateWithNewParam("failFast", !env.stoppingOnSpecFailure()); }, onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); }, onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); }, addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, diff --git a/lib/jasmine-core/boot/boot.js b/lib/jasmine-core/boot/boot.js index 8e12623d..00a65830 100644 --- a/lib/jasmine-core/boot/boot.js +++ b/lib/jasmine-core/boot/boot.js @@ -51,8 +51,8 @@ var filterSpecs = !!queryString.getParam("spec"); - var catchingExceptions = queryString.getParam("catch"); - env.catchExceptions(typeof catchingExceptions === "undefined" ? true : catchingExceptions); + var stoppingOnSpecFailure = queryString.getParam("failFast"); + env.stopOnSpecFailure(typeof stoppingOnSpecFailure === "undefined" ? true : stoppingOnSpecFailure); var throwingExpectationFailures = queryString.getParam("throwFailures"); env.throwOnExpectationFailure(throwingExpectationFailures); @@ -74,7 +74,7 @@ */ var htmlReporter = new jasmine.HtmlReporter({ env: env, - onRaiseExceptionsClick: function() { queryString.navigateWithNewParam("catch", !env.catchingExceptions()); }, + onStopExecutionClick: function() { queryString.navigateWithNewParam("failFast", !env.stoppingOnSpecFailure()); }, onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); }, onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); }, addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index 4585a6f9..b01d0a22 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -85,7 +85,7 @@ jasmineRequire.HtmlReporter = function(j$) { getContainer = options.getContainer, createElement = options.createElement, createTextNode = options.createTextNode, - onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, + onStopExecutionClick = options.onStopExecutionClick || function() {}, onThrowExpectationsClick = options.onThrowExpectationsClick || function() {}, onRandomClick = options.onRandomClick || function() {}, addToExistingQueryString = options.addToExistingQueryString || defaultQueryString, @@ -322,13 +322,13 @@ jasmineRequire.HtmlReporter = function(j$) { var optionsMenuDom = createDom('div', { className: 'jasmine-run-options' }, createDom('span', { className: 'jasmine-trigger' }, 'Options'), createDom('div', { className: 'jasmine-payload' }, - createDom('div', { className: 'jasmine-exceptions' }, + createDom('div', { className: 'jasmine-stop-on-failure' }, createDom('input', { - className: 'jasmine-raise', - id: 'jasmine-raise-exceptions', + className: 'jasmine-fail-fast', + id: 'jasmine-fail-fast', type: 'checkbox' }), - createDom('label', { className: 'jasmine-label', 'for': 'jasmine-raise-exceptions' }, 'raise exceptions')), + createDom('label', { className: 'jasmine-label', 'for': 'jasmine-fail-fast' }, 'stop execution on spec failure')), createDom('div', { className: 'jasmine-throw-failures' }, createDom('input', { className: 'jasmine-throw', @@ -346,10 +346,9 @@ jasmineRequire.HtmlReporter = function(j$) { ) ); - var raiseCheckbox = optionsMenuDom.querySelector('#jasmine-raise-exceptions'); - - raiseCheckbox.checked = !env.catchingExceptions(); - raiseCheckbox.onclick = onRaiseExceptionsClick; + var failFastCheckbox = optionsMenuDom.querySelector('#jasmine-fail-fast'); + failFastCheckbox.checked = env.stoppingOnSpecFailure(); + failFastCheckbox.onclick = onStopExecutionClick; var throwCheckbox = optionsMenuDom.querySelector('#jasmine-throw-failures'); throwCheckbox.checked = env.throwingExpectationFailures(); diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index e90947ab..0224b5e5 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -563,7 +563,9 @@ getJasmineRequireObj().Spec = function(j$) { onException: function () { self.onException.apply(self, arguments); }, - onComplete: onComplete, + onComplete: function() { + onComplete(self.result.status === 'failed' && new j$.StopExecutionError('spec failed')); + }, userContext: this.userContext() }; @@ -712,8 +714,6 @@ getJasmineRequireObj().Env = function(j$) { var totalSpecsDefined = 0; - var catchExceptions = true; - var realSetTimeout = j$.getGlobal().setTimeout; var realClearTimeout = j$.getGlobal().clearTimeout; var clearStack = j$.getClearStack(j$.getGlobal()); @@ -725,6 +725,7 @@ getJasmineRequireObj().Env = function(j$) { var currentlyExecutingSuites = []; var currentDeclarationSuite = null; var throwOnExpectationFailure = false; + var stopOnSpecFailure = false; var random = true; var seed = null; var handlingLoadErrors = true; @@ -859,23 +860,9 @@ getJasmineRequireObj().Env = function(j$) { return buildExpectationResult(attrs); }; - // TODO: fix this naming, and here's where the value comes in - this.catchExceptions = function(value) { - catchExceptions = !!value; - return catchExceptions; - }; - - this.catchingExceptions = function() { - return catchExceptions; - }; - var maximumSpecCallbackDepth = 20; var currentSpecCallbackDepth = 0; - var catchException = function(e) { - return j$.Spec.isPendingSpecException(e) || catchExceptions; - }; - this.throwOnExpectationFailure = function(value) { throwOnExpectationFailure = !!value; }; @@ -884,6 +871,14 @@ getJasmineRequireObj().Env = function(j$) { return throwOnExpectationFailure; }; + this.stopOnSpecFailure = function(value) { + stopOnSpecFailure = !!value; + }; + + this.stoppingOnSpecFailure = function() { + return stopOnSpecFailure; + }; + this.randomizeTests = function(value) { random = !!value; }; @@ -907,12 +902,17 @@ getJasmineRequireObj().Env = function(j$) { }; var queueRunnerFactory = function(options, args) { - options.catchException = catchException; + var failFast = false; + if (options.isLeaf) { + failFast = throwOnExpectationFailure; + } else if (!options.isReporter) { + failFast = stopOnSpecFailure; + } options.clearStack = options.clearStack || clearStack; options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; options.fail = self.fail; options.globalErrors = globalErrors; - options.completeOnFirstError = throwOnExpectationFailure && options.isLeaf; + options.completeOnFirstError = failFast; options.onException = options.onException || function(e) { (currentRunnable() || topSuite).onException(e); }; @@ -4447,6 +4447,9 @@ getJasmineRequireObj().pp = function(j$) { }; getJasmineRequireObj().QueueRunner = function(j$) { + function StopExecutionError() {} + StopExecutionError.prototype = new Error(); + j$.StopExecutionError = StopExecutionError; function once(fn) { var called = false; @@ -4466,12 +4469,12 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.onComplete = attrs.onComplete || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; this.onException = attrs.onException || function() {}; - this.catchException = attrs.catchException || function() { return true; }; this.userContext = attrs.userContext || new j$.UserContext(); this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; this.fail = attrs.fail || function() {}; this.globalErrors = attrs.globalErrors || { pushListener: function() {}, popListener: function() {} }; this.completeOnFirstError = !!attrs.completeOnFirstError; + this.errored = false; if (typeof(this.onComplete) !== 'function') { throw new Error('invalid onComplete ' + JSON.stringify(this.onComplete)); @@ -4517,8 +4520,10 @@ getJasmineRequireObj().QueueRunner = function(j$) { cleanup(); if (j$.isError_(err)) { - self.fail(err); - errored = true; + if (!(err instanceof StopExecutionError)) { + self.fail(err); + } + self.errored = errored = true; } function runNext() { @@ -4541,7 +4546,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { next.fail = function nextFail() { self.fail.apply(null, arguments); - errored = true; + self.errored = errored = true; next(); }; @@ -4570,8 +4575,8 @@ getJasmineRequireObj().QueueRunner = function(j$) { return { completedSynchronously: false }; } } catch (e) { - handleException(e, queueableFn); - errored = true; + onException(e); + self.errored = errored = true; } cleanup(); @@ -4579,22 +4584,13 @@ getJasmineRequireObj().QueueRunner = function(j$) { function onException(e) { self.onException(e); - errored = true; + self.errored = errored = true; } function onPromiseRejection(e) { onException(e); next(); } - - function handleException(e, queueableFn) { - onException(e); - if (!self.catchException(e)) { - //TODO: set a var when we catch an exception and - //use a finally block to close the loop in a nice way.. - throw e; - } - } }; QueueRunner.prototype.run = function(recursiveIndex) { @@ -4610,6 +4606,8 @@ getJasmineRequireObj().QueueRunner = function(j$) { return; } + self.errored = result.errored; + if (this.completeOnFirstError && result.errored) { this.skipToCleanup(iterativeIndex); return; @@ -4618,7 +4616,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.clearStack(function() { self.globalErrors.popListener(self.handleFinalError); - self.onComplete(); + self.onComplete(self.errored && new StopExecutionError()); }); }; @@ -4671,7 +4669,8 @@ getJasmineRequireObj().ReportDispatcher = function(j$) { queueRunnerFactory({ queueableFns: fns, - onComplete: onComplete + onComplete: onComplete, + isReporter: true }); } diff --git a/spec/core/QueueRunnerSpec.js b/spec/core/QueueRunnerSpec.js index 15483cfe..0e57ae96 100644 --- a/spec/core/QueueRunnerSpec.js +++ b/spec/core/QueueRunnerSpec.js @@ -158,6 +158,29 @@ describe("QueueRunner", function() { expect(queueableFn2.fn).toHaveBeenCalled(); }); + it("does not cause an explicit fail if execution is being stopped", function() { + var err = new jasmineUnderTest.StopExecutionError('foo'), + queueableFn1 = { fn: function(done) { + setTimeout(function() { done(err); }, 100); + } }, + queueableFn2 = { fn: jasmine.createSpy('fn2') }, + failFn = jasmine.createSpy('fail'), + queueRunner = new jasmineUnderTest.QueueRunner({ + queueableFns: [queueableFn1, queueableFn2], + fail: failFn + }); + + queueRunner.execute(); + + expect(failFn).not.toHaveBeenCalled(); + expect(queueableFn2.fn).not.toHaveBeenCalled(); + + jasmine.clock().tick(100); + + expect(failFn).not.toHaveBeenCalled(); + expect(queueableFn2.fn).toHaveBeenCalled(); + }); + it("sets a timeout if requested for asynchronous functions so they don't go on forever", function() { var timeout = 3, beforeFn = { fn: function(done) { }, type: 'before', timeout: function() { return timeout; } }, @@ -442,20 +465,6 @@ describe("QueueRunner", function() { expect(onExceptionCallback).toHaveBeenCalledWith(jasmine.any(Error)); }); - it("rethrows an exception if told to", function() { - var queueableFn = { fn: function() { - throw new Error('fake error'); - } }, - queueRunner = new jasmineUnderTest.QueueRunner({ - queueableFns: [queueableFn], - catchException: function(e) { return false; } - }); - - expect(function() { - queueRunner.execute(); - }).toThrowError('fake error'); - }); - it("continues running the functions even after an exception is thrown in an async spec", function() { var queueableFn = { fn: function(done) { throw new Error("error"); } }, nextQueueableFn = { fn: jasmine.createSpy("nextFunction") }, diff --git a/spec/core/ReportDispatcherSpec.js b/spec/core/ReportDispatcherSpec.js index 43db5ffa..87511ab7 100644 --- a/spec/core/ReportDispatcherSpec.js +++ b/spec/core/ReportDispatcherSpec.js @@ -21,7 +21,8 @@ describe("ReportDispatcher", function() { dispatcher.foo(123, 456, completeCallback); expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ - queueableFns: [{fn: jasmine.any(Function)}, {fn: jasmine.any(Function)}] + queueableFns: [{fn: jasmine.any(Function)}, {fn: jasmine.any(Function)}], + isReporter: true })); var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; @@ -38,7 +39,8 @@ describe("ReportDispatcher", function() { dispatcher.bar('a', 'b', completeCallback); expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ - queueableFns: [{fn: jasmine.any(Function)}, {fn: jasmine.any(Function)}] + queueableFns: [{fn: jasmine.any(Function)}, {fn: jasmine.any(Function)}], + isReporter: true })); fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; @@ -72,7 +74,8 @@ describe("ReportDispatcher", function() { dispatcher.foo(123, 456, completeCallback); expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ - queueableFns: [{fn: jasmine.any(Function)}] + queueableFns: [{fn: jasmine.any(Function)}], + isReporter: true })); var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; @@ -92,7 +95,8 @@ describe("ReportDispatcher", function() { dispatcher.foo(123, 456, completeCallback); expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ - queueableFns: [{fn: jasmine.any(Function)}] + queueableFns: [{fn: jasmine.any(Function)}], + isReporter: true })); var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; @@ -111,7 +115,8 @@ describe("ReportDispatcher", function() { dispatcher.addReporter(reporter1); dispatcher.foo(123, completeCallback); expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ - queueableFns: [{fn: jasmine.any(Function)}] + queueableFns: [{fn: jasmine.any(Function)}], + isReporter: true })); var fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; @@ -123,7 +128,8 @@ describe("ReportDispatcher", function() { dispatcher.bar(456, completeCallback); expect(queueRunnerFactory).toHaveBeenCalledWith(jasmine.objectContaining({ - queueableFns: [{fn: jasmine.any(Function)}] + queueableFns: [{fn: jasmine.any(Function)}], + isReporter: true })); fns = queueRunnerFactory.calls.mostRecent().args[0].queueableFns; diff --git a/spec/core/SpecSpec.js b/spec/core/SpecSpec.js index d0b8d169..e20ad2bb 100644 --- a/spec/core/SpecSpec.js +++ b/spec/core/SpecSpec.js @@ -157,7 +157,7 @@ describe("Spec", function() { spec.execute('cally-back', true); expect(fakeQueueRunner).toHaveBeenCalledWith(jasmine.objectContaining({ - onComplete: 'cally-back', + onComplete: jasmine.any(Function), queueableFns: [{fn: jasmine.any(Function)}], cleanupFns: [{fn: jasmine.any(Function)}] })); @@ -224,6 +224,23 @@ describe("Spec", function() { expect(done).toHaveBeenCalled(); }); + it("should call the done callback with an error if the spec is failed", function() { + var done = jasmine.createSpy('done callback'), + spec = new jasmineUnderTest.Spec({ + queueableFn: { fn: function() {} }, + catchExceptions: function() { return false; }, + resultCallback: function() {}, + queueRunnerFactory: function(attrs) { + spec.result.status = 'failed'; + attrs.onComplete(); + } + }); + + spec.execute(done); + + expect(done).toHaveBeenCalledWith(jasmine.any(jasmineUnderTest.StopExecutionError)); + }); + it("#status returns passing by default", function() { var spec = new jasmineUnderTest.Spec({queueableFn: { fn: jasmine.createSpy("spec body")} }); expect(spec.status()).toBe('passed'); diff --git a/spec/core/integration/SpecRunningSpec.js b/spec/core/integration/SpecRunningSpec.js index 464bab60..e0d8b2ed 100644 --- a/spec/core/integration/SpecRunningSpec.js +++ b/spec/core/integration/SpecRunningSpec.js @@ -947,4 +947,30 @@ describe("spec running", function () { env.execute(); }); }); + + describe("when stopOnSpecFailure is on", function() { + it("does not run further specs when one fails", function(done) { + var actions = []; + + env.it('fails', function() { + actions.push('fails'); + env.expect(1).toBe(2); + }); + + env.it('does not run', function() { + actions.push('does not run'); + }); + + env.randomizeTests(false); + env.stopOnSpecFailure(true); + + var assertions = function() { + expect(actions).toEqual(['fails']); + done(); + }; + + env.addReporter({ jasmineDone: assertions }); + env.execute(); + }); + }); }); diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index f35985d7..7614f6e1 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -436,8 +436,8 @@ describe("HtmlReporter", function() { }); }); - describe("UI for raising/catching exceptions", function() { - it("should be unchecked if the env is catching", function() { + describe("UI for stop on spec failure", function() { + it("should be unchecked for full execution", function() { var env = new jasmineUnderTest.Env(), container = document.createElement("div"), getContainer = function() { @@ -457,11 +457,11 @@ describe("HtmlReporter", function() { reporter.initialize(); reporter.jasmineDone({}); - var raisingExceptionsUI = container.querySelector(".jasmine-raise"); - expect(raisingExceptionsUI.checked).toBe(false); + var stopOnFailureUI = container.querySelector(".jasmine-fail-fast"); + expect(stopOnFailureUI.checked).toBe(false); }); - it("should be checked if the env is not catching", function() { + it("should be checked if stopping short", function() { var env = new jasmineUnderTest.Env(), container = document.createElement("div"), getContainer = function() { @@ -478,25 +478,26 @@ describe("HtmlReporter", function() { } }); + env.stopOnSpecFailure(true); + reporter.initialize(); - env.catchExceptions(false); reporter.jasmineDone({}); - var raisingExceptionsUI = container.querySelector(".jasmine-raise"); - expect(raisingExceptionsUI.checked).toBe(true); + var stopOnFailureUI = container.querySelector(".jasmine-fail-fast"); + expect(stopOnFailureUI.checked).toBe(true); }); - it("should affect the query param for catching exceptions", function() { + it("should trigger the callback when changed", function() { var env = new jasmineUnderTest.Env(), container = document.createElement("div"), - exceptionsClickHandler = jasmine.createSpy("raise exceptions checked"), + failFastHandler = jasmine.createSpy('failFast'), getContainer = function() { return container; }, reporter = new jasmineUnderTest.HtmlReporter({ env: env, + onStopExecutionClick: failFastHandler, getContainer: getContainer, - onRaiseExceptionsClick: exceptionsClickHandler, createElement: function() { return document.createElement.apply(document, arguments); }, @@ -505,12 +506,15 @@ describe("HtmlReporter", function() { } }); + env.stopOnSpecFailure(true); + reporter.initialize(); reporter.jasmineDone({}); - var input = container.querySelector(".jasmine-raise"); - input.click(); - expect(exceptionsClickHandler).toHaveBeenCalled(); + var stopOnFailureUI = container.querySelector(".jasmine-fail-fast"); + stopOnFailureUI.click(); + + expect(failFastHandler).toHaveBeenCalled(); }); }); diff --git a/src/core/Env.js b/src/core/Env.js index 21f7d760..74d18a6d 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -13,8 +13,6 @@ getJasmineRequireObj().Env = function(j$) { var totalSpecsDefined = 0; - var catchExceptions = true; - var realSetTimeout = j$.getGlobal().setTimeout; var realClearTimeout = j$.getGlobal().clearTimeout; var clearStack = j$.getClearStack(j$.getGlobal()); @@ -26,6 +24,7 @@ getJasmineRequireObj().Env = function(j$) { var currentlyExecutingSuites = []; var currentDeclarationSuite = null; var throwOnExpectationFailure = false; + var stopOnSpecFailure = false; var random = true; var seed = null; var handlingLoadErrors = true; @@ -160,23 +159,9 @@ getJasmineRequireObj().Env = function(j$) { return buildExpectationResult(attrs); }; - // TODO: fix this naming, and here's where the value comes in - this.catchExceptions = function(value) { - catchExceptions = !!value; - return catchExceptions; - }; - - this.catchingExceptions = function() { - return catchExceptions; - }; - var maximumSpecCallbackDepth = 20; var currentSpecCallbackDepth = 0; - var catchException = function(e) { - return j$.Spec.isPendingSpecException(e) || catchExceptions; - }; - this.throwOnExpectationFailure = function(value) { throwOnExpectationFailure = !!value; }; @@ -185,6 +170,14 @@ getJasmineRequireObj().Env = function(j$) { return throwOnExpectationFailure; }; + this.stopOnSpecFailure = function(value) { + stopOnSpecFailure = !!value; + }; + + this.stoppingOnSpecFailure = function() { + return stopOnSpecFailure; + }; + this.randomizeTests = function(value) { random = !!value; }; @@ -208,12 +201,17 @@ getJasmineRequireObj().Env = function(j$) { }; var queueRunnerFactory = function(options, args) { - options.catchException = catchException; + var failFast = false; + if (options.isLeaf) { + failFast = throwOnExpectationFailure; + } else if (!options.isReporter) { + failFast = stopOnSpecFailure; + } options.clearStack = options.clearStack || clearStack; options.timeout = {setTimeout: realSetTimeout, clearTimeout: realClearTimeout}; options.fail = self.fail; options.globalErrors = globalErrors; - options.completeOnFirstError = throwOnExpectationFailure && options.isLeaf; + options.completeOnFirstError = failFast; options.onException = options.onException || function(e) { (currentRunnable() || topSuite).onException(e); }; diff --git a/src/core/QueueRunner.js b/src/core/QueueRunner.js index 10a88f10..71082efb 100644 --- a/src/core/QueueRunner.js +++ b/src/core/QueueRunner.js @@ -1,4 +1,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { + function StopExecutionError() {} + StopExecutionError.prototype = new Error(); + j$.StopExecutionError = StopExecutionError; function once(fn) { var called = false; @@ -18,12 +21,12 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.onComplete = attrs.onComplete || function() {}; this.clearStack = attrs.clearStack || function(fn) {fn();}; this.onException = attrs.onException || function() {}; - this.catchException = attrs.catchException || function() { return true; }; this.userContext = attrs.userContext || new j$.UserContext(); this.timeout = attrs.timeout || {setTimeout: setTimeout, clearTimeout: clearTimeout}; this.fail = attrs.fail || function() {}; this.globalErrors = attrs.globalErrors || { pushListener: function() {}, popListener: function() {} }; this.completeOnFirstError = !!attrs.completeOnFirstError; + this.errored = false; if (typeof(this.onComplete) !== 'function') { throw new Error('invalid onComplete ' + JSON.stringify(this.onComplete)); @@ -69,8 +72,10 @@ getJasmineRequireObj().QueueRunner = function(j$) { cleanup(); if (j$.isError_(err)) { - self.fail(err); - errored = true; + if (!(err instanceof StopExecutionError)) { + self.fail(err); + } + self.errored = errored = true; } function runNext() { @@ -93,7 +98,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { next.fail = function nextFail() { self.fail.apply(null, arguments); - errored = true; + self.errored = errored = true; next(); }; @@ -122,8 +127,8 @@ getJasmineRequireObj().QueueRunner = function(j$) { return { completedSynchronously: false }; } } catch (e) { - handleException(e, queueableFn); - errored = true; + onException(e); + self.errored = errored = true; } cleanup(); @@ -131,22 +136,13 @@ getJasmineRequireObj().QueueRunner = function(j$) { function onException(e) { self.onException(e); - errored = true; + self.errored = errored = true; } function onPromiseRejection(e) { onException(e); next(); } - - function handleException(e, queueableFn) { - onException(e); - if (!self.catchException(e)) { - //TODO: set a var when we catch an exception and - //use a finally block to close the loop in a nice way.. - throw e; - } - } }; QueueRunner.prototype.run = function(recursiveIndex) { @@ -162,6 +158,8 @@ getJasmineRequireObj().QueueRunner = function(j$) { return; } + self.errored = result.errored; + if (this.completeOnFirstError && result.errored) { this.skipToCleanup(iterativeIndex); return; @@ -170,7 +168,7 @@ getJasmineRequireObj().QueueRunner = function(j$) { this.clearStack(function() { self.globalErrors.popListener(self.handleFinalError); - self.onComplete(); + self.onComplete(self.errored && new StopExecutionError()); }); }; diff --git a/src/core/ReportDispatcher.js b/src/core/ReportDispatcher.js index a8d0d576..5b2fad25 100644 --- a/src/core/ReportDispatcher.js +++ b/src/core/ReportDispatcher.js @@ -43,7 +43,8 @@ getJasmineRequireObj().ReportDispatcher = function(j$) { queueRunnerFactory({ queueableFns: fns, - onComplete: onComplete + onComplete: onComplete, + isReporter: true }); } diff --git a/src/core/Spec.js b/src/core/Spec.js index b9c9b7e8..5732b2aa 100644 --- a/src/core/Spec.js +++ b/src/core/Spec.js @@ -82,7 +82,9 @@ getJasmineRequireObj().Spec = function(j$) { onException: function () { self.onException.apply(self, arguments); }, - onComplete: onComplete, + onComplete: function() { + onComplete(self.result.status === 'failed' && new j$.StopExecutionError('spec failed')); + }, userContext: this.userContext() }; diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index 5ca871b2..9bb94ed2 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -55,7 +55,7 @@ jasmineRequire.HtmlReporter = function(j$) { getContainer = options.getContainer, createElement = options.createElement, createTextNode = options.createTextNode, - onRaiseExceptionsClick = options.onRaiseExceptionsClick || function() {}, + onStopExecutionClick = options.onStopExecutionClick || function() {}, onThrowExpectationsClick = options.onThrowExpectationsClick || function() {}, onRandomClick = options.onRandomClick || function() {}, addToExistingQueryString = options.addToExistingQueryString || defaultQueryString, @@ -292,13 +292,13 @@ jasmineRequire.HtmlReporter = function(j$) { var optionsMenuDom = createDom('div', { className: 'jasmine-run-options' }, createDom('span', { className: 'jasmine-trigger' }, 'Options'), createDom('div', { className: 'jasmine-payload' }, - createDom('div', { className: 'jasmine-exceptions' }, + createDom('div', { className: 'jasmine-stop-on-failure' }, createDom('input', { - className: 'jasmine-raise', - id: 'jasmine-raise-exceptions', + className: 'jasmine-fail-fast', + id: 'jasmine-fail-fast', type: 'checkbox' }), - createDom('label', { className: 'jasmine-label', 'for': 'jasmine-raise-exceptions' }, 'raise exceptions')), + createDom('label', { className: 'jasmine-label', 'for': 'jasmine-fail-fast' }, 'stop execution on spec failure')), createDom('div', { className: 'jasmine-throw-failures' }, createDom('input', { className: 'jasmine-throw', @@ -316,10 +316,9 @@ jasmineRequire.HtmlReporter = function(j$) { ) ); - var raiseCheckbox = optionsMenuDom.querySelector('#jasmine-raise-exceptions'); - - raiseCheckbox.checked = !env.catchingExceptions(); - raiseCheckbox.onclick = onRaiseExceptionsClick; + var failFastCheckbox = optionsMenuDom.querySelector('#jasmine-fail-fast'); + failFastCheckbox.checked = env.stoppingOnSpecFailure(); + failFastCheckbox.onclick = onStopExecutionClick; var throwCheckbox = optionsMenuDom.querySelector('#jasmine-throw-failures'); throwCheckbox.checked = env.throwingExpectationFailures(); From 84953ec210500869d7980bf5ff6407ec5442519b Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Tue, 30 Jan 2018 11:48:25 -0800 Subject: [PATCH 59/62] stop spec on failure should not default to true --- lib/jasmine-core/boot.js | 2 +- lib/jasmine-core/boot/boot.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/jasmine-core/boot.js b/lib/jasmine-core/boot.js index 74dfd70d..e96a978d 100644 --- a/lib/jasmine-core/boot.js +++ b/lib/jasmine-core/boot.js @@ -74,7 +74,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. var filterSpecs = !!queryString.getParam("spec"); var stoppingOnSpecFailure = queryString.getParam("failFast"); - env.stopOnSpecFailure(typeof stoppingOnSpecFailure === "undefined" ? true : stoppingOnSpecFailure); + env.stopOnSpecFailure(stoppingOnSpecFailure); var throwingExpectationFailures = queryString.getParam("throwFailures"); env.throwOnExpectationFailure(throwingExpectationFailures); diff --git a/lib/jasmine-core/boot/boot.js b/lib/jasmine-core/boot/boot.js index 00a65830..290dce53 100644 --- a/lib/jasmine-core/boot/boot.js +++ b/lib/jasmine-core/boot/boot.js @@ -52,7 +52,7 @@ var filterSpecs = !!queryString.getParam("spec"); var stoppingOnSpecFailure = queryString.getParam("failFast"); - env.stopOnSpecFailure(typeof stoppingOnSpecFailure === "undefined" ? true : stoppingOnSpecFailure); + env.stopOnSpecFailure(stoppingOnSpecFailure); var throwingExpectationFailures = queryString.getParam("throwFailures"); env.throwOnExpectationFailure(throwingExpectationFailures); From 0eca06a33b751c5d5fd635757e2c03dd0cee0d14 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Tue, 30 Jan 2018 14:30:28 -0800 Subject: [PATCH 60/62] Now pass a navigation helper to HtmlReporter instead of click handlers --- lib/jasmine-core/boot.js | 4 +- lib/jasmine-core/boot/boot.js | 4 +- lib/jasmine-core/jasmine-html.js | 16 +++-- spec/html/HtmlReporterSpec.js | 112 +++++++++++++++++++++++++++---- src/html/HtmlReporter.js | 16 +++-- 5 files changed, 122 insertions(+), 30 deletions(-) diff --git a/lib/jasmine-core/boot.js b/lib/jasmine-core/boot.js index e96a978d..86dfd74a 100644 --- a/lib/jasmine-core/boot.js +++ b/lib/jasmine-core/boot.js @@ -96,9 +96,7 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ var htmlReporter = new jasmine.HtmlReporter({ env: env, - onStopExecutionClick: function() { queryString.navigateWithNewParam("failFast", !env.stoppingOnSpecFailure()); }, - onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); }, - onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); }, + navigateWithNewParam: function(key, value) { return queryString.navigateWithNewParam(key, value); }, addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, getContainer: function() { return document.body; }, createElement: function() { return document.createElement.apply(document, arguments); }, diff --git a/lib/jasmine-core/boot/boot.js b/lib/jasmine-core/boot/boot.js index 290dce53..57f52e5e 100644 --- a/lib/jasmine-core/boot/boot.js +++ b/lib/jasmine-core/boot/boot.js @@ -74,9 +74,7 @@ */ var htmlReporter = new jasmine.HtmlReporter({ env: env, - onStopExecutionClick: function() { queryString.navigateWithNewParam("failFast", !env.stoppingOnSpecFailure()); }, - onThrowExpectationsClick: function() { queryString.navigateWithNewParam("throwFailures", !env.throwingExpectationFailures()); }, - onRandomClick: function() { queryString.navigateWithNewParam("random", !env.randomTests()); }, + navigateWithNewParam: function(key, value) { return queryString.navigateWithNewParam(key, value); }, addToExistingQueryString: function(key, value) { return queryString.fullStringWithNewParam(key, value); }, getContainer: function() { return document.body; }, createElement: function() { return document.createElement.apply(document, arguments); }, diff --git a/lib/jasmine-core/jasmine-html.js b/lib/jasmine-core/jasmine-html.js index b01d0a22..14cf7a0a 100644 --- a/lib/jasmine-core/jasmine-html.js +++ b/lib/jasmine-core/jasmine-html.js @@ -85,9 +85,7 @@ jasmineRequire.HtmlReporter = function(j$) { getContainer = options.getContainer, createElement = options.createElement, createTextNode = options.createTextNode, - onStopExecutionClick = options.onStopExecutionClick || function() {}, - onThrowExpectationsClick = options.onThrowExpectationsClick || function() {}, - onRandomClick = options.onRandomClick || function() {}, + navigateWithNewParam = options.navigateWithNewParam || function() {}, addToExistingQueryString = options.addToExistingQueryString || defaultQueryString, filterSpecs = options.filterSpecs, timer = options.timer || noopTimer, @@ -348,15 +346,21 @@ jasmineRequire.HtmlReporter = function(j$) { var failFastCheckbox = optionsMenuDom.querySelector('#jasmine-fail-fast'); failFastCheckbox.checked = env.stoppingOnSpecFailure(); - failFastCheckbox.onclick = onStopExecutionClick; + failFastCheckbox.onclick = function() { + navigateWithNewParam('failFast', !env.stoppingOnSpecFailure()); + }; var throwCheckbox = optionsMenuDom.querySelector('#jasmine-throw-failures'); throwCheckbox.checked = env.throwingExpectationFailures(); - throwCheckbox.onclick = onThrowExpectationsClick; + throwCheckbox.onclick = function() { + navigateWithNewParam('throwFailures', !env.throwingExpectationFailures()); + }; var randomCheckbox = optionsMenuDom.querySelector('#jasmine-random-order'); randomCheckbox.checked = env.randomTests(); - randomCheckbox.onclick = onRandomClick; + randomCheckbox.onclick = function() { + navigateWithNewParam('random', !env.randomTests()); + }; var optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'), optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'), diff --git a/spec/html/HtmlReporterSpec.js b/spec/html/HtmlReporterSpec.js index 7614f6e1..0307d7f6 100644 --- a/spec/html/HtmlReporterSpec.js +++ b/spec/html/HtmlReporterSpec.js @@ -487,16 +487,44 @@ describe("HtmlReporter", function() { expect(stopOnFailureUI.checked).toBe(true); }); - it("should trigger the callback when changed", function() { + it("should navigate and turn the setting on", function() { var env = new jasmineUnderTest.Env(), container = document.createElement("div"), - failFastHandler = jasmine.createSpy('failFast'), + navigationHandler = jasmine.createSpy('navigate'), getContainer = function() { return container; }, reporter = new jasmineUnderTest.HtmlReporter({ env: env, - onStopExecutionClick: failFastHandler, + navigateWithNewParam: navigationHandler, + getContainer: getContainer, + createElement: function() { + return document.createElement.apply(document, arguments); + }, + createTextNode: function() { + return document.createTextNode.apply(document, arguments); + } + }); + + reporter.initialize(); + reporter.jasmineDone({}); + + var stopOnFailureUI = container.querySelector(".jasmine-fail-fast"); + stopOnFailureUI.click(); + + expect(navigationHandler).toHaveBeenCalledWith('failFast', true); + }); + + it("should navigate and turn the setting off", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + navigationHandler = jasmine.createSpy('navigate'), + getContainer = function() { + return container; + }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + navigateWithNewParam: navigationHandler, getContainer: getContainer, createElement: function() { return document.createElement.apply(document, arguments); @@ -514,7 +542,7 @@ describe("HtmlReporter", function() { var stopOnFailureUI = container.querySelector(".jasmine-fail-fast"); stopOnFailureUI.click(); - expect(failFastHandler).toHaveBeenCalled(); + expect(navigationHandler).toHaveBeenCalledWith('failFast', false); }); }); @@ -569,17 +597,17 @@ describe("HtmlReporter", function() { expect(throwingExpectationsUI.checked).toBe(true); }); - it("should affect the query param for throw expectation failures", function() { + it("should navigate and change the setting to on", function() { var env = new jasmineUnderTest.Env(), container = document.createElement("div"), - throwingExceptionHandler = jasmine.createSpy('throwingExceptions'), + navigateHandler = jasmine.createSpy('navigate'), getContainer = function() { return container; }, reporter = new jasmineUnderTest.HtmlReporter({ env: env, getContainer: getContainer, - onThrowExpectationsClick: throwingExceptionHandler, + navigateWithNewParam: navigateHandler, createElement: function() { return document.createElement.apply(document, arguments); }, @@ -594,7 +622,37 @@ describe("HtmlReporter", function() { var throwingExpectationsUI = container.querySelector(".jasmine-throw"); throwingExpectationsUI.click(); - expect(throwingExceptionHandler).toHaveBeenCalled(); + expect(navigateHandler).toHaveBeenCalledWith('throwFailures', true); + }); + + it("should navigate and change the setting to off", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + navigateHandler = jasmine.createSpy('navigate'), + getContainer = function() { + return container; + }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: getContainer, + navigateWithNewParam: navigateHandler, + 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(".jasmine-throw"); + throwingExpectationsUI.click(); + + expect(navigateHandler).toHaveBeenCalledWith('throwFailures', false); }); }); @@ -649,17 +707,17 @@ describe("HtmlReporter", function() { expect(randomUI.checked).toBe(true); }); - it("should affect the query param for random tests", function() { + it("should navigate and change the setting to on", function() { var env = new jasmineUnderTest.Env(), container = document.createElement("div"), - randomHandler = jasmine.createSpy('randomHandler'), + navigateHandler = jasmine.createSpy('navigate'), getContainer = function() { return container; }, reporter = new jasmineUnderTest.HtmlReporter({ env: env, getContainer: getContainer, - onRandomClick: randomHandler, + navigateWithNewParam: navigateHandler, createElement: function() { return document.createElement.apply(document, arguments); }, @@ -668,13 +726,43 @@ describe("HtmlReporter", function() { } }); + env.randomizeTests(false); reporter.initialize(); reporter.jasmineDone({}); var randomUI = container.querySelector(".jasmine-random"); randomUI.click(); - expect(randomHandler).toHaveBeenCalled(); + expect(navigateHandler).toHaveBeenCalledWith('random', true); + }); + + it("should navigate and change the setting to off", function() { + var env = new jasmineUnderTest.Env(), + container = document.createElement("div"), + navigateHandler = jasmine.createSpy('navigate'), + getContainer = function() { + return container; + }, + reporter = new jasmineUnderTest.HtmlReporter({ + env: env, + getContainer: getContainer, + navigateWithNewParam: navigateHandler, + createElement: function() { + return document.createElement.apply(document, arguments); + }, + createTextNode: function() { + return document.createTextNode.apply(document, arguments); + } + }); + + env.randomizeTests(true); + reporter.initialize(); + reporter.jasmineDone({}); + + var randomUI = container.querySelector(".jasmine-random"); + randomUI.click(); + + expect(navigateHandler).toHaveBeenCalledWith('random', false); }); it("should show the seed bar if randomizing", function() { diff --git a/src/html/HtmlReporter.js b/src/html/HtmlReporter.js index 9bb94ed2..bef88a20 100644 --- a/src/html/HtmlReporter.js +++ b/src/html/HtmlReporter.js @@ -55,9 +55,7 @@ jasmineRequire.HtmlReporter = function(j$) { getContainer = options.getContainer, createElement = options.createElement, createTextNode = options.createTextNode, - onStopExecutionClick = options.onStopExecutionClick || function() {}, - onThrowExpectationsClick = options.onThrowExpectationsClick || function() {}, - onRandomClick = options.onRandomClick || function() {}, + navigateWithNewParam = options.navigateWithNewParam || function() {}, addToExistingQueryString = options.addToExistingQueryString || defaultQueryString, filterSpecs = options.filterSpecs, timer = options.timer || noopTimer, @@ -318,15 +316,21 @@ jasmineRequire.HtmlReporter = function(j$) { var failFastCheckbox = optionsMenuDom.querySelector('#jasmine-fail-fast'); failFastCheckbox.checked = env.stoppingOnSpecFailure(); - failFastCheckbox.onclick = onStopExecutionClick; + failFastCheckbox.onclick = function() { + navigateWithNewParam('failFast', !env.stoppingOnSpecFailure()); + }; var throwCheckbox = optionsMenuDom.querySelector('#jasmine-throw-failures'); throwCheckbox.checked = env.throwingExpectationFailures(); - throwCheckbox.onclick = onThrowExpectationsClick; + throwCheckbox.onclick = function() { + navigateWithNewParam('throwFailures', !env.throwingExpectationFailures()); + }; var randomCheckbox = optionsMenuDom.querySelector('#jasmine-random-order'); randomCheckbox.checked = env.randomTests(); - randomCheckbox.onclick = onRandomClick; + randomCheckbox.onclick = function() { + navigateWithNewParam('random', !env.randomTests()); + }; var optionsTrigger = optionsMenuDom.querySelector('.jasmine-trigger'), optionsPayload = optionsMenuDom.querySelector('.jasmine-payload'), From 42e00b3bc567527e96a31049411cb3ac25f0d4fa Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Wed, 31 Jan 2018 14:23:32 -0800 Subject: [PATCH 61/62] Remove node modules from python wheel, and update languages [finishes #87860474] --- MANIFEST.in | 1 + setup.py | 3 --- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 4d58eed7..122cd026 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ recursive-include . *.py +prune node_modules include lib/jasmine-core/*.js include lib/jasmine-core/*.css include images/*.png diff --git a/setup.py b/setup.py index ad602a41..d258ccfe 100644 --- a/setup.py +++ b/setup.py @@ -23,9 +23,6 @@ setup( 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.6', - 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', From d4b76a4d3bc5e293c570d18fff5c88b9f2a7a2d3 Mon Sep 17 00:00:00 2001 From: Gregg Van Hove Date: Tue, 6 Feb 2018 09:33:49 -0800 Subject: [PATCH 62/62] version bump to 3.0 --- lib/jasmine-core/jasmine.js | 4 +- lib/jasmine-core/version.rb | 2 +- package.json | 2 +- release_notes/3.0.md | 82 +++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 release_notes/3.0.md diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 23bb6861..50cb7c17 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -1231,7 +1231,6 @@ getJasmineRequireObj().Env = function(j$) { var focusedRunnables = []; this.fdescribe = function(description, specDefinitions) { - this.deprecated('fit and fdescribe will cause your suite to report an \'incomplete\' status in Jasmine 3.0'); ensureIsNotNested('fdescribe'); ensureIsFunction(specDefinitions, 'fdescribe'); var suite = suiteFactory(description); @@ -1356,7 +1355,6 @@ getJasmineRequireObj().Env = function(j$) { }; this.fit = function(description, fn, timeout){ - this.deprecated('fit and fdescribe will cause your suite to report an \'incomplete\' status in Jasmine 3.0'); ensureIsNotNested('fit'); ensureIsFunctionOrAsync(fn, 'fit'); var spec = specFactory(description, fn, currentDeclarationSuite, timeout); @@ -6022,5 +6020,5 @@ getJasmineRequireObj().UserContext = function(j$) { }; getJasmineRequireObj().version = function() { - return '3.0.0-pre'; + return '3.0.0'; }; diff --git a/lib/jasmine-core/version.rb b/lib/jasmine-core/version.rb index 0bbc361e..56e062fe 100644 --- a/lib/jasmine-core/version.rb +++ b/lib/jasmine-core/version.rb @@ -4,6 +4,6 @@ # module Jasmine module Core - VERSION = "3.0.0.pre" + VERSION = "3.0.0" end end diff --git a/package.json b/package.json index 4c612f2c..aac12271 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "jasmine-core", "license": "MIT", - "version": "3.0.0-pre", + "version": "3.0.0", "repository": { "type": "git", "url": "https://github.com/jasmine/jasmine.git" diff --git a/release_notes/3.0.md b/release_notes/3.0.md new file mode 100644 index 00000000..ce4c7c4c --- /dev/null +++ b/release_notes/3.0.md @@ -0,0 +1,82 @@ +# Jasmine-Core 3.0 Release Notes + +## Summary + +Jasmine 3.0 is a major release of Jasmine, and as such includes some breaking changes in addition to various new features. + +There is also a 2.99 release of Jasmine that will present deprecation warnings for suites that will encounter different behavior in 3.0. + +## Breaking Changes + +* Replace old "catch exceptions" logic with proper fail fast with error reporting + - Fixes [#414](https://github.com/jasmine/jasmine/issues/414) + - Fixes [jasmine/jasmine-npm#16](https://github.com/jasmine/jasmine/jasmine-npm/issues/16) + +* Detect an Error passed to `done` and add an expectation failure + - Fixes [#567](https://github.com/jasmine/jasmine/issues/567) + +* Unify status for xdescribe and xit + - Ensure *All's only execute if at least one child will run + - Specs will report a status of `excluded` instead of disabled + - Fixes [#1418](https://github.com/jasmine/jasmine/issues/1418) + +* Suite level errors all report the same way (on suiteDone) + +* Refactor QueueRunner and remove references to functions that Jasmine is done with + +* expect(null).toEqual(jasmine.any(Object)) no longer passes + - Fixes [#1255](https://github.com/jasmine/jasmine/issues/1255) + +* Default to running tests in random order + +## Changes + +* Remove node modules from python wheel, and update languages + +* Allow reporter callbacks to be asynchronous + - Fixes [#842](https://github.com/jasmine/jasmine/issues/842) + +* Allow adding custom spy strategies + +* Add the ability to specify the strategy to use for a spy based on which parameters are passed + +* Added links to re-run the suites containing a failing spec + +* Added a toHaveClass matcher + +* More informative pretty-printing of DOM elements + +* Allow jasmine-npm to handle its own load errors + +* Treat random= as a no-op rather than disabling randomization + +* Use prototype for spy strategy for better memory management + +* Remove console.js altogether + +* Add safari 10 and update readme to include edge + +* Determine overall status in core, not reporters + +* Filter Jasmine frames from stack traces + +* Treat afterAll errors at any level as failures + +* Improved reporting of load errors and afterAll errors + - Pass file and line number to reporters when present + - Show file and line number in the HTML reporter when present + - Visually separate adjacent errors in the HTML reporter + +* Report loading errors as loading errors, not afterAll errors + +* HTML reporter reports overall failure if there are any global errors + +* Fail if error events (e.g. syntax errors) occur during loading + +* Allow use of a predicate function to validate thrown exceptions + +* Check truthiness of toThrowError args, not arg count + +------ + +_Release Notes generated with _[Anchorman](http://github.com/infews/anchorman)_