2015-12-09 14 views
6

Ho una direttiva personalizzata che utilizza un attributo per specificare un altro controllo che modifica.Test unitario Direttiva angolare che accede all'elemento esterno

direttiva definizione dell'oggetto:

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

Una funzione sul controller della direttiva modifica il contenuto dell'elemento a bersaglio (un campo di ingresso):

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

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

I test di unità (gelsomino/Karma/PhantomJS attualmente acclude l'elemento alla pagina. Funziona, ma sembra un odore di codice.

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! 
}); 

Ho provato a riscrivere la funzione controller per evitare jQuery; questo non ha aiutato.

Come posso modificare la direttiva oi test per eliminare l'appendice/rimuovere?

+1

È molto più grande l'odore del codice che usi gli id ​​nella direttiva. È molto stile jQuery e non "modo angolare". Prendi in considerazione l'uso di 'require' in direttive sullo stesso ramo dell'albero DOM, o approcci" common-parent "o" global service "(puoi cercare o fare un'altra domanda se non sai come farlo - la spiegazione è decisamente fuori della portata di questa domanda) –

+0

@ValentynShybanov Sono aperto a soluzioni che comportano la revisione della direttiva. Lo scopo reale di questa direttiva è di inserire segnaposto mail-merge in un campo input o textarea (che non dovrebbe essere parte del modello di direttiva, e preferirei evitare la transclusione). Se desideri pubblicare un esempio del tuo metodo raccomandato o link a questo, prenderei in considerazione l'idea di accettarlo come risposta. – TrueWill

+2

Non penso che sarete in grado di uccidere l'elemento Aggiungi/Rimuovi dal codice senza refactoring della funzione 'onSelection()'. C'è qualche ragione per cui non puoi associare un 'ng-model' al tuo ingresso #bar?Se si potesse fare ciò, allora si potrebbe semplicemente impostare il valore nel controller senza bisogno di afferrare l'elemento con jQuery. – jperezov

risposta

2

La soluzione migliore è migrare la direttiva su un attributo anziché su un elemento. Ciò elimina la necessità dell'attributo target-id e non è necessario cercare l'elemento di destinazione.

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

direttiva

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 
    }; 
    }); 

prova

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

Grazie - questo è sicuramente un approccio. Uno svantaggio è che è più difficile dire "Voglio il controllo della direttiva qui sulla pagina" e "Voglio che influenzi il controllo in questa posizione sulla pagina". – TrueWill

+1

Un'altra opzione quindi (dal momento che è necessario l'accesso diretto al tag di input) consiste nell'utilizzare due direttive, una come controllo (dove l'utente effettua la selezione) e un'altra (sull'input) che applica i risultati, utilizzando un segnale per comunicare che un'azione ha avuto luogo. – morloch

1

Ecco un altro suggerimento che mantiene la logica quasi esattamente la stessa: utilizzare una seconda direttiva per fare l'obiettivo element disponibile su t egli Controller, che è quindi possibile passare alla direttiva primaria per l'elaborazione: 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 
    }; 
    }) 

prova

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'); 
    })); 
});