chai-as-promised 4.0.0 → 4.3.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 +8 -3
- package/README.md +76 -25
- package/lib/chai-as-promised.js +314 -294
- package/package.json +18 -19
package/LICENSE.txt
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
-
|
1
|
+
Copyright © 2012–2015 Domenic Denicola <d@domenic.me>
|
2
|
+
|
3
|
+
This work is free. You can redistribute it and/or modify it under the
|
4
|
+
terms of the Do What The Fuck You Want To Public License, Version 2,
|
5
|
+
as published by Sam Hocevar. See below for more details.
|
6
|
+
|
7
|
+
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
2
8
|
Version 2, December 2004
|
3
9
|
|
4
|
-
Copyright
|
10
|
+
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
5
11
|
|
6
12
|
Everyone is permitted to copy and distribute verbatim or modified
|
7
13
|
copies of this license document, and changing it is allowed as long
|
@@ -11,4 +17,3 @@
|
|
11
17
|
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
12
18
|
|
13
19
|
0. You just DO WHAT THE FUCK YOU WANT TO.
|
14
|
-
|
package/README.md
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
<a href="http://
|
2
|
-
<img src="
|
1
|
+
<a href="http://promisesaplus.com/">
|
2
|
+
<img src="https://promises-aplus.github.io/promises-spec/assets/logo-small.png"
|
3
3
|
align="right" valign="top" alt="Promises/A+ logo" />
|
4
4
|
</a>
|
5
5
|
|
@@ -24,14 +24,14 @@ doSomethingAsync().then(
|
|
24
24
|
you can write code that expresses what you really mean:
|
25
25
|
|
26
26
|
```javascript
|
27
|
-
doSomethingAsync().should.eventually.equal("foo")
|
27
|
+
return doSomethingAsync().should.eventually.equal("foo");
|
28
28
|
```
|
29
29
|
|
30
|
-
or if you have a testing framework that
|
31
|
-
|
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:
|
32
32
|
|
33
33
|
```javascript
|
34
|
-
|
34
|
+
doSomethingAsync().should.eventually.equal("foo").notify(done);
|
35
35
|
```
|
36
36
|
|
37
37
|
## How to Use
|
@@ -45,13 +45,13 @@ existing Chai assertion into one that acts on a promise:
|
|
45
45
|
(2 + 2).should.equal(4);
|
46
46
|
|
47
47
|
// becomes
|
48
|
-
return
|
48
|
+
return Promise.resolve(2 + 2).should.eventually.equal(4);
|
49
49
|
|
50
50
|
|
51
51
|
expect({ foo: "bar" }).to.have.property("foo");
|
52
52
|
|
53
53
|
// becomes
|
54
|
-
return expect(
|
54
|
+
return expect(Promise.resolve({ foo: "bar" })).to.eventually.have.property("foo");
|
55
55
|
```
|
56
56
|
|
57
57
|
There are also a few promise-specific extensions (with the usual `expect` equivalents also available):
|
@@ -73,7 +73,7 @@ any existing Chai assertion to be used on a promise:
|
|
73
73
|
assert.equal(2 + 2, 4, "This had better be true");
|
74
74
|
|
75
75
|
// becomes
|
76
|
-
return assert.eventually.equal(
|
76
|
+
return assert.eventually.equal(Promise.resolve(2 + 2), 4, "This had better be true, eventually");
|
77
77
|
```
|
78
78
|
|
79
79
|
And there are, of course, promise-specific extensions:
|
@@ -105,11 +105,65 @@ return promise.then(null, null, progressSpy).then(function () {
|
|
105
105
|
});
|
106
106
|
```
|
107
107
|
|
108
|
+
### Customizing Output Promises
|
109
|
+
|
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:
|
114
|
+
|
115
|
+
```js
|
116
|
+
chaiAsPromised.transferPromiseness = function (assertion, promise) {
|
117
|
+
assertion.then = promise.then.bind(promise); // this is all you get by default
|
118
|
+
assertion.finally = promise.finally.bind(promise);
|
119
|
+
assertion.done = promise.done.bind(promise);
|
120
|
+
};
|
121
|
+
```
|
122
|
+
|
123
|
+
### Transforming Arguments to the Asserters
|
124
|
+
|
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:
|
127
|
+
|
128
|
+
```js
|
129
|
+
chaiAsPromised.transformAsserterArgs = function (args) {
|
130
|
+
return args.map(function (x) { return x + 1; });
|
131
|
+
}
|
132
|
+
|
133
|
+
Promise.resolve(2).should.eventually.equal(2); // will now fail!
|
134
|
+
Promise.resolve(2).should.eventually.equal(3); // will now pass!
|
135
|
+
```
|
136
|
+
|
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:
|
140
|
+
|
141
|
+
```js
|
142
|
+
// This will normally fail, since within() only works on numbers.
|
143
|
+
Promise.resolve(2).should.eventually.be.within(Promise.resolve(1), Promise.resolve(6));
|
144
|
+
|
145
|
+
chaiAsPromised.transformAsserterArgs = function (args) {
|
146
|
+
return Promise.all(args);
|
147
|
+
};
|
148
|
+
|
149
|
+
// But now it will pass, since we transformed the array of promises for numbers into
|
150
|
+
// (a promise for) an array of numbers
|
151
|
+
Promise.resolve(2).should.eventually.be.within(Promise.resolve(1), Promise.resolve(6));
|
152
|
+
```
|
153
|
+
|
154
|
+
### Compatibility
|
155
|
+
|
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.
|
159
|
+
|
108
160
|
### Working with Non-Promise–Friendly Test Runners
|
109
161
|
|
110
|
-
|
111
|
-
|
112
|
-
|
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:
|
113
167
|
|
114
168
|
```javascript
|
115
169
|
it("should be fulfilled", function (done) {
|
@@ -158,12 +212,6 @@ This will pass any failures of the individual promise assertions up to the test
|
|
158
212
|
an `"expected promise to be fulfilled…"` message as would happen if you did
|
159
213
|
`Q.all([…]).should.be.fulfilled.and.notify(done)`.
|
160
214
|
|
161
|
-
### Compatibility
|
162
|
-
|
163
|
-
Chai as Promised is compatible with all promises following the [Promises/A+ specification][spec]. Notably, jQuery's
|
164
|
-
so-called “promises” are not up to spec, and Chai as Promised will not work with them. In particular, Chai as Promised
|
165
|
-
makes extensive use of the standard [transformation behavior][] of `then`, which jQuery does not support.
|
166
|
-
|
167
215
|
## Installation and Setup
|
168
216
|
|
169
217
|
### Node
|
@@ -177,7 +225,7 @@ var chaiAsPromised = require("chai-as-promised");
|
|
177
225
|
chai.use(chaiAsPromised);
|
178
226
|
```
|
179
227
|
|
180
|
-
You can of course put this code in a common test fixture file; for an example using [Mocha][
|
228
|
+
You can of course put this code in a common test fixture file; for an example using [Mocha][], see
|
181
229
|
[the Chai as Promised tests themselves][fixturedemo].
|
182
230
|
|
183
231
|
### AMD
|
@@ -205,15 +253,18 @@ automatically plug in to Chai and be ready for use:
|
|
205
253
|
<script src="chai-as-promised.js"></script>
|
206
254
|
```
|
207
255
|
|
256
|
+
### Browser Compatibility
|
257
|
+
|
258
|
+
Chai as Promised is only compatible with modern browsers (IE ≥9, Safari ≥6, no PhantomJS).
|
208
259
|
|
209
260
|
[presentation]: http://www.slideshare.net/domenicdenicola/callbacks-promises-and-coroutines-oh-my-the-evolution-of-asynchronicity-in-javascript
|
210
261
|
[chai]: http://chaijs.com/
|
211
|
-
[
|
212
|
-
[
|
213
|
-
[
|
214
|
-
[
|
215
|
-
[
|
216
|
-
[
|
262
|
+
[Mocha-promises]: http://visionmedia.github.io/mocha/#asynchronous-code
|
263
|
+
[Buster-promises]: http://docs.busterjs.org/en/latest/modules/buster-test/spec/#returning-a-promise
|
264
|
+
[blue-tape]: https://github.com/spion/blue-tape
|
265
|
+
[spec]: http://promisesaplus.com/
|
266
|
+
[transformation behavior]: http://domenic.me/2012/10/14/youre-missing-the-point-of-promises/#toc_2
|
267
|
+
[Mocha]: http://visionmedia.github.com/mocha/
|
217
268
|
[fixturedemo]: https://github.com/domenic/chai-as-promised/tree/master/test/
|
218
269
|
[amd]: https://github.com/amdjs/amdjs-api/wiki/AMD
|
219
270
|
[sinon]: http://sinonjs.org/
|
package/lib/chai-as-promised.js
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
(function (
|
1
|
+
(function () {
|
2
2
|
"use strict";
|
3
3
|
|
4
4
|
// Module systems magic dance.
|
5
5
|
|
6
|
+
/* istanbul ignore else */
|
6
7
|
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
|
7
8
|
// NodeJS
|
8
9
|
module.exports = chaiAsPromised;
|
@@ -12,346 +13,365 @@
|
|
12
13
|
return chaiAsPromised;
|
13
14
|
});
|
14
15
|
} else {
|
16
|
+
/*global self: false */
|
17
|
+
|
15
18
|
// Other environment (usually <script> tag): plug in to global chai instance directly.
|
16
19
|
chai.use(chaiAsPromised);
|
20
|
+
|
21
|
+
// Expose as a property of the global object so that consumers can configure the `transferPromiseness` property.
|
22
|
+
self.chaiAsPromised = chaiAsPromised;
|
17
23
|
}
|
18
|
-
}(function chaiAsPromised(chai, utils) {
|
19
|
-
"use strict";
|
20
24
|
|
21
|
-
|
22
|
-
|
25
|
+
chaiAsPromised.transferPromiseness = function (assertion, promise) {
|
26
|
+
assertion.then = promise.then.bind(promise);
|
27
|
+
};
|
23
28
|
|
24
|
-
function
|
25
|
-
return
|
26
|
-
|
27
|
-
typeof thenable.fail === "function" &&
|
28
|
-
typeof thenable.pipe === "function" &&
|
29
|
-
typeof thenable.progress === "function" &&
|
30
|
-
typeof thenable.state === "function";
|
31
|
-
}
|
29
|
+
chaiAsPromised.transformAsserterArgs = function (values) {
|
30
|
+
return values;
|
31
|
+
};
|
32
32
|
|
33
|
-
function
|
34
|
-
|
35
|
-
|
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";
|
36
44
|
}
|
37
|
-
|
38
|
-
|
39
|
-
|
45
|
+
|
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
|
+
}
|
40
54
|
}
|
41
|
-
}
|
42
55
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
56
|
+
function method(name, asserter) {
|
57
|
+
utils.addMethod(Assertion.prototype, name, function () {
|
58
|
+
assertIsAboutPromise(this);
|
59
|
+
return asserter.apply(this, arguments);
|
60
|
+
});
|
61
|
+
}
|
49
62
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
63
|
+
function property(name, asserter) {
|
64
|
+
utils.addProperty(Assertion.prototype, name, function () {
|
65
|
+
assertIsAboutPromise(this);
|
66
|
+
return asserter.apply(this, arguments);
|
67
|
+
});
|
68
|
+
}
|
56
69
|
|
57
|
-
|
58
|
-
|
59
|
-
|
70
|
+
function doNotify(promise, done) {
|
71
|
+
promise.then(function () { done(); }, done);
|
72
|
+
}
|
60
73
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
+
}
|
65
78
|
|
66
|
-
|
67
|
-
|
68
|
-
|
79
|
+
function assertIfNotNegated(assertion, message, extra) {
|
80
|
+
assertion.assert(false, message, null, extra.expected, extra.actual);
|
81
|
+
}
|
69
82
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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;
|
89
|
+
}
|
77
90
|
|
78
|
-
|
79
|
-
|
80
|
-
var propertyNames = Object.getOwnPropertyNames(Assertion.prototype);
|
81
|
-
|
82
|
-
var propertyDescs = {};
|
83
|
-
propertyNames.forEach(function (name) {
|
84
|
-
propertyDescs[name] = Object.getOwnPropertyDescriptor(Assertion.prototype, name);
|
85
|
-
});
|
86
|
-
|
87
|
-
property("fulfilled", function () {
|
88
|
-
var that = this;
|
89
|
-
var derivedPromise = getBasePromise(that).then(
|
90
|
-
function (value) {
|
91
|
-
that._obj = value;
|
92
|
-
assertIfNegated(that,
|
93
|
-
"expected promise not to be fulfilled but it was fulfilled with #{act}",
|
94
|
-
{ actual: value });
|
95
|
-
return value;
|
96
|
-
},
|
97
|
-
function (reason) {
|
98
|
-
assertIfNotNegated(that,
|
99
|
-
"expected promise to be fulfilled but it was rejected with #{act}",
|
100
|
-
{ actual: reason });
|
101
|
-
});
|
91
|
+
// Grab these first, before we modify `Assertion.prototype`.
|
102
92
|
|
103
|
-
|
104
|
-
});
|
105
|
-
|
106
|
-
property("rejected", function () {
|
107
|
-
var that = this;
|
108
|
-
var derivedPromise = getBasePromise(that).then(
|
109
|
-
function (value) {
|
110
|
-
that._obj = value;
|
111
|
-
assertIfNotNegated(that,
|
112
|
-
"expected promise to be rejected but it was fulfilled with #{act}",
|
113
|
-
{ actual: value });
|
114
|
-
return value;
|
115
|
-
},
|
116
|
-
function (reason) {
|
117
|
-
assertIfNegated(that,
|
118
|
-
"expected promise not to be rejected but it was rejected with #{act}",
|
119
|
-
{ actual: reason });
|
120
|
-
|
121
|
-
// Return the reason, transforming this into a fulfillment, to allow further assertions, e.g.
|
122
|
-
// `promise.should.be.rejected.and.eventually.equal("reason")`.
|
123
|
-
return reason;
|
124
|
-
}
|
125
|
-
);
|
126
|
-
|
127
|
-
that.then = derivedPromise.then.bind(derivedPromise);
|
128
|
-
});
|
129
|
-
|
130
|
-
method("rejectedWith", function (Constructor, message) {
|
131
|
-
var desiredReason = null;
|
132
|
-
var constructorName = null;
|
133
|
-
|
134
|
-
if (Constructor instanceof RegExp || typeof Constructor === "string") {
|
135
|
-
message = Constructor;
|
136
|
-
Constructor = null;
|
137
|
-
} else if (Constructor && Constructor instanceof Error) {
|
138
|
-
desiredReason = Constructor;
|
139
|
-
Constructor = null;
|
140
|
-
message = null;
|
141
|
-
} else if (typeof Constructor === "function") {
|
142
|
-
constructorName = (new Constructor()).name;
|
143
|
-
} else {
|
144
|
-
Constructor = null;
|
145
|
-
}
|
93
|
+
var propertyNames = Object.getOwnPropertyNames(Assertion.prototype);
|
146
94
|
|
147
|
-
var
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
var expected = null;
|
152
|
-
|
153
|
-
if (Constructor) {
|
154
|
-
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with #{act}";
|
155
|
-
expected = constructorName;
|
156
|
-
} else if (message) {
|
157
|
-
var verb = message instanceof RegExp ? "matching" : "including";
|
158
|
-
assertionMessage = "expected promise to be rejected with an error " + verb + " #{exp} but it was " +
|
159
|
-
"fulfilled with #{act}";
|
160
|
-
expected = message;
|
161
|
-
} else if (desiredReason) {
|
162
|
-
assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with #{act}";
|
163
|
-
expected = desiredReason;
|
164
|
-
}
|
95
|
+
var propertyDescs = {};
|
96
|
+
propertyNames.forEach(function (name) {
|
97
|
+
propertyDescs[name] = Object.getOwnPropertyDescriptor(Assertion.prototype, name);
|
98
|
+
});
|
165
99
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
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 });
|
177
114
|
}
|
115
|
+
);
|
178
116
|
|
179
|
-
|
180
|
-
|
181
|
-
"" + reason;
|
182
|
-
if (message && reasonMessage !== null && reasonMessage !== undefined) {
|
183
|
-
if (message instanceof RegExp) {
|
184
|
-
that.assert(message.test(reasonMessage),
|
185
|
-
"expected promise to be rejected with an error matching #{exp} but got #{act}",
|
186
|
-
"expected promise not to be rejected with an error matching #{exp}",
|
187
|
-
message,
|
188
|
-
reasonMessage);
|
189
|
-
}
|
190
|
-
if (typeof message === "string") {
|
191
|
-
that.assert(reasonMessage.indexOf(message) !== -1,
|
192
|
-
"expected promise to be rejected with an error including #{exp} but got #{act}",
|
193
|
-
"expected promise not to be rejected with an error including #{exp}",
|
194
|
-
message,
|
195
|
-
reasonMessage);
|
196
|
-
}
|
197
|
-
}
|
117
|
+
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
118
|
+
});
|
198
119
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
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;
|
205
138
|
}
|
206
|
-
|
207
|
-
);
|
139
|
+
);
|
208
140
|
|
209
|
-
|
210
|
-
|
141
|
+
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
142
|
+
});
|
211
143
|
|
212
|
-
|
213
|
-
|
214
|
-
|
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
|
+
}
|
215
160
|
|
216
|
-
|
217
|
-
|
218
|
-
|
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;
|
180
|
+
}
|
219
181
|
|
220
|
-
|
221
|
-
return this.eventually.deep.equal(value);
|
222
|
-
});
|
182
|
+
that._obj = value;
|
223
183
|
|
224
|
-
|
225
|
-
|
184
|
+
assertIfNotNegated(that, assertionMessage, { expected: expected, actual: value });
|
185
|
+
},
|
186
|
+
function (reason) {
|
187
|
+
if (Constructor) {
|
188
|
+
that.assert(reason instanceof Constructor,
|
189
|
+
"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);
|
193
|
+
}
|
226
194
|
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
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
|
+
}
|
231
214
|
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
});
|
239
|
-
|
240
|
-
var getterNames = propertyNames.filter(function (name) {
|
241
|
-
return name !== "_obj" && typeof propertyDescs[name].get === "function";
|
242
|
-
});
|
243
|
-
|
244
|
-
getterNames.forEach(function (getterName) {
|
245
|
-
var propertyDesc = propertyDescs[getterName];
|
246
|
-
|
247
|
-
// Chainable methods are things like `an`, which can work both for `.should.be.an.instanceOf` and as
|
248
|
-
// `should.be.an("object")`. We need to handle those specially.
|
249
|
-
var isChainableMethod = false;
|
250
|
-
try {
|
251
|
-
isChainableMethod = typeof propertyDesc.get.call({}) === "function";
|
252
|
-
} catch (e) { }
|
253
|
-
|
254
|
-
if (isChainableMethod) {
|
255
|
-
Assertion.addChainableMethod(
|
256
|
-
getterName,
|
257
|
-
function () {
|
258
|
-
var assertion = this;
|
259
|
-
function originalMethod() {
|
260
|
-
return propertyDesc.get.call(assertion).apply(assertion, arguments);
|
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);
|
261
221
|
}
|
262
|
-
doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
263
|
-
},
|
264
|
-
function () {
|
265
|
-
var originalGetter = propertyDesc.get;
|
266
|
-
doAsserterAsyncAndAddThen(originalGetter, this);
|
267
222
|
}
|
268
223
|
);
|
269
|
-
|
270
|
-
|
224
|
+
|
225
|
+
chaiAsPromised.transferPromiseness(that, derivedPromise);
|
226
|
+
});
|
227
|
+
|
228
|
+
property("eventually", function () {
|
229
|
+
utils.flag(this, "eventually", true);
|
230
|
+
});
|
231
|
+
|
232
|
+
method("notify", function (done) {
|
233
|
+
doNotify(getBasePromise(this), done);
|
234
|
+
});
|
235
|
+
|
236
|
+
method("become", function (value) {
|
237
|
+
return this.eventually.deep.equal(value);
|
238
|
+
});
|
239
|
+
|
240
|
+
////////
|
241
|
+
// `eventually`
|
242
|
+
|
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
|
+
});
|
247
|
+
|
248
|
+
methodNames.forEach(function (methodName) {
|
249
|
+
Assertion.overwriteMethod(methodName, function (originalMethod) {
|
271
250
|
return function () {
|
272
|
-
doAsserterAsyncAndAddThen(
|
251
|
+
doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
273
252
|
};
|
274
253
|
});
|
275
|
-
}
|
276
|
-
});
|
277
|
-
|
278
|
-
function doAsserterAsyncAndAddThen(asserter, assertion, args) {
|
279
|
-
// Since we're intercepting all methods/properties, we need to just pass through if they don't want
|
280
|
-
// `eventually`, or if we've already fulfilled the promise (see below).
|
281
|
-
if (!utils.flag(assertion, "eventually")) {
|
282
|
-
return asserter.apply(assertion, args);
|
283
|
-
}
|
254
|
+
});
|
284
255
|
|
285
|
-
var
|
286
|
-
|
287
|
-
// now that we have the value, we're no longer in "eventually" mode, so we won't run any of this code, just
|
288
|
-
// the base Chai code that we get to via the short-circuit above.
|
289
|
-
assertion._obj = value;
|
290
|
-
utils.flag(assertion, "eventually", false);
|
291
|
-
asserter.apply(assertion, args);
|
292
|
-
|
293
|
-
// Because asserters, for example `property`, can change the value of `_obj` (i.e. change the "object"
|
294
|
-
// flag), we need to communicate this value change to subsequent chained asserters. Since we build a
|
295
|
-
// promise chain paralleling the asserter chain, we can use it to communicate such changes.
|
296
|
-
return assertion._obj;
|
256
|
+
var getterNames = propertyNames.filter(function (name) {
|
257
|
+
return name !== "_obj" && typeof propertyDescs[name].get === "function";
|
297
258
|
});
|
298
259
|
|
299
|
-
|
300
|
-
|
260
|
+
getterNames.forEach(function (getterName) {
|
261
|
+
var propertyDesc = propertyDescs[getterName];
|
262
|
+
|
263
|
+
// Chainable methods are things like `an`, which can work both for `.should.be.an.instanceOf` and as
|
264
|
+
// `should.be.an("object")`. We need to handle those specially.
|
265
|
+
var isChainableMethod = false;
|
266
|
+
try {
|
267
|
+
isChainableMethod = typeof propertyDesc.get.call({}) === "function";
|
268
|
+
} catch (e) { }
|
269
|
+
|
270
|
+
if (isChainableMethod) {
|
271
|
+
Assertion.addChainableMethod(
|
272
|
+
getterName,
|
273
|
+
function () {
|
274
|
+
var assertion = this;
|
275
|
+
function originalMethod() {
|
276
|
+
return propertyDesc.get.call(assertion).apply(assertion, arguments);
|
277
|
+
}
|
278
|
+
doAsserterAsyncAndAddThen(originalMethod, this, arguments);
|
279
|
+
},
|
280
|
+
function () {
|
281
|
+
var originalGetter = propertyDesc.get;
|
282
|
+
doAsserterAsyncAndAddThen(originalGetter, this);
|
283
|
+
}
|
284
|
+
);
|
285
|
+
} else {
|
286
|
+
Assertion.overwriteProperty(getterName, function (originalGetter) {
|
287
|
+
return function () {
|
288
|
+
doAsserterAsyncAndAddThen(originalGetter, this);
|
289
|
+
};
|
290
|
+
});
|
291
|
+
}
|
292
|
+
});
|
301
293
|
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
294
|
+
function doAsserterAsyncAndAddThen(asserter, assertion, args) {
|
295
|
+
// Since we're intercepting all methods/properties, we need to just pass through if they don't want
|
296
|
+
// `eventually`, or if we've already fulfilled the promise (see below).
|
297
|
+
if (!utils.flag(assertion, "eventually")) {
|
298
|
+
return asserter.apply(assertion, args);
|
299
|
+
}
|
307
300
|
|
308
|
-
|
309
|
-
|
310
|
-
|
301
|
+
var derivedPromise = getBasePromise(assertion).then(function (value) {
|
302
|
+
// Set up the environment for the asserter to actually run: `_obj` should be the fulfillment value, and
|
303
|
+
// now that we have the value, we're no longer in "eventually" mode, so we won't run any of this code,
|
304
|
+
// just the base Chai code that we get to via the short-circuit above.
|
305
|
+
assertion._obj = value;
|
306
|
+
utils.flag(assertion, "eventually", false);
|
307
|
+
|
308
|
+
return args ? chaiAsPromised.transformAsserterArgs(args) : args;
|
309
|
+
}).then(function (args) {
|
310
|
+
asserter.apply(assertion, args);
|
311
|
+
|
312
|
+
// Because asserters, for example `property`, can change the value of `_obj` (i.e. change the "object"
|
313
|
+
// flag), we need to communicate this value change to subsequent chained asserters. Since we build a
|
314
|
+
// promise chain paralleling the asserter chain, we can use it to communicate such changes.
|
315
|
+
return assertion._obj;
|
316
|
+
});
|
311
317
|
|
312
|
-
|
313
|
-
if (typeof toTestAgainst === "string") {
|
314
|
-
message = toTestAgainst;
|
315
|
-
toTestAgainst = undefined;
|
318
|
+
chaiAsPromised.transferPromiseness(assertion, derivedPromise);
|
316
319
|
}
|
317
320
|
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
return assert.eventually.deepEqual(promise, value);
|
324
|
-
};
|
325
|
-
|
326
|
-
assert.doesNotBecome = function (promise, value) {
|
327
|
-
return assert.eventually.notDeepEqual(promise, value);
|
328
|
-
};
|
321
|
+
///////
|
322
|
+
// Now use the `Assertion` framework to build an `assert` interface.
|
323
|
+
var originalAssertMethods = Object.getOwnPropertyNames(assert).filter(function (propName) {
|
324
|
+
return typeof assert[propName] === "function";
|
325
|
+
});
|
329
326
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
var otherArgs = Array.prototype.slice.call(arguments, 1);
|
327
|
+
assert.isFulfilled = function (promise, message) {
|
328
|
+
return (new Assertion(promise, message)).to.be.fulfilled;
|
329
|
+
};
|
334
330
|
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
throw new chai.AssertionError(message + "\n\nOriginal reason: " + utils.inspect(reason));
|
340
|
-
};
|
331
|
+
assert.isRejected = function (promise, toTestAgainst, message) {
|
332
|
+
if (typeof toTestAgainst === "string") {
|
333
|
+
message = toTestAgainst;
|
334
|
+
toTestAgainst = undefined;
|
341
335
|
}
|
342
336
|
|
343
|
-
var
|
344
|
-
|
345
|
-
|
346
|
-
},
|
347
|
-
customRejectionHandler
|
348
|
-
);
|
337
|
+
var assertion = (new Assertion(promise, message));
|
338
|
+
return toTestAgainst !== undefined ? assertion.to.be.rejectedWith(toTestAgainst) : assertion.to.be.rejected;
|
339
|
+
};
|
349
340
|
|
350
|
-
|
351
|
-
|
352
|
-
|
341
|
+
assert.becomes = function (promise, value, message) {
|
342
|
+
return assert.eventually.deepEqual(promise, value, message);
|
343
|
+
};
|
353
344
|
|
354
|
-
|
345
|
+
assert.doesNotBecome = function (promise, value, message) {
|
346
|
+
return assert.eventually.notDeepEqual(promise, value, message);
|
355
347
|
};
|
356
|
-
|
357
|
-
}
|
348
|
+
|
349
|
+
assert.eventually = {};
|
350
|
+
originalAssertMethods.forEach(function (assertMethodName) {
|
351
|
+
assert.eventually[assertMethodName] = function (promise) {
|
352
|
+
var otherArgs = Array.prototype.slice.call(arguments, 1);
|
353
|
+
|
354
|
+
var customRejectionHandler;
|
355
|
+
var message = arguments[assert[assertMethodName].length - 1];
|
356
|
+
if (typeof message === "string") {
|
357
|
+
customRejectionHandler = function (reason) {
|
358
|
+
throw new chai.AssertionError(message + "\n\nOriginal reason: " + utils.inspect(reason));
|
359
|
+
};
|
360
|
+
}
|
361
|
+
|
362
|
+
var returnedPromise = promise.then(
|
363
|
+
function (fulfillmentValue) {
|
364
|
+
return assert[assertMethodName].apply(assert, [fulfillmentValue].concat(otherArgs));
|
365
|
+
},
|
366
|
+
customRejectionHandler
|
367
|
+
);
|
368
|
+
|
369
|
+
returnedPromise.notify = function (done) {
|
370
|
+
doNotify(returnedPromise, done);
|
371
|
+
};
|
372
|
+
|
373
|
+
return returnedPromise;
|
374
|
+
};
|
375
|
+
});
|
376
|
+
}
|
377
|
+
}());
|
package/package.json
CHANGED
@@ -8,35 +8,34 @@
|
|
8
8
|
"promises",
|
9
9
|
"promises-aplus"
|
10
10
|
],
|
11
|
-
"version": "4.
|
12
|
-
"author": "Domenic Denicola <domenic
|
11
|
+
"version": "4.3.0",
|
12
|
+
"author": "Domenic Denicola <d@domenic.me> (https://domenic.me)",
|
13
13
|
"license": "WTFPL",
|
14
|
-
"repository":
|
15
|
-
"type": "git",
|
16
|
-
"url": "git://github.com/domenic/chai-as-promised.git"
|
17
|
-
},
|
18
|
-
"bugs": "https://github.com/domenic/chai-as-promised/issues",
|
14
|
+
"repository": "domenic/chai-as-promised",
|
19
15
|
"main": "./lib/chai-as-promised.js",
|
16
|
+
"files": [
|
17
|
+
"lib"
|
18
|
+
],
|
20
19
|
"scripts": {
|
21
20
|
"test": "mocha",
|
22
21
|
"test-browser-q": "coffee ./test/browser/runner.coffee q",
|
23
22
|
"test-browser-when": "coffee ./test/browser/runner.coffee when",
|
24
23
|
"lint": "jshint ./lib",
|
25
|
-
"cover": "cover
|
24
|
+
"cover": "istanbul cover node_modules/mocha/bin/_mocha && opener ./coverage/lcov-report/lib/chai-as-promised.js.html"
|
26
25
|
},
|
27
26
|
"peerDependencies": {
|
28
|
-
"chai": ">= 1.7.0 <
|
27
|
+
"chai": ">= 1.7.0 < 3"
|
29
28
|
},
|
30
29
|
"devDependencies": {
|
31
|
-
"chai": "
|
32
|
-
"coffee-script": "
|
33
|
-
"
|
34
|
-
"ecstatic": "
|
35
|
-
"glob": "
|
36
|
-
"jshint": "
|
37
|
-
"mocha": "
|
38
|
-
"opener": "
|
39
|
-
"q": "
|
40
|
-
"underscore": "
|
30
|
+
"chai": "^2.0.0",
|
31
|
+
"coffee-script": "1.9.0",
|
32
|
+
"istanbul": "0.3.5",
|
33
|
+
"ecstatic": "0.5.8",
|
34
|
+
"glob": "^4.3.5",
|
35
|
+
"jshint": "^2.6.0",
|
36
|
+
"mocha": "^1.21.5",
|
37
|
+
"opener": "^1.4.0",
|
38
|
+
"q": "^1.1.2",
|
39
|
+
"underscore": "1.7.0"
|
41
40
|
}
|
42
41
|
}
|