snappymail/vendors/Q/spec/q-spec.js
RainLoop Team c23ba31e17 Promises (first look)
Thread dropdown
Small fixes
2015-03-14 18:27:44 +04:00

2611 lines
75 KiB
JavaScript

"use strict";
/*jshint newcap: false*/
/*global Q: true, describe: false, it: false, expect: false, beforeEach: false,
afterEach: false, require: false, jasmine: false, waitsFor: false,
runs: false */
if (typeof Q === "undefined" && typeof require !== "undefined") {
// For Node compatibility.
global.Q = require("../q");
require("./lib/jasmine-promise");
}
var REASON = "this is not an error, but it might show up in the console";
// In browsers that support strict mode, it'll be `undefined`; otherwise, the global.
var calledAsFunctionThis = (function () { return this; }());
afterEach(function () {
Q.onerror = null;
});
describe("computing sum of integers using promises", function() {
it("should compute correct result without blowing stack", function () {
var array = [];
var iters = 1000;
for (var i = 1; i <= iters; i++) {
array.push(i);
}
var pZero = Q.fulfill(0);
var result = array.reduce(function (promise, nextVal) {
return promise.then(function (currentVal) {
return Q.fulfill(currentVal + nextVal);
});
}, pZero);
return result.then(function (value) {
expect(value).toEqual(iters * (iters + 1) / 2);
});
});
});
describe("Q function", function () {
it("should result in a fulfilled promise when given a value", function () {
expect(Q(5).isFulfilled()).toBe(true);
});
it("should be the identity when given promise", function () {
var f = Q.fulfill(5);
var r = Q.reject(new Error("aaargh"));
var p = Q.promise(function () { });
expect(Q(f)).toBe(f);
expect(Q(r)).toBe(r);
expect(Q(p)).toBe(p);
});
});
describe("defer and when", function () {
it("resolve before when", function () {
var turn = 0;
var deferred = Q.defer();
deferred.resolve(10);
var promise = Q.when(deferred.promise, function (value) {
expect(turn).toEqual(1);
expect(value).toEqual(10);
});
turn++;
return promise;
});
it("reject before when", function () {
var turn = 0;
var deferred = Q.defer();
deferred.reject(-1);
var promise = Q.when(deferred.promise, function () {
expect(true).toBe(false);
}, function (value) {
expect(turn).toEqual(1);
expect(value).toEqual(-1);
});
turn++;
return promise;
});
it("when before resolve", function () {
var turn = 0;
var deferred = Q.defer();
var promise = deferred.promise.then(function (value) {
expect(turn).toEqual(2);
expect(value).toEqual(10);
turn++;
});
Q.nextTick(function () {
expect(turn).toEqual(1);
deferred.resolve(10);
turn++;
});
turn++;
return promise;
});
it("when before reject", function () {
var turn = 0;
var deferred = Q.defer();
var promise = deferred.promise.then(function () {
expect(true).toBe(false);
}, function (value) {
expect(turn).toEqual(2);
expect(value).toEqual(-1);
turn++;
});
Q.nextTick(function () {
expect(turn).toEqual(1);
deferred.reject(-1);
turn++;
});
turn++;
return promise;
});
it("resolves multiple observers", function (done) {
var nextTurn = false;
var resolution = "Taram pam param!";
var deferred = Q.defer();
var count = 10;
var i = 0;
function resolve(value) {
i++;
expect(value).toBe(resolution);
expect(nextTurn).toBe(true);
if (i === count) {
done();
}
}
while (++i <= count) {
Q.when(deferred.promise, resolve);
}
deferred.resolve(resolution);
i = 0;
nextTurn = true;
});
it("observers called even after throw", function () {
var threw = false;
var deferred = Q.defer();
Q.when(deferred.promise, function () {
threw = true;
throw new Error(REASON);
});
var promise = Q.when(deferred.promise, function (value) {
expect(value).toEqual(10);
}, function () {
expect("not").toEqual("here");
});
deferred.resolve(10);
return promise;
});
it("returns `undefined` from the deferred's methods", function () {
expect(Q.defer().resolve()).toBe(undefined);
expect(Q.defer().reject()).toBe(undefined);
});
});
describe("always next tick", function () {
it("generated by `resolve`", function () {
var turn = 0;
var promise = Q.when(Q(), function () {
expect(turn).toEqual(1);
});
turn++;
return promise;
});
it("generated by `reject`", function () {
var turn = 0;
var promise = Q.when(Q.reject(), function () {
expect(true).toBe(false);
}, function () {
expect(turn).toEqual(1);
});
turn++;
return promise;
});
it("allows overriding global nextTick", function () {
var spy = jasmine.createSpy();
spyOn(Q, 'nextTick').andCallFake(function immediateTick(task){
task();
});
Q.when(Q(), spy);
expect(spy).toHaveBeenCalled();
expect(Q.nextTick).toHaveBeenCalled();
});
});
describe("progress", function () {
it("calls a single progress listener", function () {
var progressed = false;
var deferred = Q.defer();
var promise = Q.when(
deferred.promise,
function () {
expect(progressed).toBe(true);
},
function () {
expect(true).toBe(false);
},
function () {
progressed = true;
}
);
deferred.notify();
deferred.resolve();
return promise;
});
it("calls multiple progress listeners", function () {
var progressed1 = false;
var progressed2 = false;
var deferred = Q.defer();
var promise = Q.when(
deferred.promise,
function () {
expect(progressed1).toBe(true);
expect(progressed2).toBe(true);
},
function () {
expect(true).toBe(false);
},
function () {
progressed1 = true;
}
);
Q.when(deferred.promise, null, null, function () {
progressed2 = true;
});
deferred.notify();
deferred.resolve();
return promise;
});
it("calls all progress listeners even if one throws", function () {
var progressed1 = false;
var progressed2 = false;
var progressed3 = false;
var deferred = Q.defer();
var promise = Q.when(
deferred.promise,
function () {
expect(progressed1).toBe(true);
expect(progressed2).toBe(true);
expect(progressed3).toBe(true);
},
function () {
expect(true).toBe(false);
},
function () {
progressed1 = true;
}
);
Q.onerror = function () { };
Q.when(deferred.promise, null, null, function () {
progressed2 = true;
throw new Error("just a test, ok if it shows up in the console");
});
Q.when(deferred.promise, null, null, function () {
progressed3 = true;
});
deferred.notify();
deferred.resolve();
return promise;
});
it("calls the progress listener even if later rejected", function () {
var progressed = false;
var deferred = Q.defer();
var promise = Q.when(
deferred.promise,
function () {
expect(true).toBe(false);
},
function () {
expect(progressed).toEqual(true);
},
function () {
progressed = true;
}
);
deferred.notify();
deferred.reject();
return promise;
});
it("calls the progress listener with the notify values", function () {
var progressValues = [];
var desiredProgressValues = [{}, {}, "foo", 5];
var deferred = Q.defer();
var promise = Q.when(
deferred.promise,
function () {
for (var i = 0; i < desiredProgressValues.length; ++i) {
var desired = desiredProgressValues[i];
var actual = progressValues[i];
expect(actual).toBe(desired);
}
},
function () {
expect(true).toBe(false);
},
function (value) {
progressValues.push(value);
}
);
for (var i = 0; i < desiredProgressValues.length; ++i) {
deferred.notify(desiredProgressValues[i]);
}
deferred.resolve();
return promise;
});
it("does not call the progress listener if notify is called after fulfillment", function () {
var deferred = Q.defer();
var called = false;
Q.when(deferred.promise, null, null, function () {
called = true;
});
deferred.resolve();
deferred.notify();
return Q.delay(10).then(function () {
expect(called).toBe(false);
});
});
it("does not call the progress listener if notify is called after rejection", function () {
var deferred = Q.defer();
var called = false;
Q.when(deferred.promise, null, null, function () {
called = true;
});
deferred.reject();
deferred.notify();
return Q.delay(10).then(function () {
expect(called).toBe(false);
});
});
it("should not save and re-emit progress notifications", function () {
var deferred = Q.defer();
var progressValues = [];
deferred.notify(1);
var promise = Q.when(
deferred.promise,
function () {
expect(progressValues).toEqual([2]);
},
function () {
expect(true).toBe(false);
},
function (progressValue) {
progressValues.push(progressValue);
}
);
deferred.notify(2);
deferred.resolve();
return promise;
});
it("should allow attaching progress listeners w/ .progress", function () {
var progressed = false;
var deferred = Q.defer();
deferred.promise.progress(function () {
progressed = true;
});
deferred.notify();
deferred.resolve();
return deferred.promise;
});
it("should allow attaching progress listeners w/ Q.progress", function () {
var progressed = false;
var deferred = Q.defer();
Q.progress(deferred.promise, function () {
progressed = true;
});
deferred.notify();
deferred.resolve();
return deferred.promise;
});
it("should call the progress listener with undefined context", function () {
var progressed = false;
var progressContext = {};
var deferred = Q.defer();
var promise = Q.when(
deferred.promise,
function () {
expect(progressed).toBe(true);
expect(progressContext).toBe(calledAsFunctionThis);
},
function () {
expect(true).toBe(false);
},
function () {
progressed = true;
progressContext = this;
}
);
deferred.notify();
deferred.resolve();
return promise;
});
it("should forward only the first notify argument to listeners", function () {
var progressValueArrays = [];
var deferred = Q.defer();
var promise = Q.when(
deferred.promise,
function () {
expect(progressValueArrays).toEqual([[1], [2], [4]]);
},
function () {
expect(true).toBe(false);
},
function () {
var args = Array.prototype.slice.call(arguments);
progressValueArrays.push(args);
}
);
deferred.notify(1);
deferred.notify(2, 3);
deferred.notify(4, 5, 6);
deferred.resolve();
return promise;
});
it("should work with .then as well", function () {
var progressed = false;
var deferred = Q.defer();
var promise = deferred.promise.then(
function () {
expect(progressed).toBe(true);
},
function () {
expect(true).toBe(false);
},
function () {
progressed = true;
}
);
deferred.notify();
deferred.resolve();
return promise;
});
it("should re-throw all errors thrown by listeners to Q.onerror", function () {
var theError = new Error("boo!");
var def = Q.defer();
def.promise.progress(function () {
throw theError;
});
var deferred = Q.defer();
Q.onerror = function (error) {
expect(error).toBe(theError);
deferred.resolve();
};
Q.delay(100).then(deferred.reject);
def.notify();
return deferred.promise;
});
});
describe("promises for objects", function () {
describe("get", function () {
it("fulfills a promise", function () {
var deferred = Q.defer();
deferred.resolve({a: 1});
return deferred.promise.get("a")
.then(function (a) {
expect(a).toBe(1);
});
});
it("propagates a rejection", function () {
var exception = new Error("boo!");
return Q.fcall(function () {
throw exception;
})
.get("a")
.then(function () {
expect("be").toBe("not to be");
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
});
describe("set", function () {
it("fulfills a promise", function () {
var object = {};
return Q(object)
.set("a", 1)
.then(function (result) {
expect(result).toBe(undefined);
expect(object.a).toBe(1);
});
});
it("propagates a rejection", function () {
var exception = new Error("Gah!");
return Q.reject(exception)
.set("a", 1)
.then(function () {
expect("frozen over").toBe("quite warm");
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
});
describe("del", function () {
it("fulfills a promise", function () {
var object = {a: 10};
return Q.fcall(function () {
return object;
})
.del("a")
.then(function (result) {
expect("a" in object).toBe(false);
expect(result).toBe(void 0);
}, function () {
expect("up").toBe("down");
});
});
it("propagates a rejection", function () {
var exception = new Error("hah-hah");
return Q.fcall(function () {
throw exception;
})
.del("a")
.then(function () {
expect(true).toBe(false);
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
});
describe("post", function () {
it("fulfills a promise", function () {
var subject = {
a: function a(value) {
this._a = value;
return 1 + value;
}
};
return Q.when(Q.post(subject, "a", [1]), function (two) {
expect(subject._a).toBe(1);
expect(two).toBe(2);
});
});
it("works as apply when given no name", function () {
return Q(function (a, b, c) {
return a + b + c;
})
.post(undefined, [1, 2, 3])
.then(function (sum) {
expect(sum).toEqual(6);
});
});
});
describe("send", function () {
it("fulfills a promise", function () {
var foo;
var subject = {
foo: function (_bar) {
return _bar;
},
bar: function (_foo, _bar) {
foo = _foo;
return this.foo(_bar);
}
};
return Q.send(subject, "bar", 1, 2)
.then(function (two) {
expect(foo).toEqual(1);
expect(two).toEqual(2);
});
});
it("is rejected for undefined method", function () {
var subject = {};
return Q(subject)
.send("foo")
.then(function () {
expect("here").toEqual("not here");
}, function () {
});
});
it("is rejected for undefined object", function () {
return Q()
.send("foo")
.then(function () {
expect("here").toEqual("not here");
}, function () {
});
});
});
describe("keys", function () {
function Klass (a, b) {
this.a = a;
this.b = b;
}
Klass.prototype.notOwn = 1;
it("fulfills a promise", function () {
return Q.keys(new Klass(10, 20))
.then(function (keys) {
expect(keys.sort()).toEqual(["a", "b"]);
});
});
});
});
describe("promises for functions", function () {
describe("fapply", function () {
it("fulfills a promise using arguments", function () {
return Q(function (a, b, c) {
return a + b + c;
})
.fapply([1, 2, 3])
.then(function (sum) {
expect(sum).toEqual(6);
});
});
});
describe("fcall", function () {
it("fulfills a promise using arguments", function () {
return Q(function (a, b, c) {
return a + b + c;
})
.fcall(1, 2, 3)
.then(function (sum) {
expect(sum).toEqual(6);
});
});
});
describe("fbind", function () {
it("accepts a promise for a function", function () {
return Q.fbind(Q(function (high, low) {
return high - low;
}))
(2, 1)
.then(function (difference) {
expect(difference).toEqual(1);
});
});
it("chains partial application on a promise for a function", function () {
return Q(function (a, b) {
return a * b;
})
.fbind(2)(3)
.then(function (product) {
expect(product).toEqual(6);
});
});
it("returns a fulfilled promise", function () {
var result = {};
var bound = Q.fbind(function () {
return result;
});
return bound()
.then(function (_result) {
expect(_result).toBe(result);
});
});
it("returns a rejected promise from a thrown error", function () {
var exception = new Error("Boo!");
var bound = Q.fbind(function () {
throw exception;
});
return bound()
.then(function () {
expect("flying pigs").toBe("swillin' pigs");
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
it("passes arguments through", function () {
var x = {}, y = {};
var bound = Q.fbind(function (a, b) {
expect(a).toBe(x);
expect(b).toBe(y);
});
return bound(x, y);
});
it("passes and also partially applies arguments", function () {
var x = {}, y = {};
var bound = Q.fbind(function (a, b) {
expect(a).toBe(x);
expect(b).toBe(y);
}, x);
return bound(y);
});
it("doesn't bind `this`", function () {
var theThis = { me: "this" };
var bound = Q.fbind(function () {
expect(this).toBe(theThis);
});
return bound.call(theThis);
});
});
});
describe("inspect", function () {
it("for a fulfilled promise", function () {
expect(Q(10).inspect()).toEqual({
state: "fulfilled",
value: 10
});
});
it("for a rejected promise", function () {
var error = new Error("In your face.");
var rejected = Q.reject(error);
expect(rejected.inspect()).toEqual({
state: "rejected",
reason: error
});
});
it("for a pending, unresolved promise", function () {
var pending = Q.defer().promise;
expect(pending.inspect()).toEqual({ state: "pending" });
});
it("for a promise resolved to a rejected promise", function () {
var deferred = Q.defer();
var error = new Error("Rejected!");
var rejected = Q.reject(error);
deferred.resolve(rejected);
expect(deferred.promise.inspect()).toEqual({
state: "rejected",
reason: error
});
});
it("for a promise resolved to a fulfilled promise", function () {
var deferred = Q.defer();
var fulfilled = Q(10);
deferred.resolve(fulfilled);
expect(deferred.promise.inspect()).toEqual({
state: "fulfilled",
value: 10
});
});
it("for a promise resolved to a pending promise", function () {
var a = Q.defer();
var b = Q.defer();
a.resolve(b.promise);
expect(a.promise.inspect()).toEqual({ state: "pending" });
});
});
describe("promise states", function () {
it("of fulfilled value", function () {
expect(Q.isFulfilled(void 0)).toBe(true);
expect(Q.isRejected(false)).toBe(false);
expect(Q.isPending(true)).toBe(false);
});
it("of fulfillment", function () {
var promise = Q(10);
expect(Q.isFulfilled(promise)).toBe(true);
expect(promise.isFulfilled()).toBe(true);
expect(Q.isRejected(promise)).toBe(false);
expect(promise.isRejected()).toBe(false);
expect(Q.isPending(promise)).toBe(false);
expect(promise.isPending()).toBe(false);
});
it("of rejection", function () {
var error = new Error("Oh, snap.");
var promise = Q.reject(error);
expect(promise.isFulfilled()).toBe(false);
expect(promise.isRejected()).toBe(true);
expect(promise.isPending()).toBe(false);
});
it("of rejection with a falsy value", function () {
var promise = Q.reject(undefined);
expect(promise.isFulfilled()).toBe(false);
expect(promise.isRejected()).toBe(true);
expect(promise.isPending()).toBe(false);
});
it("of deferred", function () {
var deferred = Q.defer();
var promise = deferred.promise;
expect(promise.isFulfilled()).toBe(false);
expect(promise.isRejected()).toBe(false);
expect(promise.isPending()).toBe(true);
});
it("of deferred rejection", function () {
var deferred = Q.defer();
var rejection = Q.reject(new Error("Rejected!"));
deferred.resolve(rejection);
var promise = deferred.promise;
expect(promise.isFulfilled()).toBe(false);
expect(promise.isRejected()).toBe(true);
expect(promise.isPending()).toBe(false);
});
it("of deferred fulfillment", function () {
var deferred = Q.defer();
deferred.resolve(10);
var promise = deferred.promise;
expect(promise.isFulfilled()).toBe(true);
expect(promise.isRejected()).toBe(false);
expect(promise.isPending()).toBe(false);
});
it("of deferred deferred", function () {
var a = Q.defer();
var b = Q.defer();
a.resolve(b.promise);
var promise = a.promise;
expect(promise.isFulfilled()).toBe(false);
expect(promise.isRejected()).toBe(false);
expect(promise.isPending()).toBe(true);
});
it("of isFulfilled side effects", function () {
var deferred = Q.defer();
var finished = false;
waitsFor(function () {
return finished;
});
var parentPromise = deferred.promise;
var childPromise = parentPromise.then(function () {
expect(parentPromise.isFulfilled()).toBe(true);
expect(childPromise.isFulfilled()).toBe(false);
return parentPromise.then(function (value) {
finished = true;
return value + 1;
});
});
deferred.resolve(1);
runs(function () {
expect(childPromise.isPending()).toBe(false);
expect(childPromise.isRejected()).toBe(false);
expect(childPromise.isFulfilled()).toBe(true);
expect(childPromise.inspect().value).toBe(2);
});
});
});
describe("propagation", function () {
it("propagate through then with no callback", function () {
return Q(10)
.then()
.then(function (ten) {
expect(ten).toBe(10);
});
});
it("propagate through then with modifying callback", function () {
return Q(10)
.then(function (ten) {
return ten + 10;
})
.then(function (twen) {
expect(twen).toBe(20);
});
});
it("errback recovers from exception", function () {
var error = new Error("Bah!");
return Q.reject(error)
.then(null, function (_error) {
expect(_error).toBe(error);
return 10;
})
.then(function (value) {
expect(value).toBe(10);
});
});
it("rejection propagates through then with no errback", function () {
var error = new Error("Foolish mortals!");
return Q.reject(error)
.then()
.then(null, function (_error) {
expect(_error).toBe(error);
});
});
it("rejection intercepted and rethrown", function () {
var error = new Error("Foolish mortals!");
var nextError = new Error("Silly humans!");
return Q.reject(error)
.fail(function () {
throw nextError;
})
.then(null, function (_error) {
expect(_error).toBe(nextError);
});
});
it("resolution is forwarded through deferred promise", function () {
var a = Q.defer();
var b = Q.defer();
a.resolve(b.promise);
b.resolve(10);
return a.promise.then(function (eh) {
expect(eh).toEqual(10);
});
});
it("should propagate progress by default", function () {
var d = Q.defer();
var progressValues = [];
var promise = d.promise
.then()
.then(
function () {
expect(progressValues).toEqual([1]);
},
function () {
expect(true).toBe(false);
},
function (progressValue) {
progressValues.push(progressValue);
}
);
d.notify(1);
d.resolve();
return promise;
});
it("should allow translation of progress in the progressback", function () {
var d = Q.defer();
var progressValues = [];
var promise = d.promise
.progress(function (p) {
return p + 5;
})
.then(
function () {
expect(progressValues).toEqual([10]);
},
function () {
expect(true).toBe(false);
},
function (progressValue) {
progressValues.push(progressValue);
}
);
d.notify(5);
d.resolve();
return promise;
});
it("should stop progress propagation if an error is thrown", function () {
var def = Q.defer();
var p2 = def.promise.progress(function () {
throw new Error("boo!");
});
Q.onerror = function () { /* just swallow it for this test */ };
var progressValues = [];
var result = p2.then(
function () {
expect(progressValues).toEqual([]);
},
function () {
expect(true).toBe(false);
},
function (progressValue) {
progressValues.push(progressValue);
}
);
def.notify();
def.resolve();
return result;
});
});
describe("all", function () {
it("fulfills when passed an empty array", function () {
return Q.all([]);
});
it("rejects after any constituent promise is rejected", function () {
var toResolve = Q.defer(); // never resolve
var toReject = Q.defer();
var promises = [toResolve.promise, toReject.promise];
var promise = Q.all(promises);
toReject.reject(new Error("Rejected"));
return Q.delay(250)
.then(function () {
expect(promise.isRejected()).toBe(true);
})
.timeout(1000);
});
it("resolves foreign thenables", function () {
var normal = Q(1);
var foreign = { then: function (f) { f(2); } };
return Q.all([normal, foreign])
.then(function (result) {
expect(result).toEqual([1, 2]);
});
});
it("fulfills when passed an sparse array", function () {
var toResolve = Q.defer();
var promises = [];
promises[0] = Q(0);
promises[2] = toResolve.promise;
var promise = Q.all(promises);
toResolve.resolve(2);
return promise.then(function (result) {
expect(result).toEqual([0, void 0, 2]);
});
});
it("modifies the input array", function () {
var input = [Q(0), Q(1)];
return Q.all(input).then(function (result) {
expect(result).toBe(input);
expect(input).toEqual([0, 1]);
});
});
it("sends { index, value } progress updates", function () {
var deferred1 = Q.defer();
var deferred2 = Q.defer();
var progressValues = [];
Q.delay(50).then(function () {
deferred1.notify("a");
});
Q.delay(100).then(function () {
deferred2.notify("b");
deferred2.resolve();
});
Q.delay(150).then(function () {
deferred1.notify("c");
deferred1.resolve();
});
return Q.all([deferred1.promise, deferred2.promise]).then(
function () {
expect(progressValues).toEqual([
{ index: 0, value: "a" },
{ index: 1, value: "b" },
{ index: 0, value: "c" }
]);
},
undefined,
function (progressValue) {
progressValues.push(progressValue);
}
)
});
});
describe("allSettled", function () {
it("works on an empty array", function () {
return Q.allSettled([])
.then(function (snapshots) {
expect(snapshots).toEqual([]);
});
});
it("deals with a mix of non-promises and promises", function () {
return Q.allSettled([1, Q(2), Q.reject(3)])
.then(function (snapshots) {
expect(snapshots).toEqual([
{ state: "fulfilled", value: 1 },
{ state: "fulfilled", value: 2 },
{ state: "rejected", reason: 3 }
]);
});
});
it("is settled after every constituent promise is settled", function () {
var toFulfill = Q.defer();
var toReject = Q.defer();
var promises = [toFulfill.promise, toReject.promise];
var fulfilled;
var rejected;
Q.fcall(function () {
toReject.reject();
rejected = true;
})
.delay(15)
.then(function () {
toFulfill.resolve();
fulfilled = true;
});
return Q.allSettled(promises)
.then(function () {
expect(fulfilled).toBe(true);
expect(rejected).toBe(true);
});
});
it("does not modify the input array", function () {
var input = [1, Q(2), Q.reject(3)];
return Q.allSettled(input)
.then(function (snapshots) {
expect(snapshots).not.toBe(input);
expect(snapshots).toEqual([
{ state: "fulfilled", value: 1 },
{ state: "fulfilled", value: 2 },
{ state: "rejected", reason: 3 }
]);
});
});
});
describe("spread", function () {
it("spreads values across arguments", function () {
return Q.spread([1, 2, 3], function (a, b) {
expect(b).toBe(2);
});
});
it("spreads promises for arrays across arguments", function () {
return Q([Q(10)])
.spread(function (value) {
expect(value).toEqual(10);
});
});
it("spreads arrays of promises across arguments", function () {
var deferredA = Q.defer();
var deferredB = Q.defer();
var promise = Q.spread([deferredA.promise, deferredB.promise],
function (a, b) {
expect(a).toEqual(10);
expect(b).toEqual(20);
});
Q.delay(5).then(function () {
deferredA.resolve(10);
});
Q.delay(10).then(function () {
deferredB.resolve(20);
});
return promise;
});
it("calls the errback when given a rejected promise", function () {
var err = new Error();
return Q.spread([Q(10), Q.reject(err)],
function () {
expect(true).toBe(false);
},
function (actual) {
expect(actual).toBe(err);
}
);
});
});
describe("fin", function () {
var exception1 = new Error("boo!");
var exception2 = new TypeError("evil!");
describe("when the promise is fulfilled", function () {
it("should call the callback", function () {
var called = false;
return Q("foo")
.fin(function () {
called = true;
})
.then(function () {
expect(called).toBe(true);
});
});
it("should fulfill with the original value", function () {
return Q("foo")
.fin(function () {
return "bar";
})
.then(function (result) {
expect(result).toBe("foo");
});
});
describe("when the callback returns a promise", function () {
describe("that is fulfilled", function () {
it("should fulfill with the original reason after that promise resolves", function () {
var promise = Q.delay(250);
return Q("foo")
.fin(function () {
return promise;
})
.then(function (result) {
expect(Q.isPending(promise)).toBe(false);
expect(result).toBe("foo");
});
});
});
describe("that is rejected", function () {
it("should reject with this new rejection reason", function () {
return Q("foo")
.fin(function () {
return Q.reject(exception1);
})
.then(function () {
expect(false).toBe(true);
},
function (exception) {
expect(exception).toBe(exception1);
});
});
});
});
describe("when the callback throws an exception", function () {
it("should reject with this new exception", function () {
return Q("foo")
.fin(function () {
throw exception1;
})
.then(function () {
expect(false).toBe(true);
},
function (exception) {
expect(exception).toBe(exception1);
});
});
});
});
describe("when the promise is rejected", function () {
it("should call the callback", function () {
var called = false;
return Q.reject(exception1)
.fin(function () {
called = true;
})
.then(function () {
expect(called).toBe(true);
}, function () {
expect(called).toBe(true);
});
});
it("should reject with the original reason", function () {
return Q.reject(exception1)
.fin(function () {
return "bar";
})
.then(function () {
expect(false).toBe(true);
},
function (exception) {
expect(exception).toBe(exception1);
});
});
describe("when the callback returns a promise", function () {
describe("that is fulfilled", function () {
it("should reject with the original reason after that promise resolves", function () {
var promise = Q.delay(250);
return Q.reject(exception1)
.fin(function () {
return promise;
})
.then(function () {
expect(false).toBe(true);
},
function (exception) {
expect(exception).toBe(exception1);
expect(Q.isPending(promise)).toBe(false);
});
});
});
describe("that is rejected", function () {
it("should reject with the new reason", function () {
return Q.reject(exception1)
.fin(function () {
return Q.reject(exception2);
})
.then(function () {
expect(false).toBe(true);
},
function (exception) {
expect(exception).toBe(exception2);
});
});
});
});
describe("when the callback throws an exception", function () {
it("should reject with this new exception", function () {
return Q.reject(exception1)
.fin(function () {
throw exception2;
})
.then(function () {
expect(false).toBe(true);
},
function (exception) {
expect(exception).toBe(exception2);
});
});
});
});
});
// Almost like "fin"
describe("tap", function () {
var exception1 = new Error("boo!");
describe("when the promise is fulfilled", function () {
it("should call the callback", function () {
var called = false;
return Q("foo")
.tap(function () {
called = true;
})
.then(function () {
expect(called).toBe(true);
});
});
it("should fulfill with the original value", function () {
return Q("foo")
.tap(function () {
return "bar";
})
.then(function (result) {
expect(result).toBe("foo");
});
});
describe("when the callback returns a promise", function () {
describe("that is fulfilled", function () {
it("should fulfill with the original reason after that promise resolves", function () {
var promise = Q.delay(250);
return Q("foo")
.tap(function () {
return promise;
})
.then(function (result) {
expect(Q.isPending(promise)).toBe(false);
expect(result).toBe("foo");
});
});
});
describe("that is rejected", function () {
it("should reject with this new rejection reason", function () {
return Q("foo")
.tap(function () {
return Q.reject(exception1);
})
.then(function () {
expect(false).toBe(true);
},
function (exception) {
expect(exception).toBe(exception1);
});
});
});
});
describe("when the callback throws an exception", function () {
it("should reject with this new exception", function () {
return Q("foo")
.tap(function () {
throw exception1;
})
.then(function () {
expect(false).toBe(true);
},
function (exception) {
expect(exception).toBe(exception1);
});
});
});
});
describe("when the promise is rejected", function () {
it("should not call the callback", function () {
var called = false;
return Q.reject(exception1)
.tap(function () {
called = true;
})
.then(function () {
expect(called).toBe(false);
}, function () {
expect(called).toBe(false);
});
});
});
});
describe("done", function () {
describe("when the promise is fulfilled", function () {
describe("and the callback does not throw", function () {
it("should call the callback and return nothing", function () {
var called = false;
var promise = Q();
var returnValue = promise.done(function () {
called = true;
});
return promise.fail(function () { }).fin(function () {
expect(called).toBe(true);
expect(returnValue).toBe(undefined);
});
});
});
describe("and the callback throws", function () {
it("should rethrow that error in the next turn and return nothing", function () {
var turn = 0;
Q.nextTick(function () {
++turn;
});
var returnValue = Q().done(
function () {
throw "foo";
}
);
var deferred = Q.defer();
Q.onerror = function (error) {
expect(turn).toBe(1);
expect(error).toBe("foo");
expect(returnValue).toBe(undefined);
deferred.resolve();
};
Q.delay(100).then(deferred.reject);
return deferred.promise;
});
});
});
describe("when the promise is rejected", function () {
describe("and the errback handles it", function () {
it("should call the errback and return nothing", function () {
var called = false;
var promise = Q.reject(new Error());
var returnValue = promise.done(
function () { },
function () {
called = true;
}
);
return promise.fail(function () { }).fin(function () {
expect(called).toBe(true);
expect(returnValue).toBe(undefined);
});
});
});
describe("and the errback throws", function () {
it("should rethrow that error in the next turn and return nothing", function () {
var turn = 0;
Q.nextTick(function () {
++turn;
});
var returnValue = Q.reject("bar").done(
null,
function () {
throw "foo";
}
);
var deferred = Q.defer();
Q.onerror = function (error) {
expect(turn).toBe(1);
expect(error).toBe("foo");
expect(returnValue).toBe(undefined);
deferred.resolve();
};
Q.delay(100).then(deferred.reject);
return deferred.promise;
});
});
describe("and there is no errback", function () {
it("should throw the original error in the next turn", function () {
var turn = 0;
Q.nextTick(function () {
++turn;
});
var returnValue = Q.reject("bar").done();
var deferred = Q.defer();
Q.onerror = function (error) {
expect(turn).toBe(1);
expect(error).toBe("bar");
expect(returnValue).toBe(undefined);
deferred.resolve();
};
Q.delay(10).then(deferred.reject);
return deferred.promise;
});
});
});
it("should attach a progress listener", function () {
var deferred = Q.defer();
var spy = jasmine.createSpy();
deferred.promise.done(null, null, spy);
deferred.notify(10);
deferred.resolve();
return deferred.promise.then(function () {
expect(spy).toHaveBeenCalledWith(10);
});
});
});
describe("timeout", function () {
it("should do nothing if the promise fulfills quickly", function () {
return Q.delay(10).timeout(200);
});
it("should do nothing if the promise rejects quickly", function () {
var goodError = new Error("haha!");
return Q.delay(10)
.then(function () {
throw goodError;
})
.timeout(200)
.then(undefined, function (error) {
expect(error).toBe(goodError);
});
});
it("should reject with a timeout error if the promise is too slow", function () {
return Q.delay(100)
.timeout(10)
.then(
function () {
expect(true).toBe(false);
},
function (error) {
expect(/time/i.test(error.message)).toBe(true);
}
);
});
it("should pass through progress notifications", function () {
var deferred = Q.defer();
var progressValsSeen = [];
var promise = Q.timeout(deferred.promise, 300).then(function () {
expect(progressValsSeen).toEqual([1, 2, 3]);
}, undefined, function (progressVal) {
progressValsSeen.push(progressVal);
});
Q.delay(5).then(function () { deferred.notify(1); });
Q.delay(15).then(function () { deferred.notify(2); });
Q.delay(25).then(function () { deferred.notify(3); });
Q.delay(35).then(function () { deferred.resolve(); });
return promise;
});
it("should reject with a custom timeout error if the promise is too slow and msg was provided", function () {
return Q.delay(100)
.timeout(10, "custom")
.then(
function () {
expect(true).toBe(false);
},
function (error) {
expect(/custom/i.test(error.message)).toBe(true);
expect(error.code).toBe("ETIMEDOUT");
}
);
});
it("should reject with a custom timeout error if the promise is too slow and Error object was provided", function () {
var customError = new Error("custom");
customError.isCustom = true;
return Q.delay(100)
.timeout(10, customError)
.then(
function () {
expect(true).toBe(false);
},
function (error) {
expect(/custom/i.test(error.message)).toBe(true);
expect(error.isCustom).toBe(true);
}
);
});
});
describe("delay", function () {
it("should delay fulfillment", function () {
var promise = Q(5).delay(50);
setTimeout(function () {
expect(promise.isPending()).toBe(true);
}, 40);
return promise;
});
it("should not delay rejection", function () {
var promise = Q.reject(5).delay(50);
return Q.delay(20).then(function () {
expect(promise.isPending()).toBe(false);
});
});
it("should treat a single argument as a time", function () {
var promise = Q.delay(50);
setTimeout(function () {
expect(promise.isPending()).toBe(true);
}, 40);
return promise;
});
it("should treat two arguments as a value + a time", function () {
var promise = Q.delay("what", 50);
setTimeout(function () {
expect(promise.isPending()).toBe(true);
}, 40);
return promise.then(function (value) {
expect(value).toBe("what");
});
});
it("should delay after resolution", function () {
var promise1 = Q.delay("what", 30);
var promise2 = promise1.delay(30);
setTimeout(function () {
expect(promise1.isPending()).toBe(false);
expect(promise2.isPending()).toBe(true);
}, 40);
return promise2.then(function (value) {
expect(value).toBe("what");
});
});
it("should pass through progress notifications from passed promises", function () {
var deferred = Q.defer();
var progressValsSeen = [];
var promise = Q.delay(deferred.promise, 100).then(function () {
expect(progressValsSeen).toEqual([1, 2, 3]);
}, undefined, function (progressVal) {
progressValsSeen.push(progressVal);
});
Q.delay(5).then(function () { deferred.notify(1); });
Q.delay(15).then(function () { deferred.notify(2); });
Q.delay(25).then(function () { deferred.notify(3); });
Q.delay(35).then(function () { deferred.resolve(); });
return promise;
});
});
describe("thenResolve", function () {
describe("Resolving with a non-thenable value", function () {
it("returns a promise for that object once the promise is resolved", function () {
var waited = false;
return Q.delay(20)
.then(function () {
waited = true;
})
.thenResolve("foo")
.then(function (val) {
expect(waited).toBe(true);
expect(val).toBe("foo");
});
});
describe("based off a rejected promise", function () {
it("does nothing, letting the rejection flow through", function () {
return Q.reject("boo")
.thenResolve("foo")
.then(
function () {
expect(true).toBe(false);
},
function (reason) {
expect(reason).toBe("boo");
}
);
});
});
});
describe("Resolving with an promise", function () {
it("returns a promise for the result of that promise once the promise is resolved", function () {
var waited = false;
return Q.delay(20)
.then(function () {
waited = true;
})
.thenResolve(Q("foo"))
.then(function (val) {
expect(waited).toBe(true);
expect(val).toBe("foo");
});
});
});
});
describe("thenReject", function () {
describe("Rejecting with a reason", function () {
it("returns a promise rejected with that object once the original promise is resolved", function () {
var waited = false;
return Q.delay(20)
.then(function () {
waited = true;
})
.thenReject("foo")
.then(
function () {
expect(true).toBe(false);
},
function (reason) {
expect(waited).toBe(true);
expect(reason).toBe("foo");
}
);
});
describe("based off a rejected promise", function () {
it("does nothing, letting the rejection flow through", function () {
return Q.reject("boo")
.thenResolve("foo")
.then(
function () {
expect(true).toBe(false);
},
function (reason) {
expect(reason).toBe("boo");
}
);
});
});
});
});
describe("thenables", function () {
it("assimilates a thenable with fulfillment with resolve", function () {
return Q({
then: function (resolved) {
resolved(10);
}
})
.then(function (ten) {
expect(ten).toEqual(10);
})
.then(function (undefined) {
expect(undefined).toEqual(void 0);
});
});
it("assimilates a thenable with progress and fulfillment (using resolve)", function () {
var progressValueArrays = [];
return Q({
then: function (fulfilled, rejected, progressed) {
Q.nextTick(function () {
progressed(1, 2);
progressed(3, 4, 5);
fulfilled();
});
}
})
.progress(function () {
progressValueArrays.push(Array.prototype.slice.call(arguments));
})
.then(function () {
expect(progressValueArrays).toEqual([[1], [3]]);
});
});
it("assimilates a thenable with progress and fulfillment (using when)", function () {
var progressValueArrays = [];
return Q.when({
then: function (fulfilled, rejected, progressed) {
Q.nextTick(function () {
progressed(1, 2);
progressed(3, 4, 5);
fulfilled();
});
}
})
.progress(function () {
progressValueArrays.push(Array.prototype.slice.call(arguments));
})
.then(function () {
expect(progressValueArrays).toEqual([[1], [3]]);
});
});
it("flows fulfillment into a promise pipeline", function () {
return Q({
then: function (resolved) {
resolved([10]);
}
})
.get(0)
.then(function (ten) {
expect(ten).toEqual(10);
});
});
it("assimilates an immediately-fulfilled thenable in allSettled", function () {
return Q.allSettled([
{then: function (win) {
win(10);
}}
])
.then(function (snapshots) {
expect(snapshots).toEqual([{ state: "fulfilled", value: 10 }]);
});
});
it("assimilates an eventually-fulfilled thenable in allSettled", function () {
return Q.allSettled([
{then: function (win) {
setTimeout(function () {
win(10);
}, 100);
}}
])
.then(function (snapshots) {
expect(snapshots).toEqual([{ state: "fulfilled", value: 10 }]);
});
});
});
describe("node support", function () {
var exception = new Error("That is not your favorite color.");
var obj = {
method: function (a, b, c, callback) {
callback(null, a + b + c);
},
thispChecker: function (callback) {
callback(null, this === obj);
},
errorCallbacker: function (a, b, c, callback) {
callback(exception);
},
errorThrower: function () {
throw exception;
}
};
describe("nfapply", function () {
it("fulfills with callback result", function () {
return Q.nfapply(function (a, b, c, callback) {
callback(null, a + b + c);
}, [1, 2, 3])
.then(function (sum) {
expect(sum).toEqual(6);
});
});
it("rejects with callback error", function () {
var exception = new Error("That is not your favorite color.");
return Q.nfapply(function (a, b, c, callback) {
callback(exception);
}, [1, 2, 3])
.then(function () {
expect(true).toBe(false);
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
});
describe("nfcall", function () {
it("fulfills with callback result", function () {
return Q.nfcall(function (a, b, c, callback) {
callback(null, a + b + c);
}, 1, 2, 3)
.then(function (sum) {
expect(sum).toEqual(6);
});
});
it("rejects with callback error", function () {
var exception = new Error("That is not your favorite color.");
return Q.nfcall(function (a, b, c, callback) {
callback(exception);
}, 1, 2, 3)
.then(function () {
expect(true).toBe(false);
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
});
describe("nfbind", function () {
it("mixes partial application with complete application", function () {
return Q.nfbind(function (a, b, c, d, callback) {
callback(null, a + b + c + d);
}, 1, 2).call({}, 3, 4)
.then(function (ten) {
expect(ten).toBe(10);
});
});
});
describe("nbind", function () {
it("binds this, and mixes partial application with complete application", function () {
return Q.nbind(function (a, b, c, callback) {
callback(null, this + a + b + c);
}, 1, 2).call(3 /* effectively ignored as fn bound to 1 */, 4, 5)
.then(function (twelve) {
expect(twelve).toBe(12);
});
});
it("second arg binds this", function() {
var expectedThis = { test: null };
return Q.nbind(function(callback) {
callback(null, this);
}, expectedThis).call()
.then(function(actualThis) {
expect(actualThis).toEqual(expectedThis);
});
});
});
describe("npost", function () {
it("fulfills with callback result", function () {
return Q.npost(obj, "method", [1, 2, 3])
.then(function (sum) {
expect(sum).toEqual(6);
});
});
it("gets the correct thisp", function () {
return Q.npost(obj, "thispChecker", [])
.then(function (result) {
expect(result).toBe(true);
});
});
it("rejects with callback error", function () {
return Q.npost(obj, "errorCallbacker", [1, 2, 3])
.then(function () {
expect("blue").toBe("no, yellow!");
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
it("rejects with thrown error", function () {
return Q.npost(obj, "errorThrower", [1, 2, 3])
.then(function () {
expect(true).toBe(false);
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
it("works on promises for objects with Node methods", function () {
return Q(obj)
.npost("method", [1, 2, 3])
.then(function (sum) {
expect(sum).toEqual(6);
});
});
});
describe("nsend", function () {
it("fulfills with callback result", function () {
return Q.nsend(obj, "method", 1, 2, 3)
.then(function (sum) {
expect(sum).toEqual(6);
});
});
it("gets the correct thisp", function () {
return Q.nsend(obj, "thispChecker")
.then(function (result) {
expect(result).toBe(true);
});
});
it("rejects with callback error", function () {
return Q.nsend(obj, "errorCallbacker", 1, 2, 3)
.then(function () {
expect("blue").toBe("no, yellow!");
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
it("rejects with thrown error", function () {
return Q.nsend(obj, "errorThrower", 1, 2, 3)
.then(function () {
expect(true).toBe(false);
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
it("works on promises for objects with Node methods", function () {
return Q(obj)
.nsend("method", 1, 2, 3)
.then(function (sum) {
expect(sum).toEqual(6);
});
});
});
describe("deferred.makeNodeResolver", function () {
it("fulfills a promise with a single callback argument", function () {
var deferred = Q.defer();
var callback = deferred.makeNodeResolver();
callback(null, 10);
return deferred.promise.then(function (value) {
expect(value).toBe(10);
});
});
it("fulfills a promise with multiple callback arguments", function () {
var deferred = Q.defer();
var callback = deferred.makeNodeResolver();
callback(null, 10, 20);
return deferred.promise.then(function (value) {
expect(value).toEqual([10, 20]);
});
});
it("rejects a promise", function () {
var deferred = Q.defer();
var callback = deferred.makeNodeResolver();
var exception = new Error("Holy Exception of Anitoch");
callback(exception);
return deferred.promise.then(function () {
expect(5).toBe(3);
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
});
describe("nodeify", function () {
it("calls back with a resolution", function () {
var spy = jasmine.createSpy();
Q(10).nodeify(spy);
waitsFor(function () {
return spy.argsForCall.length;
});
runs(function () {
expect(spy.argsForCall).toEqual([[null, 10]]);
});
});
it("calls back with an error", function () {
var spy = jasmine.createSpy();
Q.reject(10).nodeify(spy);
waitsFor(function () {
return spy.argsForCall.length;
});
runs(function () {
expect(spy.argsForCall).toEqual([[10]]);
});
});
it("forwards a promise", function () {
return Q(10).nodeify().then(function (ten) {
expect(ten).toBe(10);
});
});
});
});
describe("isPromise", function () {
it("returns true if passed a promise", function () {
expect(Q.isPromise(Q(10))).toBe(true);
});
it("returns false if not passed a promise", function () {
expect(Q.isPromise(undefined)).toBe(false);
expect(Q.isPromise(null)).toBe(false);
expect(Q.isPromise(10)).toBe(false);
expect(Q.isPromise("str")).toBe(false);
expect(Q.isPromise("")).toBe(false);
expect(Q.isPromise(true)).toBe(false);
expect(Q.isPromise(false)).toBe(false);
expect(Q.isPromise({})).toBe(false);
expect(Q.isPromise({
then: function () {}
})).toBe(false);
expect(Q.isPromise(function () {})).toBe(false);
});
});
describe("isPromiseAlike", function () {
it("returns true if passed a promise like object", function () {
expect(Q.isPromiseAlike(Q(10))).toBe(true);
expect(Q.isPromiseAlike({
then: function () {}
})).toBe(true);
});
it("returns false if not passed a promise like object", function () {
expect(Q.isPromiseAlike(undefined)).toBe(false);
expect(Q.isPromiseAlike(null)).toBe(false);
expect(Q.isPromiseAlike(10)).toBe(false);
expect(Q.isPromiseAlike("str")).toBe(false);
expect(Q.isPromiseAlike("")).toBe(false);
expect(Q.isPromiseAlike(true)).toBe(false);
expect(Q.isPromiseAlike(false)).toBe(false);
expect(Q.isPromiseAlike({})).toBe(false);
expect(Q.isPromiseAlike(function () {})).toBe(false);
});
});
if (typeof require === "function") {
var domain;
try {
domain = require("domain");
} catch (e) { }
if (domain) {
var EventEmitter = require("events").EventEmitter;
describe("node domain support", function () {
var d;
beforeEach(function () {
d = domain.create();
});
afterEach(function() {
d.dispose();
});
it("should work for non-promise async inside a promise handler",
function (done) {
var error = new Error("should be caught by the domain");
d.run(function () {
Q().then(function () {
setTimeout(function () {
throw error;
}, 10);
});
});
var errorTimeout = setTimeout(function () {
done(new Error("Wasn't caught"));
}, 100);
d.on("error", function (theError) {
expect(theError).toBe(error);
clearTimeout(errorTimeout);
done();
});
});
it("should transfer errors from `done` into the domain",
function (done) {
var error = new Error("should be caught by the domain");
d.run(function () {
Q.reject(error).done();
});
var errorTimeout = setTimeout(function () {
done(new Error("Wasn't caught"));
}, 100);
d.on("error", function (theError) {
expect(theError).toBe(error);
clearTimeout(errorTimeout);
done();
});
});
it("should take care of re-used event emitters", function (done) {
// See discussion in https://github.com/kriskowal/q/issues/120
var error = new Error("should be caught by the domain");
var e = new EventEmitter();
d.run(function () {
callAsync().done();
});
setTimeout(function () {
e.emit("beep");
}, 100);
var errorTimeout = setTimeout(function () {
done(new Error("Wasn't caught"));
}, 500);
d.on("error", function (theError) {
expect(theError).toBe(error);
clearTimeout(errorTimeout);
done();
});
function callAsync() {
var def = Q.defer();
e.once("beep", function () {
def.reject(error);
});
return def.promise;
}
});
});
}
}
describe("decorator functions", function () {
describe("promised", function () {
var exception = new Error("That is not the meaning of life.");
it("resolves promised arguments", function () {
var sum = Q.promised(function add(a, b) {
return a + b;
});
return sum(Q(4), Q(5)).then(function (sum) {
expect(sum).toEqual(9);
});
});
it("resolves promised `this`", function () {
var inc = Q.promised(function inc(a) {
return this + a;
});
return inc.call(Q(4), Q(5)).then(function (sum) {
expect(sum).toEqual(9);
});
});
it("is rejected if an argument is rejected", function () {
var sum = Q.promised(function add(a, b) {
return a + b;
});
return sum(Q.reject(exception), Q(4)).then(function () {
expect(4).toEqual(42);
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
it("is rejected if `this` is rejected", function () {
var inc = Q.promised(function inc(a) {
return this + a;
});
return inc.call(Q.reject(exception), Q(4)).then(function () {
expect(4).toEqual(42);
}, function (_exception) {
expect(_exception).toBe(exception);
});
});
});
});
describe("stack trace formatting", function () {
it("doesn't mangle a stack trace that gets handled twice", function () {
var d1 = Q.defer();
var d2 = Q.defer();
var captured = [];
d1.promise.done();
d2.promise.done();
Q.onerror = function (err) {
captured.push(err.stack);
};
var error = new Error("boom!");
d1.reject(error);
d2.reject(error);
return Q.all([d1.promise.fail(function () {}), d2.promise.fail(function () { })])
.then(function () {
expect(captured[0]).toEqual(captured[1]);
});
});
});
describe("possible regressions", function () {
describe("gh-9", function () {
it("treats falsy values as resolved values without error", function () {
expect(Q.isPending(null)).toEqual(false);
expect(Q.isPending(void 0)).toEqual(false);
expect(Q.isPending(false)).toEqual(false);
expect(Q.isPending()).toEqual(false);
});
});
describe("gh-22", function () {
it("ensures that the array prototype is intact", function () {
var keys = [];
for (var key in []) {
keys.push(key);
}
expect(keys.length).toBe(0);
});
});
describe("gh-73", function () {
it("does not choke on non-error rejection reasons", function () {
Q.reject(REASON).done();
var deferred = Q.defer();
Q.onerror = function (error) {
expect(error).toBe(REASON);
deferred.resolve();
};
Q.delay(10).then(deferred.reject);
return deferred.promise;
});
});
describe("gh-90", function () {
it("does not choke on rejection reasons with an undefined `stack`", function () {
var error = new RangeError(REASON);
error.stack = undefined;
Q.reject(error).done();
var deferred = Q.defer();
Q.onerror = function (theError) {
expect(theError).toBe(error);
deferred.resolve();
};
Q.delay(10).then(deferred.reject);
return deferred.promise;
});
});
describe("gh-75", function () {
it("does not double-resolve misbehaved promises", function () {
var badPromise = Q.makePromise({
post: function () { return "hello"; }
});
var resolutions = 0;
function onResolution() {
++resolutions;
}
return Q.when(badPromise, onResolution, onResolution).then(function () {
expect(resolutions).toBe(1);
});
});
});
});
describe("unhandled rejection reporting", function () {
beforeEach(function () {
Q.resetUnhandledRejections();
});
it("doesn't report a resolve, then reject (gh-252)", function () {
var deferred = Q.defer();
deferred.resolve();
deferred.reject();
expect(Q.getUnhandledReasons().length).toEqual(0);
});
it("doesn't report when you chain off a rejection", function () {
return Q.reject("this will be handled").get("property").fail(function () {
// now it should be handled.
}).fin(function() {
expect(Q.getUnhandledReasons().length).toEqual(0);
});
});
it("reports the most basic case", function () {
Q.reject("a reason");
expect(Q.getUnhandledReasons()).toEqual(["(no stack) a reason"]);
});
it("reports a stack trace", function () {
var error = new Error("a reason");
Q.reject(error);
expect(Q.getUnhandledReasons()).toEqual([error.stack]);
});
it("doesn't let you mutate the internal array", function () {
Q.reject("a reason");
Q.getUnhandledReasons().length = 0;
expect(Q.getUnhandledReasons()).toEqual(["(no stack) a reason"]);
});
it("resets after calling `Q.resetUnhandledRejections`", function () {
Q.reject("a reason");
Q.resetUnhandledRejections();
expect(Q.getUnhandledReasons()).toEqual([]);
});
it("stops tracking after calling `Q.stopUnhandledRejectionTracking`", function () {
Q.reject("a reason");
Q.stopUnhandledRejectionTracking();
Q.reject("another reason");
expect(Q.getUnhandledReasons()).toEqual([]);
});
});