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

feat(ngMock): decorator that adds Scope#$countChildScopes and Scope#$countWatchers #9926

Closed
wants to merge 1 commit into from
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
4 changes: 4 additions & 0 deletions src/ng/rootScope.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ function $RootScopeProvider() {
expect(parent.salutation).toEqual('Hello');
* ```
*
* When interacting with `Scope` in tests, additional helper methods are available on the
* instances of `Scope` type. See {@link ngMock.$rootScope.Scope ngMock Scope} for additional
* details.
*
*
* @param {Object.<string, function()>=} providers Map of service factory which need to be
* provided for the current scope. Defaults to {@link ng}.
Expand Down
87 changes: 87 additions & 0 deletions src/ngMock/angular-mocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -1823,6 +1823,7 @@ angular.module('ngMock', ['ng']).provider({
$provide.decorator('$timeout', angular.mock.$TimeoutDecorator);
$provide.decorator('$$rAF', angular.mock.$RAFDecorator);
$provide.decorator('$$asyncCallback', angular.mock.$AsyncCallbackDecorator);
$provide.decorator('$rootScope', angular.mock.$RootScopeDecorator);
}]);

/**
Expand Down Expand Up @@ -2031,6 +2032,92 @@ angular.mock.e2e.$httpBackendDecorator =
['$rootScope', '$delegate', '$browser', createHttpBackendMock];


/**
* @ngdoc type
* @name $rootScope.Scope
* @module ngMock
* @description
* {@link ng.$rootScope.Scope Scope} type decorated with helper methods useful for testing. These
* methods are automatically available on any {@link ng.$rootScope.Scope Scope} instance when
* `ngMock` module is loaded.
*
* In addition to all the regular `Scope` methods, the following helper methods are available:
*/
angular.mock.$RootScopeDecorator = function($delegate) {

var $rootScopePrototype = Object.getPrototypeOf($delegate);

$rootScopePrototype.$countChildScopes = countChildScopes;
$rootScopePrototype.$countWatchers = countWatchers;

return $delegate;

// ------------------------------------------------------------------------------------------ //

/**
* @ngdoc method
* @name $rootScope.Scope#$countChildScopes
* @module ngMock
* @description
* Counts all the direct and indirect child scopes of the current scope.
*
* The current scope is excluded from the count. The count includes all isolate child scopes.
*
* @returns {number} Total number of child scopes.
*/
function countChildScopes() {
// jshint validthis: true
var count = 0; // exclude the current scope
var pendingChildHeads = [this.$$childHead];
var currentScope;

while (pendingChildHeads.length) {
currentScope = pendingChildHeads.shift();

while (currentScope) {
count += 1;
pendingChildHeads.push(currentScope.$$childHead);
currentScope = currentScope.$$nextSibling;
}
}

return count;
}


/**
* @ngdoc method
* @name $rootScope.Scope#$countWatchers
* @module ngMock
* @description
* Counts all the watchers of direct and indirect child scopes of the current scope.
*
* The watchers of the current scope are included in the count and so are all the watchers of
* isolate child scopes.
*
* @returns {number} Total number of watchers.
*/
function countWatchers() {
// jshint validthis: true
var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope
var pendingChildHeads = [this.$$childHead];
var currentScope;

while (pendingChildHeads.length) {
currentScope = pendingChildHeads.shift();

while (currentScope) {
count += currentScope.$$watchers ? currentScope.$$watchers.length : 0;
pendingChildHeads.push(currentScope.$$childHead);
currentScope = currentScope.$$nextSibling;
}
}

return count;
}
};


if (window.jasmine || window.mocha) {

var currentSpec = null,
Expand Down
168 changes: 168 additions & 0 deletions test/ngMock/angular-mocksSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1507,6 +1507,174 @@ describe('ngMock', function() {
expect($rootElement.text()).toEqual('');
}));
});


describe('$rootScopeDecorator', function() {

describe('$countChildScopes', function() {

it('should return 0 when no child scopes', inject(function($rootScope) {
expect($rootScope.$countChildScopes()).toBe(0);

var childScope = $rootScope.$new();
expect($rootScope.$countChildScopes()).toBe(1);
expect(childScope.$countChildScopes()).toBe(0);

var grandChildScope = childScope.$new();
expect(childScope.$countChildScopes()).toBe(1);
expect(grandChildScope.$countChildScopes()).toBe(0);
}));


it('should correctly navigate complex scope tree', inject(function($rootScope) {
var child;

$rootScope.$new();
$rootScope.$new().$new().$new();
child = $rootScope.$new().$new();
child.$new();
child.$new();
child.$new().$new().$new();

expect($rootScope.$countChildScopes()).toBe(11);
}));


it('should provide the current count even after child destructions', inject(function($rootScope) {
expect($rootScope.$countChildScopes()).toBe(0);

var childScope1 = $rootScope.$new();
expect($rootScope.$countChildScopes()).toBe(1);

var childScope2 = $rootScope.$new();
expect($rootScope.$countChildScopes()).toBe(2);

childScope1.$destroy();
expect($rootScope.$countChildScopes()).toBe(1);

childScope2.$destroy();
expect($rootScope.$countChildScopes()).toBe(0);
}));


it('should work with isolate scopes', inject(function($rootScope) {
/*
RS
|
CIS
/ \
GCS GCIS
*/

var childIsolateScope = $rootScope.$new(true);
expect($rootScope.$countChildScopes()).toBe(1);

var grandChildScope = childIsolateScope.$new();
expect($rootScope.$countChildScopes()).toBe(2);
expect(childIsolateScope.$countChildScopes()).toBe(1);

var grandChildIsolateScope = childIsolateScope.$new(true);
expect($rootScope.$countChildScopes()).toBe(3);
expect(childIsolateScope.$countChildScopes()).toBe(2);

childIsolateScope.$destroy();
expect($rootScope.$countChildScopes()).toBe(0);
}));
});


describe('$countWatchers', function() {

it('should return the sum of watchers for the current scope and all of its children', inject(
function($rootScope) {

expect($rootScope.$countWatchers()).toBe(0);

var childScope = $rootScope.$new();
expect($rootScope.$countWatchers()).toBe(0);

childScope.$watch('foo');
expect($rootScope.$countWatchers()).toBe(1);
expect(childScope.$countWatchers()).toBe(1);

$rootScope.$watch('bar');
childScope.$watch('baz');
expect($rootScope.$countWatchers()).toBe(3);
expect(childScope.$countWatchers()).toBe(2);
}));


it('should correctly navigate complex scope tree', inject(function($rootScope) {
var child;

$rootScope.$watch('foo1');

$rootScope.$new();
$rootScope.$new().$new().$new();

child = $rootScope.$new().$new();
child.$watch('foo2');
child.$new();
child.$new();
child = child.$new().$new().$new();
child.$watch('foo3');
child.$watch('foo4');

expect($rootScope.$countWatchers()).toBe(4);
}));


it('should provide the current count even after child destruction and watch deregistration',
inject(function($rootScope) {

var deregisterWatch1 = $rootScope.$watch('exp1');

var childScope = $rootScope.$new();
childScope.$watch('exp2');

expect($rootScope.$countWatchers()).toBe(2);

childScope.$destroy();
expect($rootScope.$countWatchers()).toBe(1);

deregisterWatch1();
expect($rootScope.$countWatchers()).toBe(0);
}));


it('should work with isolate scopes', inject(function($rootScope) {
/*
RS=1
|
CIS=1
/ \
GCS=1 GCIS=1
*/

$rootScope.$watch('exp1');
expect($rootScope.$countWatchers()).toBe(1);

var childIsolateScope = $rootScope.$new(true);
childIsolateScope.$watch('exp2');
expect($rootScope.$countWatchers()).toBe(2);
expect(childIsolateScope.$countWatchers()).toBe(1);

var grandChildScope = childIsolateScope.$new();
grandChildScope.$watch('exp3');

var grandChildIsolateScope = childIsolateScope.$new(true);
grandChildIsolateScope.$watch('exp4');

expect($rootScope.$countWatchers()).toBe(4);
expect(childIsolateScope.$countWatchers()).toBe(3);
expect(grandChildScope.$countWatchers()).toBe(1);
expect(grandChildIsolateScope.$countWatchers()).toBe(1);

childIsolateScope.$destroy();
expect($rootScope.$countWatchers()).toBe(1);
}));
});
});
});


Expand Down