José María
José María

Reputation: 809

Unit test AngularJS directive with template containing ng-if

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

Answers (1)

Jos&#233; Mar&#237;a
Jos&#233; Mar&#237;a

Reputation: 809

Well, This is the solution I found.

Is important to note two things:

  1. The directive set replace to true.
  2. The template has a ng-if directive.

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

Related Questions