Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Have promise rejection tests mirror how Chai tests exceptions. #167

Closed
175 changes: 98 additions & 77 deletions lib/chai-as-promised.js
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
(function () {
(function (factory) {
"use strict";

// Module systems magic dance.

/* istanbul ignore else */
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
// NodeJS
module.exports = chaiAsPromised;
module.exports = factory(require("check-error"));
} else if (typeof define === "function" && define.amd) {
// AMD
define(function () {
return chaiAsPromised;
});
define(["check-error"], factory);
} else {
/*global self: false */

// Other environment (usually <script> tag): plug in to global chai instance directly.
var chaiAsPromised = factory(self.checkError);
chai.use(chaiAsPromised);

// Expose as a property of the global object so that consumers can configure the `transferPromiseness` property.
self.chaiAsPromised = chaiAsPromised;
}
}(function (checkError) {
"use strict";

chaiAsPromised.transferPromiseness = function (assertion, promise) {
assertion.then = promise.then.bind(promise);
Expand All @@ -34,6 +32,13 @@
var Assertion = chai.Assertion;
var assert = chai.assert;

// If we are using a version of Chai that has checkError on it,
// we want to use that version to be consistent. Otherwise, we use
// what was passed to the factory.
if (utils.checkError) {
checkError = utils.checkError;
}

function isLegacyJQueryPromise(thenable) {
// jQuery promises are Promises/A+-compatible since 3.0.0. jQuery 3.0.0 is also the first version
// to define the catch method.
Expand Down Expand Up @@ -92,6 +97,10 @@
return typeof assertion.then === "function" ? assertion : assertion._obj;
}

function getReasonName(reason) {
return (reason instanceof Error) ? reason.toString() : checkError.getConstructorName(reason);
}

// Grab these first, before we modify `Assertion.prototype`.

var propertyNames = Object.getOwnPropertyNames(Assertion.prototype);
Expand All @@ -105,7 +114,6 @@
var that = this;
var derivedPromise = getBasePromise(that).then(
function (value) {
that._obj = value;
assertIfNegated(that,
"expected promise not to be fulfilled but it was fulfilled with #{act}",
{ actual: value });
Expand All @@ -114,7 +122,8 @@
function (reason) {
assertIfNotNegated(that,
"expected promise to be fulfilled but it was rejected with #{act}",
{ actual: reason });
{ actual: getReasonName(reason) });
return reason;
}
);

Expand All @@ -125,7 +134,6 @@
var that = this;
var derivedPromise = getBasePromise(that).then(
function (value) {
that._obj = value;
assertIfNotNegated(that,
"expected promise to be rejected but it was fulfilled with #{act}",
{ actual: value });
Expand All @@ -134,7 +142,7 @@
function (reason) {
assertIfNegated(that,
"expected promise not to be rejected but it was rejected with #{act}",
{ actual: reason });
{ actual: getReasonName(reason) });

// Return the reason, transforming this into a fulfillment, to allow further assertions, e.g.
// `promise.should.be.rejected.and.eventually.equal("reason")`.
Expand All @@ -145,21 +153,38 @@
chaiAsPromised.transferPromiseness(that, derivedPromise);
});

method("rejectedWith", function (Constructor, message) {
var desiredReason = null;
var constructorName = null;

if (Constructor instanceof RegExp || typeof Constructor === "string") {
message = Constructor;
Constructor = null;
} else if (Constructor && Constructor instanceof Error) {
desiredReason = Constructor;
Constructor = null;
message = null;
} else if (typeof Constructor === "function") {
constructorName = (new Constructor()).name;
method("rejectedWith", function (errorLike, errMsgMatcher, message) {
var errorLikeName = null;
var negate = utils.flag(this, "negate") || false;

// rejectedWith with that is called without arguments is
// the same as a plain ".rejected" use.
if (errorLike === undefined && errMsgMatcher === undefined &&
message === undefined) {
/* jshint expr: true */
this.rejected;
return;
}

if (message !== undefined) {
utils.flag(this, "message", message);
}

if (errorLike instanceof RegExp || typeof errorLike === "string") {
errMsgMatcher = errorLike;
errorLike = null;
} else if (errorLike && errorLike instanceof Error) {
errorLikeName = errorLike.toString();
} else if (typeof errorLike === "function") {
errorLikeName = checkError.getConstructorName(errorLike);
} else {
Constructor = null;
errorLike = null;
}
var everyArgIsDefined = Boolean(errorLike && errMsgMatcher);

var matcherRelation = "including";
if (errMsgMatcher instanceof RegExp) {
matcherRelation = "matching";
}

var that = this;
Expand All @@ -168,61 +193,60 @@
var assertionMessage = null;
var expected = null;

if (Constructor) {
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
"#{act}";
expected = constructorName;
} else if (message) {
var verb = message instanceof RegExp ? "matching" : "including";
assertionMessage = "expected promise to be rejected with an error " + verb + " #{exp} but it " +
"was fulfilled with #{act}";
expected = message;
} else if (desiredReason) {
if (errorLike) {
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
"#{act}";
expected = desiredReason;
expected = errorLikeName;
} else if (errMsgMatcher) {
assertionMessage = "expected promise to be rejected with an error " + matcherRelation +
" #{exp} but it was fulfilled with #{act}";
expected = errMsgMatcher;
}

that._obj = value;

assertIfNotNegated(that, assertionMessage, { expected: expected, actual: value });
return value;
},
function (reason) {
if (Constructor) {
that.assert(reason instanceof Constructor,
"expected promise to be rejected with #{exp} but it was rejected with #{act}",
"expected promise not to be rejected with #{exp} but it was rejected with #{act}",
constructorName,
reason);
var errorLikeCompatible = errorLike && (errorLike instanceof Error ?
checkError.compatibleInstance(reason, errorLike) :
checkError.compatibleConstructor(reason, errorLike));

var errMsgMatcherCompatible = errMsgMatcher && checkError.compatibleMessage(reason, errMsgMatcher);

var reasonName = getReasonName(reason);

if (negate && everyArgIsDefined) {
if (errorLikeCompatible && errMsgMatcherCompatible) {
that.assert(true,
null,
"expected promise not to be rejected with #{exp} but it was rejected " +
"with #{act}",
errorLikeName,
reasonName);
}
}

var reasonMessage = utils.type(reason) === "object" && "message" in reason ?
reason.message :
"" + reason;
if (message && reasonMessage !== null && reasonMessage !== undefined) {
if (message instanceof RegExp) {
that.assert(message.test(reasonMessage),
"expected promise to be rejected with an error matching #{exp} but got #{act}",
"expected promise not to be rejected with an error matching #{exp}",
message,
reasonMessage);
else {
if (errorLike) {
that.assert(errorLikeCompatible,
"expected promise to be rejected with #{exp} but it was rejected with #{act}",
"expected promise not to be rejected with #{exp} but it was rejected " +
"with #{act}",
errorLikeName,
reasonName);
}
if (typeof message === "string") {
that.assert(reasonMessage.indexOf(message) !== -1,
"expected promise to be rejected with an error including #{exp} but got #{act}",
"expected promise not to be rejected with an error including #{exp}",
message,
reasonMessage);

if (errMsgMatcher) {
that.assert(errMsgMatcherCompatible,
"expected promise to be rejected with an error " + matcherRelation +
" #{exp} but got #{act}",
"expected promise not to be rejected with an error " + matcherRelation +
" #{exp}",
errMsgMatcher,
checkError.getMessage(reason));
}
}

if (desiredReason) {
that.assert(reason === desiredReason,
"expected promise to be rejected with #{exp} but it was rejected with #{act}",
"expected promise not to be rejected with #{exp}",
desiredReason,
reason);
}
return reason;
}
);

Expand Down Expand Up @@ -326,14 +350,9 @@
return (new Assertion(promise, message)).to.be.fulfilled;
};

assert.isRejected = function (promise, toTestAgainst, message) {
if (typeof toTestAgainst === "string") {
message = toTestAgainst;
toTestAgainst = undefined;
}

assert.isRejected = function (promise, errorLike, errMsgMatcher, message) {
var assertion = (new Assertion(promise, message));
return toTestAgainst !== undefined ? assertion.to.be.rejectedWith(toTestAgainst) : assertion.to.be.rejected;
return assertion.to.be.rejectedWith(errorLike, errMsgMatcher, message);
};

assert.becomes = function (promise, value, message) {
Expand Down Expand Up @@ -372,4 +391,6 @@
};
});
}
}());

return chaiAsPromised;
}));
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@
"cover": "istanbul cover node_modules/mocha/bin/_mocha && opener ./coverage/lcov-report/lib/chai-as-promised.js.html"
},
"peerDependencies": {
"chai": ">= 2.1.2 < 4"
"chai": ">= 2.1.2 < 4",
"check-error": "^1.0.2"
},
"devDependencies": {
"chai": "^3.0.0",
"coffee-script": "1.10.0",
"istanbul": "0.4.1",
"ecstatic": "^1.3.1",
"glob": "^6.0.1",
"istanbul": "0.4.1",
"jshint": "^2.8.0",
"mocha": "^2.3.4",
"opener": "^1.4.1",
Expand Down
Loading