单元测试角度指令更新ngModel

时间:2022-07-01 15:58:36

I'm trying to test a directive that I've written to validate an input and I have some trouble. The input managed by the directive should contain a valid hex color value and if the user modifies it with an invalid value, I want to cancel this modification. My directive is the following and is working as expected:

我正在尝试测试我为验证输入而编写的指令,但我遇到了一些麻烦。由指令管理的输入应包含有效的十六进制颜色值,如果用户使用无效值修改它,我想取消此修改。我的指令如下,并按预期工作:

module.directive('colorValidate', function() {
    return {
        restrict: 'A',
        scope: {
            color: '=ngModel'
        },
        link: function(scope, element) {
            var previousValue = '#ffffff';
            //pattern that accept #ff0000 or #f00
            var colorPattern = new RegExp('^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$');
            element.on('focus', function() {
                previousValue = scope.color;
            });
            element.on('blur', function() {
                if (!colorPattern.test(scope.color)) {
                    scope.$apply(function() {
                        scope.color = previousValue;
                    });
                }
                else {
                    scope.$apply(function() {
                        scope.color = scope.color.toLowerCase();
                    });
                }
            });
        }
    };
});

and here is a example of input using this directive:

以下是使用此指令的输入示例:

<input color-validate type="text" ng-model="color.color"/>

First question: Is this way of accessing and modifying the ngModel of the element correct?

第一个问题:这种访问和修改元素的ngModel的方式是否正确?

Then my main problem is the testing part. Here are two simple tests that I wrote and that are not really working:

然后我的主要问题是测试部分。以下是我编写的两个简单的测试,它们并没有真正起作用:

describe('colorValidate directive', function() {
    var scope,
        elem,
        compiled,
        html;

    beforeEach(function() {
        html = '<input color-validate type="text" ng-model="color.color"/>';

        inject(function($compile, $rootScope) {
            scope = $rootScope.$new();
            scope.color = {color: '#aaaaaa'};
            elem = angular.element(html);
            compiled = $compile(elem);
            compiled(scope);
            scope.$digest();
        });
    });

    it('should permit valid 6-chars color value', function() {
        elem.triggerHandler('focus');
        elem.val('#FF0000');
        elem.triggerHandler('blur');
        scope.$digest();
        expect(elem.val()).toBe('#FF0000');
    });

    it('should reject non valid color values', function() {
        elem.triggerHandler('focus');
        elem.val('#F00F');
        scope.$digest();
        elem.triggerHandler('blur');
        expect(elem.val()).toBe('#aaaaaa');
    });
});

The first test succeed and the second failed because the tested value is '#F00F' instead of '#aaaaaa'. Basically, none of my tests are actually modifying the ngModel value handled by the directive...

第一次测试成功,第二次测试失败,因为测试值为'#F00F'而不是'#aaaaaa'。基本上,我的测试都没有实际修改指令处理的ngModel值...

1 个解决方案

#1


7  

Calling elem.val() does not actually cause the scope.color to get updated. In other words, this test will fail:

调用elem.val()实际上并不会导致scope.color得到更新。换句话说,此测试将失败:

it("should set scope", function () {
   elem.triggerHandler("focus");
   elem.val("#FF0000");
   scope.$digest();
   elem.triggerHandler("blur");

   //Will fail: expected #aaaaaa to be #ff0000
   expect(scope.color.color).toBe("#ff0000");
});

This is because ngModel binds to key events on the input and updates the model (scope) at that point. Calling val() or value on the element doesn't trigger an event that angular would think something changed (even in a $digest loop). Therefore, you should be running your tests by changing the model values and asserting they are being accepted or reset:

这是因为ngModel绑定到输入上的键事件并在此时更新模型(范围)。在元素上调用val()或value不会触发angular会认为某些内容发生变化的事件(即使在$ digest循环中)。因此,您应该通过更改模型值并声明它们被接受或重置来运行测试:

it('should permit valid 6-chars color value', function() {
    elem.triggerHandler('focus');
    scope.color.color = '#FF0000';
    //need to trigger a digest here for the two-way binding
    scope.$digest();
    elem.triggerHandler('blur');
    //Don't need a $digest here because you call scope.$apply() within the blur in both if/else conditions
    //scope.$digest();
    expect(scope.color.color).toBe('#ff0000');
});

it('should reject non valid color values', function() {
    elem.triggerHandler('focus');
    scope.color.color = '#F00F';
    //need to trigger a digest here for the two-way binding
    scope.$digest();
    elem.triggerHandler('blur');
    expect(scope.color.color).toBe('#aaaaaa');
});

You don't need to test that the value is updated because presumably angular would have already written the tests to make sure that when the directive has two-way binding (=ngModel) the view is updated when the scope value changes within the directive.

您不需要测试该值是否已更新,因为大概是angular已经编写了测试以确保当指令具有双向绑定(= ngModel)时,当范围值在指令中更改时视图会更新。

#1


7  

Calling elem.val() does not actually cause the scope.color to get updated. In other words, this test will fail:

调用elem.val()实际上并不会导致scope.color得到更新。换句话说,此测试将失败:

it("should set scope", function () {
   elem.triggerHandler("focus");
   elem.val("#FF0000");
   scope.$digest();
   elem.triggerHandler("blur");

   //Will fail: expected #aaaaaa to be #ff0000
   expect(scope.color.color).toBe("#ff0000");
});

This is because ngModel binds to key events on the input and updates the model (scope) at that point. Calling val() or value on the element doesn't trigger an event that angular would think something changed (even in a $digest loop). Therefore, you should be running your tests by changing the model values and asserting they are being accepted or reset:

这是因为ngModel绑定到输入上的键事件并在此时更新模型(范围)。在元素上调用val()或value不会触发angular会认为某些内容发生变化的事件(即使在$ digest循环中)。因此,您应该通过更改模型值并声明它们被接受或重置来运行测试:

it('should permit valid 6-chars color value', function() {
    elem.triggerHandler('focus');
    scope.color.color = '#FF0000';
    //need to trigger a digest here for the two-way binding
    scope.$digest();
    elem.triggerHandler('blur');
    //Don't need a $digest here because you call scope.$apply() within the blur in both if/else conditions
    //scope.$digest();
    expect(scope.color.color).toBe('#ff0000');
});

it('should reject non valid color values', function() {
    elem.triggerHandler('focus');
    scope.color.color = '#F00F';
    //need to trigger a digest here for the two-way binding
    scope.$digest();
    elem.triggerHandler('blur');
    expect(scope.color.color).toBe('#aaaaaa');
});

You don't need to test that the value is updated because presumably angular would have already written the tests to make sure that when the directive has two-way binding (=ngModel) the view is updated when the scope value changes within the directive.

您不需要测试该值是否已更新,因为大概是angular已经编写了测试以确保当指令具有双向绑定(= ngModel)时,当范围值在指令中更改时视图会更新。