Do not rely on native setTimeout in the promise service tests. (#19891)

This commit is contained in:
Aleh Zasypkin 2018-06-15 11:37:26 +02:00 committed by GitHub
parent 9dc9644e95
commit 8c2a8d25c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -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);
}); });
}); });