Para probar una directiva angularjs de validación personalizada

76

Esta directiva de validación personalizada es un ejemplo presentado en el sitio oficial de angular. http://docs.angularjs.org/guide/forms Verifica que una entrada de texto esté en formato numérico o no.

var INTEGER_REGEXP = /^\-?\d*$/;
app.directive('integer', function() {
  return {
    require: 'ngModel',
    link: function(scope, elm, attrs, ctrl) {
      ctrl.$parsers.unshift(function(viewValue) {
        if (INTEGER_REGEXP.test(viewValue)) {
          // it is valid
          ctrl.$setValidity('integer', true);
          return viewValue;
        } else {
          // it is invalid, return undefined (no model update)
          ctrl.$setValidity('integer', false);
          return undefined;
        }
      });
    }
  };
});

Para probar este código unitario, escribí esto:

describe('directives', function() {
  beforeEach(module('exampleDirective'));

  describe('integer', function() {
    it('should validate an integer', function() {
      inject(function($compile, $rootScope) {
        var element = angular.element(
          '<form name="form">' +
            '<input ng-model="someNum" name="someNum" integer>' +
          '</form>'
          );
        $compile(element)($rootScope);
        $rootScope.$digest();
        element.find('input').val(5);
        expect($rootScope.someNum).toEqual(5);
      });
    });
  });
});

Entonces me sale este error:

Expected undefined to equal 5.
Error: Expected undefined to equal 5.

Pongo declaraciones impresas en todas partes para ver qué está sucediendo, y parece que nunca se llama a la directiva. ¿Cuál es una forma adecuada de probar una directiva simple como esta?

ghiden
fuente
¡Gracias por tomarse el tiempo para traer una respuesta! Solo para su información, puede extraer su respuesta y marcarla como aceptada para búsquedas posteriores, eso es aceptable por aquí ;-)
Sean Vieira
Gracias por tus consejos. Moví mi respuesta.
ghiden

Respuestas:

88

Las pruebas de la otra respuesta deben escribirse como:

describe('directives', function() {
  var $scope, form;
  beforeEach(module('exampleDirective'));
  beforeEach(inject(function($compile, $rootScope) {
    $scope = $rootScope;
    var element = angular.element(
      '<form name="form">' +
      '<input ng-model="model.somenum" name="somenum" integer />' +
      '</form>'
    );
    $scope.model = { somenum: null }
    $compile(element)($scope);
    form = $scope.form;
  }));

  describe('integer', function() {
    it('should pass with integer', function() {
      form.somenum.$setViewValue('3');
      $scope.$digest();
      expect($scope.model.somenum).toEqual('3');
      expect(form.somenum.$valid).toBe(true);
    });
    it('should not pass with string', function() {
      form.somenum.$setViewValue('a');
      $scope.$digest();
      expect($scope.model.somenum).toBeUndefined();
      expect(form.somenum.$valid).toBe(false);
    });
  });
});

Tenga en cuenta que $scope.$digest()ahora se invoca después $setViewValue. Esto establece el formulario en un estado "sucio", de lo contrario permanecería "prístino", que probablemente no es lo que desea.

jrief
fuente
¡Muchas gracias, me ayudaron mucho hoy! Pero estoy usando directamente el modelo de alcance en lugar de $setViewValue(), no sé si me faltan muchos casos ...
Florent Destremau
67

Lo descubrí leyendo el código de la aplicación angular https://github.com/angular-app/angular-app Este video también ayuda http://youtu.be/ZhfUv0spHCY?t=31m17s

Dos errores que cometí:

  • No se vincule directamente al alcance cuando esté haciendo ng-model
  • Utilice el controlador de formulario para manipular directamente qué pasar por directivas

Aquí está la versión actualizada. La directiva es la misma, solo la prueba que cambié.

describe('directives', function() {
  var $scope, form;
  beforeEach(module('exampleDirective'));
  beforeEach(inject(function($compile, $rootScope) {
    $scope = $rootScope;
    var element = angular.element(
      '<form name="form">' +
        '<input ng-model="model.somenum" name="somenum" integer />' +
      '</form>'
    );
    $scope.model = { somenum: null }
    $compile(element)($scope);
    $scope.$digest();
    form = $scope.form;
  }));

  describe('integer', function() {
    it('should pass with integer', function() {
      form.somenum.$setViewValue('3');
      expect($scope.model.somenum).toEqual('3');
      expect(form.somenum.$valid).toBe(true);
    });
    it('should not pass with string', function() {
      form.somenum.$setViewValue('a');
      expect($scope.model.somenum).toBeUndefined();
      expect(form.somenum.$valid).toBe(false);
    });
  });
});
ghiden
fuente
@ghiden Esto realmente me ayudó, sin embargo, ¿ha tenido alguna experiencia encadenando múltiples directivas y probando eso? ¿Como si hiciera una directiva por teléfono o correo electrónico? Recibo un montón de errores cada vez que lo intento y agrego una directiva adicional. Pensamientos
thescientist
¿Encadenando múltiples directivas? Supongo que incluso si aplica varias directivas a un elemento, aún desea probarlas por separado, ¿correcto? Si cada uno funciona correctamente y la prioridad se establece correctamente, supongo que deberían funcionar. A veces hago algo como la validación del modelo primero y luego procedo con la directiva de efecto visual con muy baja prioridad.
ghiden
@ghiden Encontré mi problema, estaba probando una directiva de validación de formularios y usando ng-model, y creo que mi principal problema también fue usar el nombre como directiva, así como un atributo en el elemento de entrada. Sin embargo, ahora estoy bien, ¡y lo estoy probando! Sin embargo, muchas gracias por tu ejemplo. Realmente ayudó mucho.
thescientist
1
Esto no funcionó para mí hasta que agregué $scope.$digest()después de llamar $setViewValue. ¿Estoy haciendo algo extraño o falta esto en tu ejemplo? EDITAR: lo siento, no vi la respuesta a continuación.
AndyPerlitch
Nunca puedo hacer que pase la segunda prueba (siempre es cierto) Estoy usando 1.3.12 ¿Cambió algo?
Mocksy
2

Pruebo mis directivas personalizadas buscando en el objeto "$ error" el nombre de la validación personalizada. Ejemplo:

  'use strict';

describe('Directive: validadorCorreo', function () {

  // load the directive's module
  beforeEach(module('sistemaRegistroProCivilApp'));

  var inputCorreo, formulario, elementoFormulario, scope, $compile;

  beforeEach(inject(function ($rootScope, _$compile_) {
    scope = $rootScope.$new();
    $compile = _$compile_;

    elementoFormulario = angular.element('<form name="formulario">' + 
      '<input type="text" name="correo" data-ng-model="correo" required data-validador-correo/>' + 
      '</form');
    scope.correo = '';
    elementoFormulario = $compile(elementoFormulario)(scope);
    scope.$digest();
    inputCorreo = elementoFormulario.find('input');
    formulario = scope.formulario;
    console.log(formulario.correo.$error);
  }));

  it('Deberia Validar si un correo ingresado en el input es correcto e incorrecto', inject(function ($compile) {

    inputCorreo.val('[email protected]').triggerHandler('input');
    expect(formulario.correo.$error.email).toBe(true); //Here, the name of the custom validation appears in the $error object.
    console.log(formulario.correo.$error);

    inputCorreo.val('[email protected]').triggerHandler('input');
    expect(formulario.correo.$error.email).toBeUndefined();//Here, the name of the custom validation disappears in the $error object. Is Undefined
    console.log(formulario.correo.$error.email)
  }));
});

¡Espero que yo puedo ayudarle!

EricdRose Lotso
fuente