单元测试AngularJS指令,模板包含ng-if

mis*_*iso 3 unit-testing jasmine angularjs

我正在尝试测试下一个角度指令.它监听ui-route $ stateChange事件以显示进度指示器.

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;
        });
    }
  };
});
Run Code Online (Sandbox Code Playgroud)

这是我的测试代码:

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?

  }));

});
Run Code Online (Sandbox Code Playgroud)

模板中的ng-if开始计算false,因此该元素将从DOM中删除.当我手动发出$stateChangeStart事件时,指令监听器工作,但我找不到DOM中的元素.

ng-iftrue再次评估时,如何检查模板是否添加到DOM ?

mis*_*iso 9

嗯,这是我找到的解决方案.

重要的是要注意两件事:

  1. 指令集替换true.
  2. 该模板具有ng-if指令.

这是发生的事情:

在测试中,当编译指令时:

element = $compile('<sm-progress></sm-progress>')(scope);
Run Code Online (Sandbox Code Playgroud)

<sm-progress></sm-progress> 被替换为:

<div class="in-progress" ng-if="isRouteLoading">
  <span>.</span><span>.</span><span>.</span>
<div/>
Run Code Online (Sandbox Code Playgroud)

同时,ng-if为false,因此最终呈现的代码只是一个注释.这就是ng-if如何自然地运作.

<!-- ngIf: isRouteLoading -->
Run Code Online (Sandbox Code Playgroud)

$ stateChangeStart被触发时,scope.isRouteLoading转到true,这使得ng-if将模板重新插入DOM.但在这一点上元素等于:

<!-- ngIf: isRouteLoading -->
Run Code Online (Sandbox Code Playgroud)

是不可能在其中找到指令模板.

// THE TEST FAILS
var progressEl = angular.element(element).find('in-progress');
expect(progressEl.length).toBeGreaterThan(0);
Run Code Online (Sandbox Code Playgroud)

解决方案是将html文本包装在div或任何其他元素中.因此ng-if将在其中运行,我们可以检查当时发生的事情.

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

  });
Run Code Online (Sandbox Code Playgroud)

一个jsfiddle.net,代码工作http://jsfiddle.net/fwxgyjwc/13/

我希望我已经解释得很好.我的英文不是很好.