204 lines
6.0 KiB
JavaScript
204 lines
6.0 KiB
JavaScript
|
/* global it, describe, expect, assert, Promise */
|
||
|
|
||
|
var failIfThrows = function (done) {
|
||
|
'use strict';
|
||
|
|
||
|
return function (e) {
|
||
|
done(e || new Error());
|
||
|
};
|
||
|
};
|
||
|
|
||
|
describe('Promise.all', function () {
|
||
|
'use strict';
|
||
|
|
||
|
it('should not be enumerable', function () {
|
||
|
expect(Promise).ownPropertyDescriptor('all').to.have.property('enumerable', false);
|
||
|
});
|
||
|
|
||
|
it('fulfills if passed an empty array', function (done) {
|
||
|
var iterable = [];
|
||
|
|
||
|
Promise.all(iterable).then(function (value) {
|
||
|
assert(Array.isArray(value));
|
||
|
assert.deepEqual(value, []);
|
||
|
}).then(done, failIfThrows(done));
|
||
|
});
|
||
|
|
||
|
it('fulfills if passed an empty array-like', function (done) {
|
||
|
var f = function () {
|
||
|
Promise.all(arguments).then(function (value) {
|
||
|
assert(Array.isArray(value));
|
||
|
assert.deepEqual(value, []);
|
||
|
}).then(done, failIfThrows(done));
|
||
|
};
|
||
|
f();
|
||
|
});
|
||
|
|
||
|
it('fulfills if passed an array of mixed fulfilled promises and values', function (done) {
|
||
|
var iterable = [0, Promise.resolve(1), 2, Promise.resolve(3)];
|
||
|
|
||
|
Promise.all(iterable).then(function (value) {
|
||
|
assert(Array.isArray(value));
|
||
|
assert.deepEqual(value, [0, 1, 2, 3]);
|
||
|
}).then(done, failIfThrows(done));
|
||
|
});
|
||
|
|
||
|
it('rejects if any passed promise is rejected', function (done) {
|
||
|
var foreverPending = new Promise(function () { });
|
||
|
var error = new Error('Rejected');
|
||
|
var rejected = Promise.reject(error);
|
||
|
|
||
|
var iterable = [foreverPending, rejected];
|
||
|
|
||
|
Promise.all(iterable).then(
|
||
|
function () {
|
||
|
assert(false, 'should never get here');
|
||
|
},
|
||
|
function (reason) {
|
||
|
assert.strictEqual(reason, error);
|
||
|
}
|
||
|
).then(done, failIfThrows(done));
|
||
|
});
|
||
|
|
||
|
it('resolves foreign thenables', function (done) {
|
||
|
var normal = Promise.resolve(1);
|
||
|
var foreign = { then: function (f) { f(2); } };
|
||
|
|
||
|
var iterable = [normal, foreign];
|
||
|
|
||
|
Promise.all(iterable).then(function (value) {
|
||
|
assert.deepEqual(value, [1, 2]);
|
||
|
}).then(done, failIfThrows(done));
|
||
|
});
|
||
|
|
||
|
it('fulfills when passed an sparse array, giving `undefined` for the omitted values', function (done) {
|
||
|
/* jshint elision: true */
|
||
|
/* jscs:disable disallowSpaceBeforeComma */
|
||
|
/* jscs:disable requireSpaceAfterComma */
|
||
|
/* eslint-disable no-sparse-arrays */
|
||
|
var iterable = [Promise.resolve(0), , , Promise.resolve(1)];
|
||
|
/* eslint-enable no-sparse-arrays */
|
||
|
/* jscs:enable requireSpaceAfterComma */
|
||
|
/* jscs:enable disallowSpaceBeforeComma */
|
||
|
/* jshint elision: false */
|
||
|
|
||
|
Promise.all(iterable).then(function (value) {
|
||
|
assert.deepEqual(value, [0, undefined, undefined, 1]);
|
||
|
}).then(done, failIfThrows(done));
|
||
|
});
|
||
|
|
||
|
it('does not modify the input array', function (done) {
|
||
|
var input = [0, 1];
|
||
|
var iterable = input;
|
||
|
|
||
|
Promise.all(iterable).then(function (value) {
|
||
|
assert.notStrictEqual(input, value);
|
||
|
}).then(done, failIfThrows(done));
|
||
|
});
|
||
|
|
||
|
it('should reject with a TypeError if given a non-iterable', function (done) {
|
||
|
var notIterable = {};
|
||
|
|
||
|
Promise.all(notIterable).then(
|
||
|
function () {
|
||
|
assert(false, 'should never get here');
|
||
|
},
|
||
|
function (reason) {
|
||
|
assert(reason instanceof TypeError);
|
||
|
}
|
||
|
).then(done, failIfThrows(done));
|
||
|
});
|
||
|
|
||
|
// test cases from
|
||
|
// https://github.com/domenic/promises-unwrapping/issues/89#issuecomment-33110203
|
||
|
var tamper = function (p) {
|
||
|
p.then = function (fulfill, reject) {
|
||
|
fulfill('tampered');
|
||
|
return Promise.prototype.then.call(this, fulfill, reject);
|
||
|
};
|
||
|
return p;
|
||
|
};
|
||
|
|
||
|
it('should be robust against tampering (1)', function (done) {
|
||
|
var g = [tamper(Promise.resolve(0))];
|
||
|
// Prevent countdownHolder.[[Countdown]] from ever reaching zero
|
||
|
Promise.all(g).then(
|
||
|
function () { done(); },
|
||
|
failIfThrows(done)
|
||
|
);
|
||
|
});
|
||
|
|
||
|
it('should be robust against tampering (2)', function (done) {
|
||
|
// Promise from Promise.all resolved before arguments
|
||
|
var fulfillCalled = false;
|
||
|
|
||
|
var g = [
|
||
|
Promise.resolve(0),
|
||
|
tamper(Promise.resolve(1)),
|
||
|
Promise.resolve(2).then(function () {
|
||
|
assert(!fulfillCalled, 'should be resolved before all()');
|
||
|
}).then(function () {
|
||
|
assert(!fulfillCalled, 'should be resolved before all()');
|
||
|
})['catch'](failIfThrows(done))
|
||
|
];
|
||
|
Promise.all(g).then(function () {
|
||
|
assert(!fulfillCalled, 'should be resolved last');
|
||
|
fulfillCalled = true;
|
||
|
}).then(done, failIfThrows(done));
|
||
|
});
|
||
|
|
||
|
it('should be robust against tampering (3)', function (done) {
|
||
|
var g = [
|
||
|
Promise.resolve(0),
|
||
|
tamper(Promise.resolve(1)),
|
||
|
Promise.reject(2)
|
||
|
];
|
||
|
// Promise from Promise.all resolved despite rejected promise in arguments
|
||
|
Promise.all(g).then(function () {
|
||
|
throw new Error('should not reach here!');
|
||
|
}, function (e) {
|
||
|
assert.strictEqual(e, 2);
|
||
|
}).then(done, failIfThrows(done));
|
||
|
});
|
||
|
|
||
|
it('should be robust against tampering (4)', function (done) {
|
||
|
var hijack = true;
|
||
|
var actualArguments = [];
|
||
|
var P = function (resolver) {
|
||
|
var self;
|
||
|
if (hijack) {
|
||
|
hijack = false;
|
||
|
self = new Promise(function (resolve, reject) {
|
||
|
return resolver(function (values) {
|
||
|
// record arguments & # of times resolve function is called
|
||
|
actualArguments.push(values.slice());
|
||
|
return resolve(values);
|
||
|
}, reject);
|
||
|
});
|
||
|
} else {
|
||
|
self = new Promise(resolver);
|
||
|
}
|
||
|
Object.setPrototypeOf(self, P.prototype);
|
||
|
return self;
|
||
|
};
|
||
|
if (!Object.setPrototypeOf) { return done(); } // skip test if on IE < 11
|
||
|
Object.setPrototypeOf(P, Promise);
|
||
|
P.prototype = Object.create(Promise.prototype, {
|
||
|
constructor: { value: P }
|
||
|
});
|
||
|
P.resolve = function (p) { return p; };
|
||
|
|
||
|
var g = [
|
||
|
Promise.resolve(0),
|
||
|
tamper(Promise.resolve(1)),
|
||
|
Promise.resolve(2)
|
||
|
];
|
||
|
|
||
|
// Promise.all calls resolver twice
|
||
|
P.all(g)['catch'](failIfThrows(done));
|
||
|
Promise.resolve().then(function () {
|
||
|
assert.deepEqual(actualArguments, [[0, 'tampered', 2]]);
|
||
|
}).then(done, failIfThrows(done));
|
||
|
});
|
||
|
});
|