Do not rely on native setTimeout
in the promise service tests. (#19891)
This commit is contained in:
parent
9dc9644e95
commit
8c2a8d25c1
|
@ -21,155 +21,234 @@ import expect from 'expect.js';
|
||||||
import ngMock from 'ng_mock';
|
import ngMock from 'ng_mock';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
|
||||||
describe('Promise service', function () {
|
describe('Promise service', () => {
|
||||||
let Promise;
|
let Promise;
|
||||||
let $rootScope;
|
let $rootScope;
|
||||||
|
|
||||||
|
const sandbox = sinon.createSandbox();
|
||||||
|
function tick(ms = 0) {
|
||||||
|
sandbox.clock.tick(ms);
|
||||||
|
|
||||||
|
// Ugly, but necessary for promises to resolve: https://github.com/angular/angular.js/issues/12555
|
||||||
|
$rootScope.$apply();
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(ngMock.module('kibana'));
|
beforeEach(ngMock.module('kibana'));
|
||||||
beforeEach(ngMock.inject(function ($injector) {
|
beforeEach(ngMock.inject(($injector) => {
|
||||||
|
sandbox.useFakeTimers();
|
||||||
|
|
||||||
Promise = $injector.get('Promise');
|
Promise = $injector.get('Promise');
|
||||||
$rootScope = $injector.get('$rootScope');
|
$rootScope = $injector.get('$rootScope');
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('Constructor', function () {
|
afterEach(() => sandbox.restore());
|
||||||
it('provides resolve and reject function', function () {
|
|
||||||
new Promise(function (resolve, reject) {
|
describe('Constructor', () => {
|
||||||
expect(resolve).to.be.a('function');
|
it('provides resolve and reject function', () => {
|
||||||
expect(reject).to.be.a('function');
|
const executor = sinon.stub();
|
||||||
expect(arguments).to.have.length(2);
|
new Promise(executor);
|
||||||
});
|
|
||||||
|
sinon.assert.calledOnce(executor);
|
||||||
|
sinon.assert.calledWithExactly(executor, sinon.match.func, sinon.match.func);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Promise.resolve', function (done) {
|
it('Promise.resolve', () => {
|
||||||
Promise.resolve(true).then(() => { done(); });
|
const onResolve = sinon.stub();
|
||||||
// Ugly, but necessary for promises to resolve: https://github.com/angular/angular.js/issues/12555
|
Promise.resolve(true).then(onResolve);
|
||||||
$rootScope.$apply();
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(onResolve);
|
||||||
|
sinon.assert.calledWithExactly(onResolve, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Promise.fromNode', function () {
|
describe('Promise.fromNode', () => {
|
||||||
it('creates a callback that controls a promise', function () {
|
it('creates a callback that controls a promise', () => {
|
||||||
let callback;
|
const callback = sinon.stub();
|
||||||
Promise.fromNode(cb => (callback = cb)());
|
Promise.fromNode(callback);
|
||||||
$rootScope.$apply();
|
|
||||||
expect(callback).to.be.a('function');
|
tick();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(callback);
|
||||||
|
sinon.assert.calledWithExactly(callback, sinon.match.func);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('rejects if the callback receives an error', function () {
|
it('rejects if the callback receives an error', () => {
|
||||||
const errback = sinon.stub();
|
|
||||||
const err = new Error();
|
const err = new Error();
|
||||||
Promise.fromNode(cb => cb(err)).catch(errback);
|
const onReject = sinon.stub();
|
||||||
$rootScope.$apply();
|
Promise.fromNode(sinon.stub().yields(err)).catch(onReject);
|
||||||
|
|
||||||
expect(errback.callCount).to.be(1);
|
tick();
|
||||||
expect(errback.getCall(0).args[0]).to.be(err);
|
|
||||||
|
sinon.assert.calledOnce(onReject);
|
||||||
|
sinon.assert.calledWithExactly(onReject, sinon.match.same(err));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves with the second argument', function () {
|
it('resolves with the second argument', () => {
|
||||||
const thenback = sinon.stub();
|
|
||||||
const result = {};
|
const result = {};
|
||||||
Promise.fromNode(cb => cb(null, result)).then(thenback);
|
const onResolve = sinon.stub();
|
||||||
$rootScope.$apply();
|
Promise.fromNode(sinon.stub().yields(null, result)).then(onResolve);
|
||||||
|
|
||||||
expect(thenback.callCount).to.be(1);
|
tick();
|
||||||
expect(thenback.getCall(0).args[0]).to.be(result);
|
|
||||||
|
sinon.assert.calledOnce(onResolve);
|
||||||
|
sinon.assert.calledWithExactly(onResolve, sinon.match.same(result));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves with an array if multiple arguments are received', function () {
|
it('resolves with an array if multiple arguments are received', () => {
|
||||||
const thenback = sinon.stub();
|
|
||||||
const result1 = {};
|
const result1 = {};
|
||||||
const result2 = {};
|
const result2 = {};
|
||||||
Promise.fromNode(cb => cb(null, result1, result2)).then(thenback);
|
const onResolve = sinon.stub();
|
||||||
$rootScope.$apply();
|
Promise.fromNode(sinon.stub().yields(null, result1, result2)).then(onResolve);
|
||||||
|
|
||||||
expect(thenback.callCount).to.be(1);
|
tick();
|
||||||
expect(thenback.getCall(0).args[0][0]).to.be(result1);
|
|
||||||
expect(thenback.getCall(0).args[0][1]).to.be(result2);
|
sinon.assert.calledOnce(onResolve);
|
||||||
|
sinon.assert.calledWithExactly(
|
||||||
|
onResolve,
|
||||||
|
[sinon.match.same(result1), sinon.match.same(result2)]
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('resolves with an array if multiple undefined are received', function () {
|
it('resolves with an array if multiple undefined are received', () => {
|
||||||
const thenback = sinon.stub();
|
const onResolve = sinon.stub();
|
||||||
Promise.fromNode(cb => cb(null, undefined, undefined)).then(thenback);
|
Promise.fromNode(sinon.stub().yields(null, undefined, undefined)).then(onResolve);
|
||||||
$rootScope.$apply();
|
|
||||||
|
|
||||||
expect(thenback.callCount).to.be(1);
|
tick();
|
||||||
expect(thenback.getCall(0).args[0][0]).to.be(undefined);
|
|
||||||
expect(thenback.getCall(0).args[0][1]).to.be(undefined);
|
sinon.assert.calledOnce(onResolve);
|
||||||
|
sinon.assert.calledWithExactly(onResolve, [undefined, undefined]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Promise.race()', () => {
|
describe('Promise.race()', () => {
|
||||||
let crankTimeout;
|
it(`resolves with the first resolved promise's value`, () => {
|
||||||
beforeEach(() => {
|
|
||||||
// constantly call $rootScope.$apply() in a loop so we can
|
|
||||||
// pretend that these are real promises
|
|
||||||
(function crank$apply() {
|
|
||||||
$rootScope.$apply();
|
|
||||||
crankTimeout = setTimeout(crank$apply, 1);
|
|
||||||
}());
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
clearTimeout(crankTimeout);
|
|
||||||
});
|
|
||||||
|
|
||||||
it(`resolves with the first resolved promise's value`, async () => {
|
|
||||||
const p1 = new Promise(resolve => setTimeout(resolve, 100, 1));
|
const p1 = new Promise(resolve => setTimeout(resolve, 100, 1));
|
||||||
const p2 = new Promise(resolve => setTimeout(resolve, 200, 2));
|
const p2 = new Promise(resolve => setTimeout(resolve, 200, 2));
|
||||||
expect(await Promise.race([p1, p2])).to.be(1);
|
const onResolve = sinon.stub();
|
||||||
|
Promise.race([p1, p2]).then(onResolve);
|
||||||
|
|
||||||
|
tick(200);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(onResolve);
|
||||||
|
sinon.assert.calledWithExactly(onResolve, 1);
|
||||||
});
|
});
|
||||||
it(`rejects with the first rejected promise's rejection reason`, async () => {
|
|
||||||
const p1 = new Promise((r, reject) => setTimeout(reject, 200, new Error(1)));
|
it(`rejects with the first rejected promise's rejection reason`, () => {
|
||||||
const p2 = new Promise((r, reject) => setTimeout(reject, 100, new Error(2)));
|
const p1Error = new Error('1');
|
||||||
expect(await Promise.race([p1, p2]).catch(e => e.message)).to.be('2');
|
const p1 = new Promise((r, reject) => setTimeout(reject, 200, p1Error));
|
||||||
|
|
||||||
|
const p2Error = new Error('2');
|
||||||
|
const p2 = new Promise((r, reject) => setTimeout(reject, 100, p2Error));
|
||||||
|
|
||||||
|
const onReject = sinon.stub();
|
||||||
|
Promise.race([p1, p2]).catch(onReject);
|
||||||
|
|
||||||
|
tick(200);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(onReject);
|
||||||
|
sinon.assert.calledWithExactly(onReject, sinon.match.same(p2Error));
|
||||||
});
|
});
|
||||||
it('does not wait for subsequent promises to resolve/reject', async () => {
|
|
||||||
const start = Date.now();
|
it('does not wait for subsequent promises to resolve/reject', () => {
|
||||||
const p1 = new Promise(resolve => setTimeout(resolve, 100));
|
const onP1Resolve = sinon.stub();
|
||||||
const p2 = new Promise(resolve => setTimeout(resolve, 5000));
|
const p1 = new Promise(resolve => setTimeout(resolve, 100)).then(onP1Resolve);
|
||||||
await Promise.race([p1, p2]);
|
|
||||||
const time = Date.now() - start;
|
const onP2Resolve = sinon.stub();
|
||||||
expect(time).to.not.be.lessThan(100);
|
const p2 = new Promise(resolve => setTimeout(resolve, 101)).then(onP2Resolve);
|
||||||
expect(time).to.not.be.greaterThan(2000);
|
|
||||||
|
const onResolve = sinon.stub();
|
||||||
|
Promise.race([p1, p2]).then(onResolve);
|
||||||
|
|
||||||
|
tick(100);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(onResolve);
|
||||||
|
sinon.assert.calledOnce(onP1Resolve);
|
||||||
|
sinon.assert.callOrder(onP1Resolve, onResolve);
|
||||||
|
sinon.assert.notCalled(onP2Resolve);
|
||||||
});
|
});
|
||||||
it('allows non-promises in the array', async () => {
|
|
||||||
expect(await Promise.race([1, 2, 3])).to.be(1);
|
it('allows non-promises in the array', () => {
|
||||||
|
const onResolve = sinon.stub();
|
||||||
|
Promise.race([1, 2, 3]).then(onResolve);
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(onResolve);
|
||||||
|
sinon.assert.calledWithExactly(onResolve, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('argument is undefined', () => {
|
describe('argument is undefined', () => {
|
||||||
it('rejects the promise', async () => {
|
it('rejects the promise', () => {
|
||||||
const football = {};
|
const football = {};
|
||||||
expect(await Promise.race().catch(() => football)).to.be(football);
|
const onReject = sinon.stub();
|
||||||
|
Promise.race().catch(() => football).then(onReject);
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(onReject);
|
||||||
|
sinon.assert.calledWithExactly(onReject, sinon.match.same(football));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('argument is a string', () => {
|
describe('argument is a string', () => {
|
||||||
it(`resolves with the first character`, async () => {
|
it(`resolves with the first character`, () => {
|
||||||
expect(await Promise.race('abc')).to.be('a');
|
const onResolve = sinon.stub();
|
||||||
|
Promise.race('abc').then(onResolve);
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(onResolve);
|
||||||
|
sinon.assert.calledWithExactly(onResolve, 'a');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('argument is a non-iterable object', () => {
|
describe('argument is a non-iterable object', () => {
|
||||||
it('reject the promise', async () => {
|
it('reject the promise', () => {
|
||||||
const football = {};
|
const football = {};
|
||||||
expect(await Promise.race({}).catch(() => football)).to.be(football);
|
const onReject = sinon.stub();
|
||||||
|
Promise.race({}).catch(() => football).then(onReject);
|
||||||
|
|
||||||
|
tick();
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(onReject);
|
||||||
|
sinon.assert.calledWithExactly(onReject, sinon.match.same(football));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('argument is a generator', () => {
|
describe('argument is a generator', () => {
|
||||||
it('resolves with the first resolved value', async () => {
|
it('resolves with the first resolved value', () => {
|
||||||
function *gen() {
|
function *gen() {
|
||||||
yield new Promise(resolve => setTimeout(resolve, 100, 1));
|
yield new Promise(resolve => setTimeout(resolve, 100, 1));
|
||||||
yield new Promise(resolve => setTimeout(resolve, 200, 2));
|
yield new Promise(resolve => setTimeout(resolve, 200, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(await Promise.race(gen())).to.be(1);
|
const onResolve = sinon.stub();
|
||||||
|
Promise.race(gen()).then(onResolve);
|
||||||
|
|
||||||
|
tick(200);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(onResolve);
|
||||||
|
sinon.assert.calledWithExactly(onResolve, 1);
|
||||||
});
|
});
|
||||||
it('resolves with the first non-promise value', async () => {
|
|
||||||
|
it('resolves with the first non-promise value', () => {
|
||||||
function *gen() {
|
function *gen() {
|
||||||
yield 1;
|
yield 1;
|
||||||
yield new Promise(resolve => setTimeout(resolve, 200, 2));
|
yield new Promise(resolve => setTimeout(resolve, 200, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(await Promise.race(gen())).to.be(1);
|
const onResolve = sinon.stub();
|
||||||
|
Promise.race(gen()).then(onResolve);
|
||||||
|
|
||||||
|
tick(200);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(onResolve);
|
||||||
|
sinon.assert.calledWithExactly(onResolve, 1);
|
||||||
});
|
});
|
||||||
it('iterates all values from the generator, even if one is already "resolved"', async () => {
|
|
||||||
|
it('iterates all values from the generator, even if one is already "resolved"', () => {
|
||||||
let yieldCount = 0;
|
let yieldCount = 0;
|
||||||
function *gen() {
|
function *gen() {
|
||||||
yieldCount += 1;
|
yieldCount += 1;
|
||||||
|
@ -178,7 +257,13 @@ describe('Promise service', function () {
|
||||||
yield new Promise(resolve => setTimeout(resolve, 200, 2));
|
yield new Promise(resolve => setTimeout(resolve, 200, 2));
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(await Promise.race(gen())).to.be(1);
|
const onResolve = sinon.stub();
|
||||||
|
Promise.race(gen()).then(onResolve);
|
||||||
|
|
||||||
|
tick(200);
|
||||||
|
|
||||||
|
sinon.assert.calledOnce(onResolve);
|
||||||
|
sinon.assert.calledWithExactly(onResolve, 1);
|
||||||
expect(yieldCount).to.be(2);
|
expect(yieldCount).to.be(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue