3

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?

1 Answer 1

9

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.

Sign up to request clarification or add additional context in comments.

6 Comments

@PankajParkar have you even test your own code. Don't vote down before you be sure what you are posting. Here I make a test to show you that your code fail again: jsfiddle.net/fwxgyjwc/11. Read my answer carefully.
Your code has another mistake, you put except instead of expect. But this does not matter, because you can't find '.in-progress' class inside of <!-- ngIf: isRouteLoading -->. I think it is pretty easy to understand that.
Did you tried console.log(element.find('.in-progress')) in test case
console.log(element.find('.in-progress')); LOG: {length: 0, prevObject: {0: <!-- ngIf: isRouteLoading -->, length: 1}, context: undefined, selector: '.in-progress'}
Well I already make work my test. So, thanks for expending your time helping me @PankajParkar . :).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.