goog.promise.promise_test.js Maven / Gradle / Ivy
// Copyright 2013 The Closure Library Authors. All Rights Reserved.
//
// 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.
goog.provide('goog.PromiseTest');
goog.require('goog.Promise');
goog.require('goog.Thenable');
goog.require('goog.Timer');
goog.require('goog.functions');
goog.require('goog.testing.MockClock');
goog.require('goog.testing.PropertyReplacer');
goog.require('goog.testing.TestCase');
goog.require('goog.testing.jsunit');
goog.require('goog.testing.recordFunction');
goog.require('goog.userAgent');
goog.setTestOnly('goog.PromiseTest');
function setUpPage() {
goog.testing.TestCase.getActiveTestCase().promiseTimeout = 10000; // 10s
}
// TODO(brenneman):
// - Add tests for interoperability with native Promises where available.
// - Add tests for long stack traces.
var SUPPORTS_ACCESSORS = !!window.Object.defineProperty &&
// IE8 and Safari<5.1 have an Object.defineProperty which does not work on
// some objects.
(!goog.userAgent.IE || goog.userAgent.isVersionOrHigher('9')) &&
(!goog.userAgent.SAFARI || goog.userAgent.isVersionOrHigher('534.48.3'));
var mockClock = new goog.testing.MockClock();
var stubs = new goog.testing.PropertyReplacer();
var unhandledRejections;
// Simple shared objects used as test values.
var dummy = {toString: goog.functions.constant('[object dummy]')};
var sentinel = {toString: goog.functions.constant('[object sentinel]')};
function setUp() {
unhandledRejections = goog.testing.recordFunction();
goog.Promise.setUnhandledRejectionHandler(unhandledRejections);
}
function tearDown() {
// The system should leave no pending unhandled rejections. Advance the mock
// clock (if installed) to catch any rethrows waiting in the queue.
mockClock.tick(Infinity);
mockClock.uninstall();
mockClock.reset();
stubs.reset();
}
/**
* Dummy onfulfilled or onrejected function that should not be called.
*
* @param {*} result The result passed into the callback.
*/
function shouldNotCall(result) {
fail('This should not have been called (result: ' + String(result) + ')');
}
function fulfillSoon(value, delay) {
return new goog.Promise(function(resolve, reject) {
window.setTimeout(function() { resolve(value); }, delay);
});
}
function fulfillThenableSoon(value, delay) {
return createThenable(value, delay, true /* fulfilled */);
}
function fulfillBuiltInSoon(value, delay) {
// If the environment does not provide a built-in Promise, then just use
// goog.Promise instead to allow tests which use this to continue.
if (!window.Promise) {
return fulfillSoon(value, delay);
}
return new window.Promise(function(resolve, reject) {
window.setTimeout(function() { resolve(value); }, delay);
});
}
function rejectSoon(reason, delay) {
return new goog.Promise(function(resolve, reject) {
window.setTimeout(function() { reject(reason); }, delay);
});
}
function rejectThenableSoon(value, delay) {
return createThenable(value, delay, false /* fulfilled */);
}
function rejectBuiltInSoon(value, delay) {
// If the environment does not provide a built-in Promise, then just use
// goog.Promise instead to allow tests which use this to continue.
if (!window.Promise) {
return rejectSoon(value, delay);
}
return new window.Promise(function(resolve, reject) {
window.setTimeout(function() { reject(value); }, delay);
});
}
/**
* Creates a thenable which isn't formally a promise for testing non-Promise
* thenables.
*/
function createThenableResolver() {
var resolver = goog.Promise.withResolver();
var thenable = {};
var then = function(onFulfilled, onRejected) {
var next = createThenableResolver();
next.resolve(resolver.promise.then(onFulfilled, onRejected));
return next.thenable;
};
// Count accesses of the {@code then} property when possible. Otherwise, just
// define the {@code then} method as a regular data property.
if (SUPPORTS_ACCESSORS) {
thenable.thenAccesses = 0;
window.Object.defineProperty(thenable, 'then', {
get: function() {
thenable.thenAccesses++;
return then;
}
});
} else {
thenable.then = then;
}
return {
resolve: resolver.resolve,
reject: resolver.reject,
thenable: thenable
};
}
/**
* @param {*} value The value the thenable should be fulfilled/rejected with.
* @param {number} delay The length of the delay until the thenable is resolved.
* @param {boolean} fulfill Whether to fulfill or reject the thenable.
* @return {!Thenable}
*/
function createThenable(value, delay, fulfill) {
var resolver = createThenableResolver();
window.setTimeout(function() {
if (fulfill) {
resolver.resolve(value);
} else {
resolver.reject(value);
}
}, delay);
return resolver.thenable;
}
/**
* Creates a malicious thenable that throws when the {@code then} method is
* accessed to ensure that it is caught and converted to a rejected promise
* instead of allowed to cause a synchronous exception.
* @param {*} value The value to throw.
* @return {!Thenable}
*/
function createThrowingThenable(value) {
// If the environment does not provide Object.defineProperty, then just
// use an immediately rejected promise to allow tests which use this to
// continue.
if (!SUPPORTS_ACCESSORS) {
return rejectThenableSoon(value, 0);
}
var thenable = {};
window.Object.defineProperty(
thenable, 'then', {get: function() { throw value; }});
return thenable;
}
function testThenIsFulfilled() {
var timesCalled = 0;
var p = new goog.Promise(function(resolve, reject) { resolve(sentinel); });
p.then(function(value) {
timesCalled++;
assertEquals(sentinel, value);
});
assertEquals(
'then() must return before callbacks are invoked.', 0, timesCalled);
return p.then(function() {
assertEquals('onFulfilled must be called exactly once.', 1, timesCalled);
});
}
function testThenVoidIsFulfilled() {
var timesCalled = 0;
var p = goog.Promise.resolve(sentinel);
p.thenVoid(function(value) {
timesCalled++;
assertEquals(sentinel, value);
});
assertEquals(
'thenVoid() must return before callbacks are invoked.', 0, timesCalled);
return p.then(function() {
assertEquals('onFulfilled must be called exactly once.', 1, timesCalled);
});
}
function testThenIsRejected() {
var timesCalled = 0;
var p = goog.Promise.reject(sentinel);
p.then(shouldNotCall, function(value) {
timesCalled++;
assertEquals(sentinel, value);
});
assertEquals(
'then() must return before callbacks are invoked.', 0, timesCalled);
return p.then(shouldNotCall, function() {
assertEquals('onRejected must be called exactly once.', 1, timesCalled);
});
}
function testThenVoidIsRejected() {
var timesCalled = 0;
var p = goog.Promise.reject(sentinel);
p.thenVoid(shouldNotCall, function(value) {
timesCalled++;
assertEquals(sentinel, value);
assertEquals('onRejected must be called exactly once.', 1, timesCalled);
});
assertEquals(
'thenVoid() must return before callbacks are invoked.', 0, timesCalled);
return p.then(shouldNotCall, function() {
assertEquals('onRejected must be called exactly once.', 1, timesCalled);
});
}
function testThenAsserts() {
var p = goog.Promise.resolve();
var m = assertThrows(function() { p.then({}); });
assertContains('opt_onFulfilled should be a function.', m.message);
m = assertThrows(function() { p.then(function() {}, {}); });
assertContains('opt_onRejected should be a function.', m.message);
}
function testThenVoidAsserts() {
var p = goog.Promise.resolve();
var m = assertThrows(function() { p.thenVoid({}); });
assertContains('opt_onFulfilled should be a function.', m.message);
m = assertThrows(function() { p.thenVoid(function() {}, {}); });
assertContains('opt_onRejected should be a function.', m.message);
}
function testOptionalOnFulfilled() {
return goog.Promise.resolve(sentinel)
.then(null, null)
.then(null, shouldNotCall)
.then(function(value) { assertEquals(sentinel, value); });
}
function testOptionalOnRejected() {
return goog.Promise.reject(sentinel)
.then(null, null)
.then(shouldNotCall)
.then(null, function(reason) { assertEquals(sentinel, reason); });
}
function testMultipleResolves() {
var timesCalled = 0;
var resolvePromise;
var p = new goog.Promise(function(resolve, reject) {
resolvePromise = resolve;
resolve('foo');
resolve('bar');
});
p.then(function(value) {
timesCalled++;
assertEquals('onFulfilled must be called exactly once.', 1, timesCalled);
});
// Add one more test for fulfilling after a delay.
return goog.Timer.promise(10).then(function() {
resolvePromise('baz');
assertEquals(1, timesCalled);
});
}
function testMultipleRejects() {
var timesCalled = 0;
var rejectPromise;
var p = new goog.Promise(function(resolve, reject) {
rejectPromise = reject;
reject('foo');
reject('bar');
});
p.then(shouldNotCall, function(value) {
timesCalled++;
assertEquals('onRejected must be called exactly once.', 1, timesCalled);
});
// Add one more test for rejecting after a delay.
return goog.Timer.promise(10).then(function() {
rejectPromise('baz');
assertEquals(1, timesCalled);
});
}
function testAsynchronousThenCalls() {
var timesCalled = [0, 0, 0, 0];
var p = new goog.Promise(function(resolve, reject) {
window.setTimeout(function() { resolve(); }, 30);
});
p.then(function() {
timesCalled[0]++;
assertArrayEquals([1, 0, 0, 0], timesCalled);
});
window.setTimeout(function() {
p.then(function() {
timesCalled[1]++;
assertArrayEquals([1, 1, 0, 0], timesCalled);
});
}, 10);
window.setTimeout(function() {
p.then(function() {
timesCalled[2]++;
assertArrayEquals([1, 1, 1, 0], timesCalled);
});
}, 20);
return goog.Timer.promise(40).then(function() {
return p.then(function() {
timesCalled[3]++;
assertArrayEquals([1, 1, 1, 1], timesCalled);
});
});
}
function testResolveWithPromise() {
var resolveBlocker;
var hasFulfilled = false;
var blocker =
new goog.Promise(function(resolve, reject) { resolveBlocker = resolve; });
var p = goog.Promise.resolve(blocker);
p.then(function(value) {
hasFulfilled = true;
assertEquals(sentinel, value);
}, shouldNotCall);
assertFalse(hasFulfilled);
resolveBlocker(sentinel);
return p.then(function() { assertTrue(hasFulfilled); });
}
function testResolveWithRejectedPromise() {
var rejectBlocker;
var hasRejected = false;
var blocker =
new goog.Promise(function(resolve, reject) { rejectBlocker = reject; });
var p = goog.Promise.resolve(blocker);
var child = p.then(shouldNotCall, function(reason) {
hasRejected = true;
assertEquals(sentinel, reason);
});
assertFalse(hasRejected);
rejectBlocker(sentinel);
return child.thenCatch(function() { assertTrue(hasRejected); });
}
function testRejectWithPromise() {
var resolveBlocker;
var hasFulfilled = false;
var blocker =
new goog.Promise(function(resolve, reject) { resolveBlocker = resolve; });
var p = goog.Promise.reject(blocker);
var child = p.then(function(value) {
hasFulfilled = true;
assertEquals(sentinel, value);
}, shouldNotCall);
assertFalse(hasFulfilled);
resolveBlocker(sentinel);
return child.thenCatch(function() { assertTrue(hasRejected); });
}
function testRejectWithRejectedPromise() {
var rejectBlocker;
var hasRejected = false;
var blocker =
new goog.Promise(function(resolve, reject) { rejectBlocker = reject; });
var p = goog.Promise.reject(blocker);
var child = p.then(shouldNotCall, function(reason) {
hasRejected = true;
assertEquals(sentinel, reason);
});
assertFalse(hasRejected);
rejectBlocker(sentinel);
return child.thenCatch(function() { assertTrue(hasRejected); });
}
function testResolveAndReject() {
var onFulfilledCalled = false;
var onRejectedCalled = false;
var p = new goog.Promise(function(resolve, reject) {
resolve();
reject();
});
p.then(
function() { onFulfilledCalled = true; },
function() { onRejectedCalled = true; });
return p.then(function() {
assertTrue(onFulfilledCalled);
assertFalse(onRejectedCalled);
});
}
function testResolveWithSelfRejects() {
var r;
var p = new goog.Promise(function(resolve) { r = resolve; });
r(p);
return p.then(shouldNotCall, function(e) {
assertEquals(e.message, 'Promise cannot resolve to itself');
});
}
function testResolveWithObjectStringResolves() {
return goog.Promise.resolve('[object Object]').then(function(v) {
assertEquals(v, '[object Object]');
});
}
function testRejectAndResolve() {
return new goog
.Promise(function(resolve, reject) {
reject();
resolve();
})
.then(shouldNotCall, function() { return true; });
}
function testThenReturnsBeforeCallbackWithFulfill() {
var thenHasReturned = false;
var p = goog.Promise.resolve();
var child = p.then(function() {
assertTrue(
'Callback must be called only after then() has returned.',
thenHasReturned);
});
thenHasReturned = true;
return child;
}
function testThenReturnsBeforeCallbackWithReject() {
var thenHasReturned = false;
var p = goog.Promise.reject();
var child = p.then(shouldNotCall, function() {
assertTrue(
'Callback must be called only after then() has returned.',
thenHasReturned);
});
thenHasReturned = true;
return child;
}
function testResolutionOrder() {
var callbacks = [];
return goog.Promise.resolve()
.then(function() { callbacks.push(1); }, shouldNotCall)
.then(function() { callbacks.push(2); }, shouldNotCall)
.then(function() { callbacks.push(3); }, shouldNotCall)
.then(function() {
assertArrayEquals([1, 2, 3], callbacks);
});
}
function testResolutionOrderWithThrow() {
var callbacks = [];
var p = goog.Promise.resolve();
p.then(function() { callbacks.push(1); }, shouldNotCall);
var child = p.then(function() {
callbacks.push(2);
throw Error();
}, shouldNotCall);
child.then(shouldNotCall, function() {
// The parent callbacks should be evaluated before the child.
callbacks.push(4);
});
p.then(function() { callbacks.push(3); }, shouldNotCall);
return child.then(shouldNotCall, function() {
callbacks.push(5);
assertArrayEquals([1, 2, 3, 4, 5], callbacks);
});
}
function testResolutionOrderWithNestedThen() {
var resolver = goog.Promise.withResolver();
var callbacks = [];
var p = goog.Promise.resolve();
p.then(function() {
callbacks.push(1);
p.then(function() {
callbacks.push(3);
resolver.resolve();
});
});
p.then(function() { callbacks.push(2); });
return resolver.promise.then(function() {
assertArrayEquals([1, 2, 3], callbacks);
});
}
function testRejectionOrder() {
var callbacks = [];
var p = goog.Promise.reject();
p.then(shouldNotCall, function() { callbacks.push(1); });
p.then(shouldNotCall, function() { callbacks.push(2); });
p.then(shouldNotCall, function() { callbacks.push(3); });
return p.then(shouldNotCall, function() {
assertArrayEquals([1, 2, 3], callbacks);
});
}
function testRejectionOrderWithThrow() {
var callbacks = [];
var p = goog.Promise.reject();
p.then(shouldNotCall, function() { callbacks.push(1); });
p.then(shouldNotCall, function() {
callbacks.push(2);
throw Error();
});
p.then(shouldNotCall, function() { callbacks.push(3); });
return p.then(shouldNotCall, function() {
assertArrayEquals([1, 2, 3], callbacks);
});
}
function testRejectionOrderWithNestedThen() {
var resolver = goog.Promise.withResolver();
var callbacks = [];
var p = goog.Promise.reject();
p.then(shouldNotCall, function() {
callbacks.push(1);
p.then(shouldNotCall, function() {
callbacks.push(3);
resolver.resolve();
});
});
p.then(shouldNotCall, function() { callbacks.push(2); });
return resolver.promise.then(function() {
assertArrayEquals([1, 2, 3], callbacks);
});
}
function testBranching() {
var p = goog.Promise.resolve(2);
var branch1 =
p.then(function(value) {
assertEquals('then functions should see the same value', 2, value);
return value / 2;
}).then(function(value) {
assertEquals('branch should receive the returned value', 1, value);
});
var branch2 =
p.then(function(value) {
assertEquals('then functions should see the same value', 2, value);
throw value + 1;
}).then(shouldNotCall, function(reason) {
assertEquals('branch should receive the thrown value', 3, reason);
});
var branch3 =
p.then(function(value) {
assertEquals('then functions should see the same value', 2, value);
return value * 2;
}).then(function(value) {
assertEquals('branch should receive the returned value', 4, value);
});
return goog.Promise.all([branch1, branch2, branch3]);
}
function testThenReturnsPromise() {
var parent = goog.Promise.resolve();
var child = parent.then();
assertTrue(child instanceof goog.Promise);
assertNotEquals(
'The returned Promise must be different from the input.', parent, child);
}
function testThenVoidReturnsUndefined() {
var parent = goog.Promise.resolve();
var child = parent.thenVoid();
assertUndefined(child);
}
function testBlockingPromise() {
var p = goog.Promise.resolve();
var wasFulfilled = false;
var wasRejected = false;
var p2 = p.then(function() {
return new goog.Promise(function(resolve, reject) {});
});
p2.then(
function() { wasFulfilled = true; }, function() { wasRejected = true; });
return goog.Timer.promise(10).then(function() {
assertFalse('p2 should be blocked on the returned Promise', wasFulfilled);
assertFalse('p2 should be blocked on the returned Promise', wasRejected);
});
}
function testBlockingPromiseFulfilled() {
var blockingPromise = new goog.Promise(function(resolve, reject) {
window.setTimeout(function() { resolve(sentinel); }, 0);
});
var p = goog.Promise.resolve(dummy);
var p2 = p.then(function(value) { return blockingPromise; });
return p2.then(function(value) { assertEquals(sentinel, value); });
}
function testBlockingPromiseRejected() {
var blockingPromise = new goog.Promise(function(resolve, reject) {
window.setTimeout(function() { reject(sentinel); }, 0);
});
var p = goog.Promise.resolve(blockingPromise);
return p.then(
shouldNotCall, function(reason) { assertEquals(sentinel, reason); });
}
function testBlockingThenableFulfilled() {
var thenable = {then: function(onFulfill, onReject) { onFulfill(sentinel); }};
return goog.Promise.resolve(thenable).then(function(reason) {
assertEquals(sentinel, reason);
});
}
function testBlockingThenableRejected() {
var thenable = {then: function(onFulfill, onReject) { onReject(sentinel); }};
return goog.Promise.resolve(thenable).then(
shouldNotCall, function(reason) { assertEquals(sentinel, reason); });
}
function testBlockingThenableThrows() {
var thenable = {then: function(onFulfill, onReject) { throw sentinel; }};
return goog.Promise.resolve(thenable).then(
shouldNotCall, function(reason) { assertEquals(sentinel, reason); });
}
function testBlockingThenableMisbehaves() {
var thenable = {
then: function(onFulfill, onReject) {
onFulfill(sentinel);
onFulfill(dummy);
onReject(dummy);
throw dummy;
}
};
return goog.Promise.resolve(thenable).then(function(value) {
assertEquals(
'Only the first resolution of the Thenable should have a result.',
sentinel, value);
});
}
function testNestingThenables() {
var thenableA = {
then: function(onFulfill, onReject) { onFulfill(sentinel); }
};
var thenableB = {
then: function(onFulfill, onReject) { onFulfill(thenableA); }
};
var thenableC = {
then: function(onFulfill, onReject) { onFulfill(thenableB); }
};
return goog.Promise.resolve(thenableC).then(function(value) {
assertEquals(
'Should resolve to the fulfillment value of thenableA', sentinel,
value);
});
}
function testNestingThenablesRejected() {
var thenableA = {then: function(onFulfill, onReject) { onReject(sentinel); }};
var thenableB = {
then: function(onFulfill, onReject) { onReject(thenableA); }
};
var thenableC = {
then: function(onFulfill, onReject) { onReject(thenableB); }
};
return goog.Promise.reject(thenableC).then(shouldNotCall, function(reason) {
assertEquals(
'Should resolve to rejection reason of thenableA', sentinel, reason);
});
}
function testThenCatch() {
var catchCalled = false;
return goog.Promise.reject()
.thenCatch(function(reason) {
catchCalled = true;
return sentinel;
})
.then(function(value) {
assertTrue(catchCalled);
assertEquals(sentinel, value);
});
}
function testRaceWithEmptyList() {
return goog.Promise.race([]).then(function(value) {
assertUndefined(value);
});
}
function testRaceWithFulfill() {
var a = fulfillSoon('a', 40);
var b = fulfillSoon('b', 30);
var c = fulfillSoon('c', 10);
var d = fulfillSoon('d', 20);
return goog.Promise.race([a, b, c, d])
.then(function(value) {
assertEquals('c', value);
// Return the slowest input promise to wait for it to complete.
return a;
})
.then(function(value) {
assertEquals(
'The slowest promise should resolve eventually.', 'a', value);
});
}
function testRaceWithThenables() {
var a = fulfillThenableSoon('a', 40);
var b = fulfillThenableSoon('b', 30);
var c = fulfillThenableSoon('c', 10);
var d = fulfillThenableSoon('d', 20);
return goog.Promise.race([a, b, c, d])
.then(function(value) {
assertEquals('c', value);
// Ensure that the {@code then} property was only accessed once by
// {@code goog.Promise.race}.
if (SUPPORTS_ACCESSORS) {
assertEquals(1, c.thenAccesses);
}
// Return the slowest input thenable to wait for it to complete.
return a;
})
.then(function(value) {
assertEquals(
'The slowest thenable should resolve eventually.', 'a', value);
});
}
function testRaceWithBuiltIns() {
var a = fulfillBuiltInSoon('a', 40);
var b = fulfillBuiltInSoon('b', 30);
var c = fulfillBuiltInSoon('c', 10);
var d = fulfillBuiltInSoon('d', 20);
return goog.Promise.race([a, b, c, d])
.then(function(value) {
assertEquals('c', value);
// Return the slowest input promise to wait for it to complete.
return a;
})
.then(function(value) {
assertEquals(
'The slowest promise should resolve eventually.', 'a', value);
});
}
function testRaceWithNonThenable() {
var a = fulfillSoon('a', 40);
var b = 'b';
var c = fulfillSoon('c', 10);
var d = fulfillSoon('d', 20);
return goog.Promise.race([a, b, c, d])
.then(function(value) {
assertEquals('b', value);
// Return the slowest input promise to wait for it to complete.
return a;
})
.then(function(value) {
assertEquals(
'The slowest promise should resolve eventually.', 'a', value);
});
}
function testRaceWithFalseyNonThenable() {
var a = fulfillSoon('a', 40);
var b = 0;
var c = fulfillSoon('c', 10);
var d = fulfillSoon('d', 20);
return goog.Promise.race([a, b, c, d])
.then(function(value) {
assertEquals(0, value);
// Return the slowest input promise to wait for it to complete.
return a;
})
.then(function(value) {
assertEquals(
'The slowest promise should resolve eventually.', 'a', value);
});
}
function testRaceWithFulfilledBeforeNonThenable() {
var a = fulfillSoon('a', 40);
var b = goog.Promise.resolve('b');
var c = 'c';
var d = fulfillSoon('d', 20);
return goog.Promise.race([a, b, c, d])
.then(function(value) {
assertEquals('b', value);
// Return the slowest input promise to wait for it to complete.
return a;
})
.then(function(value) {
assertEquals(
'The slowest promise should resolve eventually.', 'a', value);
});
}
function testRaceWithReject() {
var a = rejectSoon('rejected-a', 40);
var b = rejectSoon('rejected-b', 30);
var c = rejectSoon('rejected-c', 10);
var d = rejectSoon('rejected-d', 20);
return goog.Promise.race([a, b, c, d])
.then(
shouldNotCall,
function(value) {
assertEquals('rejected-c', value);
return a;
})
.then(shouldNotCall, function(reason) {
assertEquals(
'The slowest promise should resolve eventually.', 'rejected-a',
reason);
});
}
function testRaceWithRejectThenable() {
var a = rejectThenableSoon('rejected-a', 40);
var b = rejectThenableSoon('rejected-b', 30);
var c = rejectThenableSoon('rejected-c', 10);
var d = rejectThenableSoon('rejected-d', 20);
return goog.Promise.race([a, b, c, d])
.then(
shouldNotCall,
function(value) {
assertEquals('rejected-c', value);
return a;
})
.then(shouldNotCall, function(reason) {
assertEquals(
'The slowest promise should resolve eventually.', 'rejected-a',
reason);
});
}
function testRaceWithRejectBuiltIn() {
var a = rejectBuiltInSoon('rejected-a', 40);
var b = rejectBuiltInSoon('rejected-b', 30);
var c = rejectBuiltInSoon('rejected-c', 10);
var d = rejectBuiltInSoon('rejected-d', 20);
return goog.Promise.race([a, b, c, d])
.then(
shouldNotCall,
function(value) {
assertEquals('rejected-c', value);
return a;
})
.then(shouldNotCall, function(reason) {
assertEquals(
'The slowest promise should resolve eventually.', 'rejected-a',
reason);
});
}
function testRaceWithRejectAndThrowingThenable() {
var a = rejectSoon('rejected-a', 40);
var b = rejectThenableSoon('rejected-b', 30);
var c = rejectBuiltInSoon('rejected-c', 10);
var d = createThrowingThenable('rejected-d');
return goog.Promise.race([a, b, c, d])
.then(
shouldNotCall,
function(value) {
assertEquals('rejected-d', value);
return a;
})
.then(shouldNotCall, function(reason) {
assertEquals(
'The slowest promise should resolve eventually.', 'rejected-a',
reason);
});
}
function testAllWithEmptyList() {
return goog.Promise.all([]).then(function(value) {
assertArrayEquals([], value);
});
}
function testAllWithFulfill() {
var a = fulfillSoon('a', 40);
var b = fulfillSoon('b', 30);
var c = fulfillSoon('c', 10);
var d = fulfillSoon('d', 20);
// Test a falsey value.
var z = fulfillSoon(0, 30);
return goog.Promise.all([a, b, c, d, z]).then(function(value) {
assertArrayEquals(['a', 'b', 'c', 'd', 0], value);
});
}
function testAllWithThenable() {
var a = fulfillSoon('a', 40);
var b = fulfillThenableSoon('b', 30);
var c = fulfillSoon('c', 10);
var d = fulfillSoon('d', 20);
return goog.Promise.all([a, b, c, d]).then(function(value) {
assertArrayEquals(['a', 'b', 'c', 'd'], value);
// Ensure that the {@code then} property was only accessed once by
// {@code goog.Promise.all}.
if (SUPPORTS_ACCESSORS) {
assertEquals(1, b.thenAccesses);
}
});
}
function testAllWithBuiltIn() {
var a = fulfillSoon('a', 40);
var b = fulfillBuiltInSoon('b', 30);
var c = fulfillSoon('c', 10);
var d = fulfillSoon('d', 20);
return goog.Promise.all([a, b, c, d]).then(function(value) {
assertArrayEquals(['a', 'b', 'c', 'd'], value);
});
}
function testAllWithNonThenable() {
var a = fulfillSoon('a', 40);
var b = 'b';
var c = fulfillSoon('c', 10);
var d = fulfillSoon('d', 20);
// Test a falsey value.
var z = 0;
return goog.Promise.all([a, b, c, d, z]).then(function(value) {
assertArrayEquals(['a', 'b', 'c', 'd', 0], value);
});
}
function testAllWithReject() {
var a = fulfillSoon('a', 40);
var b = rejectSoon('rejected-b', 30);
var c = fulfillSoon('c', 10);
var d = fulfillSoon('d', 20);
return goog.Promise.all([a, b, c, d])
.then(
shouldNotCall,
function(reason) {
assertEquals('rejected-b', reason);
return a;
})
.then(function(value) {
assertEquals(
'Promise "a" should be fulfilled even though the all()' +
'was rejected.',
'a', value);
});
}
function testAllSettledWithEmptyList() {
return goog.Promise.allSettled([]).then(function(results) {
assertArrayEquals([], results);
});
}
function testAllSettledWithFulfillAndReject() {
var a = fulfillSoon('a', 40);
var b = rejectSoon('rejected-b', 30);
var c = 'c';
var d = rejectBuiltInSoon('rejected-d', 20);
var e = fulfillThenableSoon('e', 40);
var f = fulfillBuiltInSoon('f', 30);
var g = rejectThenableSoon('rejected-g', 10);
var h = createThrowingThenable('rejected-h');
// Test a falsey value.
var z = 0;
return goog.Promise.allSettled([a, b, c, d, e, f, g, h, z])
.then(function(results) {
assertArrayEquals(
[
{fulfilled: true, value: 'a'},
{fulfilled: false, reason: 'rejected-b'},
{fulfilled: true, value: 'c'},
{fulfilled: false, reason: 'rejected-d'},
{fulfilled: true, value: 'e'}, {fulfilled: true, value: 'f'},
{fulfilled: false, reason: 'rejected-g'},
{fulfilled: false, reason: 'rejected-h'},
{fulfilled: true, value: 0}
],
results);
// Ensure that the {@code then} property was only accessed once by
// {@code goog.Promise.allSettled}.
if (SUPPORTS_ACCESSORS) {
assertEquals(1, e.thenAccesses);
assertEquals(1, g.thenAccesses);
}
});
}
function testFirstFulfilledWithEmptyList() {
return goog.Promise.firstFulfilled([]).then(function(value) {
assertUndefined(value);
});
}
function testFirstFulfilledWithFulfill() {
var a = fulfillSoon('a', 40);
var b = rejectSoon('rejected-b', 30);
var c = rejectSoon('rejected-c', 10);
var d = fulfillSoon('d', 20);
return goog.Promise.firstFulfilled([a, b, c, d])
.then(function(value) {
assertEquals('d', value);
return c;
})
.then(
shouldNotCall,
function(reason) {
assertEquals(
'Promise "c" should be rejected before firstFulfilled() resolves.',
'rejected-c', reason);
return a;
})
.then(function(value) {
assertEquals(
'Promise "a" should be fulfilled after firstFulfilled() resolves.',
'a', value);
});
}
function testFirstFulfilledWithThenables() {
var a = fulfillThenableSoon('a', 40);
var b = rejectThenableSoon('rejected-b', 30);
var c = rejectThenableSoon('rejected-c', 10);
var d = fulfillThenableSoon('d', 20);
return goog.Promise.firstFulfilled([a, b, c, d])
.then(function(value) {
assertEquals('d', value);
// Ensure that the {@code then} property was only accessed once by
// {@code goog.Promise.firstFulfilled}.
if (SUPPORTS_ACCESSORS) {
assertEquals(1, d.thenAccesses);
}
return c;
})
.then(
shouldNotCall,
function(reason) {
assertEquals(
'Thenable "c" should be rejected before firstFulfilled() resolves.',
'rejected-c', reason);
return a;
})
.then(function(value) {
assertEquals(
'Thenable "a" should be fulfilled after firstFulfilled() resolves.',
'a', value);
});
}
function testFirstFulfilledWithBuiltIns() {
var a = fulfillBuiltInSoon('a', 40);
var b = rejectBuiltInSoon('rejected-b', 30);
var c = rejectBuiltInSoon('rejected-c', 10);
var d = fulfillBuiltInSoon('d', 20);
return goog.Promise.firstFulfilled([a, b, c, d])
.then(function(value) {
assertEquals('d', value);
return c;
})
.then(
shouldNotCall,
function(reason) {
assertEquals(
'Promise "c" should be rejected before firstFulfilled() resolves.',
'rejected-c', reason);
return a;
})
.then(function(value) {
assertEquals(
'Promise "a" should be fulfilled after firstFulfilled() resolves.',
'a', value);
});
}
function testFirstFulfilledWithNonThenable() {
var a = fulfillSoon('a', 40);
var b = rejectSoon('rejected-b', 30);
var c = rejectSoon('rejected-c', 10);
var d = 'd';
return goog.Promise.firstFulfilled([a, b, c, d])
.then(function(value) {
assertEquals('d', value);
// Return the slowest input promise to wait for it to complete.
return a;
})
.then(function(value) {
assertEquals(
'The slowest promise should resolve eventually.', 'a', value);
});
}
function testFirstFulfilledWithFalseyNonThenable() {
var a = fulfillSoon('a', 40);
var b = rejectSoon('rejected-b', 30);
var c = rejectSoon('rejected-c', 10);
var d = 0;
return goog.Promise.firstFulfilled([a, b, c, d])
.then(function(value) {
assertEquals(0, value);
// Return the slowest input promise to wait for it to complete.
return a;
})
.then(function(value) {
assertEquals(
'The slowest promise should resolve eventually.', 'a', value);
});
}
function testFirstFulfilledWithFulfilledBeforeNonThenable() {
var a = fulfillSoon('a', 40);
var b = goog.Promise.resolve('b');
var c = rejectSoon('rejected-c', 10);
var d = 'd';
return goog.Promise.firstFulfilled([a, b, c, d])
.then(function(value) {
assertEquals('b', value);
// Return the slowest input promise to wait for it to complete.
return a;
})
.then(function(value) {
assertEquals(
'The slowest promise should resolve eventually.', 'a', value);
});
}
function testFirstFulfilledWithReject() {
var a = rejectSoon('rejected-a', 40);
var b = rejectThenableSoon('rejected-b', 30);
var c = rejectBuiltInSoon('rejected-c', 10);
var d = createThrowingThenable('rejected-d');
return goog.Promise.firstFulfilled([a, b, c, d])
.then(shouldNotCall, function(reason) {
assertArrayEquals(
['rejected-a', 'rejected-b', 'rejected-c', 'rejected-d'], reason);
// Ensure that the {@code then} property was only accessed once by
// {@code goog.Promise.firstFulfilled}.
if (SUPPORTS_ACCESSORS) {
assertEquals(1, b.thenAccesses);
}
});
}
function testThenAlwaysWithFulfill() {
var thenAlwaysCalled = false;
return goog.Promise.resolve(sentinel)
.thenAlways(function() {
assertEquals(
'thenAlways should have no arguments', 0, arguments.length);
thenAlwaysCalled = true;
})
.then(function(value) {
assertEquals(sentinel, value);
assertTrue(thenAlwaysCalled);
});
}
function testThenAlwaysWithReject() {
var thenAlwaysCalled = false;
return goog.Promise.reject(sentinel)
.thenAlways(function(arg) {
assertEquals(
'thenAlways should have no arguments', 0, arguments.length);
thenAlwaysCalled = true;
})
.then(shouldNotCall, function(err) {
assertEquals(sentinel, err);
return null;
});
}
function testThenAlwaysCalledMultipleTimes() {
var calls = [];
var p = goog.Promise.resolve(sentinel);
p.then(function(value) {
assertEquals(sentinel, value);
calls.push(1);
return value;
});
p.thenAlways(function() {
assertEquals(0, arguments.length);
calls.push(2);
throw Error('thenAlways throw');
});
p.then(function(value) {
assertEquals(
'Promise result should not mutate after throw from thenAlways.',
sentinel, value);
calls.push(3);
});
p.thenAlways(function() { assertArrayEquals([1, 2, 3], calls); });
p.thenAlways(function() {
assertEquals(
'Should be one unhandled exception from the "thenAlways throw".', 1,
unhandledRejections.getCallCount());
var rejectionCall = unhandledRejections.popLastCall();
assertEquals(1, rejectionCall.getArguments().length);
var err = rejectionCall.getArguments()[0];
assertEquals('thenAlways throw', err.message);
assertEquals(goog.global, rejectionCall.getThis());
});
return p.thenAlways(function() { assertEquals(3, calls.length); });
}
function testContextWithInit() {
var initContext;
var p = new goog.Promise(function(resolve, reject) {
initContext = this;
}, sentinel);
assertEquals(sentinel, initContext);
}
function testContextWithInitDefault() {
var initContext;
var p = new goog.Promise(function(resolve, reject) { initContext = this; });
assertEquals(
'initFunc should default to being called in the global scope',
goog.global, initContext);
}
function testContextWithFulfillment() {
return goog.Promise.resolve()
.then(function() {
assertEquals(
'Call should be made in the global scope if no context is specified.',
goog.global, this);
})
.then(
function() { assertEquals(sentinel, this); }, shouldNotCall, sentinel)
.thenAlways(function() { assertEquals(sentinel, this); }, sentinel);
}
function testContextWithRejection() {
return goog.Promise.reject()
.then(
shouldNotCall,
function() {
assertEquals(
'Call should be in the default scope when no context is set.',
goog.global, this);
throw new Error('Intentional rejection');
})
.then(
shouldNotCall, function() { assertEquals(sentinel, this); }, sentinel)
.thenAlways(function() { assertEquals(sentinel, this); }, sentinel)
.thenCatch(function() { assertEquals(sentinel, this); }, sentinel);
}
function testCancel() {
var p = new goog.Promise(goog.nullFunction);
var child = p.then(shouldNotCall, function(reason) {
assertTrue(reason instanceof goog.Promise.CancellationError);
assertEquals('cancellation message', reason.message);
// Return a non-Error to resolve the cancellation rejection.
return null;
});
p.cancel('cancellation message');
return child;
}
function testThenVoidCancel() {
var thenVoidCalled = false;
var p = new goog.Promise(goog.nullFunction);
p.thenVoid(shouldNotCall, function(reason) {
assertTrue(reason instanceof goog.Promise.CancellationError);
assertEquals('cancellation message', reason.message);
thenVoidCalled = true;
});
p.cancel('cancellation message');
assertFalse(thenVoidCalled);
return p.thenCatch(function() {
assertTrue(thenVoidCalled);
// Return a non-Error to resolve the cancellation rejection.
return null;
});
}
function testCancelAfterResolve() {
var p = goog.Promise.resolve();
p.cancel();
return p.then(null, shouldNotCall);
}
function testThenVoidCancelAfterResolve() {
var p = goog.Promise.resolve();
p.cancel();
p.thenVoid(null, shouldNotCall);
return p;
}
function testCancelAfterReject() {
var p = goog.Promise.reject(sentinel);
p.cancel();
return p.then(
shouldNotCall, function(reason) { assertEquals(sentinel, reason); });
}
function testThenVoidCancelAfterReject() {
var thenVoidCalled = false;
var p = goog.Promise.reject(sentinel);
p.cancel();
p.thenVoid(shouldNotCall, function(reason) {
assertEquals(sentinel, reason);
thenVoidCalled = true;
});
return p.thenCatch(function() { assertTrue(thenVoidCalled); });
}
function testCancelPropagation() {
var cancelError;
var p = new goog.Promise(goog.nullFunction);
var p2 = p.then(shouldNotCall, function(reason) {
cancelError = reason;
assertTrue(reason instanceof goog.Promise.CancellationError);
assertEquals('parent cancel message', reason.message);
return sentinel;
}).then(function(value) {
assertEquals(
'Child promises should receive the returned value of the parent.',
sentinel, value);
}, shouldNotCall);
var p3 = p.then(shouldNotCall, function(reason) {
assertEquals(
'Every onRejected handler should receive the same cancel error.',
cancelError, reason);
assertEquals('parent cancel message', reason.message);
// Return a non-Error to resolve the cancellation rejection.
return null;
});
p.cancel('parent cancel message');
return goog.Promise.all([p2, p3]);
}
function testThenVoidCancelPropagation() {
var resolver = goog.Promise.withResolver();
var toResolveCount = 2;
var partialResolve = function() {
if (--toResolveCount == 0) {
resolver.resolve();
}
};
var cancelError;
var p = new goog.Promise(goog.nullFunction);
var p2 = p.then(shouldNotCall, function(reason) {
cancelError = reason;
assertTrue(reason instanceof goog.Promise.CancellationError);
assertEquals('parent cancel message', reason.message);
return sentinel;
});
p2.thenVoid(function(value) {
assertEquals(
'Child promises should receive the returned value of the parent.',
sentinel, value);
partialResolve();
}, shouldNotCall);
p.thenVoid(shouldNotCall, function(reason) {
assertEquals(
'Every onRejected handler should receive the same cancel error.',
cancelError, reason);
assertEquals('parent cancel message', reason.message);
partialResolve();
});
p.cancel('parent cancel message');
return resolver.promise;
}
function testCancelPropagationUpward() {
var cancelError;
var cancelCalls = [];
var parent = new goog.Promise(goog.nullFunction);
var child = parent.then(shouldNotCall, function(reason) {
assertTrue(reason instanceof goog.Promise.CancellationError);
assertEquals('grandChild cancel message', reason.message);
cancelError = reason;
cancelCalls.push('parent');
});
var grandChild = child.then(shouldNotCall, function(reason) {
assertEquals(
'Child should receive the same cancel error.', cancelError, reason);
cancelCalls.push('child');
});
var descendant = grandChild.then(shouldNotCall, function(reason) {
assertEquals(
'GrandChild should receive the same cancel error.', cancelError,
reason);
cancelCalls.push('grandChild');
assertArrayEquals(
'Each promise in the hierarchy has a single child, so canceling the ' +
'grandChild should cancel each ancestor in order.',
['parent', 'child', 'grandChild'], cancelCalls);
// Return a non-Error to resolve the cancellation rejection.
return null;
});
grandChild.cancel('grandChild cancel message');
return descendant;
}
function testThenVoidCancelPropagationUpward() {
var cancelError;
var cancelCalls = [];
var parent = new goog.Promise(goog.nullFunction);
var child = parent.then(shouldNotCall, function(reason) {
assertTrue(reason instanceof goog.Promise.CancellationError);
assertEquals('grandChild cancel message', reason.message);
cancelError = reason;
cancelCalls.push('parent');
});
var grandChild = child.then(shouldNotCall, function(reason) {
assertEquals(
'Child should receive the same cancel error.', cancelError, reason);
cancelCalls.push('child');
});
grandChild.thenVoid(shouldNotCall, function(reason) {
assertEquals(
'GrandChild should receive the same cancel error.', cancelError,
reason);
cancelCalls.push('grandChild');
});
grandChild.cancel('grandChild cancel message');
return grandChild.thenCatch(function(reason) {
assertEquals(cancelError, reason);
assertArrayEquals(
'Each promise in the hierarchy has a single child, so canceling the ' +
'grandChild should cancel each ancestor in order.',
['parent', 'child', 'grandChild'], cancelCalls);
// Return a non-Error to resolve the cancellation rejection.
return null;
});
}
function testCancelPropagationUpwardWithMultipleChildren() {
var cancelError;
var cancelCalls = [];
var parent = fulfillSoon(sentinel, 0);
parent.then(function(value) {
assertEquals(
'Non-canceled callbacks should be called after a sibling is canceled.',
sentinel, value);
});
var child = parent.then(shouldNotCall, function(reason) {
assertTrue(reason instanceof goog.Promise.CancellationError);
assertEquals('grandChild cancel message', reason.message);
cancelError = reason;
cancelCalls.push('child');
});
var grandChild = child.then(shouldNotCall, function(reason) {
assertEquals(reason, cancelError);
cancelCalls.push('grandChild');
});
grandChild.cancel('grandChild cancel message');
return grandChild.then(shouldNotCall, function(reason) {
assertEquals(reason, cancelError);
assertArrayEquals(
'The parent promise has multiple children, so only the child and ' +
'grandChild should be canceled.',
['child', 'grandChild'], cancelCalls);
// Return a non-Error to resolve the cancellation rejection.
return null;
});
}
function testThenVoidCancelPropagationUpwardWithMultipleChildren() {
var cancelError;
var cancelCalls = [];
var parent = fulfillSoon(sentinel, 0);
parent.thenVoid(function(value) {
assertEquals(
'Non-canceled callbacks should be called after a sibling is canceled.',
sentinel, value);
}, shouldNotCall);
var child = parent.then(shouldNotCall, function(reason) {
assertTrue(reason instanceof goog.Promise.CancellationError);
assertEquals('grandChild cancel message', reason.message);
cancelError = reason;
cancelCalls.push('child');
});
var grandChild = child.then(shouldNotCall, function(reason) {
assertEquals(reason, cancelError);
cancelCalls.push('grandChild');
});
grandChild.cancel('grandChild cancel message');
grandChild.thenVoid(shouldNotCall, function(reason) {
assertEquals(reason, cancelError);
cancelCalls.push('void grandChild');
});
return grandChild.then(shouldNotCall, function(reason) {
assertEquals(reason, cancelError);
assertArrayEquals(
'The parent promise has multiple children, so only the child and ' +
'grandChildren should be canceled.',
['child', 'grandChild', 'void grandChild'], cancelCalls);
// Return a non-Error to resolve the cancellation rejection.
return null;
});
}
function testCancelRecovery() {
var cancelError;
var cancelCalls = [];
var parent = fulfillSoon(sentinel, 100);
var sibling1 = parent.then(function(value) {
assertEquals(
'Non-canceled callbacks should be called after a sibling is canceled.',
sentinel, value);
});
var sibling2 = parent.then(shouldNotCall, function(reason) {
assertTrue(reason instanceof goog.Promise.CancellationError);
cancelError = reason;
cancelCalls.push('sibling2');
return sentinel;
});
var grandChild = sibling2.then(function(value) {
cancelCalls.push('child');
assertEquals(
'Returning a non-cancel value should uncancel the grandChild.', value,
sentinel);
assertArrayEquals(['sibling2', 'child'], cancelCalls);
}, shouldNotCall);
grandChild.cancel();
return goog.Promise.all([sibling1, grandChild]);
}
function testCancellationError() {
var err = new goog.Promise.CancellationError('cancel message');
assertTrue(err instanceof Error);
assertTrue(err instanceof goog.Promise.CancellationError);
assertEquals('cancel', err.name);
assertEquals('cancel message', err.message);
}
function testMockClock() {
mockClock.install();
var resolveA;
var resolveB;
var calls = [];
var p = new goog.Promise(function(resolve, reject) { resolveA = resolve; });
p.then(function(value) {
assertEquals(sentinel, value);
calls.push('then');
});
var fulfilledChild = p.then(function(value) {
assertEquals(sentinel, value);
return goog.Promise.resolve(1);
}).then(function(value) {
assertEquals(1, value);
calls.push('fulfilledChild');
});
var rejectedChild = p.then(function(value) {
assertEquals(sentinel, value);
return goog.Promise.reject(2);
}).then(shouldNotCall, function(reason) {
assertEquals(2, reason);
calls.push('rejectedChild');
});
var unresolvedChild =
p.then(function(value) {
assertEquals(sentinel, value);
return new goog.Promise(function(r) { resolveB = r; });
}).then(function(value) {
assertEquals(3, value);
calls.push('unresolvedChild');
});
resolveA(sentinel);
assertArrayEquals(
'Calls must not be resolved until the clock ticks.', [], calls);
mockClock.tick();
assertArrayEquals(
'All resolved Promises should execute in the same timestep.',
['then', 'fulfilledChild', 'rejectedChild'], calls);
resolveB(3);
assertArrayEquals(
'New calls must not resolve until the clock ticks.',
['then', 'fulfilledChild', 'rejectedChild'], calls);
mockClock.tick();
assertArrayEquals(
'All callbacks should have executed.',
['then', 'fulfilledChild', 'rejectedChild', 'unresolvedChild'], calls);
}
function testHandledRejection() {
mockClock.install();
goog.Promise.reject(sentinel).then(shouldNotCall, function(reason) {});
mockClock.tick();
assertEquals(0, unhandledRejections.getCallCount());
}
function testThenVoidHandledRejection() {
mockClock.install();
goog.Promise.reject(sentinel).thenVoid(shouldNotCall, function(reason) {});
mockClock.tick();
assertEquals(0, unhandledRejections.getCallCount());
}
function testUnhandledRejection1() {
mockClock.install();
goog.Promise.reject(sentinel);
mockClock.tick();
assertEquals(1, unhandledRejections.getCallCount());
var rejectionCall = unhandledRejections.popLastCall();
assertArrayEquals([sentinel], rejectionCall.getArguments());
assertEquals(goog.global, rejectionCall.getThis());
}
function testUnhandledRejection2() {
mockClock.install();
goog.Promise.reject(sentinel).then(shouldNotCall);
mockClock.tick();
assertEquals(1, unhandledRejections.getCallCount());
var rejectionCall = unhandledRejections.popLastCall();
assertArrayEquals([sentinel], rejectionCall.getArguments());
assertEquals(goog.global, rejectionCall.getThis());
}
function testThenVoidUnhandledRejection() {
mockClock.install();
goog.Promise.reject(sentinel).thenVoid(shouldNotCall);
mockClock.tick();
assertEquals(1, unhandledRejections.getCallCount());
var rejectionCall = unhandledRejections.popLastCall();
assertArrayEquals([sentinel], rejectionCall.getArguments());
assertEquals(goog.global, rejectionCall.getThis());
}
function testUnhandledRejection() {
var resolver = goog.Promise.withResolver();
goog.Promise.setUnhandledRejectionHandler(function(err) {
assertEquals(sentinel, err);
resolver.resolve();
});
goog.Promise.reject(sentinel);
return resolver.promise;
}
function testUnhandledThrow() {
var resolver = goog.Promise.withResolver();
goog.Promise.setUnhandledRejectionHandler(function(err) {
assertEquals(sentinel, err);
resolver.resolve();
});
goog.Promise.resolve().then(function() { throw sentinel; });
return resolver.promise;
}
function testThenVoidUnhandledThrow() {
var resolver = goog.Promise.withResolver();
goog.Promise.setUnhandledRejectionHandler(function(error) {
assertEquals(sentinel, error);
resolver.resolve();
});
goog.Promise.resolve().thenVoid(function() { throw sentinel; });
return resolver.promise;
}
function testUnhandledBlockingRejection() {
mockClock.install();
var blocker = goog.Promise.reject(sentinel);
goog.Promise.resolve(blocker);
mockClock.tick();
assertEquals(1, unhandledRejections.getCallCount());
var rejectionCall = unhandledRejections.popLastCall();
assertArrayEquals([sentinel], rejectionCall.getArguments());
assertEquals(goog.global, rejectionCall.getThis());
}
function testUnhandledRejectionAfterThenAlways() {
mockClock.install();
var resolver = goog.Promise.withResolver();
resolver.promise.thenAlways(function() {});
resolver.reject(sentinel);
mockClock.tick();
assertEquals(1, unhandledRejections.getCallCount());
var rejectionCall = unhandledRejections.popLastCall();
assertArrayEquals([sentinel], rejectionCall.getArguments());
assertEquals(goog.global, rejectionCall.getThis());
}
function testHandledBlockingRejection() {
mockClock.install();
var blocker = goog.Promise.reject(sentinel);
goog.Promise.resolve(blocker).then(shouldNotCall, function(reason) {});
mockClock.tick();
assertEquals(0, unhandledRejections.getCallCount());
}
function testThenVoidHandledBlockingRejection() {
var shouldCall = goog.testing.recordFunction();
mockClock.install();
var blocker = goog.Promise.reject(sentinel);
goog.Promise.resolve(blocker).thenVoid(shouldNotCall, shouldCall);
mockClock.tick();
assertEquals(0, unhandledRejections.getCallCount());
assertEquals(1, shouldCall.getCallCount());
}
function testUnhandledRejectionWithTimeout() {
mockClock.install();
stubs.replace(goog.Promise, 'UNHANDLED_REJECTION_DELAY', 200);
goog.Promise.reject(sentinel);
mockClock.tick(199);
assertEquals(0, unhandledRejections.getCallCount());
mockClock.tick(1);
assertEquals(1, unhandledRejections.getCallCount());
}
function testHandledRejectionWithTimeout() {
mockClock.install();
stubs.replace(goog.Promise, 'UNHANDLED_REJECTION_DELAY', 200);
var p = goog.Promise.reject(sentinel);
mockClock.tick(199);
p.then(shouldNotCall, function(reason) {});
mockClock.tick(1);
assertEquals(0, unhandledRejections.getCallCount());
}
function testUnhandledRejectionDisabled() {
mockClock.install();
stubs.replace(goog.Promise, 'UNHANDLED_REJECTION_DELAY', -1);
goog.Promise.reject(sentinel);
mockClock.tick();
assertEquals(0, unhandledRejections.getCallCount());
}
function testThenableInterface() {
var promise = new goog.Promise(function(resolve, reject) {});
assertTrue(goog.Thenable.isImplementedBy(promise));
assertFalse(goog.Thenable.isImplementedBy({}));
assertFalse(goog.Thenable.isImplementedBy('string'));
assertFalse(goog.Thenable.isImplementedBy(1));
assertFalse(goog.Thenable.isImplementedBy({then: function() {}}));
function T() {}
T.prototype.then = function(opt_a, opt_b, opt_c) {};
goog.Thenable.addImplementation(T);
assertTrue(goog.Thenable.isImplementedBy(new T));
// Test COMPILED code path.
try {
COMPILED = true;
function C() {}
C.prototype.then = function(opt_a, opt_b, opt_c) {};
goog.Thenable.addImplementation(C);
assertTrue(goog.Thenable.isImplementedBy(new C));
} finally {
COMPILED = false;
}
}
function testCreateWithResolver_Resolved() {
mockClock.install();
var timesCalled = 0;
var resolver = goog.Promise.withResolver();
resolver.promise.then(function(value) {
timesCalled++;
assertEquals(sentinel, value);
}, fail);
assertEquals(
'then() must return before callbacks are invoked.', 0, timesCalled);
mockClock.tick();
assertEquals(
'promise is not resolved until resolver is invoked.', 0, timesCalled);
resolver.resolve(sentinel);
assertEquals('resolution is delayed until the next tick', 0, timesCalled);
mockClock.tick();
assertEquals('onFulfilled must be called exactly once.', 1, timesCalled);
}
function testCreateWithResolver_Rejected() {
mockClock.install();
var timesCalled = 0;
var resolver = goog.Promise.withResolver();
resolver.promise.then(fail, function(reason) {
timesCalled++;
assertEquals(sentinel, reason);
});
assertEquals(
'then() must return before callbacks are invoked.', 0, timesCalled);
mockClock.tick();
assertEquals(
'promise is not resolved until resolver is invoked.', 0, timesCalled);
resolver.reject(sentinel);
assertEquals('resolution is delayed until the next tick', 0, timesCalled);
mockClock.tick();
assertEquals('onFulfilled must be called exactly once.', 1, timesCalled);
}
function testLinksBetweenParentsAndChildrenAreCutOnResolve() {
mockClock.install();
var parentResolver = goog.Promise.withResolver();
var parent = parentResolver.promise;
var child = parent.then(function() {});
assertNotNull(child.parent_);
assertEquals(null, parent.callbackEntries_.next);
parentResolver.resolve();
mockClock.tick();
assertNull(child.parent_);
assertEquals(null, parent.callbackEntries_);
}
function testLinksBetweenParentsAndChildrenAreCutWithUnresolvedChild() {
mockClock.install();
var parentResolver = goog.Promise.withResolver();
var parent = parentResolver.promise;
var child = parent.then(function() {
// Will never resolve.
return new goog.Promise(function() {});
});
assertNotNull(child.parent_);
assertNull(parent.callbackEntries_.next);
parentResolver.resolve();
mockClock.tick();
assertNull(child.parent_);
assertEquals(null, parent.callbackEntries_);
}
function testLinksBetweenParentsAndChildrenAreCutOnCancel() {
mockClock.install();
var parent = new goog.Promise(function() {});
var child = parent.then(function() {});
var grandChild = child.then(function() {});
assertEquals(null, child.callbackEntries_.next);
assertNotNull(child.parent_);
assertEquals(null, parent.callbackEntries_.next);
parent.cancel();
mockClock.tick();
assertNull(child.parent_);
assertNull(grandChild.parent_);
assertEquals(null, parent.callbackEntries_);
assertEquals(null, child.callbackEntries_);
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy