Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

fix(ngMock): don't break if $rootScope.$destroy() is not a function #14107

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
218 changes: 126 additions & 92 deletions test/ngMock/angular-mocksSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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('<div></div>');

// 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('<div></div>');
// 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() {});
});
});
});
});