diff --git a/src/ui/public/promises/__tests__/promises.js b/src/ui/public/promises/__tests__/promises.js index b688379e627d..b0f6971784b4 100644 --- a/src/ui/public/promises/__tests__/promises.js +++ b/src/ui/public/promises/__tests__/promises.js @@ -21,155 +21,234 @@ import expect from 'expect.js'; import ngMock from 'ng_mock'; import sinon from 'sinon'; -describe('Promise service', function () { +describe('Promise service', () => { let Promise; 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.inject(function ($injector) { + beforeEach(ngMock.inject(($injector) => { + sandbox.useFakeTimers(); + Promise = $injector.get('Promise'); $rootScope = $injector.get('$rootScope'); })); - describe('Constructor', function () { - it('provides resolve and reject function', function () { - new Promise(function (resolve, reject) { - expect(resolve).to.be.a('function'); - expect(reject).to.be.a('function'); - expect(arguments).to.have.length(2); - }); + afterEach(() => sandbox.restore()); + + describe('Constructor', () => { + it('provides resolve and reject function', () => { + const executor = sinon.stub(); + new Promise(executor); + + sinon.assert.calledOnce(executor); + sinon.assert.calledWithExactly(executor, sinon.match.func, sinon.match.func); }); }); - it('Promise.resolve', function (done) { - Promise.resolve(true).then(() => { done(); }); - // Ugly, but necessary for promises to resolve: https://github.com/angular/angular.js/issues/12555 - $rootScope.$apply(); + it('Promise.resolve', () => { + const onResolve = sinon.stub(); + Promise.resolve(true).then(onResolve); + + tick(); + + sinon.assert.calledOnce(onResolve); + sinon.assert.calledWithExactly(onResolve, true); }); - describe('Promise.fromNode', function () { - it('creates a callback that controls a promise', function () { - let callback; - Promise.fromNode(cb => (callback = cb)()); - $rootScope.$apply(); - expect(callback).to.be.a('function'); + describe('Promise.fromNode', () => { + it('creates a callback that controls a promise', () => { + const callback = sinon.stub(); + Promise.fromNode(callback); + + tick(); + + sinon.assert.calledOnce(callback); + sinon.assert.calledWithExactly(callback, sinon.match.func); }); - it('rejects if the callback receives an error', function () { - const errback = sinon.stub(); + it('rejects if the callback receives an error', () => { const err = new Error(); - Promise.fromNode(cb => cb(err)).catch(errback); - $rootScope.$apply(); + const onReject = sinon.stub(); + Promise.fromNode(sinon.stub().yields(err)).catch(onReject); - expect(errback.callCount).to.be(1); - expect(errback.getCall(0).args[0]).to.be(err); + tick(); + + sinon.assert.calledOnce(onReject); + sinon.assert.calledWithExactly(onReject, sinon.match.same(err)); }); - it('resolves with the second argument', function () { - const thenback = sinon.stub(); + it('resolves with the second argument', () => { const result = {}; - Promise.fromNode(cb => cb(null, result)).then(thenback); - $rootScope.$apply(); + const onResolve = sinon.stub(); + Promise.fromNode(sinon.stub().yields(null, result)).then(onResolve); - expect(thenback.callCount).to.be(1); - expect(thenback.getCall(0).args[0]).to.be(result); + tick(); + + sinon.assert.calledOnce(onResolve); + sinon.assert.calledWithExactly(onResolve, sinon.match.same(result)); }); - it('resolves with an array if multiple arguments are received', function () { - const thenback = sinon.stub(); + it('resolves with an array if multiple arguments are received', () => { const result1 = {}; const result2 = {}; - Promise.fromNode(cb => cb(null, result1, result2)).then(thenback); - $rootScope.$apply(); + const onResolve = sinon.stub(); + Promise.fromNode(sinon.stub().yields(null, result1, result2)).then(onResolve); - expect(thenback.callCount).to.be(1); - expect(thenback.getCall(0).args[0][0]).to.be(result1); - expect(thenback.getCall(0).args[0][1]).to.be(result2); + tick(); + + 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 () { - const thenback = sinon.stub(); - Promise.fromNode(cb => cb(null, undefined, undefined)).then(thenback); - $rootScope.$apply(); + it('resolves with an array if multiple undefined are received', () => { + const onResolve = sinon.stub(); + Promise.fromNode(sinon.stub().yields(null, undefined, undefined)).then(onResolve); - expect(thenback.callCount).to.be(1); - expect(thenback.getCall(0).args[0][0]).to.be(undefined); - expect(thenback.getCall(0).args[0][1]).to.be(undefined); + tick(); + + sinon.assert.calledOnce(onResolve); + sinon.assert.calledWithExactly(onResolve, [undefined, undefined]); }); }); describe('Promise.race()', () => { - let crankTimeout; - 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 () => { + it(`resolves with the first resolved promise's value`, () => { const p1 = new Promise(resolve => setTimeout(resolve, 100, 1)); 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))); - const p2 = new Promise((r, reject) => setTimeout(reject, 100, new Error(2))); - expect(await Promise.race([p1, p2]).catch(e => e.message)).to.be('2'); + + it(`rejects with the first rejected promise's rejection reason`, () => { + const p1Error = new Error('1'); + 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(); - const p1 = new Promise(resolve => setTimeout(resolve, 100)); - const p2 = new Promise(resolve => setTimeout(resolve, 5000)); - await Promise.race([p1, p2]); - const time = Date.now() - start; - expect(time).to.not.be.lessThan(100); - expect(time).to.not.be.greaterThan(2000); + + it('does not wait for subsequent promises to resolve/reject', () => { + const onP1Resolve = sinon.stub(); + const p1 = new Promise(resolve => setTimeout(resolve, 100)).then(onP1Resolve); + + const onP2Resolve = sinon.stub(); + const p2 = new Promise(resolve => setTimeout(resolve, 101)).then(onP2Resolve); + + 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', () => { - it('rejects the promise', async () => { + it('rejects the promise', () => { 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', () => { - it(`resolves with the first character`, async () => { - expect(await Promise.race('abc')).to.be('a'); + it(`resolves with the first character`, () => { + 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', () => { - it('reject the promise', async () => { + it('reject the promise', () => { 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', () => { - it('resolves with the first resolved value', async () => { + it('resolves with the first resolved value', () => { function *gen() { yield new Promise(resolve => setTimeout(resolve, 100, 1)); 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() { yield 1; 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; function *gen() { yieldCount += 1; @@ -178,7 +257,13 @@ describe('Promise service', function () { 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); }); });