测试AngularJS指令,该指令修改了业力 /茉莉花中的其他范围



参考:单元测试angularJS指令:范围不更新?

案例

我有一个称为可编辑的指令,该指令采用ng-model并创建一个可切换/可编辑字段。指令工作和父范围的更新正确,指令的实际功能没有问题。不过,我似乎无法编写支持这一点的测试。我花了很长时间才能通过所有警告使指令正常工作,所以我真的很想进行一些测试,以确保在所有不同情况下它继续工作。

指令(有效)

我不确定哪些部分会相关,所以我包含了整个内容。

app.directive('editable',
    ['$templateCache', '$compile',
    function ($templateCache, $compile) {
        return {
            restrict: 'A',
            transclude: true,
            templateUrl: 'template/directives/editable.html',
            replace: true,
            require: 'ngModel',
            scope: true,
            compile: function(element, attrs, transcludeFn) {
                return function (scope, iElement, iAttrs, ctrl) {
                    var validityId = 'editable-' + scope.$id;
                    scope.lastSavedValue = iElement.find('input').val();
                    scope.storeValue = function() {
                        scope.lastSavedValue = iElement.find('input').val();
                    };
                    scope.edit = function() {
                        scope.storeValue();
                        scope.editing = true;
                        $('input', iElement).focus().select();
                        ctrl.$setValidity(validityId, true);
                    };
                    scope.ok = function() {
                        var inputCtrl = iElement.find('input').controller('ngModel');
                        if(inputCtrl.$valid === true) {
                            scope.editing = false;
                            scope.value = inputCtrl.$viewValue;
                            ctrl.$setValidity(validityId, false);
                            ctrl.$setViewValue(inputCtrl.$viewValue); // Not sure (why) this is needed
                        }
                    };
                    scope['delete'] = function() {
                        scope.deleted = true;
                        scope.editing = false;
                    };
                    scope.undo = function() {
                        var inputCtrl = iElement.find('input').controller('ngModel');
                        if(scope.lastSavedValue) {
                            inputCtrl.$setViewValue(scope.lastSavedValue);
                            scope.value = scope.lastSavedValue;
                            ctrl.$setViewValue(scope.lastSavedValue);
                        }
                        iElement.find('input').val(scope.value);
                        scope.editing = false;
                        scope.deleted = false;
                        ctrl.$setValidity(validityId, false);
                    };
                    transcludeFn(scope, function(clone) {
                        var $editingReplacement = $(clone).filter('[editing]');
                        var $viewingReplacement = $(clone).filter('[viewing]');
                        var $deletedReplacement = $(clone).filter('[deleted]');
                        var $viewingControls = $('.editable-view-container .controls', iElement);
                        var $editingControls = $('.editable-input-container .controls', iElement);
                        var $deletedControls = $('.editable-delete-container .controls', iElement);
                        if($editingReplacement.length) {
                            $('.editable-input-container', iElement).html($editingReplacement.html());
                            $('.editable-input-container', iElement).append($editingControls);
                            $compile($('.editable-input-container', iElement))(scope);
                        } else {
                            $('.editable-input-container', iElement).find('input').attr('ng-model', iAttrs['ngModel']);
                            $compile($('.editable-input-container', iElement))(scope);
                        }
                        if($viewingReplacement.length) {
                            $('.editable-view-container', iElement).html($viewingReplacement.html());
                            $('.editable-view-container', iElement).append($viewingControls);
                            $compile($('.editable-view-container', iElement))(scope);
                        }
                        if($deletedReplacement.length) {
                            $('.editable-delete-container', iElement).html($deletedReplacement.html());
                            $('.editable-delete-container', iElement).append($deletedControls);
                        }
                    });
                    /**
                     * Deleted (Isolated Scope)
                     *
                     * Tracks if the user has clicked the delete button
                     * 
                     * @type {Boolean}
                     */
                    scope.deleted = false;
                    /**
                     * Editing (Isolated Scope)
                     *
                     * Tracks the state of the view
                     * 
                     * @type {Boolean}
                     */
                    scope.editing = false;
                    /**
                     * Initial Loader
                     *
                     * Run once after ctrl is loaded
                     * 
                     * @return {[type]} [description]
                     */
                    var unbindWatcher = scope.$watch(function() { return ctrl.$modelValue; }, function(newVal, oldVal) {
                        if(typeof ctrl.$modelValue !== 'undefined') {
                            scope.value = ctrl.$modelValue;
                            scope.editing = ctrl.$modelValue ? false : true;
                            unbindWatcher();
                        }
                    });
                };
            }
        };
    }
]);

规格

最后失败

describe('Editable Directive', function() {
    // Keep references to element and scope so that they are available to all tests
    var element, scope, ctrl;
    beforeEach(module('ltAccountApp'));
    beforeEach(module('templates'));
    beforeEach(inject(function ($rootScope, $compile) {
        var linkFn, el;
        // scope = $rootScope;
        scope = $rootScope.$new();
        scope.testValue = 'xxx';
        el = angular.element('
            <div editable="" ng-model="testValue"></div>
        ');
        // The $compile method returns the directive's link function
        linkFn = $compile(el);
        // The link function returns the resulting DOM object
        element = linkFn(scope);
        element.scope().$apply();
        ctrl = element.controller('ngModel');
    }));
    it('should assign input value to scope value', function() {
        expect(element.find('input').val()).toEqual(scope.testValue);
    });
    it('should have access to parent scope variable passed into directive', function() {
        expect(ctrl.$viewValue).toEqual(scope.testValue);
        expect(ctrl.$modelValue).toEqual(scope.testValue);
    });
    it('should manage state editing correctly', function() {
        expect(element.scope().editing).toBe(false);
        element.scope().edit();
        expect(element.scope().editing).toBe(true);
    });
    it('should manage state deleted correctly', function() {
        expect(element.scope().deleted).toBe(false);
        element.scope()['delete']();
        expect(element.scope().deleted).toBe(true);
    });
    it('should be able to modify parent scope variable passed into directive', function() {
        // Not sure what this does, added from referenced SO question
        // spyOn(scope, '$apply').andCallThrough();
        var newValue = 'yyy';
        element.scope().edit();
        element.find('input').val(newValue);
        element.find('input').trigger('input');
        element.scope().ok();
        expect(ctrl.$viewValue).toEqual(newValue);
        expect(ctrl.$modelValue).toEqual(newValue);
        expect(element.scope().value).toEqual(newValue);
        expect(scope.$apply).toHaveBeenCalled();
        expect(scope.testValue).toEqual(newValue); // <-fails
    });
});

所以...

一切似乎都在起作用,直到我实际上期望父范围具有更改的值。

我知道这里有很多,我感谢您可以提供的任何指导。

带有指令的角度 茉莉花的plunk(正在进行的工作)

您必须将输入字段的值更新为'kasrak'。尝试这样做:

var elm = element.find("input");
elm.val('kasrak');
elm.trigger($sniffer.hasEvent('input') ? 'input' : 'change');
$scope.$digest()

键是带有$ Sniffer服务的第三行。我在Angular-UI的Bootstrap测试中发现了这一点。

这是指向测试规格中函数的链接:https://github.com/angular-ui/bootstrap/blob/master/src/src/typeahead/test/typeahead.spec.js#l31

最新更新