konacha-chai-matchers 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,4 @@
1
- (function (chaiAsPromised) {
1
+ (function () {
2
2
  "use strict";
3
3
 
4
4
  // Module systems magic dance.
@@ -12,122 +12,133 @@
12
12
  return chaiAsPromised;
13
13
  });
14
14
  } else {
15
+ /*global self: false */
16
+
15
17
  // Other environment (usually <script> tag): plug in to global chai instance directly.
16
18
  chai.use(chaiAsPromised);
19
+
20
+ // Expose as a property of the global object so that consumers can configure the `transferPromiseness` property.
21
+ self.chaiAsPromised = chaiAsPromised;
17
22
  }
18
- }(function chaiAsPromised(chai, utils) {
19
- "use strict";
20
23
 
21
- var Assertion = chai.Assertion;
22
- var assert = chai.assert;
24
+ chaiAsPromised.transferPromiseness = function (assertion, promise) {
25
+ assertion.then = promise.then.bind(promise);
26
+ };
23
27
 
24
- function assertIsAboutPromise(assertion) {
25
- if (typeof assertion._obj.then !== "function") {
26
- throw new TypeError(utils.inspect(assertion._obj) + " is not a promise!");
28
+ function chaiAsPromised(chai, utils) {
29
+ var Assertion = chai.Assertion;
30
+ var assert = chai.assert;
31
+
32
+ function isJQueryPromise(thenable) {
33
+ return typeof thenable.always === "function" &&
34
+ typeof thenable.done === "function" &&
35
+ typeof thenable.fail === "function" &&
36
+ typeof thenable.pipe === "function" &&
37
+ typeof thenable.progress === "function" &&
38
+ typeof thenable.state === "function";
27
39
  }
28
- if (typeof assertion._obj.pipe === "function") {
29
- throw new TypeError("Chai as Promised is incompatible with jQuery's so-called “promises.” Sorry!");
40
+
41
+ function assertIsAboutPromise(assertion) {
42
+ if (typeof assertion._obj.then !== "function") {
43
+ throw new TypeError(utils.inspect(assertion._obj) + " is not a thenable.");
44
+ }
45
+ if (isJQueryPromise(assertion._obj)) {
46
+ throw new TypeError("Chai as Promised is incompatible with jQuery's thenables, sorry! Please use a " +
47
+ "Promises/A+ compatible library (see http://promisesaplus.com/).");
48
+ }
30
49
  }
31
- }
32
50
 
33
- function property(name, asserter) {
34
- utils.addProperty(Assertion.prototype, name, function () {
35
- assertIsAboutPromise(this);
36
- return asserter.apply(this, arguments);
37
- });
38
- }
51
+ function method(name, asserter) {
52
+ utils.addMethod(Assertion.prototype, name, function () {
53
+ assertIsAboutPromise(this);
54
+ return asserter.apply(this, arguments);
55
+ });
56
+ }
39
57
 
40
- function method(name, asserter) {
41
- utils.addMethod(Assertion.prototype, name, function () {
42
- assertIsAboutPromise(this);
43
- return asserter.apply(this, arguments);
44
- });
45
- }
58
+ function property(name, asserter) {
59
+ utils.addProperty(Assertion.prototype, name, function () {
60
+ assertIsAboutPromise(this);
61
+ return asserter.apply(this, arguments);
62
+ });
63
+ }
46
64
 
47
- function notify(promise, callback) {
48
- return promise.then(function () { callback(); }, callback);
49
- }
65
+ function doNotify(promise, done) {
66
+ promise.then(function () { done(); }, done);
67
+ }
50
68
 
51
- function addNotifyMethod(extensiblePromise) {
52
- extensiblePromise.notify = function (callback) {
53
- return notify(extensiblePromise, callback);
54
- };
55
- }
69
+ // These are for clarity and to bypass Chai refusing to allow `undefined` as actual when used with `assert`.
70
+ function assertIfNegated(assertion, message, extra) {
71
+ assertion.assert(true, null, message, extra.expected, extra.actual);
72
+ }
56
73
 
57
- function fulfilledAsserter() {
58
- /*jshint validthis:true */
59
- var assertion = this;
60
-
61
- var transformedPromise = assertion._obj.then(
62
- function (value) {
63
- if (utils.flag(assertion, "negate")) {
64
- // If we're negated, `this.assert`'s behavior is actually flipped, so `this.assert(true, ...)` will
65
- // throw an error, as desired.
66
- assertion.assert(true, null, "expected promise to be rejected but it was fulfilled with " +
67
- utils.inspect(value));
68
- }
74
+ function assertIfNotNegated(assertion, message, extra) {
75
+ assertion.assert(false, message, null, extra.expected, extra.actual);
76
+ }
69
77
 
70
- return value;
71
- },
72
- function (reason) {
73
- // If we're in a negated state (i.e. `.not.fulfilled`) then this assertion will get flipped and thus
74
- // pass, as desired.
75
- assertion.assert(false, "expected promise to be fulfilled but it was rejected with " +
76
- utils.inspect(reason));
77
- }
78
- );
78
+ function getBasePromise(assertion) {
79
+ // We need to chain subsequent asserters on top of ones in the chain already (consider
80
+ // `eventually.have.property("foo").that.equals("bar")`), only running them after the existing ones pass.
81
+ // So the first base-promise is `assertion._obj`, but after that we use the assertions themselves, i.e.
82
+ // previously derived promises, to chain off of.
83
+ return typeof assertion.then === "function" ? assertion : assertion._obj;
84
+ }
79
85
 
80
- return makeAssertionPromise(transformedPromise, assertion);
81
- }
86
+ // Grab these first, before we modify `Assertion.prototype`.
82
87
 
83
- function rejectedAsserter() {
84
- // THIS SHIT IS COMPLICATED. Best illustrated by exhaustive example.
85
- ////////////////////////////////////////////////////////////////////
86
- // `fulfilledPromise.should.be.rejected`:
87
- // `onOriginalFulfilled` → `this.assert(false, …)` throws → rejects
88
- // `fulfilledPromise.should.not.be.rejected`:
89
- // `onOriginalFulfilled` → `this.assert(false, …)` does nothing → fulfills
90
- // `rejectedPromise.should.be.rejected`:
91
- // `onOriginalRejected` does nothing relevant → fulfills
92
- // `rejectedPromise.should.not.be.rejected`:
93
- // `onOriginalRejected` → `this.assert(true, …)` throws → rejects
94
- // `rejectedPromise.should.be.rejected.with(xxx)`:
95
- // `onOriginalRejected` saves `rejectionReason` → fulfills →
96
- // `with(xxx)` called → `onTransformedFulfilled` → assert about xxx → fulfills/rejects based on asserts
97
- // `rejectedPromise.should.not.be.rejected.with(xxx)`:
98
- // `onOriginalRejected` saves `rejectionReason`, `this.assert(true, …)` throws → rejects →
99
- // `with(xxx)` called → `onTransformedRejected` → assert about xxx → fulfills/rejects based on asserts
100
- // `fulfilledPromise.should.be.rejected.with(xxx)`:
101
- // `onOriginalFulfilled` → `this.assert(false, …)` throws → rejects →
102
- // `with(xxx)` called → `onTransformedRejected` → `this.assert(false, …)` throws → rejected
103
- // `fulfilledPromise.should.not.be.rejected.with(xxx)`:
104
- // `onOriginalFulfilled` → `this.assert(false, …)` does nothing → fulfills →
105
- // `with(xxx)` called → `onTransformedFulfilled` → fulfills
106
-
107
- /*jshint validthis:true */
108
- var assertion = this;
109
- var rejectionReason = null;
110
-
111
- function onOriginalFulfilled(value) {
112
- assertion.assert(false, "expected promise to be rejected but it was fulfilled with " + utils.inspect(value));
113
- }
88
+ var propertyNames = Object.getOwnPropertyNames(Assertion.prototype);
114
89
 
115
- function onOriginalRejected(reason) {
116
- // Store the reason so that `with` can look at it later. Be sure to do this before asserting, since
117
- // throwing an error from the assert would cancel the process.
118
- rejectionReason = reason;
90
+ var propertyDescs = {};
91
+ propertyNames.forEach(function (name) {
92
+ propertyDescs[name] = Object.getOwnPropertyDescriptor(Assertion.prototype, name);
93
+ });
119
94
 
120
- if (utils.flag(assertion, "negate")) {
121
- assertion.assert(true, null, "expected promise to be fulfilled but it was rejected with " +
122
- utils.inspect(reason));
123
- }
95
+ property("fulfilled", function () {
96
+ var that = this;
97
+ var derivedPromise = getBasePromise(that).then(
98
+ function (value) {
99
+ that._obj = value;
100
+ assertIfNegated(that,
101
+ "expected promise not to be fulfilled but it was fulfilled with #{act}",
102
+ { actual: value });
103
+ return value;
104
+ },
105
+ function (reason) {
106
+ assertIfNotNegated(that,
107
+ "expected promise to be fulfilled but it was rejected with #{act}",
108
+ { actual: reason });
109
+ }
110
+ );
124
111
 
125
- // If we didn't throw from the assert, transform rejections into fulfillments, by not re-throwing the
126
- // reason.
127
- }
112
+ chaiAsPromised.transferPromiseness(that, derivedPromise);
113
+ });
114
+
115
+ property("rejected", function () {
116
+ var that = this;
117
+ var derivedPromise = getBasePromise(that).then(
118
+ function (value) {
119
+ that._obj = value;
120
+ assertIfNotNegated(that,
121
+ "expected promise to be rejected but it was fulfilled with #{act}",
122
+ { actual: value });
123
+ return value;
124
+ },
125
+ function (reason) {
126
+ assertIfNegated(that,
127
+ "expected promise not to be rejected but it was rejected with #{act}",
128
+ { actual: reason });
129
+
130
+ // Return the reason, transforming this into a fulfillment, to allow further assertions, e.g.
131
+ // `promise.should.be.rejected.and.eventually.equal("reason")`.
132
+ return reason;
133
+ }
134
+ );
128
135
 
129
- function withMethod(Constructor, message) {
136
+ chaiAsPromised.transferPromiseness(that, derivedPromise);
137
+ });
138
+
139
+ method("rejectedWith", function (Constructor, message) {
130
140
  var desiredReason = null;
141
+ var constructorName = null;
131
142
 
132
143
  if (Constructor instanceof RegExp || typeof Constructor === "string") {
133
144
  message = Constructor;
@@ -136,272 +147,223 @@
136
147
  desiredReason = Constructor;
137
148
  Constructor = null;
138
149
  message = null;
139
- }
140
-
141
- var messageVerb = null;
142
- var messageIsGood = null;
143
-
144
- if (message instanceof RegExp) {
145
- messageVerb = "matching";
146
- messageIsGood = function () {
147
- return message.test(rejectionReason.message);
148
- };
150
+ } else if (typeof Constructor === "function") {
151
+ constructorName = (new Constructor()).name;
149
152
  } else {
150
- messageVerb = "including";
151
- messageIsGood = function () {
152
- return rejectionReason && rejectionReason.message.indexOf(message) !== -1;
153
- };
154
- }
155
-
156
- function constructorIsGood() {
157
- return rejectionReason instanceof Constructor;
158
- }
159
-
160
- function matchesDesiredReason() {
161
- return rejectionReason === desiredReason;
153
+ Constructor = null;
162
154
  }
163
155
 
164
- function onTransformedFulfilled() {
165
- if (!utils.flag(assertion, "negate")) {
166
- if (desiredReason) {
167
- assertion.assert(matchesDesiredReason(),
168
- null,
169
- "expected promise to be rejected with " + utils.inspect(desiredReason) + " but " +
170
- "it was rejected with " + utils.inspect(rejectionReason));
171
- }
156
+ var that = this;
157
+ var derivedPromise = getBasePromise(that).then(
158
+ function (value) {
159
+ var assertionMessage = null;
160
+ var expected = null;
172
161
 
173
162
  if (Constructor) {
174
- assertion.assert(constructorIsGood(),
175
- "expected promise to be rejected with " + Constructor.prototype.name + " but it " +
176
- "was rejected with " + utils.inspect(rejectionReason));
163
+ assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
164
+ "#{act}";
165
+ expected = constructorName;
166
+ } else if (message) {
167
+ var verb = message instanceof RegExp ? "matching" : "including";
168
+ assertionMessage = "expected promise to be rejected with an error " + verb + " #{exp} but it " +
169
+ "was fulfilled with #{act}";
170
+ expected = message;
171
+ } else if (desiredReason) {
172
+ assertionMessage = "expected promise to be rejected with #{exp} but it was fulfilled with " +
173
+ "#{act}";
174
+ expected = desiredReason;
177
175
  }
178
176
 
179
- if (message) {
180
- assertion.assert(messageIsGood(),
181
- "expected promise to be rejected with an error " + messageVerb + " " + message +
182
- " but got " + utils.inspect(rejectionReason && rejectionReason.message));
183
- }
184
- }
185
- }
186
-
187
- function onTransformedRejected() {
188
- if (utils.flag(assertion, "negate")) {
189
- if (desiredReason) {
190
- assertion.assert(matchesDesiredReason(),
191
- null,
192
- "expected promise to not be rejected with " + utils.inspect(desiredReason));
193
- }
177
+ that._obj = value;
194
178
 
179
+ assertIfNotNegated(that, assertionMessage, { expected: expected, actual: value });
180
+ },
181
+ function (reason) {
195
182
  if (Constructor) {
196
- assertion.assert(constructorIsGood(),
197
- null,
198
- "expected promise to not be rejected with " + Constructor.prototype.name);
199
- }
200
-
201
- if (message) {
202
- assertion.assert(messageIsGood(),
203
- null,
204
- "expected promise to be not be rejected with an error " + messageVerb + " " +
205
- message);
206
- }
207
- } else {
208
- if (desiredReason) {
209
- assertion.assert(false,
210
- "expected promise to be rejected with " + utils.inspect(desiredReason) +
211
- " but it was fulfilled");
183
+ that.assert(reason instanceof Constructor,
184
+ "expected promise to be rejected with #{exp} but it was rejected with #{act}",
185
+ "expected promise not to be rejected with #{exp} but it was rejected with #{act}",
186
+ constructorName,
187
+ reason);
212
188
  }
213
189
 
214
- if (Constructor) {
215
- assertion.assert(false, "expected promise to be rejected with " + Constructor.prototype.name +
216
- " but it was fulfilled");
190
+ var reasonMessage = utils.type(reason) === "object" && "message" in reason ?
191
+ reason.message :
192
+ "" + reason;
193
+ if (message && reasonMessage !== null && reasonMessage !== undefined) {
194
+ if (message instanceof RegExp) {
195
+ that.assert(message.test(reasonMessage),
196
+ "expected promise to be rejected with an error matching #{exp} but got #{act}",
197
+ "expected promise not to be rejected with an error matching #{exp}",
198
+ message,
199
+ reasonMessage);
200
+ }
201
+ if (typeof message === "string") {
202
+ that.assert(reasonMessage.indexOf(message) !== -1,
203
+ "expected promise to be rejected with an error including #{exp} but got #{act}",
204
+ "expected promise not to be rejected with an error including #{exp}",
205
+ message,
206
+ reasonMessage);
207
+ }
217
208
  }
218
209
 
219
- if (message) {
220
- assertion.assert(false, "expected promise to be rejected with an error " + messageVerb + " " +
221
- message + " but it was fulfilled");
210
+ if (desiredReason) {
211
+ that.assert(reason === desiredReason,
212
+ "expected promise to be rejected with #{exp} but it was rejected with #{act}",
213
+ "expected promise not to be rejected with #{exp}",
214
+ desiredReason,
215
+ reason);
222
216
  }
223
217
  }
224
- }
225
-
226
- return makeAssertionPromise(
227
- transformedPromise.then(onTransformedFulfilled, onTransformedRejected),
228
- assertion
229
218
  );
230
- }
231
-
232
- var derivedPromise = assertion._obj.then(onOriginalFulfilled, onOriginalRejected);
233
- var transformedPromise = makeAssertionPromise(derivedPromise, assertion);
234
- Object.defineProperty(transformedPromise, "with", { enumerable: true, configurable: true, value: withMethod });
235
-
236
- return transformedPromise;
237
- }
238
-
239
- function isChaiAsPromisedAsserter(asserterName) {
240
- return ["fulfilled", "rejected", "broken", "eventually", "become"].indexOf(asserterName) !== -1;
241
- }
242
219
 
243
- function makeAssertionPromiseToDoAsserter(currentAssertionPromise, previousAssertionPromise, doAsserter) {
244
- var promiseToDoAsserter = currentAssertionPromise.then(function (fulfillmentValue) {
245
- // The previous assertion promise might have picked up some flags while waiting for fulfillment.
246
- utils.transferFlags(previousAssertionPromise, currentAssertionPromise);
247
-
248
- // Replace the object flag with the fulfillment value, so that doAsserter can operate properly.
249
- utils.flag(currentAssertionPromise, "object", fulfillmentValue);
220
+ chaiAsPromised.transferPromiseness(that, derivedPromise);
221
+ });
250
222
 
251
- // Perform the actual asserter action and return the result of it.
252
- return doAsserter();
223
+ property("eventually", function () {
224
+ utils.flag(this, "eventually", true);
253
225
  });
254
- return makeAssertionPromise(promiseToDoAsserter, currentAssertionPromise);
255
- }
256
226
 
257
- function makeAssertionPromise(promise, baseAssertion) {
258
- // An assertion-promise is an (extensible!) promise with the following additions:
259
- var assertionPromise = Object.create(promise);
227
+ method("notify", function (done) {
228
+ doNotify(getBasePromise(this), done);
229
+ });
260
230
 
261
- // 1. A `notify` method.
262
- addNotifyMethod(assertionPromise);
231
+ method("become", function (value) {
232
+ return this.eventually.deep.equal(value);
233
+ });
263
234
 
264
- // 2. An `assert` method that acts exactly as it would on an assertion. This is called by promisified
265
- // asserters after the promise fulfills.
266
- assertionPromise.assert = function () {
267
- return Assertion.prototype.assert.apply(assertionPromise, arguments);
268
- };
235
+ ////////
236
+ // `eventually`
269
237
 
270
- Object.defineProperty(assertionPromise, "_obj", Object.getOwnPropertyDescriptor(Assertion.prototype, "_obj"));
238
+ // We need to be careful not to trigger any getters, thus `Object.getOwnPropertyDescriptor` usage.
239
+ var methodNames = propertyNames.filter(function (name) {
240
+ return name !== "assert" && typeof propertyDescs[name].value === "function";
241
+ });
271
242
 
272
- // 3. Chai asserters, which act upon the promise's fulfillment value.
273
- var asserterNames = Object.getOwnPropertyNames(Assertion.prototype);
274
- asserterNames.forEach(function (asserterName) {
275
- // We already added `notify` and `assert`; don't mess with those.
276
- if (asserterName === "notify" || asserterName === "assert" || asserterName === "_obj") {
277
- return;
278
- }
243
+ methodNames.forEach(function (methodName) {
244
+ Assertion.overwriteMethod(methodName, function (originalMethod) {
245
+ return function () {
246
+ doAsserterAsyncAndAddThen(originalMethod, this, arguments);
247
+ };
248
+ });
249
+ });
279
250
 
280
- // Only add asserters for other libraries; poison-pill Chai as Promised ones.
281
- if (isChaiAsPromisedAsserter(asserterName)) {
282
- utils.addProperty(assertionPromise, asserterName, function () {
283
- throw new Error("Cannot use Chai as Promised asserters more than once in an assertion.");
284
- });
285
- return;
286
- }
251
+ var getterNames = propertyNames.filter(function (name) {
252
+ return name !== "_obj" && typeof propertyDescs[name].get === "function";
253
+ });
287
254
 
288
- // The asserter will need to be added differently depending on its type. In all cases we use
289
- // `makeAssertionPromiseToDoAsserter`, which, given this current `assertionPromise` we are going to
290
- // return, plus the `baseAssertion` we are basing it off of, will return a new assertion-promise that
291
- // builds off of `assertionPromise` and `baseAssertion` to perform the actual asserter action upon
292
- // fulfillment.
293
- var propertyDescriptor = Object.getOwnPropertyDescriptor(Assertion.prototype, asserterName);
294
-
295
- if (typeof propertyDescriptor.value === "function") {
296
- // Case 1: simple method asserters
297
- utils.addMethod(assertionPromise, asserterName, function () {
298
- var args = arguments;
299
-
300
- return makeAssertionPromiseToDoAsserter(assertionPromise, baseAssertion, function () {
301
- return propertyDescriptor.value.apply(assertionPromise, args);
302
- });
303
- });
304
- } else if (typeof propertyDescriptor.get === "function") {
305
- // Case 2: property asserters. These break down into two subcases: chainable methods, and pure
306
- // properties. An example of the former is `a`/`an`: `.should.be.an.instanceOf` vs.
307
- // `should.be.an("object")`.
308
- var isChainableMethod = false;
309
- try {
310
- isChainableMethod = typeof propertyDescriptor.get.call({}) === "function";
311
- } catch (e) { }
312
-
313
- if (isChainableMethod) {
314
- // Case 2A: chainable methods. Recreate the chainable method, but operating on the augmented
315
- // promise. We need to copy both the assertion behavior and the chaining behavior, since the
316
- // chaining behavior might for example set flags on the object.
317
- utils.addChainableMethod(
318
- assertionPromise,
319
- asserterName,
320
- function () {
321
- var args = arguments;
322
-
323
- return makeAssertionPromiseToDoAsserter(assertionPromise, baseAssertion, function () {
324
- // Due to https://github.com/chaijs/chai/commit/514dd6ce466d7b4110b38345e4073d586c017f3f
325
- // we can't use `propertyDescriptor.get().apply`.
326
- return Function.prototype.apply.call(propertyDescriptor.get(), assertionPromise, args);
327
- });
328
- },
329
- function () {
330
- // As above.
331
- return Function.prototype.call.call(propertyDescriptor.get, assertionPromise);
255
+ getterNames.forEach(function (getterName) {
256
+ var propertyDesc = propertyDescs[getterName];
257
+
258
+ // Chainable methods are things like `an`, which can work both for `.should.be.an.instanceOf` and as
259
+ // `should.be.an("object")`. We need to handle those specially.
260
+ var isChainableMethod = false;
261
+ try {
262
+ isChainableMethod = typeof propertyDesc.get.call({}) === "function";
263
+ } catch (e) { }
264
+
265
+ if (isChainableMethod) {
266
+ Assertion.addChainableMethod(
267
+ getterName,
268
+ function () {
269
+ var assertion = this;
270
+ function originalMethod() {
271
+ return propertyDesc.get.call(assertion).apply(assertion, arguments);
332
272
  }
333
- );
334
- } else {
335
- // Case 2B: pure property case
336
- utils.addProperty(assertionPromise, asserterName, function () {
337
- return makeAssertionPromiseToDoAsserter(assertionPromise, baseAssertion, function () {
338
- return propertyDescriptor.get.call(assertionPromise);
339
- });
340
- });
341
- }
273
+ doAsserterAsyncAndAddThen(originalMethod, this, arguments);
274
+ },
275
+ function () {
276
+ var originalGetter = propertyDesc.get;
277
+ doAsserterAsyncAndAddThen(originalGetter, this);
278
+ }
279
+ );
280
+ } else {
281
+ Assertion.overwriteProperty(getterName, function (originalGetter) {
282
+ return function () {
283
+ doAsserterAsyncAndAddThen(originalGetter, this);
284
+ };
285
+ });
342
286
  }
343
287
  });
344
288
 
345
- return assertionPromise;
346
- }
347
-
348
- property("fulfilled", fulfilledAsserter);
349
- property("rejected", rejectedAsserter);
350
- property("broken", rejectedAsserter);
351
-
352
- property("eventually", function () {
353
- return makeAssertionPromise(this._obj, this);
354
- });
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);
294
+ }
355
295
 
356
- method("become", function (value) {
357
- return this.eventually.eql(value);
358
- });
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
+ asserter.apply(assertion, args);
303
+
304
+ // Because asserters, for example `property`, can change the value of `_obj` (i.e. change the "object"
305
+ // flag), we need to communicate this value change to subsequent chained asserters. Since we build a
306
+ // promise chain paralleling the asserter chain, we can use it to communicate such changes.
307
+ return assertion._obj;
308
+ });
359
309
 
360
- method("notify", function (callback) {
361
- return notify(this._obj, callback);
362
- });
310
+ chaiAsPromised.transferPromiseness(assertion, derivedPromise);
311
+ }
363
312
 
364
- // Now use the Assertion framework to build an `assert` interface.
365
- var originalAssertMethods = Object.getOwnPropertyNames(assert).filter(function (propName) {
366
- return typeof assert[propName] === "function";
367
- });
313
+ ///////
314
+ // Now use the `Assertion` framework to build an `assert` interface.
315
+ var originalAssertMethods = Object.getOwnPropertyNames(assert).filter(function (propName) {
316
+ return typeof assert[propName] === "function";
317
+ });
368
318
 
369
- assert.isFulfilled = function (promise, message) {
370
- return (new Assertion(promise, message)).to.be.fulfilled;
371
- };
319
+ assert.isFulfilled = function (promise, message) {
320
+ return (new Assertion(promise, message)).to.be.fulfilled;
321
+ };
372
322
 
373
- assert.isRejected = assert.isBroken = function (promise, toTestAgainst, message) {
374
- if (typeof toTestAgainst === "string") {
375
- message = toTestAgainst;
376
- toTestAgainst = null;
377
- }
323
+ assert.isRejected = function (promise, toTestAgainst, message) {
324
+ if (typeof toTestAgainst === "string") {
325
+ message = toTestAgainst;
326
+ toTestAgainst = undefined;
327
+ }
378
328
 
379
- var shouldBeRejectedPromise = (new Assertion(promise, message)).to.be.rejected;
329
+ var assertion = (new Assertion(promise, message));
330
+ return toTestAgainst !== undefined ? assertion.to.be.rejectedWith(toTestAgainst) : assertion.to.be.rejected;
331
+ };
380
332
 
381
- // Use `['with']` to handle crappy non-ES5 environments like PhantomJS.
382
- return toTestAgainst ? shouldBeRejectedPromise['with'](toTestAgainst) : shouldBeRejectedPromise;
383
- };
333
+ assert.becomes = function (promise, value) {
334
+ return assert.eventually.deepEqual(promise, value);
335
+ };
384
336
 
385
- assert.eventually = {};
386
- originalAssertMethods.forEach(function (assertMethodName) {
387
- assert.eventually[assertMethodName] = function (promise) {
388
- var otherArgs = Array.prototype.slice.call(arguments, 1);
337
+ assert.doesNotBecome = function (promise, value) {
338
+ return assert.eventually.notDeepEqual(promise, value);
339
+ };
389
340
 
390
- var promiseToAssert = promise.then(function (fulfillmentValue) {
391
- return assert[assertMethodName].apply(assert, [fulfillmentValue].concat(otherArgs));
392
- });
341
+ assert.eventually = {};
342
+ originalAssertMethods.forEach(function (assertMethodName) {
343
+ assert.eventually[assertMethodName] = function (promise) {
344
+ var otherArgs = Array.prototype.slice.call(arguments, 1);
345
+
346
+ var customRejectionHandler;
347
+ var message = arguments[assert[assertMethodName].length - 1];
348
+ if (typeof message === "string") {
349
+ customRejectionHandler = function (reason) {
350
+ throw new chai.AssertionError(message + "\n\nOriginal reason: " + utils.inspect(reason));
351
+ };
352
+ }
393
353
 
394
- var augmentedPromiseToAssert = Object.create(promiseToAssert);
395
- addNotifyMethod(augmentedPromiseToAssert);
396
- return augmentedPromiseToAssert;
397
- };
398
- });
354
+ var returnedPromise = promise.then(
355
+ function (fulfillmentValue) {
356
+ return assert[assertMethodName].apply(assert, [fulfillmentValue].concat(otherArgs));
357
+ },
358
+ customRejectionHandler
359
+ );
399
360
 
400
- assert.becomes = function (promise, value) {
401
- return assert.eventually.deepEqual(promise, value);
402
- };
361
+ returnedPromise.notify = function (done) {
362
+ doNotify(returnedPromise, done);
363
+ };
403
364
 
404
- assert.doesNotBecome = function (promise, value) {
405
- return assert.eventually.notDeepEqual(promise, value);
406
- };
407
- }));
365
+ return returnedPromise;
366
+ };
367
+ });
368
+ }
369
+ }());