8000 fix(ngMock): don't break if `$rootScope.$destroy()` is not a function by gkalpak · Pull Request #14107 · angular/angular.js · GitHub
[go: up one dir, main page]

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 1 commit
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
Next Next commit
fix(ngMock): don't break if $rootScope.$destroy() is not a function
Previously, `angular-mocks` was calling `$rootScope.$destroy()` after each test as part of it's
cleaning up, assuming that it was always available. This could break if `$rootScope` was mocked
and the mocked version didn't provide the `$destroy()` method.
This commit prevents the error by first checking that `$rootScope.$destroy` is present.

Fixes #14106
  • Loading branch information
gkalpak committed Feb 22, 2016
commit 60811a2a5b48dd915bd67ebefe65c129787229da
3 changes: 2 additions & 1 deletion src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -2612,7 +2612,8 @@ if (window.jasmine || window.mocha) {
}
angular.element.cleanData(cleanUpNodes);

injector.get('$rootScope').$destroy();
var $rootScope = injector.get('$rootScope');
if ($rootScope.$destroy) $rootScope.$destroy();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add a comment about this being possibly undefined due to mocking?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

// clean up jquery's fragment cache
Expand Down
209 changes: 116 additions & 93 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,154 @@ 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;
});
});
return $delegate;
});
});

// Inject the `$rootElement` to ensure it has been created
inject(function($rootElement) {
expect($rootElement.injector()).toBeDefined();
});
}
);

// 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();
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)

// 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();
// 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;


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();
})
);


// Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places
inject(function() {});
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('without `$destroy()` method', 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

// Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places
inject(function() {});
module({$rootScope: {}});

// Ensure the `$injector` is created - if there is no `$injector`, no clean-up takes places
inject(function() {});
});
});
});
});
0