我正在尝试测试下一个 Angular 指令。它监听 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;
});
}
};
});
这是我的测试代码:
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?
}));
});
模板中的 ng-if 开始评估为 false
,因此该元素从 DOM 中删除。当我手动发出 $stateChangeStart
事件时,指令监听器工作,但我在 DOM 中找不到元素。
当 ng-if 再次评估为 true
时,如何检查模板是否已添加到 DOM 中?
最佳答案
嗯,这就是我找到的解决方案。
需要注意两点:
- 指令将 replace 设置为
true
. - 模板有一个 ng-if 指令。
会发生这样的事情:
在测试中,指令编译时:
element = $compile('<sm-progress></sm-progress>')(scope);
<sm-progress></sm-progress>
替换为:
<div class="in-progress" ng-if="isRouteLoading">
<span>.</span><span>.</span><span>.</span>
<div/>
同时,ng-if 为 false,所以最终渲染的代码只是一个注释。这就是 ng-if 的自然运作方式。
<!-- ngIf: isRouteLoading -->
当 $stateChangeStart 被触发时,scope.isRouteLoading 转到 true
,这使得 ng-if 将模板重新插入到 DOM。但在那一点 element 等于:
<!-- ngIf: isRouteLoading -->
不可能在里面找到指令模板。
// THE TEST FAILS
var progressEl = angular.element(element).find('in-progress');
expect(progressEl.length).toBeGreaterThan(0);
解决方案是将 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
});
带有代码的 jsfiddle.net http://jsfiddle.net/fwxgyjwc/13/
我希望我已经解释得足够好。我的英语不是很好。
关于angularjs - 使用包含 ng-if 的模板对 AngularJS 指令进行单元测试,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34848102/