12

I am writing tests for an AngularJS directive which fires events of a <textarea> when certain keys are pressed. It all works fine per my manual testing. I want to be good and have a full unit-test suite too, but I have run into a problem I can't solve on my own:

I want to send a specific keyCode in my triggerHandler() call in my test, but I can't find a way to specify the key that actually works. I am aware of a lot of questions and answers on the topic of building and sending events with specific data, but none of them work on my setup:

My setup

  • Karma test runner
  • PhantomJS browser running the tests (but also tried Firefox and Chrome without success)
  • I'm not using jQuery and I'm hoping there is a regular JS solution. There must be!

Test code

var event = document.createEvent("Events");
event.initEvent("keydown", true, true);
event.keyCode = 40; // in debugging the test in Firefox, the event object can be seen to have no "keyCode" property even after this step
textarea.triggerHandler(event); // my keydown handler does not fire

The strange thing is, I can type the first 3 lines into the console in Chrome and see that the event is being created with the keyCode property set to 40. So it seems like it should work.

Also, when I call the last line like this textarea.triggerHandler("keydown"); it works and the event handler is triggered. However, there is no keyCode to work with, so it is pointless.

I suspect it may be something to do with the nature of the test running against a DOM that is different to a regular page running in the browser. But I can't figure it out!

Sergio
  • 28,539
  • 11
  • 85
  • 132
Michael Bromley
  • 4,792
  • 4
  • 35
  • 57
  • This is what you apparently need http://stackoverflow.com/questions/832059/definitive-way-to-trigger-keypress-events-with-jquery – sabof Mar 22 '14 at 06:59
  • Hi, that discussion is specifically about jQuery, which I'm not using. I'm hoping I don't need to include jQuery in my unit tests just for this. – Michael Bromley Mar 22 '14 at 07:05
  • I think I've already answered this for pure JS, but apparently that question was deleted. You should be able to find more info on MDN. – sabof Mar 22 '14 at 07:09
  • In other [thread][1] I've added answer and described how to test keydown. [1]: http://stackoverflow.com/questions/18001169/how-do-i-trigger-a-keyup-keydown-event-in-an-angularjs-unit-test/28146595#28146595 – Maciej Dzikowicki Jan 26 '15 at 08:52

2 Answers2

11

I've used the following solution to test it and having it working in Chrome, FF, PhantomJS and IE9+ based on this SO answer. It doesn't work in Safari - tried millions of other solution without any success...

function jsKeydown(code){
  var oEvent = document.createEvent('KeyboardEvent');

  // Chromium Hack: filter this otherwise Safari will complain
  if( navigator.userAgent.toLowerCase().indexOf('chrome') > -1 ){
    Object.defineProperty(oEvent, 'keyCode', {
      get : function() {
        return this.keyCodeVal;
      }
    });     
    Object.defineProperty(oEvent, 'which', {
      get : function() {
        return this.keyCodeVal;
      }
    });
  }

  if (oEvent.initKeyboardEvent) {
    oEvent.initKeyboardEvent("keydown", true, true, document.defaultView, false, false, false, false, code, code);
  } else {
    oEvent.initKeyEvent("keydown", true, true, document.defaultView, false, false, false, false, code, 0);
  }

  oEvent.keyCodeVal = code;

  if (oEvent.keyCode !== code) {
    console.log("keyCode mismatch " + oEvent.keyCode + "(" + oEvent.which + ") -> "+ code);
  }

  document.getElementById("idToUseHere").dispatchEvent(oEvent);
}

// press DEL key
jsKeydown(46);

Hope it helps

Update

Today I've found and tested this solution which is offers a much wider coverage of browsers (enabling the legacy support):

https://gist.github.com/termi/4654819

All the credit goes to the author of this GIST. The code does support Safari, PhantomJS and IE9 - tested for the first 2.

Community
  • 1
  • 1
MarcoL
  • 9,829
  • 3
  • 37
  • 50
  • Excellent, thanks a lot! I modified it slightly to return the event object, which I then put inside the Angular `element.triggerHandler()` method, and it works well. I had given up on this one; I'm now busy finishing off my test suite! – Michael Bromley May 18 '14 at 12:45
  • 1
    @MarcoCI you are using PhantomJS? I keep getting an error that the keyCode is mismatched. – mzabriskie Jun 18 '14 at 23:07
  • Same here. Some sort this problem ? – MarJano Jul 02 '14 at 19:52
  • Just updated with a better solution that covers Safari and PhantomJS. – MarcoL Jul 03 '14 at 09:43
  • 2
    The linked code doesn't work in PhantomJS 1.9.8. `TypeError: 'undefined' is not a function (evaluating 'Function.prototype.call.bind(Object.prototype.hasOwnProperty)')` – helion3 May 22 '15 at 15:11
  • @helion3 bind is not available in phantom but you can add this polyfill to your dev dependencies to avoid that error: https://github.com/kdimatteo/bind-polyfill – pacog Sep 04 '15 at 11:07
  • This doesn't work in PhantomJS 1.9.8, keyCode always are 0 – John Feb 09 '16 at 07:29
0

Adding to @MarcoL answer, I'd like to point out for future readers who might stumble on this question, that the methods initKeyboardEvent and initKeyEvent are deprecated methods, and should no longer be used. See here and here.

Instead as the MDN docs suggested, events should be created via their respective constructor.

JeanPaul A.
  • 3,613
  • 1
  • 20
  • 29