chai-as-promised 5.0.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
package/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright © 2012–2015 Domenic Denicola <d@domenic.me>
1
+ Copyright © 2012–2016 Domenic Denicola <d@domenic.me>
2
2
 
3
3
  This work is free. You can redistribute it and/or modify it under the
4
4
  terms of the Do What The Fuck You Want To Public License, Version 2,
package/README.md CHANGED
@@ -27,19 +27,19 @@ you can write code that expresses what you really mean:
27
27
  return doSomethingAsync().should.eventually.equal("foo");
28
28
  ```
29
29
 
30
- or if you have a testing framework that doesn't allow returning promises to signal asynchronous test completion, then
31
- you can use the following workaround:
30
+ or if you have a case where `return` is not preferable (e.g. style considerations) or not possible (e.g. the testing framework doesn't allow returning promises to signal asynchronous test completion), then you can use the following workaround (where `done()` is supplied by the test framework):
32
31
 
33
32
  ```javascript
34
33
  doSomethingAsync().should.eventually.equal("foo").notify(done);
35
34
  ```
36
35
 
36
+ *Notice*: either `return` or `notify(done)` _must_ be used with promise assertions. This can be a slight departure from the existing format of assertions being used on a project or by a team. Those other assertions are likely synchronous and thus do not require special handling.
37
+
37
38
  ## How to Use
38
39
 
39
40
  ### `should`/`expect` Interface
40
41
 
41
- The most powerful extension provided by Chai as Promised is the `eventually` property. With it, you can transform any
42
- existing Chai assertion into one that acts on a promise:
42
+ The most powerful extension provided by Chai as Promised is the `eventually` property. With it, you can transform any existing Chai assertion into one that acts on a promise:
43
43
 
44
44
  ```javascript
45
45
  (2 + 2).should.equal(4);
@@ -66,8 +66,7 @@ return promise.should.be.rejectedWith(Error); // other variants of Chai's `throw
66
66
 
67
67
  ### `assert` Interface
68
68
 
69
- As with the `should`/`expect` interface, Chai as Promised provides an `eventually` extender to `chai.assert`, allowing
70
- any existing Chai assertion to be used on a promise:
69
+ As with the `should`/`expect` interface, Chai as Promised provides an `eventually` extender to `chai.assert`, allowing any existing Chai assertion to be used on a promise:
71
70
 
72
71
  ```javascript
73
72
  assert.equal(2 + 2, 4, "This had better be true");
@@ -91,9 +90,7 @@ return assert.isRejected(promise, /error message matcher/, "optional message");
91
90
 
92
91
  ### Progress Callbacks
93
92
 
94
- Chai as Promised does not have any intrinsic support for testing promise progress callbacks. The properties you would
95
- want to test are probably much better suited to a library like [Sinon.JS][sinon], perhaps in conjunction with
96
- [Sinon–Chai][sinon-chai]:
93
+ Chai as Promised does not have any intrinsic support for testing promise progress callbacks. The properties you would want to test are probably much better suited to a library like [Sinon.JS][sinon], perhaps in conjunction with [Sinon–Chai][sinon-chai]:
97
94
 
98
95
  ```javascript
99
96
  var progressSpy = sinon.spy();
