From 4e9b56881b4d5af40101a0cf2dbed7301065bfc8 Mon Sep 17 00:00:00 2001 From: Lucas Mirelmann Date: Tue, 24 Jun 2014 18:00:18 +0200 Subject: [PATCH] feat(shutdown): Add the ability for an app to shutdown Adds a new `$shutdown` service that can be used to shutdown an app and the `$shutdownProvider` that can be used to register tasks that need to be executed when shutting down an app --- src/.jshintrc | 1 + src/Angular.js | 44 ++++++++++- src/AngularPublic.js | 4 + src/ng/browser.js | 95 +++++++++++++++++++----- src/ng/interval.js | 8 +- src/ng/rootScope.js | 17 +++-- src/ngMock/angular-mocks.js | 82 ++++++++++++++++++-- test/AngularSpec.js | 104 ++++++++++++++++++++++++++ test/ng/browserSpecs.js | 2 +- test/ng/intervalSpec.js | 144 ++++++++++++------------------------ test/ng/rootScopeSpec.js | 6 +- 11 files changed, 370 insertions(+), 137 deletions(-) diff --git a/src/.jshintrc b/src/.jshintrc index 8c32819a6fa9..fde7535b56c4 100644 --- a/src/.jshintrc +++ b/src/.jshintrc @@ -86,6 +86,7 @@ "angularInit": false, "bootstrap": false, "getTestability": false, + "shutdown": false, "snake_case": false, "bindJQuery": false, "assertArg": false, diff --git a/src/Angular.js b/src/Angular.js index bbb603f82f11..7894ed17093a 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -1692,7 +1692,7 @@ function bootstrap(element, modules, config) { modules = modules || []; modules.unshift(['$provide', function($provide) { - $provide.value('$rootElement', element); + $provide.provider('$rootElement', rootElementProviderFactory(element)); }]); if (config.debugInfoEnabled) { @@ -1772,6 +1772,48 @@ function getTestability(rootElement) { return injector.get('$$testability'); } +function rootElementProviderFactory(rootElement) { + return ['$shutdownProvider', function($shutdownProvider) { + $shutdownProvider.register(function() { + if (rootElement.dealoc) { + rootElement.dealoc(); + } else { + rootElement.find('*').removeData(); + rootElement.removeData(); + } + }); + this.$get = function() { + return rootElement; + }; + }]; +} + +function $ShutdownProvider() { + var fns = []; + this.$get = function() { + return function() { + while (fns.length) { + var fn = fns.shift(); + fn(); + } + }; + }; + + this.register = function(fn) { + fns.push(fn); + }; +} + +function shutdown(element) { + var injector; + + injector = angular.element(element).injector(); + if (!injector) { + throw ngMinErr('shtdwn', 'Element not part of an app'); + } + injector.get('$shutdown')(); +} + var SNAKE_CASE_REGEXP = /[A-Z]/g; function snake_case(name, separator) { separator = separator || '_'; diff --git a/src/AngularPublic.js b/src/AngularPublic.js index 43fbe048c08e..5f012153f68a 100644 --- a/src/AngularPublic.js +++ b/src/AngularPublic.js @@ -86,6 +86,7 @@ $$SanitizeUriProvider, $SceProvider, $SceDelegateProvider, + $ShutdownProvider, $SnifferProvider, $TemplateCacheProvider, $TemplateRequestProvider, @@ -125,6 +126,7 @@ var version = { function publishExternalAPI(angular) { extend(angular, { 'bootstrap': bootstrap, + 'shutdown': shutdown, 'copy': copy, 'extend': extend, 'merge': merge, @@ -160,6 +162,8 @@ function publishExternalAPI(angular) { angularModule('ng', ['ngLocale'], ['$provide', function ngModule($provide) { + // $shutdown provider needs to be first as other providers might use it. + $provide.provider('$shutdown', $ShutdownProvider); // $$sanitizeUriProvider needs to be before $compileProvider as it is used by it. $provide.provider({ $$sanitizeUri: $$SanitizeUriProvider diff --git a/src/ng/browser.js b/src/ng/browser.js index 4a1d21bcc69e..5d5a8b9fc6b4 100644 --- a/src/ng/browser.js +++ b/src/ng/browser.js @@ -28,7 +28,11 @@ function Browser(window, document, $log, $sniffer) { history = window.history, setTimeout = window.setTimeout, clearTimeout = window.clearTimeout, - pendingDeferIds = {}; + setInterval = window.setInterval, + clearInterval = window.clearInterval, + pendingDeferIds = {}, + currentIntervalIds = {}, + active = true; self.isMock = false; @@ -79,6 +83,19 @@ function Browser(window, document, $log, $sniffer) { } }; + self.shutdown = function() { + active = false; + forEach(currentIntervalIds, function(ignore, intervalId) { + delete currentIntervalIds[intervalId]; + clearInterval(+intervalId); + }); + forEach(pendingDeferIds, function(ignore, timeoutId) { + delete pendingDeferIds[timeoutId]; + clearTimeout(+timeoutId); + }); + jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); + }; + ////////////////////////////////////////////////////////////// // URL API ////////////////////////////////////////////////////////////// @@ -270,16 +287,6 @@ function Browser(window, document, $log, $sniffer) { return callback; }; - /** - * @private - * Remove popstate and hashchange handler from window. - * - * NOTE: this api is intended for use only by $rootScope. - */ - self.$$applicationDestroyed = function() { - jqLite(window).off('hashchange popstate', cacheStateAndFireUrlChange); - }; - /** * Checks whether the url has changed outside of Angular. * Needs to be exported to be able to check for changes that have been done in sync, @@ -321,12 +328,16 @@ function Browser(window, document, $log, $sniffer) { */ self.defer = function(fn, delay) { var timeoutId; - outstandingRequestCount++; - timeoutId = setTimeout(function() { - delete pendingDeferIds[timeoutId]; - completeOutstandingRequest(fn); - }, delay || 0); - pendingDeferIds[timeoutId] = true; + if (active) { + outstandingRequestCount++; + timeoutId = setTimeout(function() { + delete pendingDeferIds[timeoutId]; + completeOutstandingRequest(fn); + }, delay || 0); + pendingDeferIds[timeoutId] = true; + } else { + timeoutId = 0; + } return timeoutId; }; @@ -351,11 +362,57 @@ function Browser(window, document, $log, $sniffer) { return false; }; + + /** + * @name $browser#interval + * @param {function()} fn A function, whose execution should be executed. + * @param {number=} interval Interval in milliseconds on how often to execute the function. + * @returns {*} IntervalId that can be used to cancel the task via `$browser.interval.cancel()`. + * + * @description + * Executes a fn asynchronously via `setInterval(fn, interval)`. + * + */ + self.interval = function(fn, interval) { + var intervalId; + if (active) { + intervalId = setInterval(fn, interval); + currentIntervalIds[intervalId] = true; + } else { + intervalId = 0; + } + return intervalId; + }; + + + /** + * @name $browser#interval.cancel + * + * @description + * Cancels an interval task identified with `intervalId`. + * + * @param {*} intervalId Token returned by the `$browser.interval` function. + * @returns {boolean} Returns `true` if the task was successfully canceled, and + * `false` if the task was already canceled. + */ + self.interval.cancel = function(intervalId) { + if (currentIntervalIds[intervalId]) { + delete currentIntervalIds[intervalId]; + clearInterval(intervalId); + return true; + } + return false; + }; } -function $BrowserProvider() { +function $BrowserProvider($shutdownProvider) { + var browser; + + $shutdownProvider.register(function() { if (browser) { browser.shutdown(); } }); this.$get = ['$window', '$log', '$sniffer', '$document', function($window, $log, $sniffer, $document) { - return new Browser($window, $document, $log, $sniffer); + return browser = new Browser($window, $document, $log, $sniffer); }]; } + +$BrowserProvider.$inject = ['$shutdownProvider']; diff --git a/src/ng/interval.js b/src/ng/interval.js index 36b655e2ce65..2c78506be02e 100644 --- a/src/ng/interval.js +++ b/src/ng/interval.js @@ -135,8 +135,6 @@ function $IntervalProvider() { function interval(fn, delay, count, invokeApply) { var hasParams = arguments.length > 4, args = hasParams ? sliceArgs(arguments, 4) : [], - setInterval = $window.setInterval, - clearInterval = $window.clearInterval, iteration = 0, skipApply = (isDefined(invokeApply) && !invokeApply), deferred = (skipApply ? $$q : $q).defer(), @@ -144,7 +142,7 @@ function $IntervalProvider() { count = isDefined(count) ? count : 0; - promise.$$intervalId = setInterval(function tick() { + promise.$$intervalId = $browser.interval(function tick() { if (skipApply) { $browser.defer(callback); } else { @@ -154,7 +152,7 @@ function $IntervalProvider() { if (count > 0 && iteration >= count) { deferred.resolve(iteration); - clearInterval(promise.$$intervalId); + $browser.interval.cancel(promise.$$intervalId); delete intervals[promise.$$intervalId]; } @@ -191,7 +189,7 @@ function $IntervalProvider() { // Interval cancels should not report as unhandled promise. intervals[promise.$$intervalId].promise.catch(noop); intervals[promise.$$intervalId].reject('canceled'); - $window.clearInterval(promise.$$intervalId); + $browser.interval.cancel(promise.$$intervalId); delete intervals[promise.$$intervalId]; return true; } diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index eddcf61da4d6..a3dca8b58f7a 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -67,11 +67,13 @@ * They also provide event emission/broadcast and subscription facility. See the * {@link guide/scope developer guide on scopes}. */ -function $RootScopeProvider() { +$RootScopeProvider.$inject = ['$shutdownProvider']; +function $RootScopeProvider($shutdownProvider) { var TTL = 10; var $rootScopeMinErr = minErr('$rootScope'); var lastDirtyWatch = null; var applyAsyncId = null; + var $rootScope; this.digestTtl = function(value) { if (arguments.length) { @@ -94,8 +96,10 @@ function $RootScopeProvider() { return ChildScope; } - this.$get = ['$exceptionHandler', '$parse', '$browser', - function($exceptionHandler, $parse, $browser) { + $shutdownProvider.register(function() { if ($rootScope) { $rootScope.$destroy(); } }); + + this.$get = ['$exceptionHandler', '$parse', '$browser', '$shutdown', + function($exceptionHandler, $parse, $browser, $shutdown) { function destroyChildScope($event) { $event.currentScope.$$destroyed = true; @@ -907,8 +911,7 @@ function $RootScopeProvider() { this.$$destroyed = true; if (this === $rootScope) { - //Remove handlers attached to window when $rootScope is removed - $browser.$$applicationDestroyed(); + $shutdown(); } incrementWatchersCount(this, -this.$$watchersCount); @@ -1308,7 +1311,7 @@ function $RootScopeProvider() { } }; - var $rootScope = new Scope(); + $rootScope = new Scope(); //The internal queues. Expose them on the $rootScope for debugging/testing purposes. var asyncQueue = $rootScope.$$asyncQueue = []; @@ -1374,3 +1377,5 @@ function $RootScopeProvider() { } }]; } + + diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index be9e064257d3..378935006fd3 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -23,12 +23,17 @@ angular.mock = {}; * The api of this service is the same as that of the real {@link ng.$browser $browser}, except * that there are several helper methods available which can be used in tests. */ -angular.mock.$BrowserProvider = function() { +angular.mock.$BrowserProvider = function($shutdownProvider) { + var browser; + + $shutdownProvider.register(function() { if (browser) { browser.shutdown(); } }); this.$get = function() { - return new angular.mock.$Browser(); + return browser = new angular.mock.$Browser(); }; }; +angular.mock.$BrowserProvider.$inject = ['$shutdownProvider']; + angular.mock.$Browser = function() { var self = this; @@ -58,7 +63,6 @@ angular.mock.$Browser = function() { return listener; }; - self.$$applicationDestroyed = angular.noop; self.$$checkUrlChange = angular.noop; self.deferredFns = []; @@ -135,6 +139,72 @@ angular.mock.$Browser = function() { self.baseHref = function() { return this.$$baseHref; }; + + self.interval = function(fn, interval) { + self.interval.repeatFns.push({ + nextTime:(self.interval.now + interval), + delay: interval, + fn: fn, + id: self.interval.nextRepeatId + }); + self.interval.repeatFns.sort(function(a,b) { return a.nextTime - b.nextTime;}); + + return self.interval.nextRepeatId++; + }; + + self.interval.cancel = function(id) { + var fnIndex; + + angular.forEach(self.interval.repeatFns, function(fn, index) { + if (fn.id === id) fnIndex = index; + }); + + if (fnIndex !== undefined) { + self.interval.repeatFns.splice(fnIndex, 1); + return true; + } + + return false; + }; + + self.interval.flush = function(delay) { + var nextTime; + + if (angular.isDefined(delay)) { + // A delay was passed so compute the next time + nextTime = self.interval.now + delay; + } else { + if (self.interval.repeatFns.length) { + // No delay was passed so set the next time so that all the existing intervals run at least once. + nextTime = self.repeatFns[self.repeatFns.length - 1].nextTime; + delay = nextTime - self.interval.now; + } else { + // No delay passed, but there are no interval tasks so flush - indicates an error! + throw new Error('No interval tasks to be flushed'); + } + } + + while (self.interval.repeatFns.length && self.interval.repeatFns[0].nextTime <= nextTime) { + // Increment the time and call the next deferred function + self.interval.now = self.interval.repeatFns[0].nextTime; + var task = self.interval.repeatFns[0]; + task.fn(); + task.nextTime += task.delay; + self.interval.repeatFns.sort(function(a,b) { return a.nextTime - b.nextTime;}); + } + + // Ensure that the current time is correct + self.interval.now = nextTime; + + return delay; + }; + + self.interval.repeatFns = []; + self.interval.nextRepeatId = 0; + self.interval.now = 0; + + self.shutdown = angular.noop; + }; angular.mock.$Browser.prototype = { @@ -2911,10 +2981,8 @@ angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { } angular.element.cleanData(cleanUpNodes); - // Ensure `$destroy()` is available, before calling it - // (a mocked `$rootScope` might not implement it (or not even be an object at all)) - var $rootScope = injector.get('$rootScope'); - if ($rootScope && $rootScope.$destroy) $rootScope.$destroy(); + var $shutdown = injector.get('$shutdown'); + if ($shutdown) $shutdown(); } // clean up jquery's fragment cache diff --git a/test/AngularSpec.js b/test/AngularSpec.js index ec334690019d..efc7f877e5c8 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -1671,6 +1671,110 @@ describe('angular', function() { }); }); + describe('shutdown', function() { + it('should be able to shutdown an angular app', function() { + var element = jqLite('
bootstrap me!
'); + angular.bootstrap(element); + + angular.shutdown(element); + expect(element.data('$injector')).toBeUndefined(); + expect(element.text()).toBe('bootstrap me!'); + }); + + it('should be able to shutdown on any element that is part of the app', function() { + var element = jqLite('
Bootstrap me!
'); + + angular.bootstrap(element); + angular.shutdown(element.find('span')); + expect(element.data('$injector')).toBeUndefined(); + expect(element.text()).toBe('Bootstrap me!'); + }); + + it('should throw if trying to shutdown an element that is not part of an app', function() { + var element = jqLite('
Not an app
'); + + expect(function() { + angular.shutdown(element); + }).toThrowMinErr('ng', 'shtdwn', 'Element not part of an app'); + }); + + it('should preserve the html as it is', function() { + var element = jqLite('
Hello WorldDo not see me
'); + + angular.bootstrap(element); + angular.shutdown(element); + expect(element.text()).toBe('Hello World'); + }); + + it('should remove all the data from all the elements', function() { + var element = jqLite('
Bootstrap me!
'); + + angular.bootstrap(element); + element.find('span').data('foo', 'bar'); + angular.shutdown(element); + expect(element.find('span').data('foo')).toBeUndefined(); + }); + + describe('setTimeout and setInterval shutdown', function() { + var setTimeoutSpy, setInternalSpy; + beforeEach(inject(function($window) { + spyOn($window, 'setInterval').and.returnValue(42); + spyOn($window, 'clearInterval'); + spyOn($window, 'setTimeout'); + })); + + it('should stop calling the window setTimeout', inject(function($window) { + var element = jqLite('
Bootstrap me!
'), + injector = angular.bootstrap(element), + $timeout = injector.get('$timeout'); + + $timeout(function() {}); + expect($window.setTimeout).toHaveBeenCalledOnce(); + angular.shutdown(element); + $timeout(function() {}); + expect($window.setTimeout).toHaveBeenCalledOnce(); + })); + + it('should cancel all active interval calls', inject(function($window) { + var element = jqLite('
Bootstrap me!
'), + injector = angular.bootstrap(element), + $interval = injector.get('$interval'); + + $interval(function() {}, 100); + expect($window.setInterval).toHaveBeenCalledOnce(); + expect($window.clearInterval).not.toHaveBeenCalled(); + angular.shutdown(element); + expect($window.clearInterval).toHaveBeenCalledOnce(); + expect($window.clearInterval.calls.mostRecent().args[0]).toBe(42); + })); + + it('should not cancel intervals that were already canceled', inject(function($window) { + var element = jqLite('
Bootstrap me!
'), + injector = angular.bootstrap(element), + $interval = injector.get('$interval'), + intervalPromise = $interval(function() {}, 100); + + $interval.cancel(intervalPromise); + expect($window.setInterval).toHaveBeenCalledOnce(); + expect($window.clearInterval).toHaveBeenCalledOnce(); + angular.shutdown(element); + expect($window.setInterval).toHaveBeenCalledOnce(); + expect($window.clearInterval).toHaveBeenCalledOnce(); + })); + }); + + it('should destroy the root scope', function() { + var element = jqLite('
Bootstrap me!
'), + injector = angular.bootstrap(element), + rootScope = injector.get('$rootScope'), + listener = jasmine.createSpy('listener'); + + rootScope.$on('$destroy', listener); + angular.shutdown(element); + expect(listener).toHaveBeenCalled(); + }); + }); + describe('angular service', function() { it('should override services', function() { diff --git a/test/ng/browserSpecs.js b/test/ng/browserSpecs.js index 9140bcb71e03..2664824ee55b 100755 --- a/test/ng/browserSpecs.js +++ b/test/ng/browserSpecs.js @@ -738,7 +738,7 @@ describe('browser', function() { browser.onUrlChange(callback); fakeWindow.location.href = 'http://server/new'; - browser.$$applicationDestroyed(); + browser.shutdown(); fakeWindow.fire('popstate'); expect(callback).not.toHaveBeenCalled(); diff --git a/test/ng/intervalSpec.js b/test/ng/intervalSpec.js index 300cef7e1f2e..73cf2cdb06ae 100644 --- a/test/ng/intervalSpec.js +++ b/test/ng/intervalSpec.js @@ -4,109 +4,63 @@ describe('$interval', function() { /* global $IntervalProvider: false */ beforeEach(module(function($provide) { - var repeatFns = [], - nextRepeatId = 0, - now = 0, - $window; - - $window = { - setInterval: function(fn, delay, count) { - repeatFns.push({ - nextTime:(now + delay), - delay: delay, - fn: fn, - id: nextRepeatId - }); - repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;}); - - return nextRepeatId++; - }, - - clearInterval: function(id) { - var fnIndex; - - angular.forEach(repeatFns, function(fn, index) { - if (fn.id === id) fnIndex = index; - }); - - if (isDefined(fnIndex)) { - repeatFns.splice(fnIndex, 1); - return true; - } - - return false; - }, - - flush: function(millis) { - now += millis; - while (repeatFns.length && repeatFns[0].nextTime <= now) { - var task = repeatFns[0]; - task.fn(); - task.nextTime += task.delay; - repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;}); - } - return millis; - } - }; - $provide.provider('$interval', $IntervalProvider); - $provide.value('$window', $window); })); - it('should run tasks repeatedly', inject(function($interval, $window) { + it('should run tasks repeatedly', inject(function($interval, $browser) { var counter = 0; $interval(function() { counter++; }, 1000); expect(counter).toBe(0); - $window.flush(1000); + $browser.interval.flush(1000); expect(counter).toBe(1); - $window.flush(1000); + $browser.interval.flush(1000); expect(counter).toBe(2); })); it('should call $apply after each task is executed', - inject(function($interval, $rootScope, $window) { + inject(function($interval, $rootScope, $browser) { var applySpy = spyOn($rootScope, '$apply').and.callThrough(); $interval(noop, 1000); expect(applySpy).not.toHaveBeenCalled(); - $window.flush(1000); + $browser.interval.flush(1000); expect(applySpy).toHaveBeenCalledOnce(); applySpy.calls.reset(); $interval(noop, 1000); $interval(noop, 1000); - $window.flush(1000); + $browser.interval.flush(1000); expect(applySpy).toHaveBeenCalledTimes(3); })); it('should NOT call $apply if invokeApply is set to false', - inject(function($interval, $rootScope, $window) { + inject(function($interval, $rootScope, $browser) { var applySpy = spyOn($rootScope, '$apply').and.callThrough(); $interval(noop, 1000, 0, false); expect(applySpy).not.toHaveBeenCalled(); - $window.flush(2000); + $browser.interval.flush(2000); expect(applySpy).not.toHaveBeenCalled(); })); it('should NOT call $evalAsync or $digest if invokeApply is set to false', - inject(function($interval, $rootScope, $window, $timeout) { + inject(function($interval, $rootScope, $browser, $timeout) { var evalAsyncSpy = spyOn($rootScope, '$evalAsync').and.callThrough(); var digestSpy = spyOn($rootScope, '$digest').and.callThrough(); var notifySpy = jasmine.createSpy('notify'); $interval(notifySpy, 1000, 1, false); - $window.flush(2000); + $browser.interval.flush(2000); $timeout.flush(); // flush $browser.defer() timeout expect(notifySpy).toHaveBeenCalledOnce(); @@ -128,50 +82,50 @@ describe('$interval', function() { }); }); - inject(function($interval, $window) { + inject(function($interval, $browser) { var counter = 0; $interval(function() { counter++; }, 1000); expect(counter).toBe(0); - $window.flush(1000); + $browser.interval.flush(1000); expect(counter).toBe(1); - $window.flush(1000); + $browser.interval.flush(1000); expect(counter).toBe(2); }); }); - it('should allow you to specify the delay time', inject(function($interval, $window) { + it('should allow you to specify the delay time', inject(function($interval, $browser) { var counter = 0; $interval(function() { counter++; }, 123); expect(counter).toBe(0); - $window.flush(122); + $browser.interval.flush(122); expect(counter).toBe(0); - $window.flush(1); + $browser.interval.flush(1); expect(counter).toBe(1); })); - it('should allow you to specify a number of iterations', inject(function($interval, $window) { + it('should allow you to specify a number of iterations', inject(function($interval, $browser) { var counter = 0; $interval(function() {counter++;}, 1000, 2); - $window.flush(1000); + $browser.interval.flush(1000); expect(counter).toBe(1); - $window.flush(1000); + $browser.interval.flush(1000); expect(counter).toBe(2); - $window.flush(1000); + $browser.interval.flush(1000); expect(counter).toBe(2); })); - it('should allow you to specify a number of arguments', inject(function($interval, $window) { + it('should allow you to specify a number of arguments', inject(function($interval, $browser) { var task1 = jasmine.createSpy('task1'), task2 = jasmine.createSpy('task2'), task3 = jasmine.createSpy('task3'); @@ -179,7 +133,7 @@ describe('$interval', function() { $interval(task2, 1000, 2, true, 'Task2'); $interval(task3, 1000, 2, true, 'I', 'am', 'a', 'Task3', 'spy'); - $window.flush(1000); + $browser.interval.flush(1000); expect(task1).toHaveBeenCalledWith('Task1'); expect(task2).toHaveBeenCalledWith('Task2'); expect(task3).toHaveBeenCalledWith('I', 'am', 'a', 'Task3', 'spy'); @@ -188,7 +142,7 @@ describe('$interval', function() { task2.calls.reset(); task3.calls.reset(); - $window.flush(1000); + $browser.interval.flush(1000); expect(task1).toHaveBeenCalledWith('Task1'); expect(task2).toHaveBeenCalledWith('Task2'); expect(task3).toHaveBeenCalledWith('I', 'am', 'a', 'Task3', 'spy'); @@ -197,7 +151,7 @@ describe('$interval', function() { it('should return a promise which will be updated with the count on each iteration', - inject(function($interval, $window) { + inject(function($interval, $browser) { var log = [], promise = $interval(function() { log.push('tick'); }, 1000); @@ -206,16 +160,16 @@ describe('$interval', function() { function(note) { log.push('promise update: ' + note); }); expect(log).toEqual([]); - $window.flush(1000); + $browser.interval.flush(1000); expect(log).toEqual(['tick', 'promise update: 0']); - $window.flush(1000); + $browser.interval.flush(1000); expect(log).toEqual(['tick', 'promise update: 0', 'tick', 'promise update: 1']); })); it('should return a promise which will be resolved after the specified number of iterations', - inject(function($interval, $window) { + inject(function($interval, $browser) { var log = [], promise = $interval(function() { log.push('tick'); }, 1000, 2); @@ -224,9 +178,9 @@ describe('$interval', function() { function(note) { log.push('promise update: ' + note); }); expect(log).toEqual([]); - $window.flush(1000); + $browser.interval.flush(1000); expect(log).toEqual(['tick', 'promise update: 0']); - $window.flush(1000); + $browser.interval.flush(1000); expect(log).toEqual([ 'tick', 'promise update: 0', 'tick', 'promise update: 1', 'promise success: 2' @@ -242,39 +196,39 @@ describe('$interval', function() { it('should delegate exception to the $exceptionHandler service', inject( - function($interval, $exceptionHandler, $window) { + function($interval, $exceptionHandler, $browser) { $interval(function() { throw "Test Error"; }, 1000); expect($exceptionHandler.errors).toEqual([]); - $window.flush(1000); + $browser.interval.flush(1000); expect($exceptionHandler.errors).toEqual(["Test Error"]); - $window.flush(1000); + $browser.interval.flush(1000); expect($exceptionHandler.errors).toEqual(["Test Error", "Test Error"]); })); it('should call $apply even if an exception is thrown in callback', inject( - function($interval, $rootScope, $window) { + function($interval, $rootScope, $browser) { var applySpy = spyOn($rootScope, '$apply').and.callThrough(); $interval(function() { throw "Test Error"; }, 1000); expect(applySpy).not.toHaveBeenCalled(); - $window.flush(1000); + $browser.interval.flush(1000); expect(applySpy).toHaveBeenCalled(); })); it('should still update the interval promise when an exception is thrown', - inject(function($interval, $window) { + inject(function($interval, $browser) { var log = [], promise = $interval(function() { throw "Some Error"; }, 1000); promise.then(function(value) { log.push('promise success: ' + value); }, function(err) { log.push('promise error: ' + err); }, function(note) { log.push('promise update: ' + note); }); - $window.flush(1000); + $browser.interval.flush(1000); expect(log).toEqual(['promise update: 0']); })); @@ -282,7 +236,7 @@ describe('$interval', function() { describe('cancel', function() { - it('should cancel tasks', inject(function($interval, $window) { + it('should cancel tasks', inject(function($interval, $browser) { var task1 = jasmine.createSpy('task1', 1000), task2 = jasmine.createSpy('task2', 1000), task3 = jasmine.createSpy('task3', 1000), @@ -294,7 +248,7 @@ describe('$interval', function() { $interval.cancel(promise3); $interval.cancel(promise1); - $window.flush(1000); + $browser.interval.flush(1000); expect(task1).not.toHaveBeenCalled(); expect(task2).toHaveBeenCalledOnce(); @@ -302,7 +256,7 @@ describe('$interval', function() { })); - it('should cancel the promise', inject(function($interval, $rootScope, $window) { + it('should cancel the promise', inject(function($interval, $rootScope, $browser) { var promise = $interval(noop, 1000), log = []; promise.then(function(value) { log.push('promise success: ' + value); }, @@ -310,9 +264,9 @@ describe('$interval', function() { function(note) { log.push('promise update: ' + note); }); expect(log).toEqual([]); - $window.flush(1000); + $browser.interval.flush(1000); $interval.cancel(promise); - $window.flush(1000); + $browser.interval.flush(1000); $rootScope.$apply(); // For resolving the promise - // necessary since q uses $rootScope.evalAsync. @@ -321,13 +275,13 @@ describe('$interval', function() { it('should return true if a task was successfully canceled', - inject(function($interval, $window) { + inject(function($interval, $browser) { var task1 = jasmine.createSpy('task1'), task2 = jasmine.createSpy('task2'), promise1, promise2; promise1 = $interval(task1, 1000, 1); - $window.flush(1000); + $browser.interval.flush(1000); promise2 = $interval(task2, 1000, 1); expect($interval.cancel(promise1)).toBe(false); @@ -341,19 +295,19 @@ describe('$interval', function() { })); }); - describe('$window delegation', function() { - it('should use $window.setInterval instead of the global function', inject(function($interval, $window) { - var setIntervalSpy = spyOn($window, 'setInterval'); + describe('$browser delegation', function() { + it('should use $borwser.interval instead of the global function', inject(function($interval, $browser) { + var setIntervalSpy = spyOn($browser, 'interval'); $interval(noop, 1000); expect(setIntervalSpy).toHaveBeenCalled(); })); - it('should use $window.clearInterval instead of the global function', inject(function($interval, $window) { - var clearIntervalSpy = spyOn($window, 'clearInterval'); + it('should use $browser.interval.cancel instead of the global function', inject(function($interval, $browser) { + var clearIntervalSpy = spyOn($browser.interval, 'cancel'); $interval(noop, 1000, 1); - $window.flush(1000); + $browser.interval.flush(1000); expect(clearIntervalSpy).toHaveBeenCalled(); clearIntervalSpy.calls.reset(); diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index 70e565b7b434..019ee6b6eb9a 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -1075,10 +1075,10 @@ describe('Scope', function() { })); - it('should call $browser.$$applicationDestroyed when destroying rootScope', inject(function($rootScope, $browser) { - spyOn($browser, '$$applicationDestroyed'); + it('should call shutdown when destroying rootScope', inject(function($rootScope, $browser) { + spyOn($browser, 'shutdown'); $rootScope.$destroy(); - expect($browser.$$applicationDestroyed).toHaveBeenCalledOnce(); + expect($browser.shutdown).toHaveBeenCalledOnce(); }));