| <!DOCTYPE html> |
| <html> |
| <!-- |
| Copyright 2009 The Closure Library Authors. All Rights Reserved. |
| Author: arv@google.com (Erik Arvidsson) |
| --> |
| <head> |
| <title>Closure Unit Tests - goog.async.Deferred</title> |
| <script src="../../../../../closure/goog/base.js"></script> |
| <script> |
| |
| goog.require('goog.Promise'); |
| goog.require('goog.Thenable'); |
| goog.require('goog.array'); |
| goog.require('goog.async.Deferred'); |
| goog.require('goog.string'); |
| goog.require('goog.testing.MockClock'); |
| goog.require('goog.testing.PropertyReplacer'); |
| goog.require('goog.testing.jsunit'); |
| |
| </script> |
| </head> |
| <body> |
| <script> |
| |
| var Deferred = goog.async.Deferred; |
| var AlreadyCalledError = Deferred.AlreadyCalledError; |
| var CanceledError = Deferred.CanceledError; |
| |
| // Unhandled errors may be sent to the browser on a timeout. |
| var mockClock = new goog.testing.MockClock(); |
| var stubs = new goog.testing.PropertyReplacer(); |
| |
| function setUp() { |
| mockClock.install(); |
| } |
| |
| function tearDown() { |
| // Advance the mockClock to fire any unhandled exception timeouts. |
| mockClock.tick(); |
| mockClock.uninstall(); |
| stubs.reset(); |
| } |
| |
| function assertEqualsCallback(msg, expected) { |
| return function(res) { |
| assertEquals(msg, expected, res); |
| // Since the assertion is an exception that will be caught inside the |
| // Deferred object, we must advance the clock to see if it has failed. |
| mockClock.tick(); |
| return res; |
| }; |
| } |
| |
| function increment(res) { |
| return res + 1; |
| } |
| |
| function throwStuff(res) { |
| throw res; |
| } |
| |
| function catchStuff(res) { |
| return res; |
| } |
| |
| function returnError(res) { |
| return Error(res); |
| } |
| |
| function neverHappen(res) { |
| fail('This should not happen'); |
| } |
| |
| function testNormal() { |
| var d = new Deferred(); |
| d.addCallback(assertEqualsCallback('pre-deferred callback', 1)); |
| d.callback(1); |
| d.addCallback(increment); |
| d.addCallback(assertEqualsCallback('post-deferred callback', 2)); |
| d.addCallback(throwStuff); |
| d.addCallback(neverHappen); |
| d.addErrback(catchStuff); |
| d.addCallback(assertEqualsCallback('throw -> err, catch -> success', 2)); |
| d.addCallback(returnError); |
| d.addCallback(neverHappen); |
| d.addErrback(catchStuff); |
| d.addCallback(assertEqualsCallback('return -> err, catch -> succcess', 2)); |
| } |
| |
| function testCancel() { |
| var count = 0; |
| function canceled(d) { |
| count++; |
| } |
| |
| function canceledError(res) { |
| assertTrue(res instanceof CanceledError); |
| } |
| |
| var d = new Deferred(canceled); |
| d.addCallback(neverHappen); |
| d.addErrback(canceledError); |
| d.cancel(); |
| |
| assertEquals(1, count); |
| } |
| |
| function testSucceedFail() { |
| var count = 0; |
| |
| var d = Deferred.succeed(1).addCallback(assertEqualsCallback('succeed', 1)); |
| |
| // default error |
| d = Deferred.fail().addCallback(neverHappen); |
| d = d.addErrback(function(res) { |
| count++; |
| return res; |
| }); |
| |
| // default wrapped error |
| d = Deferred.fail('web taco').addCallback(neverHappen).addErrback(catchStuff); |
| d = d.addCallback(assertEqualsCallback('wrapped fail', 'web taco')); |
| |
| // default unwrapped error |
| d = Deferred.fail(Error('ugh')).addCallback(neverHappen).addErrback( |
| catchStuff); |
| d = d.addCallback(assertEqualsCallback('unwrapped fail', 'ugh')); |
| |
| assertEquals(1, count); |
| } |
| |
| function testDeferredDependencies() { |
| function deferredIncrement(res) { |
| var rval = Deferred.succeed(res); |
| rval.addCallback(increment); |
| return rval; |
| } |
| |
| var d = Deferred.succeed(1).addCallback(deferredIncrement); |
| d = d.addCallback(assertEqualsCallback('dependent deferred succeed', 2)); |
| |
| function deferredFailure(res) { |
| return Deferred.fail(res); |
| } |
| |
| d = Deferred.succeed('ugh').addCallback(deferredFailure).addErrback( |
| catchStuff); |
| d = d.addCallback(assertEqualsCallback('dependent deferred fail', 'ugh')); |
| } |
| |
| // Test double-calling, double-failing, etc. |
| function testDoubleCalling() { |
| var ex = assertThrows(function() { |
| Deferred.succeed(1).callback(2); |
| neverHappen(); |
| }); |
| assertTrue('double call', ex instanceof AlreadyCalledError); |
| } |
| |
| function testDoubleCalling2() { |
| var ex = assertThrows(function() { |
| Deferred.fail(1).errback(2); |
| neverHappen(); |
| }); |
| assertTrue('double-fail', ex instanceof AlreadyCalledError); |
| } |
| |
| function testDoubleCalling3() { |
| var ex = assertThrows(function() { |
| var d = Deferred.succeed(1); |
| d.cancel(); |
| d = d.callback(2); |
| assertTrue('swallowed one callback, no canceler', true); |
| d.callback(3); |
| neverHappen(); |
| }); |
| assertTrue('swallow cancel', ex instanceof AlreadyCalledError); |
| } |
| |
| function testDoubleCalling4() { |
| var count = 0; |
| function canceled(d) { |
| count++; |
| } |
| |
| var ex = assertThrows(function() { |
| var d = new Deferred(canceled); |
| d.cancel(); |
| d = d.callback(1); |
| }); |
| |
| assertTrue('non-swallowed cancel', ex instanceof AlreadyCalledError); |
| assertEquals(1, count); |
| } |
| |
| // Test incorrect Deferred usage |
| function testIncorrectUsage() { |
| var d = new Deferred(); |
| |
| var ex = assertThrows(function() { |
| d.callback(new Deferred()); |
| neverHappen(); |
| }); |
| assertTrue('deferred not allowed for callback', ex instanceof Error); |
| } |
| |
| function testIncorrectUsage2() { |
| var d = new Deferred(); |
| |
| var ex = assertThrows(function() { |
| d.errback(new Deferred()); |
| neverHappen(); |
| }); |
| assertTrue('deferred not allowed for errback', ex instanceof Error); |
| } |
| |
| function testIncorrectUsage3() { |
| var d = new Deferred(); |
| (new Deferred()).addCallback(function() {return d;}).callback(1); |
| |
| var ex = assertThrows(function() { |
| d.addCallback(function() {}); |
| neverHappen(); |
| }); |
| assertTrue('chained deferred not allowed to be re-used', ex instanceof Error); |
| } |
| |
| function testCallbackScope1() { |
| var c1 = {}, c2 = {}; |
| var callbackScope = null; |
| var errbackScope = null; |
| |
| var d = new Deferred(); |
| d.addCallback(function() { |
| callbackScope = this; |
| throw Error('Foo'); |
| }, c1); |
| d.addErrback(function() { |
| errbackScope = this; |
| }, c2); |
| d.callback(); |
| assertEquals('Incorrect callback scope', c1, callbackScope); |
| assertEquals('Incorrect errback scope', c2, errbackScope); |
| } |
| |
| function testCallbackScope2() { |
| var callbackScope = null; |
| var errbackScope = null; |
| |
| var d = new Deferred(); |
| d.addCallback(function() { |
| callbackScope = this; |
| throw Error('Foo'); |
| }); |
| d.addErrback(function() { |
| errbackScope = this; |
| }); |
| d.callback(); |
| assertEquals('Incorrect callback scope', window, callbackScope); |
| assertEquals('Incorrect errback scope', window, errbackScope); |
| } |
| |
| function testCallbackScope3() { |
| var c = {}; |
| var callbackScope = null; |
| var errbackScope = null; |
| |
| var d = new Deferred(null, c); |
| d.addCallback(function() { |
| callbackScope = this; |
| throw Error('Foo'); |
| }); |
| d.addErrback(function() { |
| errbackScope = this; |
| }); |
| d.callback(); |
| assertEquals('Incorrect callback scope', c, callbackScope); |
| assertEquals('Incorrect errback scope', c, errbackScope); |
| } |
| |
| function testChainedDeferred1() { |
| var calls = []; |
| |
| var d2 = new Deferred(); |
| d2.addCallback(function() {calls.push('B1');}); |
| d2.addCallback(function() {calls.push('B2');}); |
| |
| var d1 = new Deferred(); |
| d1.addCallback(function() {calls.push('A1');}); |
| d1.addCallback(function() {calls.push('A2');}); |
| d1.chainDeferred(d2); |
| d1.addCallback(function() {calls.push('A3');}); |
| |
| d1.callback(); |
| assertEquals('A1,A2,B1,B2,A3', calls.join(',')); |
| } |
| |
| function testChainedDeferred2() { |
| var calls = []; |
| |
| var d2 = new Deferred(); |
| d2.addCallback(function() {calls.push('B1');}); |
| d2.addErrback(function(err) {calls.push('B2'); throw Error('x');}); |
| |
| var d1 = new Deferred(); |
| d1.addCallback(function(err) {throw Error('foo');}); |
| d1.chainDeferred(d2); |
| d1.addCallback(function() {calls.push('A1');}); |
| d1.addErrback(function() {calls.push('A2');}); |
| |
| d1.callback(); |
| assertEquals('B2,A2', calls.join(',')); |
| |
| var ex = assertThrows(function() { |
| mockClock.tick(); |
| neverHappen(); |
| }); |
| assertTrue('Should catch unhandled throw from d2.', ex.message == 'x'); |
| } |
| |
| function testUndefinedResultAndCallbackSequence() { |
| var results = []; |
| var d = new Deferred(); |
| d.addCallback(function(res) {return 'foo';}); |
| d.addCallback(function(res) {results.push(res); return 'bar';}); |
| d.addCallback(function(res) {results.push(res);}); |
| d.addCallback(function(res) {results.push(res);}); |
| d.callback(); |
| assertEquals('foo,bar,bar', results.join(',')); |
| } |
| |
| function testUndefinedResultAndErrbackSequence() { |
| var results = []; |
| var d = new Deferred(); |
| d.addCallback(function(res) {throw Error('uh oh');}); |
| d.addErrback(function(res) {results.push('A');}); |
| d.addCallback(function(res) {results.push('B');}); |
| d.addErrback(function(res) {results.push('C');}); |
| d.callback(); |
| assertEquals('A,C', results.join(',')); |
| } |
| |
| function testHasFired() { |
| var d1 = new Deferred(); |
| var d2 = new Deferred(); |
| |
| assertFalse(d1.hasFired()); |
| assertFalse(d2.hasFired()); |
| |
| d1.callback(); |
| d2.errback(); |
| assertTrue(d1.hasFired()); |
| assertTrue(d2.hasFired()); |
| } |
| |
| function testUnhandledErrors() { |
| var d = new Deferred(); |
| d.addCallback(throwStuff); |
| |
| var ex = assertThrows(function() { |
| d.callback(123); |
| mockClock.tick(); |
| neverHappen(); |
| }); |
| assertEquals('Unhandled throws should hit the browser.', 123, ex); |
| |
| assertNotThrows( |
| 'Errbacks added after a failure should resume.', |
| function() { |
| d.addErrback(catchStuff); |
| mockClock.tick(); |
| }); |
| |
| d.addCallback(assertEqualsCallback('Should recover after throw.', 1)); |
| mockClock.tick(); |
| } |
| |
| function testStrictUnhandledErrors() { |
| stubs.replace(Deferred, 'STRICT_ERRORS', true); |
| var err = Error('never handled'); |
| |
| // The registered errback exists, but doesn't modify the error value. |
| var d = Deferred.succeed(); |
| d.addCallback(function(res) { throw err; }); |
| d.addErrback(function(unhandledErr) {}); |
| |
| var caught = assertThrows( |
| 'The error should be rethrown at the next clock tick.', |
| function() { mockClock.tick(); }); |
| assertEquals(err, caught); |
| } |
| |
| function testStrictHandledErrors() { |
| stubs.replace(Deferred, 'STRICT_ERRORS', true); |
| |
| // The registered errback returns a non-error value. |
| var d = Deferred.succeed(); |
| d.addCallback(function(res) { throw Error('eventually handled'); }); |
| d.addErrback(function(unhandledErr) { return true; }); |
| |
| assertNotThrows( |
| 'The error was handled and should not be rethrown', |
| function() { mockClock.tick(); }); |
| d.addCallback(function(res) { assertTrue(res); }); |
| } |
| |
| function testStrictBlockedErrors() { |
| stubs.replace(Deferred, 'STRICT_ERRORS', true); |
| |
| var d1 = Deferred.fail(Error('blocked failure')); |
| var d2 = new Deferred(); |
| |
| d1.addBoth(function() { return d2; }); |
| assertNotThrows( |
| 'd1 should be blocked until d2 fires.', |
| function() { mockClock.tick(); }); |
| |
| d2.callback('unblocked'); |
| d1.addCallback(assertEqualsCallback( |
| 'd1 should receive the fired result from d2.', 'unblocked')); |
| } |
| |
| function testStrictCanceledErrors() { |
| stubs.replace(Deferred, 'STRICT_ERRORS', true); |
| |
| var d = Deferred.canceled(); |
| assertNotThrows( |
| 'CanceledErrors should not be rethrown to the global scope.', |
| function() { mockClock.tick(); }); |
| } |
| |
| function testSynchronousErrorCanceling() { |
| var d = new Deferred(); |
| d.addCallback(throwStuff); |
| |
| assertNotThrows( |
| 'Adding an errback to the end of a failing Deferred should cancel the ' + |
| 'unhandled error timeout.', |
| function() { |
| d.callback(1); |
| d.addErrback(catchStuff); |
| mockClock.tick(); |
| }); |
| |
| d.addCallback(assertEqualsCallback('Callback should fire', 1)); |
| } |
| |
| function testThrowNonError() { |
| var results = []; |
| |
| var d = new Deferred(); |
| d.addCallback(function(res) { |
| throw res; |
| }); |
| d.addErrback(function(res) { |
| results.push(res); |
| return 6; |
| }); |
| d.addCallback(function(res) { |
| results.push(res); |
| }); |
| |
| d.callback(7); |
| assertArrayEquals( |
| 'Errback should have been called with 7, followed by callback with 6.', |
| [7, 6], results); |
| } |
| |
| function testThrownErrorWithNoErrbacks() { |
| var d = new Deferred(); |
| d.addCallback(function() { |
| throw Error('foo'); |
| }); |
| d.addCallback(goog.nullFunction); |
| |
| function assertCallback() { |
| d.callback(1); |
| mockClock.tick(); // Should cause error because throwing is delayed. |
| } |
| |
| assertThrows('A thrown error should be rethrown if there is no ' + |
| 'errback to catch it.', assertCallback); |
| } |
| |
| function testThrownErrorCallbacksDoNotCancel() { |
| var d = new Deferred(); |
| d.addCallback(function() { |
| throw Error('foo'); |
| }); |
| |
| function assertCallback() { |
| d.callback(1); |
| // Add another callback after the fact. Note this is not an errback! |
| d.addCallback(neverHappen); |
| mockClock.tick(); // Should cause error because throwing is delayed. |
| } |
| |
| assertThrows('A thrown error should be rethrown if there is no ' + |
| 'errback to catch it.', assertCallback); |
| } |
| |
| function testAwaitDeferred() { |
| |
| var results = []; |
| |
| function fn(x) { |
| return function() { |
| results.push(x); |
| }; |
| } |
| |
| var d2 = new Deferred(); |
| d2.addCallback(fn('b')); |
| |
| // d1 -> a -> (wait for d2) -> c |
| var d1 = new Deferred(); |
| d1.addCallback(fn('a')); |
| d1.awaitDeferred(d2); |
| d1.addCallback(fn('c')); |
| |
| // calls 'a' then yields for d2. |
| d1.callback(null); |
| |
| // will get called after d2. |
| d1.addCallback(fn('d')); |
| |
| assertEquals('a', results.join('')); |
| |
| // d3 -> w -> (wait for d2) -> x |
| var d3 = new Deferred(); |
| d3.addCallback(fn('w')); |
| d3.awaitDeferred(d2); |
| d3.addCallback(fn('x')); |
| |
| // calls 'w', then yields for d2. |
| d3.callback(); |
| |
| |
| // will get called after d2. |
| d3.addCallback(fn('y')); |
| |
| assertEquals('aw', results.join('')); |
| |
| // d1 calls 'd', d3 calls 'y' |
| d2.callback(null); |
| |
| assertEquals('awbcdxy', results.join('')); |
| |
| // d3 and d2 already called, so 'z' called immediately. |
| d3.addCallback(fn('z')); |
| |
| assertEquals('awbcdxyz', results.join('')); |
| } |
| |
| function testAwaitDeferred_withPromise() { |
| |
| var results = []; |
| |
| function fn(x) { |
| return function() { |
| results.push(x); |
| }; |
| } |
| |
| var resolver = new goog.Promise.withResolver(); |
| resolver.promise.then(fn('b')); |
| |
| // d1 -> a -> (wait for promise) -> c |
| var d1 = new Deferred(); |
| d1.addCallback(fn('a')); |
| d1.awaitDeferred(resolver.promise); |
| d1.addCallback(fn('c')); |
| |
| // calls 'a' then yields for promise. |
| d1.callback(1); |
| |
| // will get called after promise. |
| d1.addCallback(fn('d')); |
| |
| assertEquals('a', results.join('')); |
| |
| // d3 -> w -> (wait for promise) -> x |
| var d3 = new Deferred(); |
| d3.addCallback(fn('w')); |
| d3.awaitDeferred(resolver.promise); |
| d3.addCallback(fn('x')); |
| |
| // calls 'w', then yields for promise. |
| d3.callback(2); |
| |
| |
| // will get called after promise. |
| d3.addCallback(fn('y')); |
| |
| assertEquals('aw', results.join('')); |
| |
| // d1 calls 'd', d3 calls 'y' |
| resolver.resolve(); |
| mockClock.tick(); |
| |
| assertEquals('awbcdxy', results.join('')); |
| |
| // d3 and promise already called, so 'z' called immediately. |
| d3.addCallback(fn('z')); |
| |
| assertEquals('awbcdxyz', results.join('')); |
| } |
| |
| function testAwaitDeferredWithErrors() { |
| var results = []; |
| |
| function fn(x) { |
| return function(e) { |
| results.push(x); |
| }; |
| } |
| |
| var d2 = new Deferred(); |
| d2.addErrback(fn('a')); |
| |
| var d1 = new Deferred(); |
| d1.awaitDeferred(d2); |
| d1.addCallback(fn('x')); |
| d1.addErrback(fn('b')); |
| d1.callback(null); |
| |
| assertEquals('', results.join('')); |
| |
| d2.addCallback(fn('z')); |
| d2.addErrback(fn('c')); |
| d2.errback(null); |
| |
| // First errback added to d2 prints 'a'. |
| // Next 'd' was chained, so execute its err backs, printing 'b'. |
| // Finally 'c' was added last by d2's errback. |
| assertEquals('abc', results.join('')); |
| } |
| |
| function testNonErrorErrback() { |
| var results = []; |
| |
| function fn(x) { |
| return function(e) { |
| results.push(x); |
| }; |
| } |
| |
| var d = new Deferred(); |
| d.addCallback(fn('a')); |
| d.addErrback(fn('b')); |
| |
| d.addCallback(fn('c')); |
| d.addErrback(fn('d')); |
| |
| d.errback('foo'); |
| |
| assertEquals('bd', results.join('')); |
| } |
| |
| function testUnequalReturnValueForErrback() { |
| var results = []; |
| |
| function fn(x) { |
| return function(e) { |
| results.push(x); |
| }; |
| } |
| |
| var d = new Deferred(); |
| d.addCallback(fn('a')); |
| d.addErrback(function() { |
| results.push('b'); |
| return 'bar'; |
| }); |
| |
| d.addCallback(fn('c')); |
| d.addErrback(fn('d')); |
| |
| d.errback('foo'); |
| |
| assertEquals('bc', results.join('')); |
| } |
| |
| function testBranch() { |
| function fn(x) { |
| return function(arr) { |
| return arr.concat(x); |
| }; |
| } |
| |
| var d = new Deferred(); |
| d.addCallback(fn(1)); |
| d.addCallback(fn(2)); |
| var d2 = d.branch(); |
| d.addCallback(fn(3)); |
| d2.addCallback(fn(4)); |
| |
| d.callback([]); |
| |
| assertTrue('both deferreds should have fired', d.hasFired()); |
| assertTrue('both deferreds should have fired', d2.hasFired()); |
| d.addCallback(function(arr) { assertArrayEquals([1, 2, 3], arr); }); |
| d2.addCallback(function(arr) { assertArrayEquals([1, 2, 4], arr); }); |
| } |
| |
| function testDiamondBranch() { |
| function fn(x) { |
| return function(arr) { |
| return arr.concat(x); |
| }; |
| } |
| |
| var d = new Deferred(); |
| d.addCallback(fn(1)); |
| |
| var d2 = d.branch(); |
| d2.addCallback(fn(2)); |
| |
| // Chain the branch back to the original. There is no good reason to do this |
| // cever. |
| d.addCallback(function(ret) {return d2;}); |
| d.callback([]); |
| |
| // But no reason it shouldn't work! |
| d.addCallback(function(arr) { assertArrayEquals([1, 2], arr); }); |
| } |
| |
| function testRepeatedBranch() { |
| var d = new Deferred().addCallback(increment); |
| |
| d.branch(). |
| addCallback(assertEqualsCallback('branch should be after increment', 2)). |
| addCallback(function(res) {return d.branch();}). |
| addCallback(assertEqualsCallback('second branch should be the same', 2)); |
| d.callback(1); |
| } |
| |
| function testCancelThroughBranch() { |
| var wasCanceled = false; |
| var d = new Deferred(function() { wasCanceled = true; }); |
| var branch1 = d.branch(true); |
| var branch2 = d.branch(true); |
| |
| branch1.cancel(); |
| assertFalse(wasCanceled); |
| branch2.cancel(); |
| assertTrue(wasCanceled); |
| } |
| |
| function testCancelThroughSeveralBranches() { |
| var wasCanceled = false; |
| var d = new Deferred(function() { wasCanceled = true; }); |
| var branch = d.branch(true).branch(true).branch(true); |
| |
| branch.cancel(); |
| assertTrue(wasCanceled); |
| } |
| |
| function testBranchCancelThenCallback() { |
| var wasCanceled = false; |
| var d = new Deferred(function() { wasCanceled = true; }); |
| var wasCalled = false; |
| d.addCallback(function() { wasCalled = true; }); |
| var branch1 = d.branch(); |
| var branch2 = d.branch(); |
| |
| var branch1WasCalled = false; |
| var branch2WasCalled = false; |
| branch1.addCallback(function() { branch1WasCalled = true; }); |
| branch2.addCallback(function() { branch2WasCalled = true; }); |
| |
| var branch1HadErrback = false; |
| var branch2HadErrback = false; |
| branch1.addErrback(function() { branch1HadErrback = true; }); |
| branch2.addErrback(function() { branch2HadErrback = true; }); |
| |
| branch1.cancel(); |
| assertFalse(wasCanceled); |
| assertTrue(branch1HadErrback); |
| assertFalse(branch2HadErrback); |
| |
| d.callback(); |
| assertTrue(wasCalled); |
| assertFalse(branch1WasCalled); |
| assertTrue(branch2WasCalled); |
| } |
| |
| function testDeepCancelOnBranch() { |
| var wasCanceled = false; |
| var d = new Deferred(function() { wasCanceled = true; }); |
| var branch1 = d.branch(true); |
| var branch2 = d.branch(true).branch(true).branch(true); |
| |
| var branch1HadErrback = false; |
| var branch2HadErrback = false; |
| branch1.addErrback(function() { branch1HadErrback = true; }); |
| branch2.addErrback(function() { branch2HadErrback = true; }); |
| |
| branch2.cancel(true /* opt_deepCancel */); |
| assertTrue(wasCanceled); |
| assertTrue(branch1HadErrback); |
| assertTrue(branch2HadErrback); |
| } |
| |
| function testCancelOnRoot() { |
| var wasCanceled = false; |
| var d = new Deferred(function() { wasCanceled = true; }); |
| var branch = d.branch(true).branch(true).branch(true); |
| |
| d.cancel(); |
| assertTrue(wasCanceled); |
| } |
| |
| function testCancelOnLeafBranch() { |
| var wasCanceled = false; |
| var branchWasCanceled = false; |
| var d = new Deferred(function() { wasCanceled = true; }); |
| var branch = d.branch(true).branch(true).branch(true); |
| branch.addErrback(function() { branchWasCanceled = true; }); |
| |
| branch.cancel(); |
| assertTrue(wasCanceled); |
| assertTrue(branchWasCanceled); |
| } |
| |
| function testCancelOnIntermediateBranch() { |
| var rootWasCanceled = false; |
| |
| var d = new Deferred(function() { rootWasCanceled = true; }); |
| var branch = d.branch(true).branch(true).branch(true); |
| |
| var deepBranch1 = branch.branch(true); |
| var deepBranch2 = branch.branch(true); |
| |
| branch.cancel(); |
| assertTrue(rootWasCanceled); |
| assertTrue(deepBranch1.hasFired()); |
| assertTrue(deepBranch2.hasFired()); |
| } |
| |
| function testCancelWithSomeCompletedBranches() { |
| var d = new Deferred(); |
| var branch1 = d.branch(true); |
| |
| var branch1HadCallback = false; |
| var branch1HadErrback = false; |
| branch1. |
| addCallback(function() { branch1HadCallback = true; }). |
| addErrback(function() { branch1HadErrback = true; }); |
| d.callback(true); |
| |
| assertTrue(branch1HadCallback); |
| assertFalse(branch1HadErrback); |
| |
| var rootHadCallback = false; |
| var rootHadErrback = false; |
| // Block the root on a new Deferred indefinitely. |
| d. |
| addCallback(function() { rootHadCallback = true; }). |
| addCallback(function() { return new Deferred(); }). |
| addErrback(function() { rootHadErrback = true; }); |
| var branch2 = d.branch(true); |
| |
| assertTrue(rootHadCallback); |
| assertFalse(rootHadErrback); |
| |
| branch2.cancel(); |
| assertFalse(branch1HadErrback); |
| assertTrue('Canceling the last active branch should cancel the parent.', |
| rootHadErrback); |
| } |
| |
| function testStaticCanceled() { |
| var callbackCalled = false; |
| var errbackResult = null; |
| |
| var d = goog.async.Deferred.canceled(); |
| d.addCallback(function() { callbackCalled = true;} ); |
| d.addErrback(function(err) { errbackResult = err;} ); |
| |
| assertTrue('Errback should have been called with a canceled error', |
| errbackResult instanceof goog.async.Deferred.CanceledError); |
| assertFalse('Callback should not have been called', callbackCalled); |
| } |
| |
| function testWhenWithValues() { |
| var called = false; |
| Deferred.when(4, function(obj) { |
| called = true; |
| assertEquals(4, obj); |
| }); |
| assertTrue('Fn should have been called', called); |
| } |
| |
| function testWhenWithDeferred() { |
| var called = false; |
| |
| var d = new Deferred(); |
| Deferred.when(d, function(obj) { |
| called = true; |
| assertEquals(6, obj); |
| }); |
| assertFalse('Fn should not have been called yet', called); |
| d.callback(6); |
| assertTrue('Fn should have been called', called); |
| } |
| |
| function testWhenDoesntAlterOriginalChain() { |
| var calls = 0; |
| |
| var d1 = new Deferred(); |
| var d2 = Deferred.when(d1, function(obj) { |
| calls++; |
| return obj * 2; |
| }); |
| d1.addCallback(function(obj) { |
| assertEquals('Original chain should get original value', 5, obj); |
| calls++; |
| }); |
| d2.addCallback(function(obj) { |
| assertEquals('Branched chain should get modified value', 10, obj); |
| calls++; |
| }); |
| |
| d1.callback(5); |
| |
| assertEquals('There should have been 3 callbacks', 3, calls); |
| } |
| |
| function testAssertNoErrors() { |
| var d = new Deferred(); |
| d.addCallback(function() { |
| throw new Error('Foo'); |
| }); |
| d.callback(1); |
| |
| var ex = assertThrows(function() { |
| Deferred.assertNoErrors(); |
| neverHappen(); |
| }); |
| assertEquals('Expected to get thrown error', 'Foo', ex.message); |
| |
| assertNotThrows( |
| 'Calling Deferred.assertNoErrors() a second time with only one ' + |
| 'scheduled error should pass.', |
| function() { |
| Deferred.assertNoErrors(); |
| }); |
| } |
| |
| function testThen() { |
| var result; |
| var result2; |
| var d = new Deferred(); |
| assertEquals(d.then, d['then']); |
| d.then(function(r) { |
| return result = r; |
| }).then(function (r2) { |
| result2 = r2; |
| }); |
| d.callback('done'); |
| assertUndefined(result); |
| mockClock.tick(); |
| assertEquals('done', result); |
| assertEquals('done', result2); |
| } |
| |
| function testThen_reject() { |
| var result, error, error2; |
| var d = new Deferred(); |
| assertEquals(d.then, d['then']); |
| d.then(function(r) { |
| result = r; |
| }, function(e) { |
| error = e; |
| }); |
| d.errback(new Error('boom')); |
| assertUndefined(result); |
| mockClock.tick(); |
| assertUndefined(result); |
| assertEquals('boom', error.message); |
| } |
| |
| function testPromiseAll() { |
| var d = new Deferred(); |
| var p = new goog.Promise(function(resolve) { |
| resolve('promise'); |
| }); |
| goog.Promise.all([d, p]).then(function(values) { |
| assertEquals(2, values.length); |
| assertEquals('deferred', values[0]); |
| assertEquals('promise', values[1]); |
| }); |
| d.callback('deferred'); |
| mockClock.tick(); |
| } |
| |
| function testPromiseBlocksDeferred() { |
| var result; |
| var d = new Deferred(); |
| var p = new goog.Promise(function(resolve) { |
| resolve('promise'); |
| }); |
| d.callback(); |
| d.addCallback(function() { |
| return p; |
| }); |
| d.addCallback(function(r) { |
| result = r; |
| }); |
| |
| assertUndefined(result); |
| mockClock.tick(); |
| assertEquals('promise', result); |
| } |
| |
| function testFromPromise() { |
| var result; |
| var p = new goog.Promise(function(resolve) { |
| resolve('promise'); |
| }); |
| var d = Deferred.fromPromise(p); |
| d.addCallback(function(value) { |
| result = value; |
| }); |
| assertUndefined(result); |
| mockClock.tick(); |
| assertEquals('promise', result); |
| } |
| |
| function testPromiseBlocksDeferredAndRejects() { |
| var result; |
| var d = new Deferred(); |
| var p = new goog.Promise(function(resolve, reject) { |
| reject(new Error('error')); |
| }); |
| d.callback(); |
| d.addCallback(function(r) { |
| return p; |
| }); |
| d.addErrback(function(r) { |
| result = r; |
| }); |
| |
| assertUndefined(result); |
| mockClock.tick(); |
| assertEquals('error', result.message); |
| } |
| |
| function testPromiseFromCanceledDeferred() { |
| var result; |
| var d = new Deferred(); |
| d.cancel(); |
| |
| var p = d.then(neverHappen, function(reason) { |
| result = reason; |
| }); |
| |
| mockClock.tick(); |
| assertTrue(result instanceof goog.Promise.CancellationError); |
| } |
| |
| function testThenableInterface() { |
| var d = new Deferred(); |
| assertTrue(goog.Thenable.isImplementedBy(d)); |
| } |
| |
| function testAddBothPropagatesToErrback() { |
| var log = []; |
| var deferred = new goog.async.Deferred(); |
| deferred.addBoth(goog.nullFunction); |
| deferred.addErrback(function () { |
| log.push('errback'); |
| }); |
| deferred.errback(new Error('my error')); |
| |
| mockClock.tick(1); |
| assertArrayEquals(['errback'], log); |
| } |
| |
| function testAddBothDoesNotPropagateUncaughtExceptions() { |
| var deferred = new goog.async.Deferred(); |
| deferred.addBoth(goog.nullFunction); |
| deferred.errback(new Error('my error')); |
| mockClock.tick(1); |
| } |
| |
| function testAddFinally() { |
| var deferred = new goog.async.Deferred(); |
| deferred.addFinally(goog.nullFunction); |
| deferred.errback(new Error('my error')); |
| |
| try { |
| mockClock.tick(1); |
| } catch (e) { |
| assertEquals('my error', e.message); |
| } |
| } |
| </script> |
| </body> |
| </html> |