diff --git a/lib/jasmine-core/jasmine.js b/lib/jasmine-core/jasmine.js index 57372e93..b6bd9040 100644 --- a/lib/jasmine-core/jasmine.js +++ b/lib/jasmine-core/jasmine.js @@ -60,8 +60,8 @@ var getJasmineRequireObj = (function (jasmineGlobal) { j$.StackTrace = jRequire.StackTrace(j$); j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$); j$.ExpectationFilterChain = jRequire.ExpectationFilterChain(); + j$.Expector = jRequire.Expector(j$); j$.Expectation = jRequire.Expectation(j$); - j$.AsyncExpectation = jRequire.AsyncExpectation(j$); j$.buildExpectationResult = jRequire.buildExpectationResult(); j$.JsApiReporter = jRequire.JsApiReporter(); j$.matchersUtil = jRequire.matchersUtil(j$); @@ -970,7 +970,7 @@ getJasmineRequireObj().Env = function(j$) { }; j$.Expectation.addCoreMatchers(j$.matchers); - j$.AsyncExpectation.addCoreMatchers(j$.asyncMatchers); + j$.Expectation.addAsyncCoreMatchers(j$.asyncMatchers); var nextSpecId = 0; var getNextSpecId = function() { @@ -983,7 +983,7 @@ getJasmineRequireObj().Env = function(j$) { }; var expectationFactory = function(actual, spec) { - return j$.Expectation.Factory({ + return j$.Expectation.factory({ util: j$.matchersUtil, customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, @@ -997,7 +997,7 @@ getJasmineRequireObj().Env = function(j$) { }; var asyncExpectationFactory = function(actual, spec) { - return j$.AsyncExpectation.factory({ + return j$.Expectation.asyncFactory({ util: j$.matchersUtil, customEqualityTesters: runnableResources[spec.id].customEqualityTesters, actual: actual, @@ -2146,141 +2146,6 @@ getJasmineRequireObj().Truthy = function(j$) { return Truthy; }; -getJasmineRequireObj().AsyncExpectation = function(j$) { - var promiseForMessage = { - jasmineToString: function() { return 'a promise'; } - }; - - /** - * Asynchronous matchers. - * @namespace async-matchers - */ - function AsyncExpectation(options) { - var global = options.global || j$.getGlobal(); - this.util = options.util || { buildFailureMessage: function() {} }; - this.customEqualityTesters = options.customEqualityTesters || []; - this.addExpectationResult = options.addExpectationResult || function(){}; - this.actual = options.actual; - this.filters = new j$.ExpectationFilterChain(); - - if (!global.Promise) { - throw new Error('expectAsync is unavailable because the environment does not support promises.'); - } - - if (!j$.isPromiseLike(this.actual)) { - throw new Error('Expected expectAsync to be called with a promise.'); - } - } - - function wrapCompare(name, matcherFactory) { - return function() { - var self = this; - var args = Array.prototype.slice.call(arguments), - expected = args.slice(0); - - args.unshift(this.actual); - - // Capture the call stack here, before we go async, so that it will - // contain frames that are relevant to the user instead of just parts - // of Jasmine. - var errorForStack = j$.util.errorWithStack(); - - var matcherCompare = this.instantiateMatcher(matcherFactory); - - return matcherCompare.apply(self, args).then(function(result) { - args[0] = promiseForMessage; - self.processResult(result, name, expected, args, errorForStack); - }); - }; - } - - AsyncExpectation.prototype.processResult = function(result, name, expected, args, errorForStack) { - var message = this.buildMessage(result, name, args); - - if (expected.length === 1) { - expected = expected[0]; - } - - this.addExpectationResult( - result.pass, - { - matcherName: name, - passed: result.pass, - message: message, - error: undefined, - errorForStack: errorForStack, - actual: this.actual, - expected: expected // TODO: this may need to be arrayified/sliced - } - ); - }; - - AsyncExpectation.prototype.buildMessage = j$.Expectation.prototype.buildMessage; - - AsyncExpectation.prototype.instantiateMatcher = j$.Expectation.prototype.instantiateMatcher; - - AsyncExpectation.prototype.addFilter = j$.Expectation.prototype.addFilter; - - AsyncExpectation.addCoreMatchers = function(matchers) { - var prototype = AsyncExpectation.prototype; - for (var matcherName in matchers) { - var matcher = matchers[matcherName]; - prototype[matcherName] = wrapCompare(matcherName, matcher); - } - }; - - AsyncExpectation.factory = function(options) { - var expect = new AsyncExpectation(options); - expect.not = expect.addFilter(negatingFilter); - - expect.withContext = function(message) { - var result = this.addFilter(new ContextAddingFilter(message)); - result.not = result.addFilter(negatingFilter); - return result; - }; - - return expect; - }; - - var negatingFilter = { - selectComparisonFunc: function(matcher) { - function defaultNegativeCompare() { - return matcher.compare.apply(this, arguments).then(function(result) { - result.pass = !result.pass; - return result; - }); - } - - return defaultNegativeCompare; - }, - buildFailureMessage: function(result, matcherName, args, util) { - if (result.message) { - if (j$.isFunction_(result.message)) { - return result.message(); - } else { - return result.message; - } - } - - args = args.slice(); - args.unshift(true); - args.unshift(matcherName); - return util.buildFailureMessage.apply(null, args); - } - }; - - - function ContextAddingFilter(message) { - this.message = message; - } - - ContextAddingFilter.prototype.modifyFailureMessage = function(msg) { - return this.message + ': ' + msg; - }; - - return AsyncExpectation; -}; - getJasmineRequireObj().CallTracker = function(j$) { /** @@ -2919,141 +2784,135 @@ getJasmineRequireObj().ExceptionFormatter = function(j$) { }; getJasmineRequireObj().Expectation = function(j$) { + var promiseForMessage = { + jasmineToString: function() { return 'a promise'; } + }; /** * Matchers that come with Jasmine out of the box. * @namespace matchers */ function Expectation(options) { - this.util = options.util || { buildFailureMessage: function() {} }; - this.customEqualityTesters = options.customEqualityTesters || []; - this.actual = options.actual; - this.addExpectationResult = options.addExpectationResult || function(){}; - this.filters = new j$.ExpectationFilterChain(); + this.expector = new j$.Expector(options); var customMatchers = options.customMatchers || {}; for (var matcherName in customMatchers) { - this[matcherName] = wrapCompare(matcherName, customMatchers[matcherName]); + this[matcherName] = wrapSyncCompare(matcherName, customMatchers[matcherName]); } } - function wrapCompare(name, matcherFactory) { + Expectation.prototype.withContext = function withContext(message) { + return addFilter(this, new ContextAddingFilter(message)); + }; + + Object.defineProperty(Expectation.prototype, 'not', { + get: function() { + return addFilter(this, syncNegatingFilter); + } + }); + + /** + * Asynchronous matchers. + * @namespace async-matchers + */ + function AsyncExpectation(options) { + var global = options.global || j$.getGlobal(); + this.expector = new j$.Expector(options); + + if (!global.Promise) { + throw new Error('expectAsync is unavailable because the environment does not support promises.'); + } + + if (!j$.isPromiseLike(this.expector.actual)) { + throw new Error('Expected expectAsync to be called with a promise.'); + } + } + + AsyncExpectation.prototype.withContext = function withContext(message) { + return addFilter(this, new ContextAddingFilter(message)); + }; + + Object.defineProperty(AsyncExpectation.prototype, 'not', { + get: function() { + return addFilter(this, asyncNegatingFilter); + } + }); + + function wrapSyncCompare(name, matcherFactory) { return function() { - var args = Array.prototype.slice.call(arguments, 0), - expected = args.slice(0); - - args.unshift(this.actual); - - var matcherCompare = this.instantiateMatcher(matcherFactory); - var result = matcherCompare.apply(null, args); - this.processResult(result, name, expected, args); + var result = this.expector.compare(name, matcherFactory, arguments); + this.expector.processResult(result); }; } - Expectation.prototype.instantiateMatcher = function(matcherFactory) { - var matcher = matcherFactory(this.util, this.customEqualityTesters); - var comparisonFunc = this.filters.selectComparisonFunc(matcher); - return comparisonFunc || matcher.compare; - }; + function wrapAsyncCompare(name, matcherFactory) { + return function() { + var self = this; - Expectation.prototype.processResult = function(result, name, expected, args) { - var message = this.buildMessage(result, name, args); + // Capture the call stack here, before we go async, so that it will contain + // frames that are relevant to the user instead of just parts of Jasmine. + var errorForStack = j$.util.errorWithStack(); - if (expected.length === 1) { - expected = expected[0]; + return this.expector.compare(name, matcherFactory, arguments).then(function(result) { + self.expector.processResult(result, errorForStack, promiseForMessage); + }); + }; + } + + function addCoreMatchers(prototype, matchers, wrapper) { + for (var matcherName in matchers) { + var matcher = matchers[matcherName]; + prototype[matcherName] = wrapper(matcherName, matcher); } + } - this.addExpectationResult( - result.pass, - { - matcherName: name, - passed: result.pass, - message: message, - error: result.error, - actual: this.actual, - expected: expected // TODO: this may need to be arrayified/sliced - } - ); - }; + function addFilter(source, filter) { + var result = Object.create(source); + result.expector = source.expector.addFilter(filter); + return result; + } - Expectation.prototype.buildMessage = function(result, name, args) { - var util = this.util; - - if (result.pass) { - return ''; - } - - var msg = this.filters.buildFailureMessage(result, name, args, util, defaultMessage); - return this.filters.modifyFailureMessage(msg || defaultMessage()); - - function defaultMessage() { - if (!result.message) { - args = args.slice(); - args.unshift(false); - args.unshift(name); - return util.buildFailureMessage.apply(null, args); - } else if (j$.isFunction_(result.message)) { + function negatedFailureMessage(result, matcherName, args, util) { + if (result.message) { + if (j$.isFunction_(result.message)) { return result.message(); } else { return result.message; } } - }; - Expectation.prototype.addFilter = function(filter) { - var result = Object.create(this); - result.filters = this.filters.addFilter(filter); + args = args.slice(); + args.unshift(true); + args.unshift(matcherName); + return util.buildFailureMessage.apply(null, args); + } + + function negate(result) { + result.pass = !result.pass; return result; - }; + } - Expectation.addCoreMatchers = function(matchers) { - var prototype = Expectation.prototype; - for (var matcherName in matchers) { - var matcher = matchers[matcherName]; - prototype[matcherName] = wrapCompare(matcherName, matcher); - } - }; - - Expectation.Factory = function(options) { - var expect = new Expectation(options || {}); - expect.not = expect.addFilter(negatingFilter); - - expect.withContext = function(message) { - var result = this.addFilter(new ContextAddingFilter(message)); - result.not = result.addFilter(negatingFilter); - return result; - }; - - return expect; - }; - - - var negatingFilter = { + var syncNegatingFilter = { selectComparisonFunc: function(matcher) { function defaultNegativeCompare() { - var result = matcher.compare.apply(null, arguments); - result.pass = !result.pass; - return result; + return negate(matcher.compare.apply(null, arguments)); } return matcher.negativeCompare || defaultNegativeCompare; }, - buildFailureMessage: function(result, matcherName, args, util) { - if (result.message) { - if (j$.isFunction_(result.message)) { - return result.message(); - } else { - return result.message; - } - } - - args = args.slice(); - args.unshift(true); - args.unshift(matcherName); - return util.buildFailureMessage.apply(null, args); - } + buildFailureMessage: negatedFailureMessage }; + var asyncNegatingFilter = { + selectComparisonFunc: function(matcher) { + function defaultNegativeCompare() { + return matcher.compare.apply(this, arguments).then(negate); + } + + return defaultNegativeCompare; + }, + buildFailureMessage: negatedFailureMessage + }; function ContextAddingFilter(message) { this.message = message; @@ -3063,7 +2922,20 @@ getJasmineRequireObj().Expectation = function(j$) { return this.message + ': ' + msg; }; - return Expectation; + return { + factory: function(options) { + return new Expectation(options || {}); + }, + addCoreMatchers: function(matchers) { + addCoreMatchers(Expectation.prototype, matchers, wrapSyncCompare); + }, + asyncFactory: function(options) { + return new AsyncExpectation(options || {}); + }, + addAsyncCoreMatchers: function(matchers) { + addCoreMatchers(AsyncExpectation.prototype, matchers, wrapAsyncCompare); + } + }; }; getJasmineRequireObj().ExpectationFilterChain = function() { @@ -3179,6 +3051,87 @@ getJasmineRequireObj().buildExpectationResult = function() { return buildExpectationResult; }; +getJasmineRequireObj().Expector = function(j$) { + function Expector(options) { + this.util = options.util || { buildFailureMessage: function() {} }; + this.customEqualityTesters = options.customEqualityTesters || []; + this.actual = options.actual; + this.addExpectationResult = options.addExpectationResult || function(){}; + this.filters = new j$.ExpectationFilterChain(); + } + + Expector.prototype.instantiateMatcher = function(matcherName, matcherFactory, args) { + this.matcherName = matcherName; + this.args = Array.prototype.slice.call(args, 0); + this.expected = this.args.slice(0); + + this.args.unshift(this.actual); + + var matcher = matcherFactory(this.util, this.customEqualityTesters); + var comparisonFunc = this.filters.selectComparisonFunc(matcher); + return comparisonFunc || matcher.compare; + }; + + Expector.prototype.buildMessage = function(result) { + var self = this; + + if (result.pass) { + return ''; + } + + var msg = this.filters.buildFailureMessage(result, this.matcherName, this.args, this.util, defaultMessage); + return this.filters.modifyFailureMessage(msg || defaultMessage()); + + function defaultMessage() { + if (!result.message) { + var args = self.args.slice(); + args.unshift(false); + args.unshift(self.matcherName); + return self.util.buildFailureMessage.apply(null, args); + } else if (j$.isFunction_(result.message)) { + return result.message(); + } else { + return result.message; + } + } + }; + + Expector.prototype.compare = function(matcherName, matcherFactory, args) { + var matcherCompare = this.instantiateMatcher(matcherName, matcherFactory, args); + return matcherCompare.apply(null, this.args); + }; + + Expector.prototype.addFilter = function(filter) { + var result = Object.create(this); + result.filters = this.filters.addFilter(filter); + return result; + }; + + Expector.prototype.processResult = function(result, errorForStack, actualOverride) { + this.args[0] = actualOverride || this.args[0]; + var message = this.buildMessage(result); + + if (this.expected.length === 1) { + this.expected = this.expected[0]; + } + + this.addExpectationResult( + result.pass, + { + matcherName: this.matcherName, + passed: result.pass, + message: message, + error: errorForStack ? undefined : result.error, + errorForStack: errorForStack || undefined, + actual: this.actual, + expected: this.expected // TODO: this may need to be arrayified/sliced + } + ); + }; + + return Expector; +}; + getJasmineRequireObj().formatErrorMsg = function() { function generateErrorMsg(domain, usage) { var usageDefinition = usage ? '\nUsage: ' + usage : ''; diff --git a/spec/core/AsyncExpectationSpec.js b/spec/core/AsyncExpectationSpec.js index 3c260f13..906b3081 100644 --- a/spec/core/AsyncExpectationSpec.js +++ b/spec/core/AsyncExpectationSpec.js @@ -1,20 +1,20 @@ describe('AsyncExpectation', function() { beforeEach(function() { - jasmineUnderTest.AsyncExpectation.addCoreMatchers(jasmineUnderTest.asyncMatchers); + jasmineUnderTest.Expectation.addAsyncCoreMatchers(jasmineUnderTest.asyncMatchers); }); describe('Factory', function() { it('throws an Error if promises are not available', function() { var thenable = {then: function() {}}, options = {global: {}, actual: thenable} - function f() { jasmineUnderTest.AsyncExpectation.factory(options); } + function f() { jasmineUnderTest.Expectation.asyncFactory(options); } expect(f).toThrowError('expectAsync is unavailable because the environment does not support promises.'); }); it('throws an Error if the argument is not a promise', function() { jasmine.getEnv().requirePromises(); function f() { - jasmineUnderTest.AsyncExpectation.factory({actual: 'not a promise'}); + jasmineUnderTest.Expectation.asyncFactory({actual: 'not a promise'}); } expect(f).toThrowError('Expected expectAsync to be called with a promise.'); }); @@ -26,7 +26,7 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.resolve(), - expectation = jasmineUnderTest.AsyncExpectation.factory({ + expectation = jasmineUnderTest.Expectation.asyncFactory({ util: jasmineUnderTest.matchersUtil, actual: actual, addExpectationResult: addExpectationResult @@ -47,7 +47,7 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.reject(), - expectation = jasmineUnderTest.AsyncExpectation.factory({ + expectation = jasmineUnderTest.Expectation.asyncFactory({ util: jasmineUnderTest.matchersUtil, actual: actual, addExpectationResult: addExpectationResult @@ -66,18 +66,19 @@ describe('AsyncExpectation', function() { it('propagates rejections from the comparison function', function() { jasmine.getEnv().requirePromises(); - var error = new Error('AsyncExpectationSpec failure'); + var error = new Error('ExpectationSpec failure'); - spyOn(jasmineUnderTest.AsyncExpectation.prototype, 'toBeResolved') - .and.returnValue(Promise.reject(error)); var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = dummyPromise(), - expectation = new jasmineUnderTest.AsyncExpectation({ + expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult }); + spyOn(expectation, 'toBeResolved') + .and.returnValue(Promise.reject(error)); + return expectation.toBeResolved() .then( function() { fail('Expected a rejection'); }, @@ -93,7 +94,7 @@ describe('AsyncExpectation', function() { buildFailureMessage: function() { return 'failure message'; } }, addExpectationResult = jasmine.createSpy('addExpectationResult'), - expectation = jasmineUnderTest.AsyncExpectation.factory({ + expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: Promise.reject('rejected'), addExpectationResult: addExpectationResult, util: util @@ -116,7 +117,7 @@ describe('AsyncExpectation', function() { buildFailureMessage: function() { return 'failure message'; } }, addExpectationResult = jasmine.createSpy('addExpectationResult'), - expectation = jasmineUnderTest.AsyncExpectation.factory({ + expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: Promise.reject('b'), addExpectationResult: addExpectationResult, util: util @@ -141,7 +142,7 @@ describe('AsyncExpectation', function() { }, addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.reject(), - expectation = jasmineUnderTest.AsyncExpectation.factory({ + expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, util: util @@ -162,7 +163,7 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.resolve(), - expectation = jasmineUnderTest.AsyncExpectation.factory({ + expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, util: jasmineUnderTest.matchersUtil @@ -183,7 +184,7 @@ describe('AsyncExpectation', function() { var addExpectationResult = jasmine.createSpy('addExpectationResult'), actual = Promise.resolve('a'), - expectation = jasmineUnderTest.AsyncExpectation.factory({ + expectation = jasmineUnderTest.Expectation.asyncFactory({ actual: actual, addExpectationResult: addExpectationResult, util: jasmineUnderTest.matchersUtil diff --git a/spec/core/ExpectationSpec.js b/spec/core/ExpectationSpec.js index e0471c08..72a4426f 100644 --- a/spec/core/ExpectationSpec.js +++ b/spec/core/ExpectationSpec.js @@ -6,7 +6,7 @@ describe("Expectation", function() { }, expectation; - expectation = new jasmineUnderTest.Expectation({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers }); @@ -22,7 +22,7 @@ describe("Expectation", function() { jasmineUnderTest.Expectation.addCoreMatchers(coreMatchers); - expectation = new jasmineUnderTest.Expectation({}); + expectation = jasmineUnderTest.Expectation.factory({}); expect(expectation.toQuux).toBeDefined(); }); @@ -40,7 +40,7 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - expectation = new jasmineUnderTest.Expectation({ + expectation = jasmineUnderTest.Expectation.factory({ util: util, customMatchers: matchers, customEqualityTesters: customEqualityTesters, @@ -68,7 +68,7 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - expectation = new jasmineUnderTest.Expectation({ + expectation = jasmineUnderTest.Expectation.factory({ util: util, customMatchers: matchers, actual: "an actual", @@ -94,7 +94,7 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - expectation = new jasmineUnderTest.Expectation({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, util: util, actual: "an actual", @@ -109,7 +109,8 @@ describe("Expectation", function() { message: "", error: undefined, expected: "hello", - actual: "an actual" + actual: "an actual", + errorForStack: undefined }); }); @@ -127,7 +128,7 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - expectation = new jasmineUnderTest.Expectation({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, util: util, actual: "an actual", @@ -142,7 +143,8 @@ describe("Expectation", function() { expected: "hello", actual: "an actual", message: "", - error: undefined + error: undefined, + errorForStack: undefined }); }); @@ -162,7 +164,7 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - expectation = new jasmineUnderTest.Expectation({ + expectation = jasmineUnderTest.Expectation.factory({ actual: "an actual", customMatchers: matchers, addExpectationResult: addExpectationResult @@ -176,7 +178,8 @@ describe("Expectation", function() { expected: "hello", actual: "an actual", message: "I am a custom message", - error: undefined + error: undefined, + errorForStack: undefined }); }); @@ -196,7 +199,7 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - expectation = new jasmineUnderTest.Expectation({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult @@ -210,7 +213,8 @@ describe("Expectation", function() { expected: "hello", actual: "an actual", message: "I am a custom message", - error: undefined + error: undefined, + errorForStack: undefined }); }); @@ -229,7 +233,7 @@ describe("Expectation", function() { actual = "an actual", expectation; - expectation = jasmineUnderTest.Expectation.Factory({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult @@ -243,7 +247,8 @@ describe("Expectation", function() { message: "", error: undefined, expected: "hello", - actual: actual + actual: actual, + errorForStack: undefined }); }); @@ -262,7 +267,7 @@ describe("Expectation", function() { actual = "an actual", expectation; - expectation = jasmineUnderTest.Expectation.Factory({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, actual: "an actual", util: util, @@ -277,7 +282,8 @@ describe("Expectation", function() { expected: "hello", actual: actual, message: "default message", - error: undefined + error: undefined, + errorForStack: undefined }); }); @@ -298,7 +304,7 @@ describe("Expectation", function() { actual = "an actual", expectation; - expectation = jasmineUnderTest.Expectation.Factory({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult @@ -312,7 +318,8 @@ describe("Expectation", function() { expected: "hello", actual: actual, message: "I am a custom message", - error: undefined + error: undefined, + errorForStack: undefined }); }); @@ -329,7 +336,7 @@ describe("Expectation", function() { actual = "an actual", expectation; - expectation = jasmineUnderTest.Expectation.Factory({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult @@ -343,7 +350,8 @@ describe("Expectation", function() { expected: "hello", actual: actual, message: "", - error: undefined + error: undefined, + errorForStack: undefined }); }); @@ -365,7 +373,7 @@ describe("Expectation", function() { actual = "an actual", expectation; - expectation = jasmineUnderTest.Expectation.Factory({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult, @@ -379,7 +387,8 @@ describe("Expectation", function() { expected: "hello", actual: actual, message: "I'm a custom message", - error: undefined + error: undefined, + errorForStack: undefined }); }); @@ -401,7 +410,7 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - expectation = new jasmineUnderTest.Expectation({ + expectation = jasmineUnderTest.Expectation.factory({ actual: "an actual", customMatchers: matchers, addExpectationResult: addExpectationResult @@ -415,7 +424,8 @@ describe("Expectation", function() { expected: "hello", actual: "an actual", message: "I am a custom message", - error: customError + error: customError, + errorForStack: undefined }); }); @@ -437,7 +447,7 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - expectation = jasmineUnderTest.Expectation.Factory({ + expectation = jasmineUnderTest.Expectation.factory({ actual: "an actual", customMatchers: matchers, addExpectationResult: addExpectationResult @@ -451,7 +461,8 @@ describe("Expectation", function() { expected: "hello", actual: "an actual", message: "I am a custom message", - error: customError + error: customError, + errorForStack: undefined }); }); @@ -473,7 +484,7 @@ describe("Expectation", function() { addExpectationResult = jasmine.createSpy("addExpectationResult"), expectation; - expectation = jasmineUnderTest.Expectation.Factory({ + expectation = jasmineUnderTest.Expectation.factory({ actual: "an actual", customMatchers: matchers, addExpectationResult: addExpectationResult @@ -487,7 +498,8 @@ describe("Expectation", function() { expected: "hello", actual: "an actual", message: "I am a custom message", - error: customError + error: customError, + errorForStack: undefined }); }); @@ -504,7 +516,7 @@ describe("Expectation", function() { buildFailureMessage: function() { return "failure message"; } }, addExpectationResult = jasmine.createSpy("addExpectationResult"), - expectation = jasmineUnderTest.Expectation.Factory({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, util: util, actual: "an actual", @@ -529,7 +541,7 @@ describe("Expectation", function() { } }, addExpectationResult = jasmine.createSpy("addExpectationResult"), - expectation = jasmineUnderTest.Expectation.Factory({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult @@ -558,7 +570,7 @@ describe("Expectation", function() { } }, addExpectationResult = jasmine.createSpy("addExpectationResult"), - expectation = jasmineUnderTest.Expectation.Factory({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, actual: "an actual", addExpectationResult: addExpectationResult @@ -582,7 +594,7 @@ describe("Expectation", function() { } }, addExpectationResult = jasmine.createSpy("addExpectationResult"), - expectation = jasmineUnderTest.Expectation.Factory({ + expectation = jasmineUnderTest.Expectation.factory({ customMatchers: matchers, util: jasmineUnderTest.matchersUtil, actual: "an actual", @@ -614,7 +626,7 @@ describe("Expectation", function() { } }, addExpectationResult = jasmine.createSpy("addExpectationResult"), - expectation = jasmineUnderTest.Expectation.Factory({ + expectation = jasmineUnderTest.Expectation.factory({ actual: "an actual", customMatchers: matchers, addExpectationResult: addExpectationResult diff --git a/src/core/AsyncExpectation.js b/src/core/AsyncExpectation.js deleted file mode 100644 index c88f699b..00000000 --- a/src/core/AsyncExpectation.js +++ /dev/null @@ -1,134 +0,0 @@ -getJasmineRequireObj().AsyncExpectation = function(j$) { - var promiseForMessage = { - jasmineToString: function() { return 'a promise'; } - }; - - /** - * Asynchronous matchers. - * @namespace async-matchers - */ - function AsyncExpectation(options) { - var global = options.global || j$.getGlobal(); - this.util = options.util || { buildFailureMessage: function() {} }; - this.customEqualityTesters = options.customEqualityTesters || []; - this.addExpectationResult = options.addExpectationResult || function(){}; - this.actual = options.actual; - this.filters = new j$.ExpectationFilterChain(); - - if (!global.Promise) { - throw new Error('expectAsync is unavailable because the environment does not support promises.'); - } - - if (!j$.isPromiseLike(this.actual)) { - throw new Error('Expected expectAsync to be called with a promise.'); - } - } - - function wrapCompare(name, matcherFactory) { - return function() { - var self = this; - var args = Array.prototype.slice.call(arguments), - expected = args.slice(0); - - args.unshift(this.actual); - - // Capture the call stack here, before we go async, so that it will - // contain frames that are relevant to the user instead of just parts - // of Jasmine. - var errorForStack = j$.util.errorWithStack(); - - var matcherCompare = this.instantiateMatcher(matcherFactory); - - return matcherCompare.apply(self, args).then(function(result) { - args[0] = promiseForMessage; - self.processResult(result, name, expected, args, errorForStack); - }); - }; - } - - AsyncExpectation.prototype.processResult = function(result, name, expected, args, errorForStack) { - var message = this.buildMessage(result, name, args); - - if (expected.length === 1) { - expected = expected[0]; - } - - this.addExpectationResult( - result.pass, - { - matcherName: name, - passed: result.pass, - message: message, - error: undefined, - errorForStack: errorForStack, - actual: this.actual, - expected: expected // TODO: this may need to be arrayified/sliced - } - ); - }; - - AsyncExpectation.prototype.buildMessage = j$.Expectation.prototype.buildMessage; - - AsyncExpectation.prototype.instantiateMatcher = j$.Expectation.prototype.instantiateMatcher; - - AsyncExpectation.prototype.addFilter = j$.Expectation.prototype.addFilter; - - AsyncExpectation.addCoreMatchers = function(matchers) { - var prototype = AsyncExpectation.prototype; - for (var matcherName in matchers) { - var matcher = matchers[matcherName]; - prototype[matcherName] = wrapCompare(matcherName, matcher); - } - }; - - AsyncExpectation.factory = function(options) { - var expect = new AsyncExpectation(options); - expect.not = expect.addFilter(negatingFilter); - - expect.withContext = function(message) { - var result = this.addFilter(new ContextAddingFilter(message)); - result.not = result.addFilter(negatingFilter); - return result; - }; - - return expect; - }; - - var negatingFilter = { - selectComparisonFunc: function(matcher) { - function defaultNegativeCompare() { - return matcher.compare.apply(this, arguments).then(function(result) { - result.pass = !result.pass; - return result; - }); - } - - return defaultNegativeCompare; - }, - buildFailureMessage: function(result, matcherName, args, util) { - if (result.message) { - if (j$.isFunction_(result.message)) { - return result.message(); - } else { - return result.message; - } - } - - args = args.slice(); - args.unshift(true); - args.unshift(matcherName); - return util.buildFailureMessage.apply(null, args); - } - }; - - - function ContextAddingFilter(message) { - this.message = message; - } - - ContextAddingFilter.prototype.modifyFailureMessage = function(msg) { - return this.message + ': ' + msg; - }; - - return AsyncExpectation; -}; diff --git a/src/core/Env.js b/src/core/Env.js index a5f6c4df..3b9c2b97 100644 --- a/src/core/Env.js +++ b/src/core/Env.js @@ -193,7 +193,7 @@ getJasmineRequireObj().Env = function(j$) { }; j$.Expectation.addCoreMatchers(j$.matchers); - j$.AsyncExpectation.addCoreMatchers(j$.asyncMatchers); + j$.Expectation.addAsyncCoreMatchers(j$.asyncMatchers); var nextSpecId = 0; var getNextSpecId = function() { @@ -206,7 +206,7 @@ getJasmineRequireObj().Env = function(j$) { }; var expectationFactory = function(actual, spec) { - return j$.Expectation.Factory({ + return j$.Expectation.factory({ util: j$.matchersUtil, customEqualityTesters: runnableResources[spec.id].customEqualityTesters, customMatchers: runnableResources[spec.id].customMatchers, @@ -220,7 +220,7 @@ getJasmineRequireObj().Env = function(j$) { }; var asyncExpectationFactory = function(actual, spec) { - return j$.AsyncExpectation.factory({ + return j$.Expectation.asyncFactory({ util: j$.matchersUtil, customEqualityTesters: runnableResources[spec.id].customEqualityTesters, actual: actual, diff --git a/src/core/Expectation.js b/src/core/Expectation.js index e6b6fc70..34f777a4 100644 --- a/src/core/Expectation.js +++ b/src/core/Expectation.js @@ -1,139 +1,133 @@ getJasmineRequireObj().Expectation = function(j$) { + var promiseForMessage = { + jasmineToString: function() { return 'a promise'; } + }; /** * Matchers that come with Jasmine out of the box. * @namespace matchers */ function Expectation(options) { - this.util = options.util || { buildFailureMessage: function() {} }; - this.customEqualityTesters = options.customEqualityTesters || []; - this.actual = options.actual; - this.addExpectationResult = options.addExpectationResult || function(){}; - this.filters = new j$.ExpectationFilterChain(); + this.expector = new j$.Expector(options); var customMatchers = options.customMatchers || {}; for (var matcherName in customMatchers) { - this[matcherName] = wrapCompare(matcherName, customMatchers[matcherName]); + this[matcherName] = wrapSyncCompare(matcherName, customMatchers[matcherName]); } } - function wrapCompare(name, matcherFactory) { + Expectation.prototype.withContext = function withContext(message) { + return addFilter(this, new ContextAddingFilter(message)); + }; + + Object.defineProperty(Expectation.prototype, 'not', { + get: function() { + return addFilter(this, syncNegatingFilter); + } + }); + + /** + * Asynchronous matchers. + * @namespace async-matchers + */ + function AsyncExpectation(options) { + var global = options.global || j$.getGlobal(); + this.expector = new j$.Expector(options); + + if (!global.Promise) { + throw new Error('expectAsync is unavailable because the environment does not support promises.'); + } + + if (!j$.isPromiseLike(this.expector.actual)) { + throw new Error('Expected expectAsync to be called with a promise.'); + } + } + + AsyncExpectation.prototype.withContext = function withContext(message) { + return addFilter(this, new ContextAddingFilter(message)); + }; + + Object.defineProperty(AsyncExpectation.prototype, 'not', { + get: function() { + return addFilter(this, asyncNegatingFilter); + } + }); + + function wrapSyncCompare(name, matcherFactory) { return function() { - var args = Array.prototype.slice.call(arguments, 0), - expected = args.slice(0); - - args.unshift(this.actual); - - var matcherCompare = this.instantiateMatcher(matcherFactory); - var result = matcherCompare.apply(null, args); - this.processResult(result, name, expected, args); + var result = this.expector.compare(name, matcherFactory, arguments); + this.expector.processResult(result); }; } - Expectation.prototype.instantiateMatcher = function(matcherFactory) { - var matcher = matcherFactory(this.util, this.customEqualityTesters); - var comparisonFunc = this.filters.selectComparisonFunc(matcher); - return comparisonFunc || matcher.compare; - }; + function wrapAsyncCompare(name, matcherFactory) { + return function() { + var self = this; - Expectation.prototype.processResult = function(result, name, expected, args) { - var message = this.buildMessage(result, name, args); + // Capture the call stack here, before we go async, so that it will contain + // frames that are relevant to the user instead of just parts of Jasmine. + var errorForStack = j$.util.errorWithStack(); - if (expected.length === 1) { - expected = expected[0]; + return this.expector.compare(name, matcherFactory, arguments).then(function(result) { + self.expector.processResult(result, errorForStack, promiseForMessage); + }); + }; + } + + function addCoreMatchers(prototype, matchers, wrapper) { + for (var matcherName in matchers) { + var matcher = matchers[matcherName]; + prototype[matcherName] = wrapper(matcherName, matcher); } + } - this.addExpectationResult( - result.pass, - { - matcherName: name, - passed: result.pass, - message: message, - error: result.error, - actual: this.actual, - expected: expected // TODO: this may need to be arrayified/sliced - } - ); - }; + function addFilter(source, filter) { + var result = Object.create(source); + result.expector = source.expector.addFilter(filter); + return result; + } - Expectation.prototype.buildMessage = function(result, name, args) { - var util = this.util; - - if (result.pass) { - return ''; - } - - var msg = this.filters.buildFailureMessage(result, name, args, util, defaultMessage); - return this.filters.modifyFailureMessage(msg || defaultMessage()); - - function defaultMessage() { - if (!result.message) { - args = args.slice(); - args.unshift(false); - args.unshift(name); - return util.buildFailureMessage.apply(null, args); - } else if (j$.isFunction_(result.message)) { + function negatedFailureMessage(result, matcherName, args, util) { + if (result.message) { + if (j$.isFunction_(result.message)) { return result.message(); } else { return result.message; } } - }; - Expectation.prototype.addFilter = function(filter) { - var result = Object.create(this); - result.filters = this.filters.addFilter(filter); + args = args.slice(); + args.unshift(true); + args.unshift(matcherName); + return util.buildFailureMessage.apply(null, args); + } + + function negate(result) { + result.pass = !result.pass; return result; - }; + } - Expectation.addCoreMatchers = function(matchers) { - var prototype = Expectation.prototype; - for (var matcherName in matchers) { - var matcher = matchers[matcherName]; - prototype[matcherName] = wrapCompare(matcherName, matcher); - } - }; - - Expectation.Factory = function(options) { - var expect = new Expectation(options || {}); - expect.not = expect.addFilter(negatingFilter); - - expect.withContext = function(message) { - var result = this.addFilter(new ContextAddingFilter(message)); - result.not = result.addFilter(negatingFilter); - return result; - }; - - return expect; - }; - - - var negatingFilter = { + var syncNegatingFilter = { selectComparisonFunc: function(matcher) { function defaultNegativeCompare() { - var result = matcher.compare.apply(null, arguments); - result.pass = !result.pass; - return result; + return negate(matcher.compare.apply(null, arguments)); } return matcher.negativeCompare || defaultNegativeCompare; }, - buildFailureMessage: function(result, matcherName, args, util) { - if (result.message) { - if (j$.isFunction_(result.message)) { - return result.message(); - } else { - return result.message; - } - } - - args = args.slice(); - args.unshift(true); - args.unshift(matcherName); - return util.buildFailureMessage.apply(null, args); - } + buildFailureMessage: negatedFailureMessage }; + var asyncNegatingFilter = { + selectComparisonFunc: function(matcher) { + function defaultNegativeCompare() { + return matcher.compare.apply(this, arguments).then(negate); + } + + return defaultNegativeCompare; + }, + buildFailureMessage: negatedFailureMessage + }; function ContextAddingFilter(message) { this.message = message; @@ -143,5 +137,18 @@ getJasmineRequireObj().Expectation = function(j$) { return this.message + ': ' + msg; }; - return Expectation; + return { + factory: function(options) { + return new Expectation(options || {}); + }, + addCoreMatchers: function(matchers) { + addCoreMatchers(Expectation.prototype, matchers, wrapSyncCompare); + }, + asyncFactory: function(options) { + return new AsyncExpectation(options || {}); + }, + addAsyncCoreMatchers: function(matchers) { + addCoreMatchers(AsyncExpectation.prototype, matchers, wrapAsyncCompare); + } + }; }; diff --git a/src/core/Expector.js b/src/core/Expector.js new file mode 100644 index 00000000..a8c717f2 --- /dev/null +++ b/src/core/Expector.js @@ -0,0 +1,80 @@ +getJasmineRequireObj().Expector = function(j$) { + function Expector(options) { + this.util = options.util || { buildFailureMessage: function() {} }; + this.customEqualityTesters = options.customEqualityTesters || []; + this.actual = options.actual; + this.addExpectationResult = options.addExpectationResult || function(){}; + this.filters = new j$.ExpectationFilterChain(); + } + + Expector.prototype.instantiateMatcher = function(matcherName, matcherFactory, args) { + this.matcherName = matcherName; + this.args = Array.prototype.slice.call(args, 0); + this.expected = this.args.slice(0); + + this.args.unshift(this.actual); + + var matcher = matcherFactory(this.util, this.customEqualityTesters); + var comparisonFunc = this.filters.selectComparisonFunc(matcher); + return comparisonFunc || matcher.compare; + }; + + Expector.prototype.buildMessage = function(result) { + var self = this; + + if (result.pass) { + return ''; + } + + var msg = this.filters.buildFailureMessage(result, this.matcherName, this.args, this.util, defaultMessage); + return this.filters.modifyFailureMessage(msg || defaultMessage()); + + function defaultMessage() { + if (!result.message) { + var args = self.args.slice(); + args.unshift(false); + args.unshift(self.matcherName); + return self.util.buildFailureMessage.apply(null, args); + } else if (j$.isFunction_(result.message)) { + return result.message(); + } else { + return result.message; + } + } + }; + + Expector.prototype.compare = function(matcherName, matcherFactory, args) { + var matcherCompare = this.instantiateMatcher(matcherName, matcherFactory, args); + return matcherCompare.apply(null, this.args); + }; + + Expector.prototype.addFilter = function(filter) { + var result = Object.create(this); + result.filters = this.filters.addFilter(filter); + return result; + }; + + Expector.prototype.processResult = function(result, errorForStack, actualOverride) { + this.args[0] = actualOverride || this.args[0]; + var message = this.buildMessage(result); + + if (this.expected.length === 1) { + this.expected = this.expected[0]; + } + + this.addExpectationResult( + result.pass, + { + matcherName: this.matcherName, + passed: result.pass, + message: message, + error: errorForStack ? undefined : result.error, + errorForStack: errorForStack || undefined, + actual: this.actual, + expected: this.expected // TODO: this may need to be arrayified/sliced + } + ); + }; + + return Expector; +}; diff --git a/src/core/requireCore.js b/src/core/requireCore.js index 181c2c32..8ff7448b 100644 --- a/src/core/requireCore.js +++ b/src/core/requireCore.js @@ -38,8 +38,8 @@ var getJasmineRequireObj = (function (jasmineGlobal) { j$.StackTrace = jRequire.StackTrace(j$); j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$); j$.ExpectationFilterChain = jRequire.ExpectationFilterChain(); + j$.Expector = jRequire.Expector(j$); j$.Expectation = jRequire.Expectation(j$); - j$.AsyncExpectation = jRequire.AsyncExpectation(j$); j$.buildExpectationResult = jRequire.buildExpectationResult(); j$.JsApiReporter = jRequire.JsApiReporter(); j$.matchersUtil = jRequire.matchersUtil(j$);