如何正确应用范围所以顺序无关紧要

时间:2014-12-18 11:59:07

标签: angularjs unit-testing jasmine

我正在尝试为与其他输入字段的值匹配的指令编写单元测试。问题是如果我在应用指令的元素之前定义要匹配的元素它可以正常工作,否则它会失败。

模板

时工作正常
tpl = '<input name="verifyNewPassword" ng-model="verifyNewPassword" type="password"/>';
tpl += '<input name="newPassword" ng-model="newPassword" type="password" equals-to="userForm.verifyNewPassword"/>';

,当模板

时失败
tpl = '<input name="newPassword" ng-model="newPassword" type="password" equals-to="userForm.verifyNewPassword"/>';
tpl+='<input name="verifyNewPassword" ng-model="verifyNewPassword" type="password"/>';

这是我的指示

.directive('equalsTo', function() {
    return {
        require: 'ngModel',
        link: function(scope, elm, attrs, ctrl) {
            var sc = scope;
            scope.$watch(attrs.ngModel, function() {
                var eqCtrl = scope.$eval(attrs.equalsTo);
                console.log('Value1: ' + ctrl.$viewValue + ', Value2: ' + eqCtrl.$viewValue);
                if (ctrl.$viewValue===eqCtrl.$viewValue || (!!!ctrl.$viewValue && !!!eqCtrl.$viewValue)) {
                    ctrl.$setValidity('equalsTo', true);
                    eqCtrl.$setValidity('equalsTo', true);
                } else {
                    ctrl.$setValidity('equalsTo', false);
                    eqCtrl.$setValidity('equalsTo', false);
                }
            });
        }
    };
})

这是我的测试代码:

describe('Unit: Testing Directives', function() {
    var elm, scope;

    beforeEach(function() {
        module('mctApp');

        inject(function($rootScope, $compile) {
            scope = $rootScope.$new();
        });
    });

    function compileDirective(tpl) {
        if(!tpl) {
            tpl = '<input name="newPassword" ng-model="newPassword" type="password" equals-to="userForm.verifyNewPassword"/>';
            tpl += '<input name="verifyNewPassword" ng-model="verifyNewPassword" type="password"/>';            
        }
        tpl = '<form name="userForm">' + tpl + '</form>';

        inject(function($compile) {
            var form = $compile(tpl)(scope);
        });

        scope.$digest();

    }

    it('must be valid form as both values are equal', function() {
        scope.newPassword = 'abcdef';
        scope.verifyNewPassword = 'abcdef';
        compileDirective();                 
        expect(scope.userForm.$valid).toBeTruthy();
    });
});

3 个答案:

答案 0 :(得分:2)

测试失败,因为watch最初触发时,$viewValue ngModelController的equals-to为NaN,因此字段有效性设置为false,表单无效。

http://plnkr.co/edit/OZVmogR6GT2pIKUrHpuX?p=preview

当您正在观看输入ngModel分配给范围的对象时 - 已经设置为&#34; abcdef&#34; - 手表仅被召唤一次。如果您观察输入的ngModel.$viewValue,则可以保证初始状态始终正确,无论DOM中输入的顺序如何。

我还认为,观察这个价值更有意义,因为它是你正在比较的价值。

.directive('equalsTo', function() {
    return {
        require: 'ngModel',
        link: function(scope, elm, attrs, ctrl) {
            var sc = scope;
            scope.$watch(attrs.equalsTo + '.$viewValue', function() {
                var eqCtrl = scope.$eval(attrs.equalsTo);
                console.log('Value1: ' + ctrl.$viewValue + ', Value2: ' + eqCtrl.$viewValue);
                if (ctrl.$viewValue===eqCtrl.$viewValue || (!!!ctrl.$viewValue && !!!eqCtrl.$viewValue)) {
                    ctrl.$setValidity('equalsTo', true);
                    eqCtrl.$setValidity('equalsTo', true);
                } else {
                    ctrl.$setValidity('equalsTo', false);
                    eqCtrl.$setValidity('equalsTo', false);
                }
            });
        }
    };
})

http://plnkr.co/edit/OZVmogR6GT2pIKUrHpuX?p=preview

注意

如果您正在使用angular 1.3+,那么将新的验证器管道视为解决同一问题的更优雅方法可能是值得的。

答案 1 :(得分:2)

首先,对于您的模型场景,有一个比同步验证两个输入更好的UX解决方案。应根据您的密码格式限制验证第一个字段(密码),并且只检查第二个字段(密码确认)是否与密码相等。这有助于用户在选择有效且可重新输入密码时保持理智。 (换句话说,密码确认是第二个输入的唯一域。如果你搞砸了确认,它不会突然使以前的输入无效,直到那一点,有效。)

<input type="password" ng-model="password" required ng-pattern="/^(?=.*\w)(?=.*\W)/">
<input type="password" ng-model="passwordConfirmation" equal-to="password">

这种方法不仅可以帮助您的用户体验,它还可以帮助您避免不相关元素之间的直接交互(AngularJS中很少有好主意)。


其次,你是以非Angular的方式解决问题。输入是视图的一部分,它只是模型的表示,也是用户与之交互的手段。因此,您应该针对模型进行验证,而不是针对其他输入进行验证。这是可能的,因为你不需要指令来混淆另一个输入。

.directive('equalTo', ['$parse', function ($parse) {
    return {
        require: 'ngModel',
        compile: function (element, attrs) {
            var getOtherValue = $parse(attrs.equalTo);

            return function link ($scope, $element, $attrs, ngModelCtrl) {
                ngModelCtrl.$validators.equalTo = function (value) {
                    return (value === getOtherValue($scope));
                };
            };
        }
    };
}])

(如果使用AngularJS版本$parsers,则使用$setValidity&amp; $validators代替< 1.3

测试指令将是轻而易举的,因为您只需要调整模型。


作为旁注,即使您确定要立即验证两个输入,您最好根据相应的模型值验证每个输入(在两者上使用我的equalTo指令输入)而不是强制兄弟控制器之间的直接通信。

答案 2 :(得分:2)

我认为您在应用范围时需要使用$timeout。 $ timeout关注$ digest进程,因为它在$ digest完成后应用了范围。

这就是我用来应用范围的方式。

$timeout(function(){
    $scope.$apply()
}

在测试中,您可以使用$ timeout.flush()来同步刷新延迟函数的队列。