UNPKG

shaka-player

Version:
761 lines (658 loc) 22.3 kB
/** * @license * Copyright 2016 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // Based on: https://github.com/promises-aplus/promises-tests describe('Promise polyfill', function() { /** @const */ var Promise = shaka.polyfill.Promise; /** @const */ var dummy = { 'FOO': 'BAR' }; describe('resolve', function() { it('Promise.resolve returns a resolved promise', function(done) { var p = Promise.resolve(1); expect(p).toBeTruthy(); expect(typeof p).toBe('object'); expect(typeof p.then).toBe('function'); p.then(function(i) { expect(i).toBe(1); done(); }, fail); }); it('Promise.resolve takes on status of passed promise', function(done) { var rejected = Promise.reject(1); var pending = new Promise(function(resolve, reject) {}); var rejectedResult = 0; Promise.resolve(rejected).then(fail, function(i) { // This should be called with the value of 1, as it's rejected. rejectedResult = i; }); // This should never be called, as it's pending. Promise.resolve(pending).then(fail); setTimeout(function() { expect(rejectedResult).toBe(1); done(); }, 50); }); it('Promise constructor arguments correctly resolve Promise', function(done) { var resolved = false; new Promise(function(resolve, reject) { expect(resolved).toBe(false); resolve(); }).then(function() { expect(resolved).toBe(false); resolved = true; }, fail); setTimeout(function() { expect(resolved).toBe(true); done(); }, 50); }); it('Promise constructor ignores reject after resolve', function(done) { new Promise(function(resolve, reject) { resolve(); reject(); }).catch(fail).then(done); }); it('Promise constructor ignores resolve after reject', function(done) { new Promise(function(resolve, reject) { reject(); resolve(); }).then(fail, function() {}).then(done); }); }); describe('reject', function() { it('Promise.reject returns a rejected promise', function(done) { var p = Promise.reject(1); expect(p).toBeTruthy(); expect(typeof p).toBe('object'); expect(typeof p.then).toBe('function'); p.then(fail, function(i) { expect(i).toBe(1); done(); }); }); it('Promise constructor arguments correctly reject Promise', function(done) { var rejected = false; new Promise(function(resolve, reject) { expect(rejected).toBe(false); reject(); }).then(fail, function() { expect(rejected).toBe(false); rejected = true; }); setTimeout(function() { expect(rejected).toBe(true); done(); }, 50); }); }); describe('then', function() { // 2.2.2: If `onFulfilled` is a function, // 2.2.2.2: it must not be called before `promise` is fulfilled it('onFulfilled not called before resolved', function(done) { var p = deferred(); var resolved = false; setTimeout(function() { resolved = true; p.resolve(dummy); }, 50); p.promise.then(function() { expect(resolved).toBe(true); done(); }); }); // 2.2.2.3: it must not be called more than once. it('onFulfilled only called once', function(done) { var p = deferred(); var fulfilled = jasmine.createSpy('onFulfilled'); var rejected = jasmine.createSpy('onRejected'); p.promise.then(shaka.test.Util.spyFunc(fulfilled), shaka.test.Util.spyFunc(rejected)); p.resolve(dummy); p.resolve(dummy); p.reject(dummy); setTimeout(function() { p.resolve(dummy); }, 50); setTimeout(function() { expect(fulfilled.calls.count()).toBe(1); expect(rejected).not.toHaveBeenCalled(); done(); }, 100); }); // 2.2.3: If `onRejected` is a function, // 2.2.3.2: it must not be called before `promise` is rejected it('onRejected not called before rejected', function(done) { var p = deferred(); var rejected = false; setTimeout(function() { rejected = true; p.reject(dummy); }, 50); p.promise.then(fail, function() { expect(rejected).toBe(true); done(); }); }); // 2.2.3.3: it must not be called more than once. it('onRejected only called once', function(done) { var p = deferred(); var fulfilled = jasmine.createSpy('onFulfilled'); var rejected = jasmine.createSpy('onRejected'); p.promise.then(shaka.test.Util.spyFunc(fulfilled), shaka.test.Util.spyFunc(rejected)); p.reject(dummy); p.reject(dummy); p.resolve(dummy); setTimeout(function() { p.reject(dummy); }, 50); setTimeout(function() { expect(rejected.calls.count()).toBe(1); expect(fulfilled).not.toHaveBeenCalled(); done(); }, 100); }); // 2.2.4: `onFulfilled` or `onRejected` must not be called until the // execution context stack contains only platform code. it('not called synchronously', function(done) { var p = Promise.resolve(dummy); var finished = false; p.then(function() { expect(finished).toBe(true); done(); }); finished = true; }); // 2.2.5: `onFulfilled` and `onRejected` must be called as functions (i.e // with no `this` value). it('not called with `this`', function(done) { Promise.resolve(dummy) .then(/** @this {*} */ function() { expect(this).toBe(window); throw new Error(); }) .then(fail, /** @this {*} */ function() { expect(this).toBe(window); }) .then(/** @this {*} */ function() { 'use strict'; expect(this).toBe(undefined); throw new Error(); }) .then(fail, /** @this {*} */ function() { 'use strict'; expect(this).toBe(undefined); }) .catch(fail) .then(done); }); // 2.2.7: `then` must return a promise: // `promise2 = promise1.then(onFulfilled, onRejected)` it('must return a promise', function() { var p = deferred(); var p2 = p.promise.then(); expect(p2).toBeTruthy(); expect(typeof p2).toBe('object'); expect(typeof p2.then).toBe('function'); }); // 2.2.7.1: If either `onFulfilled` or `onRejected` returns a value `x`, // run the Promise Resolution Procedure `[[Resolve]](promise2, x)` it('when callback returns Promise, handle correctly', function(done) { var p = Promise.resolve(dummy); var p2 = deferred(); p2.resolved = false; var p3 = deferred(); p3.resolved = false; var p4 = deferred(); p4.resolved = false; p.then(function() { return p2.promise; }, fail).then(function(i) { expect(i).toBe(2); expect(p2.resolved).toBe(true); return p3.promise; }).then(fail, function(i) { expect(i).toBe(3); expect(p3.resolved).toBe(true); return p4.promise; }).then(function(i) { expect(i).toBe(4); expect(p4.resolved).toBe(true); }).catch(fail).then(done); setTimeout(function() { p2.resolve(2); p2.resolved = true; }, 50); setTimeout(function() { p3.reject(3); p3.resolved = true; }, 100); setTimeout(function() { p4.resolve(4); p4.resolved = true; }, 150); }); // 2.2.7.2: If either `onFulfilled` or `onRejected` throws an exception // `e`, `promise2` must be rejected with `e` as the reason. it('if callbacks throw, invoke onRejected of child', function(done) { var p = Promise.resolve(dummy); var e = new Error(); p.then(function() { throw e; }) .then(fail, function(caught) { expect(caught).toBe(e); }) .then(done); }); it('chains correctly', function(done) { var p = Promise.resolve(dummy); p.then(function() { return 1; }) .then(function(i) { expect(i).toBe(1); }) .catch(fail) .then(function() { throw new Error(); }) .then(fail) .catch(function(e) { expect(e instanceof Error).toBe(true); return 2; }) .catch(fail) .then(function(i) { expect(i).toBe(2); throw new Error(); }) .then(fail) .catch(function() { throw new Error(); }) .then(fail) .catch() .then(fail) .catch(function() {}) .then(done); }); }); describe('all', function() { it('returns a Promise', function() { var p = Promise.all([]); expect(p).toBeTruthy(); expect(typeof p).toBe('object'); expect(typeof p.then).toBe('function'); }); it('resolves when all resolved', function(done) { var p1 = deferred(); var p2 = deferred(); var p3 = deferred(); var all = Promise.all([p1.promise, p2.promise, p3.promise]); all.then(function() { expect(p1.resolved).toBe(true); expect(p2.resolved).toBe(true); expect(p3.resolved).toBe(true); }).catch(fail).then(done); p3.resolve(); p3.resolved = true; setTimeout(function() { p2.resolve(); p2.resolved = true; }, 50); setTimeout(function() { p1.resolve(); p1.resolved = true; }, 100); }); it('resolves with an array of values from each Promise', function(done) { var p1 = Promise.resolve(1); var p2 = Promise.resolve(2); var p3 = Promise.resolve(3); var all = Promise.all([p1, p2, p3]); all.then(function(args) { expect(args).toBeTruthy(); expect(args.length).toBe(3); expect(args[0]).toBe(1); expect(args[1]).toBe(2); expect(args[2]).toBe(3); }).catch(fail).then(done); }); it('rejects if one rejects', function(done) { var p1 = Promise.resolve(1); var p2 = Promise.reject(2); var all = Promise.all([p1, p2]); all.then(fail, function(e) { expect(e).toBe(2); }).then(done); }); it('rejects with the first value if two reject', function(done) { var p1 = deferred(); var p2 = deferred(); var all = Promise.all([p1.promise, p2.promise]); all.then(fail, function(e) { expect(e).toBe(1); }).then(done); p1.reject(1); p2.reject(2); }); it('is not resolved until all Promise chains are resolved', function(done) { var p = deferred(); var p1 = Promise.resolve(0); var p2 = Promise.resolve().then(function() { return p.promise; }); var all = Promise.all([p1, p2]); var spy = jasmine.createSpy('resolve').and.callFake(done); all.then(shaka.test.Util.spyFunc(spy), fail); setTimeout(function() { expect(spy).not.toHaveBeenCalled(); p.resolve(0); }, 50); }); it('resolves with values in the order given', function(done) { var p1 = deferred(); var p2 = deferred(); var p3 = deferred(); Promise.all([p1.promise, p2.promise, p3.promise]).then(function(data) { expect(data.length).toBe(3); expect(data[0]).toBe(1); expect(data[1]).toBe(2); expect(data[2]).toBe(3); }).catch(fail).then(done); // Ensure the order of resolving does not change the order of the results. p3.resolve(3); setTimeout(function() { p1.resolve(1); }, 50); setTimeout(function() { p2.resolve(2); }, 100); }); describe('resolves with plain values', function() { function runTest(value, name) { it(name, function(done) { Promise.all([value]) .then(function(given) { expect(given).toEqual([value]); }) .catch(fail) .then(done); }); } runTest(null, 'null'); runTest(false, 'false'); runTest(5, 'a number'); runTest({}, 'an object'); }); }); describe('race', function() { it('returns a Promise', function() { var p = Promise.race([]); expect(p).toBeTruthy(); expect(typeof p).toBe('object'); expect(typeof p.then).toBe('function'); }); it('resolves when one resolves', function(done) { var p1 = deferred(); var p2 = deferred(); var p3 = deferred(); var anyDone = false; var race = Promise.race([p1.promise, p2.promise, p3.promise]); race.then(function() { expect(anyDone).toBe(true); }) .catch(fail).then(done); setTimeout(function() { p2.resolve(); anyDone = true; }, 50); }); it('resolves with the first resolved Promise', function(done) { var p1 = deferred(); var p2 = Promise.resolve(2); var p3 = deferred(); var race = Promise.race([p1.promise, p2, p3.promise]); race.then(function(arg) { expect(arg).toBe(2); }) .catch(fail).then(done); }); it('only gives the first value resolved', function(done) { var p1 = deferred(); var p2 = deferred(); var p3 = deferred(); var race = Promise.race([p1.promise, p2.promise, p3.promise]); race.then(function(arg) { expect(arg).toBe(2); }) .catch(fail).then(done); p1.resolve(2); p2.resolve(1); }); it('rejects if one rejects before resolving', function(done) { var p1 = deferred(); var p2 = Promise.reject(2); var race = Promise.race([p1.promise, p2]); race.then(fail, function(e) { expect(e).toBe(2); }).then(done); p1.resolve(); }); it('rejects with first value if two reject', function(done) { var p1 = deferred(); var p2 = deferred(); var race = Promise.race([p1.promise, p2.promise]); race.then(fail, function(e) { expect(e).toBe(2); }).then(done); p1.reject(2); p2.reject(1); }); it('resolved even if one rejects after resolving', function(done) { var p1 = deferred(); var p2 = deferred(); var race = Promise.race([p1.promise, p2.promise]); race.then(function(e) { expect(e).toBe(2); }, fail).then(done); p1.resolve(2); p2.reject(0); }); it('if a function returns a Promise, wait for it to resolve/reject', function(done) { var p = deferred(); var p1 = Promise.resolve().then(function() { return p.promise; }); var race = Promise.race([p1]); var spy = jasmine.createSpy('resolve').and.callFake(done); race.then(shaka.test.Util.spyFunc(spy), fail); setTimeout(function() { expect(spy).not.toHaveBeenCalled(); p.resolve(0); }, 50); }); describe('resolves with plain values', function() { function runTest(value, name) { it(name, function(done) { Promise.race([value]) .then(function(given) { expect(given).toBe(value); }) .catch(fail) .then(done); }); } runTest(null, 'null'); runTest(false, 'false'); runTest(5, 'a number'); runTest({}, 'an object'); }); it('supports null in array', function(done) { var p1 = deferred(); var p2 = null; Promise.race([p1.promise, p2]).then(function(arg) { expect(arg).toBe(null); }).catch(fail).then(done); }); }); describe('conformance', function() { it('if constructor throws, reject with thrown value', function(done) { var thrown = new Error(); new Promise(function(a, b) { throw thrown; }) .then(fail) .catch(function(e) { expect(e).toBe(thrown); }) .then(done); }); // 2.1.2.1: When fulfilled, a promise: must not transition to any other // state. it('once fulfilled must not change state', function(done) { var p = deferred(); var called = false; p.promise.then(function() { expect(called).toBe(false); called = true; }, function() { fail('Promise rejected when should resolve'); done(); }); p.resolve(dummy); p.reject(dummy); setTimeout(done, 100); }); // 2.1.3.1: When rejected, a promise: must not transition to any other // state. it('once rejected must not change state', function(done) { var p = deferred(); var called = false; p.promise.then(function() { fail('Promise resolved when should reject'); done(); }, function() { expect(called).toBe(false); called = true; }); p.reject(dummy); p.resolve(dummy); setTimeout(done, 100); }); // 2.2.1.1: If `onFulfilled` is not a function, it must be ignored. describe('if onFulfilled is not a function, ignore', function() { function runTest(nonFunction, name) { it(name, function(done) { expect(true).toBe(true); Promise.reject(dummy).then(nonFunction, function() {}).then(done); }); } runTest(undefined, 'undefined'); runTest(null, 'null'); runTest(false, 'false'); runTest(5, 'a number'); runTest({}, 'an object'); }); // 2.2.1.2: If `onRejected` is not a function, it must be ignored. describe('if onRejected is not a function, ignore', function() { function runTest(nonFunction, name) { it(name, function(done) { expect(true).toBe(true); Promise.resolve(dummy).then(function() {}, nonFunction).then(done); }); } runTest(undefined, 'undefined'); runTest(null, 'null'); runTest(false, 'false'); runTest(5, 'a number'); runTest({}, 'an object'); }); // 2.2.6.1: If/when `promise` is fulfilled, all respective `onFulfilled` // callbacks must execute in the order of their originating calls to `then`. it('onFulfilled called in the order then was called', function(done) { var p = deferred(); var calls = 0; p.promise.then(function() { expect(++calls).toBe(1); }); p.promise.then(function() { expect(++calls).toBe(2); // Ensure that it handles exceptions correctly. throw new Error(); }); p.promise.then(function() { expect(++calls).toBe(3); }); p.resolve(dummy); setTimeout(function() { expect(calls).toBe(3); done(); }, 50); }); // 2.2.6.2: If/when `promise` is rejected, all respective `onRejected` // callbacks must execute in the order of their originating calls to `then`. it('onRejected called in the order then was called', function(done) { var p = deferred(); var calls = 0; p.promise.then(undefined, function() { expect(++calls).toBe(1); }); p.promise.then(undefined, function() { expect(++calls).toBe(2); // Ensure that it handles exceptions correctly. throw new Error(); }); p.promise.then(undefined, function() { expect(++calls).toBe(3); }); p.reject(dummy); setTimeout(function() { expect(calls).toBe(3); done(); }, 50); }); // 2.2.7.3: If `onFulfilled` is not a function and `promise1` is fulfilled, // `promise2` must be fulfilled with the same value. describe('if onFulfilled is not a function, forward to child', function() { function runTest(nonFunction, name) { it(name, function(done) { Promise.resolve(dummy).then(nonFunction) .then(function(value) { expect(value).toBe(dummy); }) .catch(fail) .then(done); }); } runTest(undefined, 'undefined'); runTest(null, 'null'); runTest(false, 'false'); runTest(5, 'a number'); runTest({}, 'an object'); }); // 2.2.7.4: If `onRejected` is not a function and `promise1` is rejected, // `promise2` must be rejected with the same reason. describe('if onRejected is not a function, forward to child', function() { function runTest(nonFunction, name) { it(name, function(done) { Promise.reject(dummy).then(undefined, nonFunction) .then(fail) .catch(function(value) { expect(value).toBe(dummy); }) .then(done); }); } runTest(undefined, 'undefined'); runTest(null, 'null'); runTest(false, 'false'); runTest(5, 'a number'); runTest({}, 'an object'); }); // 2.3.1: If `promise` and `x` refer to the same object, reject `promise` // with a `TypeError' as the reason. it('if then returns the same Promise, reject', function(done) { var p = Promise.resolve(dummy); var next = p.then(function() { return next; }); next.then(fail, function(reason) { expect(reason instanceof TypeError).toBe(true); }).then(done); }); }); /** * @return {{ * promise: shaka.polyfill.Promise, * resolve: function(*=), * reject: function(*=) * }} */ function deferred() { var resolveLocal; var rejectLocal; var p = new Promise(function(resolve, reject) { resolveLocal = resolve; rejectLocal = reject; }); return { promise: p, resolve: resolveLocal, reject: rejectLocal }; } });