// 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.async.nextTickTest'); goog.setTestOnly('goog.async.nextTickTest'); goog.require('goog.Promise'); goog.require('goog.Timer'); goog.require('goog.async.nextTick'); goog.require('goog.debug.ErrorHandler'); goog.require('goog.debug.entryPointRegistry'); goog.require('goog.dom'); goog.require('goog.dom.TagName'); goog.require('goog.labs.userAgent.browser'); goog.require('goog.testing.MockClock'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.jsunit'); var clock; var propertyReplacer = new goog.testing.PropertyReplacer(); function setUp() { clock = null; } function tearDown() { if (clock) { clock.uninstall(); } // Unset the cached setImmediate_ behavior so it's re-evaluated for each test. goog.async.nextTick.setImmediate_ = undefined; propertyReplacer.reset(); } function testNextTick() { return new goog.Promise(function(resolve, reject) { var c = 0; var max = 100; var async = true; var counterStep = function(i) { async = false; assertEquals('Order correct', i, c); c++; if (c === max) { resolve(); } }; for (var i = 0; i < max; i++) { goog.async.nextTick(goog.partial(counterStep, i)); } assertTrue(async); }); } function testNextTickSetImmediate() { return new goog.Promise(function(resolve, reject) { var c = 0; var max = 100; var async = true; var counterStep = function(i) { async = false; assertEquals('Order correct', i, c); c++; if (c === max) { resolve(); } }; for (var i = 0; i < max; i++) { goog.async.nextTick( goog.partial(counterStep, i), undefined, /* opt_useSetImmediate */ true); } assertTrue(async); }); } function testNextTickContext() { return new goog.Promise(function(resolve, reject) { var context = {}; var c = 0; var max = 10; var async = true; var counterStep = function(i) { async = false; assertEquals('Order correct', i, c); assertEquals(context, this); c++; if (c === max) { resolve(); } }; for (var i = 0; i < max; i++) { goog.async.nextTick(goog.partial(counterStep, i), context); } assertTrue(async); }); } function testNextTickMockClock() { clock = new goog.testing.MockClock(true); var result = ''; goog.async.nextTick(function() { result += 'a'; }); goog.async.nextTick(function() { result += 'b'; }); goog.async.nextTick(function() { result += 'c'; }); assertEquals('', result); clock.tick(0); assertEquals('abc', result); } function testNextTickDoesntSwallowError() { return new goog.Promise(function(resolve, reject) { var sentinel = 'sentinel'; propertyReplacer.replace(window, 'onerror', function(e) { e = '' + e; // Don't test for contents in IE7, which does not preserve the exception // message. if (e.indexOf('Exception thrown and not caught') == -1) { assertContains(sentinel, e); } resolve(); return false; }); goog.async.nextTick(function() { throw sentinel; }); }); } function testNextTickProtectEntryPoint() { return new goog.Promise(function(resolve, reject) { var errorHandlerCallbackCalled = false; var errorHandler = new goog.debug.ErrorHandler(function() { errorHandlerCallbackCalled = true; }); // MS Edge will always use goog.global.setImmediate, so ensure we get // to setImmediate_ here. See useSetImmediate_ implementation for details on // Edge special casing. propertyReplacer.set( goog.async.nextTick, 'useSetImmediate_', function() { return false; }); // This is only testing wrapping the callback with the protected entry // point, so it's okay to replace this function with a fake. propertyReplacer.set(goog.async.nextTick, 'setImmediate_', function(cb) { try { cb(); fail('The callback should have thrown an error.'); } catch (e) { assertTrue(errorHandlerCallbackCalled); assertTrue(e instanceof goog.debug.ErrorHandler.ProtectedFunctionError); } finally { // Restore setImmediate so it doesn't interfere with Promise behavior. propertyReplacer.reset(); } resolve(); }); goog.debug.entryPointRegistry.monitorAll(errorHandler); goog.async.nextTick(function() { throw Error('This should be caught by the protected function.'); }); }); } function testNextTick_notStarvedBySetTimeout() { // This test will timeout when affected by // http://codeforhire.com/2013/09/21/setimmediate-and-messagechannel-broken-on-internet-explorer-10/ // This test would fail without the fix introduced in cl/72472221 // It keeps scheduling 0 timeouts and a single nextTick. If the nextTick // ever fires, the IE specific problem does not occur. var timeout; function busy() { timeout = setTimeout(function() { busy(); }, 0); } busy(); return new goog.Promise(function(resolve, reject) { goog.async.nextTick(function() { if (timeout) { clearTimeout(timeout); } resolve(); }); }); } /** * Test a scenario in which the iframe used by the postMessage polyfill gets a * message that does not have match what is expected. In this case, the polyfill * should not try to invoke a callback (which would result in an error because * there would be no callbacks in the linked list). */ function testPostMessagePolyfillDoesNotPumpCallbackQueueIfMessageIsIncorrect() { // EDGE/IE does not use the postMessage polyfill. if (goog.labs.userAgent.browser.isIE() || goog.labs.userAgent.browser.isEdge()) { return; } // Force postMessage polyfill for setImmediate. propertyReplacer.set(window, 'setImmediate', undefined); propertyReplacer.set(window, 'MessageChannel', undefined); var callbackCalled = false; goog.async.nextTick(function() { callbackCalled = true; }); var frame = goog.dom.getElementsByTagName(goog.dom.TagName.IFRAME)[0]; frame.contentWindow.postMessage( 'bogus message', window.location.protocol + '//' + window.location.host, "*"); var error = null; frame.contentWindow.onerror = function(e) { error = e; }; return goog.Timer.promise(3) .then(function() { assert('Callback should have been called.', callbackCalled); assertNull('An unexpected error was thrown.', error); }) .thenAlways(function() { goog.dom.removeNode(frame); }); } function testBehaviorOnPagesWithOverriddenWindowConstructor() { propertyReplacer.set(goog.global, 'Window', {}); testNextTick(); testNextTickSetImmediate(); testNextTickMockClock(); }