Reputation: 809
I am trying to test the next angular directive. It listens to ui-route $stateChange event to show a progress indicator.
angular.module('sm.components.progress', ['ui.router'])
.directive('smProgress', function($rootScope) {
return {
restrict: 'E',
replace: true,
template: '<div class="in-progress" ng-if="isRouteLoading">' +
'<span>.</span><span>.</span><span>.</span>' +
'<div/>',
controller: function($scope) {
$scope.isRouteLoading = false;
$rootScope.$on('$stateChangeStart',
function() {
$scope.isRouteLoading = true;
});
$rootScope.$on('$stateChangeSuccess',
function() {
$scope.isRouteLoading = false;
});
}
};
});
This is my test code:
describe('smProgress', function() {
var $compile, $rootScope, element, scope;
beforeEach(module('sm.components.progress'));
beforeEach(inject(function(_$compile_, _$rootScope_) {
$compile = _$compile_;
$rootScope = _$rootScope_;
scope = $rootScope.$new();
}));
afterEach(function() {
element.remove();
});
it('$rootScope.isRouteLoading should be false on start',
function() {
element = $compile('<sm-progress></sm-progress>')(scope);
scope.$digest();
expect(scope.isRouteLoading).toBe(false);
$rootScope.$emit('$stateChangeStart');
//The directive listen to the $stateChangeStart and
//scope.isRouteLoading become true as expected
expect(scope.isRouteLoading).toBe(true);
scope.$digest();
// checks if the template is rendered when ng-if evaluates true
// how?
}));
});
The ng-if in the template start evaluating to false
, so the element is removed from the DOM. When I emit manually the $stateChangeStart
event, the directive listener works, but I can't find the element in the DOM.
How can I check if the template is added to then DOM when the ng-if evaluates to true
again?
Upvotes: 3
Views: 5362
Reputation: 809
Well, This is the solution I found.
Is important to note two things:
true
.This is what happens:
In the test, when the directive is compiled:
element = $compile('<sm-progress></sm-progress>')(scope);
<sm-progress></sm-progress>
is replaced by:
<div class="in-progress" ng-if="isRouteLoading">
<span>.</span><span>.</span><span>.</span>
<div/>
at the same time, ng-if is false, so the final rendered code is just a comment. This is how ng-if works naturally.
<!-- ngIf: isRouteLoading -->
When $stateChangeStart is fired, scope.isRouteLoading goes to true
, that makes ng-if reinsert the template to the DOM. But in that point element is equal to:
<!-- ngIf: isRouteLoading -->
Is impossible to find inside of it the directive template.
// THE TEST FAILS
var progressEl = angular.element(element).find('in-progress');
expect(progressEl.length).toBeGreaterThan(0);
The solution is to wrap the html text in a div or any other element. So ng-if will operate inside of it and we can inspect what happen then.
it('scope.isRouteLoading should be false on start', function() {
// wrap sm-progress with a div tag
element = $compile('<div><sm-progress></sm-progress></div>')(scope);
scope.$digest();
expect(scope.isRouteLoading).toBe(false);
// In this point element compiles to:
// <div class="ng-scope"><!-- ngIf: isRouteLoading --></div>
$rootScope.$emit('$stateChangeStart');
expect(scope.isRouteLoading).toBe(true);
scope.$digest();
// Now element compiles to:
// <div class="ng-scope">
// <!-- ngIf: isRouteLoading -->
// <div class="sm-progress ng-scope" ng-if="isRouteLoading">
// <span>.</span><span>.</span><span>.</span>
// </div>
// <!-- end ngIf: isRouteLoading -->
// </div>
// Now the test pass.
var progressEl = angular.element(element).find('.in-progress');
expect(progressEl.length).toBeGreaterThan(0); //element is present
});
A jsfiddle.net with the code working http://jsfiddle.net/fwxgyjwc/13/
I hope I have explained well enough. My English is not very good.
Upvotes: 9