2015-12-09 190 views
6

我有一个自定义指令,它使用一个属性来指定它修改的另一个控件。单元测试访问外部元素的角度指令

指令定义对象:

{ 
    restrict: 'E', 
    templateUrl: 'myTemplate.html', 
    scope: { 
     targetId: '@' 
    }, 
    controller: MyController, 
    controllerAs: 'vm', 
    bindToController: true 
} 

指令的控制器上的函数修改目标元素的内容(输入字段):

function onSelection (value) { 
    var $element = $('#' + vm.targetId); 

    $element.val('calculated stuff'); 
    $element.trigger('input'); 
} 

单元测试(茉莉花/噶/ PhantomJS )当前将该元素附加到页面上。这工作,但它似乎是一种代码味道。

beforeEach(inject(function($rootScope, $compile) { 
    var elementHtml = '<my-directive target-id="bar"></my-directive>' + 
     '<input type="text" id="bar">'; 

    scope = $rootScope.$new();  
    angularElement = angular.element(elementHtml); 
    angularElement.appendTo(document.body); // HELP ME KILL THIS! 
    element = $compile(angularElement)(scope); 
    scope.$digest(); 
})); 

afterEach(function() { 
    angularElement.remove(); // HELP ME KILL THIS! 
}); 

我试着重写控制器函数以避免jQuery;这没有帮助。

如何修改指令或测试以消除appendTo/remove?

+1

它是更大的代码气味,你在你的指令中使用id。这是非常jQuery的风格,而不是“角度的方式”。考虑在DOM树的同一分支上使用'require',或者“common-parent”或“global service”接近其他的方法(如果你不知道如何去做,那么你可以搜索或提出另一个问题 - 解释是绝对的这个问题的范围) –

+0

@ValentynShybanov我接受修改指令的解决方案。该指令的实际用途是在输入或textarea字段中插入邮件合并占位符(不应该是指令模板的一部分,我宁愿避免跨越)。如果你想发布一个你推荐的方法或链接的例子,我会考虑接受这个答案。 – TrueWill

+2

我不认为你可以在不重构onSelection()函数的情况下杀死代码中的add/remove元素。是否有任何理由不能将'ng-model'绑定到#bar输入中?如果你能做到这一点,那么你可以在控制器中设置值,而无需使用jQuery获取元素。 – jperezov

回答

2

最好的办法是将指令迁移到属性而不是元素。这消除了对target-id属性的需要,并且不需要搜索目标元素。

http://jsfiddle.net/morloch/621rp33L/

指令

angular.module('testApp', []) 
    .directive('myDirective', function() { 
    var targetElement; 
    function MyController() { 
     var vm = this; 
     vm.onSelection = function() { 
     targetElement.val('calculated stuff'); 
     targetElement.trigger('input'); 
     } 
    } 
    return { 
     template: '<div></div>', 
     restrict: 'A', 
     scope: { 
     targetId: '@' 
     }, 
     link: function postLink(scope, element, attrs) { 
     targetElement = element; 
     }, 
     controller: MyController, 
     controllerAs: 'vm', 
     bindToController: true 
    }; 
    }); 

测试

describe('Directive: myDirective', function() { 
    // load the directive's module 
    beforeEach(module('testApp')); 

    var element, controller, scope; 

    beforeEach(inject(function($rootScope, $compile) { 
    scope = $rootScope.$new(); 
    element = angular.element('<input my-directive type="text" id="bar">'); 
    $compile(element)(scope); 
    scope.$digest(); 
    controller = element.controller('myDirective'); 
    })); 

    it('should have an empty val', inject(function() { 
    expect(element.val()).toBe(''); 
    })); 

    it('should have a calculated val after select', inject(function() { 
    controller.onSelection(); 
    expect(element.val()).toBe('calculated stuff'); 
    })); 
}); 
+0

谢谢 - 这绝对是一种方法。一个缺点是说“我希望页面上的指令控制”和“我希望它影响页面上这个位置的控件”更难。 – TrueWill

+1

然后另一个选择(因为您需要直接访问输入标签)是使用两个指令,一个作为控制(用户进行选择的地方)和另一个(应用于输入的)应用结果的指令,使用信号进行通信一个行动已经发生。 – morloch

1

这里,让您的逻辑几乎一模一样的另一项建议:使用第二个指令,使目标在t上可用的element他控制器,然后你就可以传递给您的主指令进行处理:http://jsfiddle.net/morloch/p8r2Lz1L/

getElement

.directive('getElement', function() { 
    return { 
     restrict: 'A', 
     scope: { 
     getElement: '=' 
     }, 
     link: function postLink(scope, element, attrs) { 
     scope.getElement = element; 
     } 
    }; 
    }) 

myDirective

.directive('myDirective', function() { 
    function MyController() { 
     var vm = this; 
     vm.onSelection = function() { 
     vm.targetElement.val('calculated stuff'); 
     vm.targetElement.trigger('input'); 
     } 
    } 
    return { 
     template: '<div></div>', 
     restrict: 'E', 
     scope: { 
     targetElement: '=' 
     }, 
     controller: MyController, 
     controllerAs: 'vm', 
     bindToController: true 
    }; 
    }) 

测试

describe('Directive: myDirective', function() { 
    // load the directive's module 
    beforeEach(module('testApp')); 

    var element, controller, scope; 

    beforeEach(inject(function($rootScope, $compile) { 
    scope = $rootScope.$new(); 
    element = angular.element('<input get-element="elementBar" type="text" id="bar"><my-directive target-element="elementBar"></my-directive>'); 
    $compile(element)(scope); 
    scope.$digest(); 
    controller = $(element[1]).controller('myDirective'); 
    })); 

    it('should have an empty val', inject(function() { 
    expect($(element[0]).val()).toBe(''); 
    })); 

    it('should have a calculated val after select', inject(function() { 
    controller.onSelection(); 
    expect($(element[0]).val()).toBe('calculated stuff'); 
    })); 
});