@@ -107,10 +104,7 @@ return promise.then(null, null, progressSpy).then(function () {
107
104
 
108
105
  ### Customizing Output Promises
109
106
 
110
- By default, the promises returned by Chai as Promised's assertions are regular Chai assertion objects, extended with
111
- a single `then` method derived from the input promise. To change this behavior, for instance to output a promise with
112
- more useful sugar methods such as are found in most promise libraries, you can override
113
- `chaiAsPromised.transferPromiseness`. Here's an example that transfer's Q's `finally` and `done` methods:
107
+ By default, the promises returned by Chai as Promised's assertions are regular Chai assertion objects, extended with a single `then` method derived from the input promise. To change this behavior, for instance to output a promise with more useful sugar methods such as are found in most promise libraries, you can override `chaiAsPromised.transferPromiseness`. Here's an example that transfer's Q's `finally` and `done` methods:
114
108
 
115
109
  ```js
116
110
  chaiAsPromised.transferPromiseness = function (assertion, promise) {
@@ -122,8 +116,7 @@ chaiAsPromised.transferPromiseness = function (assertion, promise) {
122
116
 
123
117
  ### Transforming Arguments to the Asserters
124
118
 
125
- Another advanced customization hook Chai as Promised allows is if you want to transform the arguments to the
126
- asserters, possibly asynchronously. Here is a toy example:
119
+ Another advanced customization hook Chai as Promised allows is if you want to transform the arguments to the asserters, possibly asynchronously. Here is a toy example:
127
120
 
128
121
  ```js
129
122
  chaiAsPromised.transformAsserterArgs = function (args) {
@@ -131,12 +124,10 @@ chaiAsPromised.transformAsserterArgs = function (args) {
131
124
  }
132
125
 
133
126
  Promise.resolve(2).should.eventually.equal(2); // will now fail!
134
- Promise.resolve(2).should.eventually.equal(3); // will now pass!
127
+ Promise.resolve(3).should.eventually.equal(2); // will now pass!
135
128
  ```
136
129
 
137
- The transform can even be asynchronous, returning a promise for an array instead of an array directly. An example
138
- of that might be using `Promise.all` so that an array of promises becomes a promise for an array. If you do that,
139
- then you can compare promises against other promises using the asserters:
130
+ The transform can even be asynchronous, returning a promise for an array instead of an array directly. An example of that might be using `Promise.all` so that an array of promises becomes a promise for an array. If you do that, then you can compare promises against other promises using the asserters:
140
131
 
141
132
  ```js
142
133
  // This will normally fail, since within() only works on numbers.
@@ -153,17 +144,15 @@ Promise.resolve(2).should.eventually.be.within(Promise.resolve(1), Promise.resol
153
144
 
154
145
  ### Compatibility
155
146
 
156
- Chai as Promised is compatible with all promises following the [Promises/A+ specification][spec]. Notably, jQuery's
157
- so-called “promises” are not up to spec, and Chai as Promised will not work with them. In particular, Chai as Promised
158
- makes extensive use of the standard [transformation behavior][] of `then`, which jQuery does not support.
147
+ Chai as Promised is compatible with all promises following the [Promises/A+ specification][spec].
148
+
149
+ Notably, jQuery's promises were not up to spec before jQuery 3.0, and Chai as Promised will not work with them. In particular, Chai as Promised makes extensive use of the standard [transformation behavior][] of `then`, which jQuery<3.0 does not support.
150
+
151
+ Angular promises have a special digest cycle for their processing, and [need extra setup code to work with Chai as Promised](http://stackoverflow.com/a/37374041/3191).
159
152
 
160
153
  ### Working with Non-Promise–Friendly Test Runners
161
154
 
162
- Some test runners (e.g. Jasmine, QUnit, or tap/tape) do not have the ability to use the returned promise to signal
163
- asynchronous test completion. If possible, I'd recommend switching to ones that do, such as [Mocha][mocha-promises],
164
- [Buster][buster-promises], or [blue-tape][]. But if that's not an option, Chai as Promised still has you covered. As
165
- long as your test framework takes a callback indicating when the asynchronous test run is over, Chai as Promised can
166
- adapt to that situation with its `notify` method, like so:
155
+ Some test runners (e.g. Jasmine, QUnit, or tap/tape) do not have the ability to use the returned promise to signal asynchronous test completion. If possible, I'd recommend switching to ones that do, such as [Mocha][mocha-promises], [Buster][buster-promises], or [blue-tape][]. But if that's not an option, Chai as Promised still has you covered. As long as your test framework takes a callback indicating when the asynchronous test run is over, Chai as Promised can adapt to that situation with its `notify` method, like so:
167
156
 
168
157
  ```javascript
169
158
  it("should be fulfilled", function (done) {
@@ -175,12 +164,9 @@ it("should be rejected", function (done) {
175
164
  });
176
165
  ```
177
166
 
178
- In these examples, if the conditions are not met, the test runner will receive an error of the form `"expected promise
179
- to be fulfilled but it was rejected with [Error: error message]"`, or `"expected promise to be rejected but it was
180
- fulfilled."`
167
+ In these examples, if the conditions are not met, the test runner will receive an error of the form `"expected promise to be fulfilled but it was rejected with [Error: error message]"`, or `"expected promise to be rejected but it was fulfilled."`
181
168
 
182
- There's another form of `notify` which is useful in certain situations, like doing assertions after a promise is
183
- complete. For example:
169
+ There's another form of `notify` which is useful in certain situations, like doing assertions after a promise is complete. For example:
184
170
 
185
171
  ```javascript
186
172
  it("should change the state", function (done) {
@@ -191,26 +177,23 @@ it("should change the state", function (done) {
191
177
  });
192
178
  ```
193
179
 
194
- Notice how `.notify(done)` is hanging directly off of `.should`, instead of appearing after a promise assertion. This
195
- indicates to Chai as Promised that it should pass fulfillment or rejection directly through to the testing framework.
196
- Thus, the above code will fail with a Chai as Promised error (`"expected promise to be fulfilled…"`) if `promise` is
197
- rejected, but will fail with a simple Chai error (`expected "before" to equal "after"`) if `otherState` does not change.
180
+ Notice how `.notify(done)` is hanging directly off of `.should`, instead of appearing after a promise assertion. This indicates to Chai as Promised that it should pass fulfillment or rejection directly through to the testing framework. Thus, the above code will fail with a Chai as Promised error (`"expected promise to be fulfilled…"`) if `promise` is rejected, but will fail with a simple Chai error (`expected "before" to equal "after"`) if `otherState` does not change.
198
181
 
199
- Another example of where this can be useful is when performing assertions on multiple promises:
182
+ ### Multiple Promise Assertions
183
+
184
+ To perform assertions on multiple promises, use `Promise.all` to combine multiple Chai as Promised assertions:
200
185
 
201
186
  ```javascript
202
- it("should all be well", function (done) {
203
- Q.all([
187
+ it("should all be well", function () {
188
+ return Promise.all([
204
189
  promiseA.should.become("happy"),
205
190
  promiseB.should.eventually.have.property("fun times"),
206
191
  promiseC.should.be.rejectedWith(TypeError, "only joyful types are allowed")
207
- ]).should.notify(done);
192
+ ]);
208
193
  });
209
194
  ```
210
195
 
211
- This will pass any failures of the individual promise assertions up to the test framework, instead of wrapping them in
212
- an `"expected promise to be fulfilled…"` message as would happen if you did
213
- `Q.all([…]).should.be.fulfilled.and.notify(done)`.
196
+ This will pass any failures of the individual promise assertions up to the test framework, instead of wrapping them in an `"expected promise to be fulfilled…"` message as would happen if you did `return Promise.all([…]).should.be.fulfilled`. If you can't use `return`, then use `.should.notify(done)`, similar to the previous examples.
214
197
 
215
198
  ## Installation and Setup
216
199
 
@@ -223,35 +206,21 @@ var chai = require("chai");
223
206
  var chaiAsPromised = require("chai-as-promised");
224
207
 
225
208
  chai.use(chaiAsPromised);
226
- ```
227
-
228
- You can of course put this code in a common test fixture file; for an example using [Mocha][], see
229
- [the Chai as Promised tests themselves][fixturedemo].
230
209
 
231
- ### AMD
232
-
233
- Chai as Promised supports being used as an [AMD][amd] module, registering itself anonymously (just like Chai). So,
234
- assuming you have configured your loader to map the Chai and Chai as Promised files to the respective module IDs
235
- `"chai"` and `"chai-as-promised"`, you can use them as follows:
236
-
237
- ```javascript
238
- define(function (require, exports, module) {
239
- var chai = require("chai");
240
- var chaiAsPromised = require("chai-as-promised");
241
-
242
- chai.use(chaiAsPromised);
243
- });
210
+ // Then either:
211
+ var expect = chai.expect;
212
+ // or:
213
+ var assert = chai.assert;
214
+ // or:
215
+ chai.should();
216
+ // according to your preference of assertion style
244
217
  ```
245
218
 
246
- ### `<script>` tag
219
+ You can of course put this code in a common test fixture file; for an example using [Mocha][], see [the Chai as Promised tests themselves][fixturedemo].
247
220
 
248
- If you include Chai as Promised directly with a `<script>` tag, after the one for Chai itself, then it will
249
- automatically plug in to Chai and be ready for use:
221
+ ### In the Browser
250
222
 
251
- ```html
252
- <script src="chai.js"></script>
253
- <script src="chai-as-promised.js"></script>
254
- ```
223
+ To use Chai as Promised in environments that don't support Node.js-like CommonJS modules, you'll need to use a bundling tool like [browserify][].
255
224
 
256
225
  ### Karma
257
226
 
@@ -275,3 +244,4 @@ Chai as Promised is only compatible with modern browsers (IE ≥9, Safari ≥6,
275
244
  [sinon-chai]: https://github.com/domenic/sinon-chai
276
245
  [Karma]: https://karma-runner.github.io/
277
246
  [karma-chai-as-promised]: https://github.com/vlkosinov/karma-chai-as-promised
247
+ [browserify]: http://browserify.org/
@@ -1,371 +1,374 @@
1
- (function () {
2
- "use strict";
3
-
4
- // Module systems magic dance.
5
-
6
- /* istanbul ignore else */
7
- if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
8
- // NodeJS
9
- module.exports = chaiAsPromised;
10
- } else if (typeof define === "function" && define.amd) {
11
- // AMD
12
- define(function () {
13
- return chaiAsPromised;
14
- });
15
- } else {
16
- /*global self: false */
1
+ "use strict";
2
+ var checkError = require("check-error");
3
+
4
+ module.exports = function (chai, utils) {
5
+ var Assertion = chai.Assertion;
6
+ var assert = chai.assert;
7
+
8
+ // If we are using a version of Chai that has checkError on it,
9
+ // we want to use that version to be consistent. Otherwise, we use
10
+ // what was passed to the factory.
11
+ if (utils.checkError) {
12
+ checkError = utils.checkError;
13
+ }
17
14
 
18
- // Other environment (usually <script> tag): plug in to global chai instance directly.
19
- chai.use(chaiAsPromised);
15
+ function isLegacyJQueryPromise(thenable) {
16
+ // jQuery promises are Promises/A+-compatible since 3.0.0. jQuery 3.0.0 is also the first version
17
+ // to define the catch method.
18
+ return typeof thenable.catch !== "function" &&
19
+ typeof thenable.always === "function" &&
20
+ typeof thenable.done === "function" &&
21
+ typeof thenable.fail === "function" &&
22
+ typeof thenable.pipe === "function" &&
23
+ typeof thenable.progress === "function" &&
24
+ typeof thenable.state === "function";
25
+ }
20
26
 
21
- // Expose as a property of the global object so that consumers can configure the `transferPromiseness` property.
22
- self.chaiAsPromised = chaiAsPromised;
27
+ function assertIsAboutPromise(assertion) {
28
+ if (typeof assertion._obj.then !== "function") {
29
+ throw new TypeError(utils.inspect(assertion._obj) + " is not a thenable.");
30
+ }
31
+ if (isLegacyJQueryPromise(assertion._obj)) {
32
+ throw new TypeError("Chai as Promised is incompatible with thenables of jQuery<3.0.0, sorry! Please " +
33
+ "upgrade jQuery or use another Promises/A+ compatible library (see " +
34
+ "http://promisesaplus.com/).");
35
+ }
23
36
  }
24
37
 
25
- chaiAsPromised.transferPromiseness = function (assertion, promise) {
26
- assertion.then = promise.then.bind(promise);
27
- };
38
+ function method(name, asserter) {
39
+ utils.addMethod(Assertion.prototype, name, function () {
40
+ assertIsAboutPromise(this);
41
+ return asserter.apply(this, arguments);
42
+ });
43
+ }
28
44
 
29
- chaiAsPromised.transformAsserterArgs = function (values) {
30
- return values;
31
- };
45
+ function property(name, asserter) {
46
+ utils.addProperty(Assertion.prototype, name, function () {
47
+ assertIsAboutPromise(this);
48
+ return asserter.apply(this, arguments);
49
+ });
50
+ }
32
51
 
33
- function chaiAsPromised(chai, utils) {
34
- var Assertion = chai.Assertion;
35
- var assert = chai.assert;
36
-
37
- function isJQueryPromise(thenable) {
38
- return typeof thenable.always === "function" &&
39
- typeof thenable.done === "function" &&
40
- typeof thenable.fail === "function" &&
41
- typeof thenable.pipe === "function" &&
42
- typeof thenable.progress === "function" &&
43
- typeof thenable.state === "function";
44
- }
52
+ function doNotify(promise, done) {
53
+ promise.then(function () { done(); }, done);
54
+ }
45
55
 
46
- function assertIsAboutPromise(assertion) {
47
- if (typeof assertion._obj.then !== "function") {
48
- throw new TypeError(utils.inspect(assertion._obj) + " is not a thenable.");
49
- }
50
- if (isJQueryPromise(assertion._obj)) {
51
- throw new TypeError("Chai as Promised is incompatible with jQuery's thenables, sorry! Please use a " +
52
- "Promises/A+ compatible library (see http://promisesaplus.com/).");
53
- }
54
- }
56
+ // These are for clarity and to bypass Chai refusing to allow `undefined` as actual when used with `assert`.
57
+ function assertIfNegated(assertion, message, extra) {
58
+ assertion.assert(true, null, message, extra.expected, extra.actual);
59
+ }
55
60
 
56
- function method(name, asserter) {
57
- utils.addMethod(Assertion.prototype, name, function () {
58
- assertIsAboutPromise(this);
59
- return asserter.apply(this, arguments);
60
- });
61
- }
61
+ function assertIfNotNegated(assertion, message, extra) {
62
+ assertion.assert(false, message, null, extra.expected, extra.actual);
63
+ }
62
64
 
63
- function property(name, asserter) {
64
- utils.addProperty(Assertion.prototype, name, function () {
65
- assertIsAboutPromise(this);
66
- return asserter.apply(this, arguments);
67
- });
68
- }
65
+ function getBasePromise(assertion) {
66
+ // We need to chain subsequent asserters on top of ones in the chain already (consider
67
+ // `eventually.have.property("foo").that.equals("bar")`), only running them after the existing ones pass.
68
+ // So the first base-promise is `assertion._obj`, but after that we use the assertions themselves, i.e.
69
+ // previously derived promises, to chain off of.
70
+ return typeof assertion.then === "function" ? assertion : assertion._obj;
71
+ }
69
72
 
70
- function doNotify(promise, done) {
71
- promise.then(function () { done(); }, done);
72
- }
73
+ function getReasonName(reason) {
74
+ return (reason instanceof Error) ? reason.toString() : checkError.getConstructorName(reason);
75
+ }
73
76
 
74
- // These are for clarity and to bypass Chai refusing to allow `undefined` as actual when used with `assert`.
75
- function assertIfNegated(assertion, message, extra) {
76
- assertion.assert(true, null, message, extra.expected, extra.actual);
77
+ // Grab these first, before we modify `Assertion.prototype`.
78
+
79
+ var propertyNames = Object.getOwnPropertyNames(Assertion.prototype);
80
+
81
+ var propertyDescs = {};
82
+ propertyNames.forEach(function (name) {
83
+ propertyDescs[name] = Object.getOwnPropertyDescriptor(Assertion.prototype, name);
84
+ });
85
+
86
+ property("fulfilled", function () {
87
+ var that = this;
88
+ var derivedPromise = getBasePromise(that).then(
89
+ function (value) {
90
+ assertIfNegated(that,
91
+ "expected promise not to be fulfilled but it was fulfilled with #{act}",
92
+ { actual: value });
93
+ return value;
94
+ },
95
+ function (reason) {
96
+ assertIfNotNegated(that,
97
+ "expected promise to be fulfilled but it was rejected with #{act}",
98
+ { actual: getReasonName(reason) });
99
+ return reason;
100
+ }
101
+ );
102
+
103
+ module.exports.transferPromiseness(that, derivedPromise);
104
+ });
105
+
106
+ property("rejected", function () {
107
+ var that = this;
108
+ var derivedPromise = getBasePromise(that).then(
109
+ function (value) {
110
+ assertIfNotNegated(that,
111
+ "expected promise to be rejected but it was fulfilled with #{act}",
112
+ { actual: value });
113
+ return value;
114
+ },
115
+ function (reason) {
116
+ assertIfNegated(that,
117
+ "expected promise not to be rejected but it was rejected with #{act}",
118
+ { actual: getReasonName(reason) });
119
+
120
+ // Return the reason, transforming this into a fulfillment, to allow further assertions, e.g.
121
+ // `promise.should.be.rejected.and.eventually.equal("reason")`.
122
+ return reason;
123
+ }
124
+ );
125
+
126
+ module.exports.transferPromiseness(that, derivedPromise);
127
+ });
128
+
129
+ method("rejectedWith", function (errorLike, errMsgMatcher, message) {
130
+ var errorLikeName = null;
131
+ var negate = utils.flag(this, "negate") || false;
132
+
133
+ // rejectedWith with that is called without arguments is
134
+ // the same as a plain ".rejected" use.
135
+ if (errorLike === undefined && errMsgMatcher === undefined &&
136
+ message === undefined) {
137
+ /* jshint expr: true */
138
+ this.rejected;
139
+ return;
77
140
  }
78
141
 
79
- function assertIfNotNegated(assertion, message, extra) {
80
- assertion.assert(false, message, null, extra.expected, extra.actual);
142
+ if (message !== undefined) {
143
+ utils.flag(this, "message", message);
81
144
  }
82
145
 
83
- function getBasePromise(assertion) {
84
- // We need to chain subsequent asserters on top of ones in the chain already (consider
85
- // `eventually.have.property("foo").that.equals("bar")`), only running them after the existing ones pass.
86
- // So the first base-promise is `assertion._obj`, but after that we use the assertions themselves, i.e.
87
- // previously derived promises, to chain off of.
88
- return typeof assertion.then === "function" ? assertion : assertion._obj;
146
+ if (errorLike instanceof RegExp || typeof errorLike === "string") {
147
+ errMsgMatcher = errorLike;
148
+ errorLike = null;
149
+ } else if (errorLike && errorLike instanceof Error) {
150
+ errorLikeName = errorLike.toString();
151
+ } else if (typeof errorLike === "function") {
152
+ errorLikeName = checkError.getConstructorName(errorLike);
153
+ } else {
154
+ errorLike = null;
89
155
  }
156
+ var everyArgIsDefined = Boolean(errorLike && errMsgMatcher);
90
157
 
91
- // Grab these first, before we modify `Assertion.prototype`.
92
-
93
- var propertyNames = Object.getOwnPropertyNames(Assertion.prototype);
94
-
95
- var propertyDescs = {};
96
- propertyNames.forEach(function (name) {
97
- propertyDescs[name] = Object.getOwnPropertyDescriptor(Assertion.prototype, name);
98
- });
158
+ var matcherRelation = "including";
159
+ if (errMsgMatcher instanceof RegExp) {
160
+ matcherRelation = "matching";
161
+ }
99
162
 
100
- property("fulfilled", function () {
101
- var that = this;
102
- var derivedPromise = getBasePromise(that).then(
103
- function (value) {
104
- that._obj = value;
105
- assertIfNegated(that,
106
- "expected promise not to be fulfilled but it was fulfilled with #{act}",
107
- { actual: value });
108
- return value;
109
- },
110
- function (reason) {
111
- assertIfNotNegated(that,
112
- "expected promise to be fulfilled but it was rejected with #{act}",
113
- { actual: reason });
163
+ var that = this;
164
+ var derivedPromise = getBasePromise(that).then(
165
+ function (value) {
166
+ var assertionMessage = null;
167
+ var expected = null;
168
+
169
+ if (errorLike) {
170
+ assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
171
+ "#{act}";
172
+ expected = errorLikeName;
173
+ } else if (errMsgMatcher) {
174
+ assertionMessage = "expected promise to be rejected with an error " + matcherRelation +
175
+ " #{exp} but it was fulfilled with #{act}";
176
+ expected = errMsgMatcher;
114
177
  }
115
- );
116
-
117
- chaiAsPromised.transferPromiseness(that, derivedPromise);
118
- });
119
178
 
120
- property("rejected", function () {
121
- var that = this;
122
- var derivedPromise = getBasePromise(that).then(
123
- function (value) {
124
- that._obj = value;
125
- assertIfNotNegated(that,
126
- "expected promise to be rejected but it was fulfilled with #{act}",
127
- { actual: value });
128
- return value;
129
- },
130
- function (reason) {
131
- assertIfNegated(that,
132
- "expected promise not to be rejected but it was rejected with #{act}",
133
- { actual: reason });
134
-
135
- // Return the reason, transforming this into a fulfillment, to allow further assertions, e.g.
136
- // `promise.should.be.rejected.and.eventually.equal("reason")`.
137
- return reason;
138
- }
139
- );
140
-
141
- chaiAsPromised.transferPromiseness(that, derivedPromise);
142
- });
143
-
144
- method("rejectedWith", function (Constructor, message) {
145
- var desiredReason = null;
146
- var constructorName = null;
147
-
148
- if (Constructor instanceof RegExp || typeof Constructor === "string") {
149
- message = Constructor;
150
- Constructor = null;
151
- } else if (Constructor && Constructor instanceof Error) {
152
- desiredReason = Constructor;
153
- Constructor = null;
154
- message = null;
155
- } else if (typeof Constructor === "function") {
156
- constructorName = (new Constructor()).name;
157
- } else {
158
- Constructor = null;
159
- }
160
-
161
- var that = this;
162
- var derivedPromise = getBasePromise(that).then(
163
- function (value) {
164
- var assertionMessage = null;
165
- var expected = null;
166
-
167
- if (Constructor) {
168
- assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
169
- "#{act}";
170
- expected = constructorName;
171
- } else if (message) {
172
- var verb = message instanceof RegExp ? "matching" : "including";
173
- assertionMessage = "expected promise to be rejected with an error " + verb + " #{exp} but it " +
174
- "was fulfilled with #{act}";
175
- expected = message;
176
- } else if (desiredReason) {
177
- assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
178
- "#{act}";
179
- expected = desiredReason;
179
+ assertIfNotNegated(that, assertionMessage, { expected: expected, actual: value });
180
+ return value;
181
+ },
182
+ function (reason) {
183
+ var errorLikeCompatible = errorLike && (errorLike instanceof Error ?
184
+ checkError.compatibleInstance(reason, errorLike) :
185
+ checkError.compatibleConstructor(reason, errorLike));
186
+
187
+ var errMsgMatcherCompatible = errMsgMatcher && checkError.compatibleMessage(reason, errMsgMatcher);
188
+
189
+ var reasonName = getReasonName(reason);
190
+
191
+ if (negate && everyArgIsDefined) {
192
+ if (errorLikeCompatible && errMsgMatcherCompatible) {
193
+ that.assert(true,
194
+ null,
195
+ "expected promise not to be rejected with #{exp} but it was rejected " +
196
+ "with #{act}",
197
+ errorLikeName,
198
+ reasonName);
180
199
  }
181
-
182
- that._obj = value;
183
-
184
- assertIfNotNegated(that, assertionMessage, { expected: expected, actual: value });
185
- },
186
- function (reason) {
187
- if (Constructor) {
188
- that.assert(reason instanceof Constructor,
200
+ }
201
+ else {
202
+ if (errorLike) {
203
+ that.assert(errorLikeCompatible,
189
204
  "expected promise to be rejected with #{exp} but it was rejected with #{act}",
190
- "expected promise not to be rejected with #{exp} but it was rejected with #{act}",
191
- constructorName,
192
- reason);
205
+ "expected promise not to be rejected with #{exp} but it was rejected " +
206
+ "with #{act}",
207
+ errorLikeName,
208
+ reasonName);
193
209
  }
194
210
 
195
- var reasonMessage = utils.type(reason) === "object" && "message" in reason ?
196
- reason.message :
197
- "" + reason;
198
- if (message && reasonMessage !== null && reasonMessage !== undefined) {
199
- if (message instanceof RegExp) {
200
- that.assert(message.test(reasonMessage),
201
- "expected promise to be rejected with an error matching #{exp} but got #{act}",
202
- "expected promise not to be rejected with an error matching #{exp}",
203
- message,
204
- reasonMessage);
205
- }
206
- if (typeof message === "string") {
207
- that.assert(reasonMessage.indexOf(message) !== -1,
208
- "expected promise to be rejected with an error including #{exp} but got #{act}",
209
- "expected promise not to be rejected with an error including #{exp}",
210
- message,
211
- reasonMessage);
212
- }
213
- }
214
-
215
- if (desiredReason) {
216
- that.assert(reason === desiredReason,
217
- "expected promise to be rejected with #{exp} but it was rejected with #{act}",
218
- "expected promise not to be rejected with #{exp}",
219
- desiredReason,
220
- reason);
211
+ if (errMsgMatcher) {
212
+ that.assert(errMsgMatcherCompatible,
213
+ "expected promise to be rejected with an error " + matcherRelation +
214
+ " #{exp} but got #{act}",
215
+ "expected promise not to be rejected with an error " + matcherRelation +
216
+ " #{exp}",
217
+ errMsgMatcher,
218
+ checkError.getMessage(reason));
221
219
  }
222
220
  }
223
- );
224
221
 
225
- chaiAsPromised.transferPromiseness(that, derivedPromise);
226
- });
222
+ return reason;
223
+ }
224
+ );
227
225
 
228
- property("eventually", function () {
229
- utils.flag(this, "eventually", true);
230
- });
226
+ module.exports.transferPromiseness(that, derivedPromise);
227
+ });
231
228
 
232
- method("notify", function (done) {
233
- doNotify(getBasePromise(this), done);
234
- });
229
+ property("eventually", function () {
230
+ utils.flag(this, "eventually", true);
231
+ });
235
232
 
236
- method("become", function (value) {
237
- return this.eventually.deep.equal(value);
238
- });
233
+ method("notify", function (done) {
234
+ doNotify(getBasePromise(this), done);
235
+ });
239
236
 
240
- ////////
241
- // `eventually`
237
+ method("become", function (value, message) {
238
+ return this.eventually.deep.equal(value, message);
239
+ });
242
240
 
243
- // We need to be careful not to trigger any getters, thus `Object.getOwnPropertyDescriptor` usage.
244
- var methodNames = propertyNames.filter(function (name) {
245
- return name !== "assert" && typeof propertyDescs[name].value === "function";
246
- });
241
+ ////////
242
+ // `eventually`
247
243
 
248
- methodNames.forEach(function (methodName) {
249
- Assertion.overwriteMethod(methodName, function (originalMethod) {
250
- return function () {
251
- doAsserterAsyncAndAddThen(originalMethod, this, arguments);
252
- };
253
- });
254
- });
244
+ // We need to be careful not to trigger any getters, thus `Object.getOwnPropertyDescriptor` usage.
245
+ var methodNames = propertyNames.filter(function (name) {
246
+ return name !== "assert" && typeof propertyDescs[name].value === "function";
247
+ });
255
248
 
256
- var getterNames = propertyNames.filter(function (name) {
257
- return name !== "_obj" && typeof propertyDescs[name].get === "function";
249
+ methodNames.forEach(function (methodName) {
250
+ Assertion.overwriteMethod(methodName, function (originalMethod) {
251
+ return function () {
252
+ doAsserterAsyncAndAddThen(originalMethod, this, arguments);
253
+ };
258
254
  });
259
-
260
- getterNames.forEach(function (getterName) {
261
- // Chainable methods are things like `an`, which can work both for `.should.be.an.instanceOf` and as
262
- // `should.be.an("object")`. We need to handle those specially.
263
- var isChainableMethod = Assertion.prototype.__methods.hasOwnProperty(getterName);
264
-
265
- if (isChainableMethod) {
266
- Assertion.overwriteChainableMethod(
267
- getterName,
268
- function (originalMethod) {
269
- return function() {
270
- doAsserterAsyncAndAddThen(originalMethod, this, arguments);
271
- };
272
- },
273
- function (originalGetter) {
274
- return function() {
275
- doAsserterAsyncAndAddThen(originalGetter, this);
276
- };
277
- }
278
- );
279
- } else {
280
- Assertion.overwriteProperty(getterName, function (originalGetter) {
281
- return function () {
255
+ });
256
+
257
+ var getterNames = propertyNames.filter(function (name) {
258
+ return name !== "_obj" && typeof propertyDescs[name].get === "function";
259
+ });
260
+
261
+ getterNames.forEach(function (getterName) {
262
+ // Chainable methods are things like `an`, which can work both for `.should.be.an.instanceOf` and as
263
+ // `should.be.an("object")`. We need to handle those specially.
264
+ var isChainableMethod = Assertion.prototype.__methods.hasOwnProperty(getterName);
265
+
266
+ if (isChainableMethod) {
267
+ Assertion.overwriteChainableMethod(
268
+ getterName,
269
+ function (originalMethod) {
270
+ return function() {
271
+ doAsserterAsyncAndAddThen(originalMethod, this, arguments);
272
+ };
273
+ },
274
+ function (originalGetter) {
275
+ return function() {
282
276
  doAsserterAsyncAndAddThen(originalGetter, this);
283
277
  };
284
- });
285
- }
286
- });
287
-
288
- function doAsserterAsyncAndAddThen(asserter, assertion, args) {
289
- // Since we're intercepting all methods/properties, we need to just pass through if they don't want
290
- // `eventually`, or if we've already fulfilled the promise (see below).
291
- if (!utils.flag(assertion, "eventually")) {
292
- return asserter.apply(assertion, args);
293
- }
294
-
295
- var derivedPromise = getBasePromise(assertion).then(function (value) {
296
- // Set up the environment for the asserter to actually run: `_obj` should be the fulfillment value, and
297
- // now that we have the value, we're no longer in "eventually" mode, so we won't run any of this code,
298
- // just the base Chai code that we get to via the short-circuit above.
299
- assertion._obj = value;
300
- utils.flag(assertion, "eventually", false);
301
-
302
- return args ? chaiAsPromised.transformAsserterArgs(args) : args;
303
- }).then(function (args) {
304
- asserter.apply(assertion, args);
305
-
306
- // Because asserters, for example `property`, can change the value of `_obj` (i.e. change the "object"
307
- // flag), we need to communicate this value change to subsequent chained asserters. Since we build a
308
- // promise chain paralleling the asserter chain, we can use it to communicate such changes.
309
- return assertion._obj;
278
+ }
279
+ );
280
+ } else {
281
+ Assertion.overwriteProperty(getterName, function (originalGetter) {
282
+ return function () {
283
+ doAsserterAsyncAndAddThen(originalGetter, this);
284
+ };
310
285
  });
286
+ }
287
+ });
311
288
 
312
- chaiAsPromised.transferPromiseness(assertion, derivedPromise);
289
+ function doAsserterAsyncAndAddThen(asserter, assertion, args) {
290
+ // Since we're intercepting all methods/properties, we need to just pass through if they don't want
291
+ // `eventually`, or if we've already fulfilled the promise (see below).
292
+ if (!utils.flag(assertion, "eventually")) {
293
+ return asserter.apply(assertion, args);
313
294
  }
314
295
 
315
- ///////
316
- // Now use the `Assertion` framework to build an `assert` interface.
317
- var originalAssertMethods = Object.getOwnPropertyNames(assert).filter(function (propName) {
318
- return typeof assert[propName] === "function";
296
+ var derivedPromise = getBasePromise(assertion).then(function (value) {
297
+ // Set up the environment for the asserter to actually run: `_obj` should be the fulfillment value, and
298
+ // now that we have the value, we're no longer in "eventually" mode, so we won't run any of this code,
299
+ // just the base Chai code that we get to via the short-circuit above.
300
+ assertion._obj = value;
301
+ utils.flag(assertion, "eventually", false);
302
+
303
+ return args ? module.exports.transformAsserterArgs(args) : args;
304
+ }).then(function (args) {
305
+ asserter.apply(assertion, args);
306
+
307
+ // Because asserters, for example `property`, can change the value of `_obj` (i.e. change the "object"
308
+ // flag), we need to communicate this value change to subsequent chained asserters. Since we build a
309
+ // promise chain paralleling the asserter chain, we can use it to communicate such changes.
310
+ return assertion._obj;
319
311
  });
320
312
 
321
- assert.isFulfilled = function (promise, message) {
322
- return (new Assertion(promise, message)).to.be.fulfilled;
323
- };
324
-
325
- assert.isRejected = function (promise, toTestAgainst, message) {
326
- if (typeof toTestAgainst === "string") {
327
- message = toTestAgainst;
328
- toTestAgainst = undefined;
329
- }
313
+ module.exports.transferPromiseness(assertion, derivedPromise);
314
+ }
330
315
 
331
- var assertion = (new Assertion(promise, message));
332
- return toTestAgainst !== undefined ? assertion.to.be.rejectedWith(toTestAgainst) : assertion.to.be.rejected;
333
- };
316
+ ///////
317
+ // Now use the `Assertion` framework to build an `assert` interface.
318
+ var originalAssertMethods = Object.getOwnPropertyNames(assert).filter(function (propName) {
319
+ return typeof assert[propName] === "function";
320
+ });
334
321
 
335
- assert.becomes = function (promise, value, message) {
336
- return assert.eventually.deepEqual(promise, value, message);
337
- };
322
+ assert.isFulfilled = function (promise, message) {
323
+ return (new Assertion(promise, message)).to.be.fulfilled;
324
+ };
338
325
 
339
- assert.doesNotBecome = function (promise, value, message) {
340
- return assert.eventually.notDeepEqual(promise, value, message);
341
- };
326
+ assert.isRejected = function (promise, errorLike, errMsgMatcher, message) {
327
+ var assertion = (new Assertion(promise, message));
328
+ return assertion.to.be.rejectedWith(errorLike, errMsgMatcher, message);
329
+ };
342
330
 
343
- assert.eventually = {};
344
- originalAssertMethods.forEach(function (assertMethodName) {
345
- assert.eventually[assertMethodName] = function (promise) {
346
- var otherArgs = Array.prototype.slice.call(arguments, 1);
331
+ assert.becomes = function (promise, value, message) {
332
+ return assert.eventually.deepEqual(promise, value, message);
333
+ };
347
334
 
348
- var customRejectionHandler;
349
- var message = arguments[assert[assertMethodName].length - 1];
350
- if (typeof message === "string") {
351
- customRejectionHandler = function (reason) {
352
- throw new chai.AssertionError(message + "\n\nOriginal reason: " + utils.inspect(reason));
353
- };
354
- }
335
+ assert.doesNotBecome = function (promise, value, message) {
336
+ return assert.eventually.notDeepEqual(promise, value, message);
337
+ };
355
338
 
356
- var returnedPromise = promise.then(
357
- function (fulfillmentValue) {
358
- return assert[assertMethodName].apply(assert, [fulfillmentValue].concat(otherArgs));
359
- },
360
- customRejectionHandler
361
- );
339
+ assert.eventually = {};
340
+ originalAssertMethods.forEach(function (assertMethodName) {
341
+ assert.eventually[assertMethodName] = function (promise) {
342
+ var otherArgs = Array.prototype.slice.call(arguments, 1);
362
343
 
363
- returnedPromise.notify = function (done) {
364
- doNotify(returnedPromise, done);
344
+ var customRejectionHandler;
345
+ var message = arguments[assert[assertMethodName].length - 1];
346
+ if (typeof message === "string") {
347
+ customRejectionHandler = function (reason) {
348
+ throw new chai.AssertionError(message + "\n\nOriginal reason: " + utils.inspect(reason));
365
349
  };
350
+ }
351
+
352
+ var returnedPromise = promise.then(
353
+ function (fulfillmentValue) {
354
+ return assert[assertMethodName].apply(assert, [fulfillmentValue].concat(otherArgs));
355
+ },
356
+ customRejectionHandler
357
+ );
366
358
 
367
- return returnedPromise;
359
+ returnedPromise.notify = function (done) {
360
+ doNotify(returnedPromise, done);
368
361
  };
369
- });
370
- }
371
- }());
362
+
363
+ return returnedPromise;
364
+ };
365
+ });
366
+ };
367
+
368
+ module.exports.transferPromiseness = function (assertion, promise) {
369
+ assertion.then = promise.then.bind(promise);
370
+ };
371
+
372
+ module.exports.transformAsserterArgs = function (values) {
373
+ return values;
374
+ };
package/package.json CHANGED
@@ -1,43 +1,47 @@
1
1
  {
2
- "name": "chai-as-promised",
3
- "description": "Extends Chai with assertions about promises.",
4
- "keywords": [
5
- "chai",
6
- "testing",
7
- "assertions",
8
- "promises",
9
- "promises-aplus"
10
- ],
11
- "version": "5.0.0",
12
- "author": "Domenic Denicola <d@domenic.me> (https://domenic.me)",
13
- "license": "WTFPL",
14
- "repository": "domenic/chai-as-promised",
15
- "main": "./lib/chai-as-promised.js",
16
- "files": [
17
- "lib"
18
- ],
19
- "scripts": {
20
- "test": "npm run test-plugin && npm run test-intercompatibility",
21
- "test-plugin": "mocha",
22
- "test-intercompatibility": "mocha test-intercompatibility --opts test-intercompatibility/mocha.opts",
23
- "test-browser-q": "coffee ./test/browser/runner.coffee q",
24
- "test-browser-when": "coffee ./test/browser/runner.coffee when",
25
- "lint": "jshint ./lib",
26
- "cover": "istanbul cover node_modules/mocha/bin/_mocha && opener ./coverage/lcov-report/lib/chai-as-promised.js.html"
27
- },
28
- "peerDependencies": {
29
- "chai": ">= 2.1.2 < 3"
30
- },
31
- "devDependencies": {
32
- "chai": "^2.1.2",
33
- "coffee-script": "1.9.0",
34
- "istanbul": "0.3.5",
35
- "ecstatic": "0.5.8",
36
- "glob": "^4.3.5",
37
- "jshint": "^2.6.0",
38
- "mocha": "^1.21.5",
39
- "opener": "^1.4.0",
40
- "q": "^1.1.2",
41
- "underscore": "1.7.0"
42
- }
2
+ "name": "chai-as-promised",
3
+ "description": "Extends Chai with assertions about promises.",
4
+ "keywords": [
5
+ "chai",
6
+ "chai-plugin",
7
+ "browser",
8
+ "async",
9
+ "testing",
10
+ "assertions",
11
+ "promises",
12
+ "promises-aplus"
13
+ ],
14
+ "version": "6.0.0",
15
+ "author": "Domenic Denicola <d@domenic.me> (https://domenic.me)",
16
+ "license": "WTFPL",
17
+ "repository": "domenic/chai-as-promised",
18
+ "main": "./lib/chai-as-promised.js",
19
+ "files": [
20
+ "lib"
21
+ ],
22
+ "scripts": {
23
+ "test": "npm run test-plugin && npm run test-intercompatibility",
24
+ "test-plugin": "mocha",
25
+ "test-intercompatibility": "mocha test-intercompatibility --opts test-intercompatibility/mocha.opts",
26
+ "lint": "jshint ./lib",
27
+ "cover": "istanbul cover node_modules/mocha/bin/_mocha && opener ./coverage/lcov-report/lib/chai-as-promised.js.html"
28
+ },
29
+ "dependencies": {
30
+ "check-error": "^1.0.2"
31
+ },
32
+ "peerDependencies": {
33
+ "chai": ">= 2.1.2 < 4"
34
+ },
35
+ "devDependencies": {
36
+ "chai": "^3.0.0",
37
+ "coffee-script": "1.10.0",
38
+ "ecstatic": "^1.3.1",
39
+ "glob": "^6.0.1",
40
+ "istanbul": "0.4.1",
41
+ "jshint": "^2.8.0",
42
+ "mocha": "^2.3.4",
43
+ "opener": "^1.4.1",
44
+ "q": "^1.4.1",
45
+ "underscore": "1.8.3"
46
+ }
43
47
  }