Угловые тесты: как ожидать запуска событий элемента?

#angularjs #jasmine #angular-directive

#angularjs #жасмин #angular-директива #jasmine

Вопрос:

Я украшаю формы следующим образом:

 angular.module('Validation').directive('form', function() {
  return {
    restrict: 'E',
    link: function(scope, element) {
      var inputs = element[0].querySelectorAll('[name]');

      element.on('submit', function() {
        for (var i = 0; i < inputs.length; i  ) {
          angular.element(inputs[i]).triggerHandler('blur');
        }
      });
    }
  };
});
  

Теперь я пытаюсь протестировать эту директиву:

 describe('Directive: form', function() {
  beforeEach(module('Validation'));

  var $rootScope, $compile, scope, form, input, textarea;

  function compileElement(elementHtml) {
    scope = $rootScope.$new();
    form = angular.element(elementHtml);
    input = form.find('input');
    textarea = form.find('textarea');
    $compile(form)(scope);
    scope.$digest();
  }

  beforeEach(inject(function(_$rootScope_, _$compile_) {
    $rootScope = _$rootScope_;
    $compile = _$compile_;

    compileElement('<form><input type="text" name="email"><textarea name="message"></textarea></form>');
  }));

  it('should trigger "blur" on all inputs when submitted', function() {
    spyOn(input, 'trigger');
    form.triggerHandler('submit');
    expect(input.trigger).toHaveBeenCalled(); // Expected spy trigger to have been called.
  });
});
  

Но тест завершается неудачей.

Каков правильный угловой способ проверки этой директивы?

Комментарии:

1. Какое сообщение об ошибке вы получаете?

2. Вы тестируете в PhantomJS или другом безголовом браузере?

Ответ №1:

У вас есть некоторые проблемы:

1) input = form.find('input'); и angular.element(inputs[i]); являются ли 2 разных объекта-оболочки одним и тем же базовым объектом DOM.

2) Вместо этого вы должны создать spy on triggerHandler .

3) Вы работаете напрямую с DOM, который сложно протестировать.

Примером этого является: angular.element(inputs[i]) не вводится, поэтому нам трудно подделать его в наших модульных тестах.

Чтобы убедиться, что точка 1) возвращает тот же объект. Мы можем подделать angular.element , чтобы вернуть предварительно подготовленное значение, которое является input = form.find('input');

 //Jasmine 1.3: andCallFake
//Jasmine 2.0: and.callFake
angular.element = jasmine.createSpy("angular.element").and.callFake(function(){
         return input; //return the same object created by form.find('input');
});
  

Примечание: поскольку form это уже директива AngularJS, чтобы избежать конфликта с уже определенной директивой, вы должны создать другую директиву и применить ее к form . Что-то вроде этого:

 <form mycustomdirective></form>
  

Я не уверен, нужно ли это. Поскольку мы отслеживаем глобальную функцию (angular.element), которая может использоваться во многих местах, нам может потребоваться сохранить предыдущую функцию и восстановить ее в конце теста. Ваш полный исходный код выглядит следующим образом:

 it('should trigger "blur" on all inputs when submitted', function() {

    var angularElement = angular.element; //save previous function

    angular.element = jasmine.createSpy("angular.element").and.callFake(function(){
         return input;
    });

    spyOn(input, 'triggerHandler');
    form.triggerHandler('submit');

    angular.element = angularElement; //restore
    expect(input.triggerHandler).toHaveBeenCalled(); // Expected spy trigger to have been called.
  });
  

Запуск ДЕМО

Ответ №2:

Вероятно, это как-то связано с вызовом события «отправить» во время теста.

Команда angular создала довольно забавный класс, чтобы помочь им в этом, похоже, он охватывает множество крайних случаев — см. https://github.com/angular/angular.js/blob/master/src/ngScenario/browserTrigger.js

Хотя этот помощник от ngScenario, я использую его в своих модульных тестах для преодоления проблем, вызывающих некоторые события в безголовых браузерах, таких как PhantomJS.

Мне пришлось использовать это для тестирования очень похожей директивы, которая выполняет действие при отправке формы см. Тест здесь https://github.com/jonsamwell/angular-auto-validate/blob/master/tests/config/ngSubmitDecorator.spec.js (см. строку 38).

Мне пришлось использовать это, поскольку я использую безголовый браузер для целей тестирования разработки. Похоже, что для запуска события в браузере этого типа элемент, который запускает событие, также должен быть присоединен к dom.

Кроме того, поскольку директива form уже есть в angular, вам следует либо украсить эту директиву, либо присвоить этой директиве новое имя. На самом деле я бы посоветовал вам украсить директиву ngSubmit вместо директивы form, поскольку это больше подходит для отправки формы. На самом деле у меня есть очень хороший пример этого, поскольку я сделал это в модуле проверки, который у меня есть с открытым исходным кодом. Это должно дать вам очень хорошее начало.

Источник директивы здесь

Директивные тесты здесь

Комментарии:

1. «Кроме того, поскольку директива form уже есть в angular, вам следует либо украсить эту директиву, либо присвоить этой директиве новое имя». Почему это так?

2. @ExpertSystem Я считаю, что это противоречило бы директиве angular form, и это выглядело бы менее похоже на код вуду, если бы вместо этого использовалась директива с четким значением, т.е. blur-contorls-on-submit=»» вместо того, чтобы это просто волшебным образом происходило в каждой форме. Приложение должно состоять из значимых частей, и только потому, что мы находимся на земле html, не означает, что мы должны следовать принципу единой ответственности с директивами (в любом случае, мои 10 центов!!!! 🙂 )

3. Я бы не стал конфликтовать. Может быть столько директив с одинаковым именем, сколько вы хотите (и все они применяются к элементу). Это действительно зависит от того, чего хочет пользователь. Если вы хотите, чтобы что-то применялось ко всем формам, тогда имеет смысл объявить другую form директиву вместо того, чтобы добавлять дополнительную директиву в каждую форму.

4. Я не согласен с применением его к директиве form . Работая в командах, нельзя ожидать, что люди будут знать каждый бит кода, и если что-то странное начнет происходить с формой при отправке, это будет стоить им времени и усилий, чтобы выяснить причину, и может даже привести к взломам кода, чтобы заставить его прекратить это делать. Я думаю, что директивы должны быть короткими и четкими и определять, что они делают с элементом, чтобы избежать путаницы.

5. Здесь вы упускаете главное. Опять же: «Если вы хотите, чтобы что-то применялось ко всем формам, тогда имеет смысл объявить другую директиву формы» , это решение на уровне проекта. Если вам нужно что-то применить ко всем формам, то создание директивы с другим именем не будет автоматически применять ее ко всем формам. Вам все равно нужно сообщить всем остальным участникам.

Ответ №3:

Попробуйте подключиться к событию размытия:

   it('should trigger "blur" on all inputs when submitted', function() {
    var blurCalled = false;
    input.on('blur', function() { blurCalled = true; });
    form.triggerHandler('submit');
    expect(blurCalled).toBe(true);
  });