6

I've been building a directive that restricts a user from pressing certain invalid characters, in this case, using the keypress event binding to the input element that uses my directive. I've been trying to test this functionality, but i don't understand how to achieve this.

My directive

angular
    .module('gp.rutValidator')
    .directive('gpRutValidator', directive);

  directive.$inject = ['$filter'];

  function directive($filter){
    var ddo = {
      restrict: 'A',
      require: 'ngModel',
      link: linkFn
    };
    return ddo;

    function linkFn(scope, element, attrs, ngModel){

      //valid characters are digits, dash and letter k
      var regexValidKeys = (/[\d\.\-k]/i);

      element.bind('keypress', function(e){

        var key = e.key || String.fromCharCode(e.keyCode);

        if (!regexValidKeys.test(key)) {
          e.preventDefault();
          return false;
        }

      });

    }
  }

My test

describe('Angular Rut Validator Directive',validatorDirectiveSpec);

  function validatorDirectiveSpec(){

    //////////////  GLOBALS   ////////////////////////////////
    var scope, element, evt;
    //////////////  BEFORE EACH ////////////////////////////////
    beforeEach(module('gp.rutValidator'));
    beforeEach(inject(eachSpec));

    function eachSpec($rootScope, $compile){
      element = angular.element('<input ng-model="rut" gp-rut-validator>');
      scope = $rootScope.$new();
      $compile(element)(scope);
      scope.$digest();
    }

    ////////////////// HELPERS ///////////////////////////////////
    function pressKey(keyCode) {
      try {
        // Chrome, Safari, Firefox
        evt = new KeyboardEvent('keypress');
        delete evt.keyCode;
        Object.defineProperty(evt, 'keyCode', {'value': keyCode});
      }
      catch (e) {
        // PhantomJS 
        evt = document.createEvent('Events');
        evt.initEvent('keypress', true, true);
        evt.keyCode = keyCode;
      }
      element[0].dispatchEvent(evt);
    }
    //////////////////   SPECS //////////////////////////////////
    it('1. Should be reject no valid characters', spec1);

    function spec1(){
      var ngModelCtrl = element.controller('ngModel'),
          invalidCharacterKeys = [
                                    'a'.charCodeAt(0),
                                    'z'.charCodeAt(0),
                                    'b'.charCodeAt(0),
                                    '#'.charCodeAt(0)
                                  ];

      invalidCharacterKeys.forEach(function(keyCode){
        pressKey(keyCode);
        scope.$digest();
        expect(scope.rut).toBe('');
      });

    }

  }

But I get the error Expected undefined to be ''.

What am I doing wrong?

Example in Codepen => http://codepen.io/gpincheiraa/pen/ozWyvA

3 Answers3

1

How about replacing the Event.prototye.preventDefault:

var original = Event.prototype.preventDefault;
Event.prototype.preventDefault = function(){
    original.bind(this);
    handlers.filter((handler) => handler.keyCode == this.keyCode).forEach((handler)=>handler.handle(this));
    handlers = handlers.filter((handler)=> handler.keyCode != this.keyCode);
}

and make him call a predefined Callback:

function pressKey(keyCode, cb) {
        try {
            // Chrome, Safari, Firefox
            handlers.push({keyCode : keyCode, handle: cb});
            evt = new KeyboardEvent('keypress');
            delete evt.keyCode;
            Object.defineProperty(evt, 'keyCode', {'value': keyCode});
            element[0].dispatchEvent(evt);
        }
        catch (e) {
            // PhantomJS 
            evt = document.createEvent('Events');
            evt.initEvent('keypress', true, true);
            evt.keyCode = keyCode;
            element[0].dispatchEvent(evt);
        }
    }

and then call pressKey like this:

pressKey(keyCode, (event)=>{expect(event.keyCode).toBe(keyCode)}));

I am not 100% sure if this is what you've asked for...

jaecktec
  • 440
  • 4
  • 15
0

Use this code it('1. Should be reject not valid characters') instead of it('1. Should be reject no valid characters', spec1);

Rejayi CS
  • 1,034
  • 1
  • 10
  • 22
0

How about extracting logic out of the infrastructural / low level code, and testing it in a clearer way?

In other words, define a service which takes key as an input, and then returns true/false result. Service does not listen to the html element's event, it just takes input and gives output. So, you can test this service (as any other service) using your jasmine unit-tests. Then, inject this service into your directive and call it from the event handler (binding still happens inside directive). If the service returns false, also call e.preventDefault(), otherwise allow the key.

Obviously, this approach will not test how you are subscribing to the html element's event (bind), but I don't think that's your intention at all. This will test which keys are accepted/denied just fine, by following the cleaner separation of concerns - this is MVC after all, which is all about separating concerns.

Tengiz
  • 8,011
  • 30
  • 39