chai-as-promised 5.0.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +1 -1
- package/README.md +37 -67
- package/lib/chai-as-promised.js +321 -318
- package/package.json +45 -41
package/LICENSE.txt
CHANGED
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
|
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(
|
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].
|
157
|
-
|
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
|
-
|
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 (
|
203
|
-
|
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
|
-
])
|
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
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
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
|
-
|
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
|
-
|
249
|
-
automatically plug in to Chai and be ready for use:
|
221
|
+
### In the Browser
|
250
222
|
|
251
|
-
|
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/
|
package/lib/chai-as-promised.js
CHANGED
@@ -1,371 +1,374 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
-
|
19
|
-
|
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
|
-
|
22
|
-
|
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
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
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
|
34
|
-
|
35
|
-
|
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
|
-
|
47
|
-
|
48
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
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
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
71
|
-
|
72
|
-
|
73
|
+
function getReasonName(reason) {
|
74
|
+
return (reason instanceof Error) ? reason.toString() : checkError.getConstructorName(reason);
|
75
|
+
}
|
73
76
|
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
80
|
-
|
142
|
+
if (message !== undefined) {
|
143
|
+
utils.flag(this, "message", message);
|
81
144
|
}
|
82
145
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
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
|
-
|
183
|
-
|
184
|
-
|
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
|
191
|
-
|
192
|
-
|
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
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
226
|
-
|
222
|
+
return reason;
|
223
|
+
}
|
224
|
+
);
|
227
225
|
|
228
|
-
|
229
|
-
|
230
|
-
});
|
226
|
+
module.exports.transferPromiseness(that, derivedPromise);
|
227
|
+
});
|
231
228
|
|
232
|
-
|
233
|
-
|
234
|
-
|
229
|
+
property("eventually", function () {
|
230
|
+
utils.flag(this, "eventually", true);
|
231
|
+
});
|
235
232
|
|
236
|
-
|
237
|
-
|
238
|
-
|
233
|
+
method("notify", function (done) {
|
234
|
+
doNotify(getBasePromise(this), done);
|
235
|
+
});
|
239
236
|
|
240
|
-
|
241
|
-
|
237
|
+
method("become", function (value, message) {
|
238
|
+
return this.eventually.deep.equal(value, message);
|
239
|
+
});
|
242
240
|
|
243
|
-
|
244
|
-
|
245
|
-
return name !== "assert" && typeof propertyDescs[name].value === "function";
|
246
|
-
});
|
241
|
+
////////
|
242
|
+
// `eventually`
|
247
243
|
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
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
|
-
|
257
|
-
|
249
|
+
methodNames.forEach(function (methodName) {
|
250
|
+
Assertion.overwriteMethod(methodName, function (originalMethod) {
|
251
|
+
return function () {
|
252
|
+
doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
253
|
+
};
|
258
254
|
});
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
)
|
279
|
-
|
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
|
-
|
289
|
-
|
290
|
-
|
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
|
-
|
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
|
-
|
317
|
-
|
318
|
-
|
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
|
-
|
322
|
-
|
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
|
-
|
332
|
-
|
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
|
-
|
336
|
-
|
337
|
-
|
322
|
+
assert.isFulfilled = function (promise, message) {
|
323
|
+
return (new Assertion(promise, message)).to.be.fulfilled;
|
324
|
+
};
|
338
325
|
|
339
|
-
|
340
|
-
|
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
|
-
|
344
|
-
|
345
|
-
|
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
|
-
|
349
|
-
|
350
|
-
|
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
|
-
|
357
|
-
|
358
|
-
|
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
|
-
|
364
|
-
|
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
|
-
|
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
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
"
|
12
|
-
"
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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
|
}
|