diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index bedba7f8da1d..09b639d032cb 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -2612,7 +2612,10 @@ if (window.jasmine || window.mocha) { } angular.element.cleanData(cleanUpNodes); - injector.get('$rootScope').$destroy(); + // 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(); } // clean up jquery's fragment cache diff --git a/test/ngMock/angular-mocksSpec.js b/test/ngMock/angular-mocksSpec.js index 1dc2c36b8104..166d10d0dc2c 100644 --- a/test/ngMock/angular-mocksSpec.js +++ b/test/ngMock/angular-mocksSpec.js @@ -1674,25 +1674,6 @@ describe('ngMock', function() { }); - describe('$rootScope', function() { - var destroyed = false; - var oldRootScope; - - it('should destroy $rootScope after each test', inject(function($rootScope) { - $rootScope.$on('$destroy', function() { - destroyed = true; - }); - oldRootScope = $rootScope; - })); - - it('should have destroyed the $rootScope from the previous test', inject(function($rootScope) { - expect(destroyed).toBe(true); - expect($rootScope).not.toBe(oldRootScope); - expect(oldRootScope.$$destroyed).toBe(true); - })); - }); - - describe('$rootScopeDecorator', function() { describe('$countChildScopes', function() { @@ -2418,112 +2399,165 @@ describe('make sure that we can create an injector outside of tests', function() describe('`afterEach` clean-up', function() { - describe('undecorated `$rootElement`', function() { - var prevRootElement; - var prevCleanDataSpy; + describe('`$rootElement`', function() { + describe('undecorated', function() { + var prevRootElement; + var prevCleanDataSpy; - it('should set up spies so the next test can verify `$rootElement` was cleaned up', function() { - module(function($provide) { - $provide.decorator('$rootElement', function($delegate) { - prevRootElement = $delegate; + it('should set up spies for the next test to verify that `$rootElement` was cleaned up', + function() { + module(function($provide) { + $provide.decorator('$rootElement', function($delegate) { + prevRootElement = $delegate; - // Spy on `angular.element.cleanData()`, so the next test can verify - // that it has been called as necessary - prevCleanDataSpy = spyOn(angular.element, 'cleanData').andCallThrough(); + // Spy on `angular.element.cleanData()`, so the next test can verify + // that it has been called as necessary + prevCleanDataSpy = spyOn(angular.element, 'cleanData').andCallThrough(); + + return $delegate; + }); + }); + + // Inject the `$rootElement` to ensure it has been created + inject(function($rootElement) { + expect($rootElement.injector()).toBeDefined(); + }); + } + ); - return $delegate; - }); - }); - // Inject the `$rootElement` to ensure it has been created - inject(function($rootElement) { - expect($rootElement.injector()).toBeDefined(); + it('should clean up `$rootElement` after each test', function() { + // One call is made by `testabilityPatch`'s `dealoc()` + // We want to verify the subsequent call, made by `angular-mocks` + expect(prevCleanDataSpy.callCount).toBe(2); + + var cleanUpNodes = prevCleanDataSpy.calls[1].args[0]; + expect(cleanUpNodes.length).toBe(1); + expect(cleanUpNodes[0]).toBe(prevRootElement[0]); }); }); - it('should clean up `$rootElement` after each test', function() { - // One call is made by `testabilityPatch`'s `dealoc()` - // We want to verify the subsequent call, made by `angular-mocks` - expect(prevCleanDataSpy.callCount).toBe(2); + describe('decorated', function() { + var prevOriginalRootElement; + var prevRootElement; + var prevCleanDataSpy; - var cleanUpNodes = prevCleanDataSpy.calls[1].args[0]; - expect(cleanUpNodes.length).toBe(1); - expect(cleanUpNodes[0]).toBe(prevRootElement[0]); - }); - }); + it('should set up spies for the next text to verify that `$rootElement` was cleaned up', + function() { + module(function($provide) { + $provide.decorator('$rootElement', function($delegate) { + prevOriginalRootElement = $delegate; - describe('decorated `$rootElement`', function() { - var prevOriginalRootElement; - var prevRootElement; - var prevCleanDataSpy; + // Mock `$rootElement` to be able to verify that the correct object is cleaned up + prevRootElement = angular.element('
'); + // Spy on `angular.element.cleanData()`, so the next test can verify + // that it has been called as necessary + prevCleanDataSpy = spyOn(angular.element, 'cleanData').andCallThrough(); - it('should set up spies so the next text can verify `$rootElement` was cleaned up', function() { - module(function($provide) { - $provide.decorator('$rootElement', function($delegate) { - prevOriginalRootElement = $delegate; + return prevRootElement; + }); + }); - // Mock `$rootElement` to be able to verify that the correct object is cleaned up - prevRootElement = angular.element('
'); + // Inject the `$rootElement` to ensure it has been created + inject(function($rootElement) { + expect($rootElement).toBe(prevRootElement); + expect(prevOriginalRootElement.injector()).toBeDefined(); + expect(prevRootElement.injector()).toBeUndefined(); + + // If we don't clean up `prevOriginalRootElement`-related data now, `testabilityPatch` will + // complain about a memory leak, because it doesn't clean up after the original + // `$rootElement` + // This is a false alarm, because `angular-mocks` would have cleaned up in a subsequent + // `afterEach` block + prevOriginalRootElement.removeData(); + }); + } + ); - // Spy on `angular.element.cleanData()`, so the next test can verify - // that it has been called as necessary - prevCleanDataSpy = spyOn(angular.element, 'cleanData').andCallThrough(); - return prevRootElement; - }); - }); + it('should clean up `$rootElement` (both original and decorated) after each test', + function() { + // One call is made by `testabilityPatch`'s `dealoc()` + // We want to verify the subsequent call, made by `angular-mocks` + expect(prevCleanDataSpy.callCount).toBe(2); + + var cleanUpNodes = prevCleanDataSpy.calls[1].args[0]; + expect(cleanUpNodes.length).toBe(2); + expect(cleanUpNodes[0]).toBe(prevOriginalRootElement[0]); + expect(cleanUpNodes[1]).toBe(prevRootElement[0]); + } + ); + }); - // Inject the `$rootElement` to ensure it has been created - inject(function($rootElement) { - expect($rootElement).toBe(prevRootElement); - expect(prevOriginalRootElement.injector()).toBeDefined(); - expect(prevRootElement.injector()).toBeUndefined(); - // If we don't clean up `prevOriginalRootElement`-related data now, `testabilityPatch` will - // complain about a memory leak, because it doesn't clean up after the original - // `$rootElement` - // This is a false alarm, because `angular-mocks` would have cleaned up in a subsequent - // `afterEach` block - prevOriginalRootElement.removeData(); + describe('uninstantiated or falsy', function() { + it('should not break if `$rootElement` was never instantiated', function() { + // Just an empty test to verify that `angular-mocks` doesn't break, + // when trying to clean up `$rootElement`, if `$rootElement` was never injected in the test + // (and thus never instantiated/created) + + // Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places + inject(function() {}); }); - }); - it('should clean up `$rootElement` (both original and decorated) after each test', function() { - // One call is made by `testabilityPatch`'s `dealoc()` - // We want to verify the subsequent call, made by `angular-mocks` - expect(prevCleanDataSpy.callCount).toBe(2); + it('should not break if the decorated `$rootElement` is falsy (e.g. `null`)', function() { + module({$rootElement: null}); - var cleanUpNodes = prevCleanDataSpy.calls[1].args[0]; - expect(cleanUpNodes.length).toBe(2); - expect(cleanUpNodes[0]).toBe(prevOriginalRootElement[0]); - expect(cleanUpNodes[1]).toBe(prevRootElement[0]); + // Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places + inject(function() {}); + }); }); }); - describe('uninstantiated or falsy `$rootElement`', function() { - it('should not break if `$rootElement` was never instantiated', function() { - // Just an empty test to verify that `angular-mocks` doesn't break, - // when trying to clean up `$rootElement`, if `$rootElement` was never injected in the test - // (and thus never instantiated/created) + describe('`$rootScope`', function() { + describe('undecorated', function() { + var prevRootScope; + var prevDestroySpy; - // Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places - inject(function() {}); + + it('should set up spies for the next test to verify that `$rootScope` was cleaned up', + inject(function($rootScope) { + prevRootScope = $rootScope; + prevDestroySpy = spyOn($rootScope, '$destroy').andCallThrough(); + }) + ); + + + it('should clean up `$rootScope` after each test', inject(function($rootScope) { + expect($rootScope).not.toBe(prevRootScope); + expect(prevDestroySpy).toHaveBeenCalledOnce(); + expect(prevRootScope.$$destroyed).toBe(true); + })); }); - it('should not break if the decorated `$rootElement` is falsy (e.g. `null`)', function() { - module(function($provide) { - $provide.value('$rootElement', null); + describe('falsy or without `$destroy()` method', function() { + it('should not break if `$rootScope` is falsy (e.g. `null`)', function() { + // Just an empty test to verify that `angular-mocks` doesn't break, + // when trying to clean up a mocked `$rootScope` set to `null` + + module({$rootScope: null}); + + // Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places + inject(function() {}); }); - // Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places - inject(function() {}); + + it('should not break if `$rootScope.$destroy` is not a function', function() { + // Just an empty test to verify that `angular-mocks` doesn't break, + // when trying to clean up a mocked `$rootScope` without a `$destroy()` method + + module({$rootScope: {}}); + + // Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places + inject(function() {}); + }); }); }); });