/** @module certainty */
var Subject = require('./subject');
var DeferredSubject = require('./deferredSubject');
var subjectFactory = require('./factoryBase');
/** A class which functions as a subject, but doesn't actually execute any assertions until
the promise has resolved. Once the promise succeeds, any method calls will be played back
on the result value of the promise.
@param {PromiseSubject} subject The subject wrapping the promise.
@param {Promise} promise The promise being tested.
@constructor
@extends DeferredSubject
*/
function EventualSubject(subject, promise) {
DeferredSubject.call(this);
this.subject = subject;
var self = this;
this.promise = promise.then(
function(value) {
// Play back the recorded calls on a new subject which is created based on the resolved
// value of the promise.
var valueSubject = subjectFactory.newSubject(subject.failureStrategy, value);
valueSubject.failureMessage = subject.failureMessage;
self.run(valueSubject);
return value;
},
function(reason) {
subject.fail('Expected promise ' + subject.describe() +
' to succeed, but failed with ' + this.format(reason) + '.');
return reason;
}
);
// Make this object a thenable
this.then = this.promise.then.bind(this.promise);
this.catch = this.promise.catch.bind(this.promise);
}
EventualSubject.prototype = Object.create(DeferredSubject.prototype);
EventualSubject.prototype.constructor = DeferredSubject;
/** Subject subclass which provides assertion methods for promise types.
@param {FailureStrategy} failureStrategy The failure strategy to use when an assertion fails.
@param {*} value The value being checked.
@constructor
@extends Subject
*/
function PromiseSubject(failureStrategy, value) {
Subject.call(this, failureStrategy, value);
// Make this object a thenable
this.then = this.value.then.bind(this.value);
this.catch = this.value.catch.bind(this.value);
}
PromiseSubject.prototype = Object.create(Subject.prototype);
PromiseSubject.prototype.constructor = PromiseSubject;
/** Ensure that the promise eventually succeeds.
@return {PromiseSubject} `this` for chaining.
*/
PromiseSubject.prototype.succeeds = function () {
var self = this;
return this.value.catch(
function (reason) {
self.fail('Expected promise ' + self.describe() + ' to succeed, but failed with ' +
this.format(reason) + '.');
return reason;
});
};
/** Ensure that the promise eventually succeeds with the specified value.
@param {*} expected The value that the promise must resolve to.
@return {PromiseSubject} `this` for chaining.
*/
PromiseSubject.prototype.succeedsWith = function (expected) {
var self = this;
return this.value.then(
function (value) {
if (value !== expected) {
self.fail('Expected promise ' + self.describe() + ' to resolve to ' +
this.format(expected) + ' actual value was ' + this.format(value) + '.');
}
return value;
},
function (reason) {
self.fail('Expected promise ' + self.describe() + ' to succeed, but failed with ' +
this.format(reason) + '.');
return reason;
});
};
/** Ensure that the promise eventually fails.
@return {PromiseSubject} `this` for chaining.
*/
PromiseSubject.prototype.fails = function () {
var self = this;
return this.value.then(
function (value) {
self.fail('Expected promise ' + self.describe() + ' to fail, but succeeded with ' +
this.format(value) + '.');
return value;
},
function (reason) {
return reason;
});
};
/** Ensure that the promise eventually fails with the specified reason.
@param {*} expected The expected reason for rejection.
@return {PromiseSubject} `this` for chaining.
*/
PromiseSubject.prototype.failsWith = function (expected) {
var self = this;
return this.value.then(
function (value) {
self.fail('Expected promise ' + self.describe() + ' to fail, but succeeded with ' +
this.format(value) + '.');
return value;
},
function (reason) {
if (reason !== expected) {
self.fail('Expected promise ' + self.describe() + ' to be rejected with ' +
this.format(expected) + ' actual reason was ' + this.format(reason) + '.');
}
return reason;
});
};
/** Ensure that the promise succeds, and returns a 'deferred subject' - an object that allows
assertions methods to be called, but doesn't actually execute the assertions until after
the promise has resolved.
@return {DeferredSubject} The deferred subject.
*/
PromiseSubject.prototype.eventually = function () {
return new EventualSubject(this, this.value);
};
module.exports = PromiseSubject;