2015-12-09 128 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'); 
    })); 
});