153
src/core/AsyncExpectation.js
Normal file
153
src/core/AsyncExpectation.js
Normal file
@@ -0,0 +1,153 @@
|
||||
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.isNot = options.isNot;
|
||||
|
||||
if (!global.Promise) {
|
||||
throw new Error('expectAsync is unavailable because the environment does not support promises.');
|
||||
}
|
||||
|
||||
if (!j$.isPromise(this.actual)) {
|
||||
throw new Error('Expected expectAsync to be called with a promise.');
|
||||
}
|
||||
|
||||
['toBeResolved', 'toBeRejected', 'toBeResolvedTo'].forEach(wrapCompare.bind(this));
|
||||
}
|
||||
|
||||
function wrapCompare(name) {
|
||||
var compare = this[name];
|
||||
this[name] = function() {
|
||||
var self = this;
|
||||
var args = Array.prototype.slice.call(arguments);
|
||||
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();
|
||||
|
||||
return compare.apply(self, args).then(function(result) {
|
||||
var message;
|
||||
|
||||
if (self.isNot) {
|
||||
result.pass = !result.pass;
|
||||
}
|
||||
|
||||
args[0] = promiseForMessage;
|
||||
message = j$.Expectation.finalizeMessage(self.util, name, self.isNot, args, result);
|
||||
|
||||
self.addExpectationResult(result.pass, {
|
||||
matcherName: name,
|
||||
passed: result.pass,
|
||||
message: message,
|
||||
error: undefined,
|
||||
errorForStack: errorForStack,
|
||||
actual: self.actual
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Expect a promise to be resolved.
|
||||
* @function
|
||||
* @async
|
||||
* @name async-matchers#toBeResolved
|
||||
* @example
|
||||
* await expectAsync(aPromise).toBeResolved();
|
||||
* @example
|
||||
* return expectAsync(aPromise).toBeResolved();
|
||||
*/
|
||||
AsyncExpectation.prototype.toBeResolved = function(actual) {
|
||||
return actual.then(
|
||||
function() { return {pass: true}; },
|
||||
function() { return {pass: false}; }
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Expect a promise to be rejected.
|
||||
* @function
|
||||
* @async
|
||||
* @name async-matchers#toBeRejected
|
||||
* @example
|
||||
* await expectAsync(aPromise).toBeRejected();
|
||||
* @example
|
||||
* return expectAsync(aPromise).toBeRejected();
|
||||
*/
|
||||
AsyncExpectation.prototype.toBeRejected = function(actual) {
|
||||
return actual.then(
|
||||
function() { return {pass: false}; },
|
||||
function() { return {pass: true}; }
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Expect a promise to be resolved to a value equal to the expected, using deep equality comparison.
|
||||
* @function
|
||||
* @async
|
||||
* @name async-matchers#toBeResolvedTo
|
||||
* @param {Object} expected - Value that the promise is expected to resolve to
|
||||
* @example
|
||||
* await expectAsync(aPromise).toBeResolvedTo({prop: 'value'});
|
||||
* @example
|
||||
* return expectAsync(aPromise).toBeResolvedTo({prop: 'value'});
|
||||
*/
|
||||
AsyncExpectation.prototype.toBeResolvedTo = function(actualPromise, expectedValue) {
|
||||
var self = this;
|
||||
|
||||
function prefix(passed) {
|
||||
return 'Expected a promise ' +
|
||||
(passed ? 'not ' : '') +
|
||||
'to be resolved to ' + j$.pp(expectedValue);
|
||||
}
|
||||
|
||||
return actualPromise.then(
|
||||
function(actualValue) {
|
||||
if (self.util.equals(actualValue, expectedValue, self.customEqualityTesters)) {
|
||||
return {
|
||||
pass: true,
|
||||
message: prefix(true) + '.'
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
pass: false,
|
||||
message: prefix(false) + ' but it was resolved to ' + j$.pp(actualValue) + '.'
|
||||
};
|
||||
}
|
||||
},
|
||||
function() {
|
||||
return {
|
||||
pass: false,
|
||||
message: prefix(false) + ' but it was rejected.'
|
||||
};
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
AsyncExpectation.factory = function(options) {
|
||||
var expect = new AsyncExpectation(options);
|
||||
|
||||
options = j$.util.clone(options);
|
||||
options.isNot = true;
|
||||
expect.not = new AsyncExpectation(options);
|
||||
|
||||
return expect;
|
||||
};
|
||||
|
||||
|
||||
return AsyncExpectation;
|
||||
};
|
||||
@@ -117,6 +117,19 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
}
|
||||
};
|
||||
|
||||
var asyncExpectationFactory = function(actual, spec) {
|
||||
return j$.AsyncExpectation.factory({
|
||||
util: j$.matchersUtil,
|
||||
customEqualityTesters: runnableResources[spec.id].customEqualityTesters,
|
||||
actual: actual,
|
||||
addExpectationResult: addExpectationResult
|
||||
});
|
||||
|
||||
function addExpectationResult(passed, result) {
|
||||
return spec.addExpectationResult(passed, result);
|
||||
}
|
||||
};
|
||||
|
||||
var defaultResourcesForRunnable = function(id, parentRunnableId) {
|
||||
var resources = {spies: [], customEqualityTesters: [], customMatchers: {}, customSpyStrategies: {}};
|
||||
|
||||
@@ -247,6 +260,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
id: getNextSuiteId(),
|
||||
description: 'Jasmine__TopLevel__Suite',
|
||||
expectationFactory: expectationFactory,
|
||||
asyncExpectationFactory: asyncExpectationFactory,
|
||||
expectationResultFactory: expectationResultFactory
|
||||
});
|
||||
defaultResourcesForRunnable(topSuite.id);
|
||||
@@ -521,6 +535,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
description: description,
|
||||
parentSuite: currentDeclarationSuite,
|
||||
expectationFactory: expectationFactory,
|
||||
asyncExpectationFactory: asyncExpectationFactory,
|
||||
expectationResultFactory: expectationResultFactory,
|
||||
throwOnExpectationFailure: throwOnExpectationFailure
|
||||
});
|
||||
@@ -614,6 +629,7 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
id: getNextSpecId(),
|
||||
beforeAndAfterFns: beforeAndAfterFns(suite),
|
||||
expectationFactory: expectationFactory,
|
||||
asyncExpectationFactory: asyncExpectationFactory,
|
||||
resultCallback: specResultCallback,
|
||||
getSpecName: function(spec) {
|
||||
return getSpecName(spec, suite);
|
||||
@@ -695,6 +711,14 @@ getJasmineRequireObj().Env = function(j$) {
|
||||
return currentRunnable().expect(actual);
|
||||
};
|
||||
|
||||
this.expectAsync = function(actual) {
|
||||
if (!currentRunnable()) {
|
||||
throw new Error('\'expectAsync\' was used when there was no current spec, this could be because an asynchronous test timed out');
|
||||
}
|
||||
|
||||
return currentRunnable().expectAsync(actual);
|
||||
};
|
||||
|
||||
this.beforeEach = function(beforeEachFunction, timeout) {
|
||||
ensureIsNotNested('beforeEach');
|
||||
ensureIsFunctionOrAsync(beforeEachFunction, 'beforeEach');
|
||||
|
||||
@@ -21,7 +21,7 @@ getJasmineRequireObj().Expectation = function() {
|
||||
return function() {
|
||||
var args = Array.prototype.slice.call(arguments, 0),
|
||||
expected = args.slice(0),
|
||||
message = '';
|
||||
message;
|
||||
|
||||
args.unshift(this.actual);
|
||||
|
||||
@@ -39,20 +39,7 @@ getJasmineRequireObj().Expectation = function() {
|
||||
}
|
||||
|
||||
var result = matcherCompare.apply(null, args);
|
||||
|
||||
if (!result.pass) {
|
||||
if (!result.message) {
|
||||
args.unshift(this.isNot);
|
||||
args.unshift(name);
|
||||
message = this.util.buildFailureMessage.apply(null, args);
|
||||
} else {
|
||||
if (Object.prototype.toString.apply(result.message) === '[object Function]') {
|
||||
message = result.message();
|
||||
} else {
|
||||
message = result.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
message = Expectation.finalizeMessage(this.util, name, this.isNot, args, result);
|
||||
|
||||
if (expected.length == 1) {
|
||||
expected = expected[0];
|
||||
@@ -73,6 +60,23 @@ getJasmineRequireObj().Expectation = function() {
|
||||
};
|
||||
};
|
||||
|
||||
Expectation.finalizeMessage = function(util, name, isNot, args, result) {
|
||||
if (result.pass) {
|
||||
return '';
|
||||
} else if (result.message) {
|
||||
if (Object.prototype.toString.apply(result.message) === '[object Function]') {
|
||||
return result.message();
|
||||
} else {
|
||||
return result.message;
|
||||
}
|
||||
} else {
|
||||
args = args.slice();
|
||||
args.unshift(isNot);
|
||||
args.unshift(name);
|
||||
return util.buildFailureMessage.apply(null, args);
|
||||
}
|
||||
};
|
||||
|
||||
Expectation.addCoreMatchers = function(matchers) {
|
||||
var prototype = Expectation.prototype;
|
||||
for (var matcherName in matchers) {
|
||||
|
||||
@@ -45,7 +45,9 @@ getJasmineRequireObj().buildExpectationResult = function() {
|
||||
|
||||
var error = options.error;
|
||||
if (!error) {
|
||||
if (options.stack) {
|
||||
if (options.errorForStack) {
|
||||
error = options.errorForStack;
|
||||
} else if (options.stack) {
|
||||
error = options;
|
||||
} else {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
getJasmineRequireObj().Spec = function(j$) {
|
||||
function Spec(attrs) {
|
||||
this.expectationFactory = attrs.expectationFactory;
|
||||
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
|
||||
this.resultCallback = attrs.resultCallback || function() {};
|
||||
this.id = attrs.id;
|
||||
this.description = attrs.description || '';
|
||||
@@ -57,6 +58,10 @@ getJasmineRequireObj().Spec = function(j$) {
|
||||
return this.expectationFactory(actual, this);
|
||||
};
|
||||
|
||||
Spec.prototype.expectAsync = function(actual) {
|
||||
return this.asyncExpectationFactory(actual, this);
|
||||
};
|
||||
|
||||
Spec.prototype.execute = function(onComplete, excluded) {
|
||||
var self = this;
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ getJasmineRequireObj().Suite = function(j$) {
|
||||
this.parentSuite = attrs.parentSuite;
|
||||
this.description = attrs.description;
|
||||
this.expectationFactory = attrs.expectationFactory;
|
||||
this.asyncExpectationFactory = attrs.asyncExpectationFactory;
|
||||
this.expectationResultFactory = attrs.expectationResultFactory;
|
||||
this.throwOnExpectationFailure = !!attrs.throwOnExpectationFailure;
|
||||
|
||||
@@ -37,6 +38,10 @@ getJasmineRequireObj().Suite = function(j$) {
|
||||
return this.expectationFactory(actual, this);
|
||||
};
|
||||
|
||||
Suite.prototype.expectAsync = function(actual) {
|
||||
return this.asyncExpectationFactory(actual, this);
|
||||
};
|
||||
|
||||
Suite.prototype.getFullName = function() {
|
||||
var fullName = [];
|
||||
for (var parentSuite = this; parentSuite; parentSuite = parentSuite.parentSuite) {
|
||||
|
||||
@@ -120,7 +120,7 @@ getJasmineRequireObj().base = function(j$, jasmineGlobal) {
|
||||
};
|
||||
|
||||
j$.isPromise = function(obj) {
|
||||
return typeof jasmineGlobal.Promise !== 'undefined' && obj.constructor === jasmineGlobal.Promise;
|
||||
return typeof jasmineGlobal.Promise !== 'undefined' && obj && obj.constructor === jasmineGlobal.Promise;
|
||||
};
|
||||
|
||||
j$.fnNameFor = function(func) {
|
||||
|
||||
@@ -38,6 +38,7 @@ var getJasmineRequireObj = (function (jasmineGlobal) {
|
||||
j$.StackTrace = jRequire.StackTrace(j$);
|
||||
j$.ExceptionFormatter = jRequire.ExceptionFormatter(j$);
|
||||
j$.Expectation = jRequire.Expectation();
|
||||
j$.AsyncExpectation = jRequire.AsyncExpectation(j$);
|
||||
j$.buildExpectationResult = jRequire.buildExpectationResult();
|
||||
j$.JsApiReporter = jRequire.JsApiReporter();
|
||||
j$.matchersUtil = jRequire.matchersUtil(j$);
|
||||
|
||||
@@ -167,6 +167,25 @@ getJasmineRequireObj().interface = function(jasmine, env) {
|
||||
return env.expect(actual);
|
||||
},
|
||||
|
||||
/**
|
||||
* Create an asynchronous expectation for a spec. Note that the matchers
|
||||
* that are provided by an asynchronous expectation all return promises
|
||||
* which must be either returned from the spec or waited for using `await`
|
||||
* in order for Jasmine to associate them with the correct spec.
|
||||
* @name expectAsync
|
||||
* @function
|
||||
* @global
|
||||
* @param {Object} actual - Actual computed value to test expectations against.
|
||||
* @return {async-matchers}
|
||||
* @example
|
||||
* await expectAsync(somePromise).toBeResolved();
|
||||
* @example
|
||||
* return expectAsync(somePromise).toBeResolved();
|
||||
*/
|
||||
expectAsync: function(actual) {
|
||||
return env.expectAsync(actual);
|
||||
},
|
||||
|
||||
/**
|
||||
* Mark a spec as pending, expectation results will be ignored.
|
||||
* @name pending
|
||||
|
||||
Reference in New Issue
Block a user