Friday, June 28, 2013

Still Can't Dynamically Create Keyboard Events in Dart (But Getting Closer)

‹prev | My Chain | next›

For all the work that I have doing recently with it, I should really write a book about Dart testing. It really is a blast and is so nice to have a solid client-side testing library that can also be easily run under continuous integration. The problem, of course, it that I know too much about it now—I would break my code for writing books and based on the last season of Dexter, breaking a code is clearly a bad thing.

Anyhow, tonight's exploration of Dart testing came from a comment from Don Olmstead on an earlier post about what a pain it is to test (or simulate) keyboard events in Dart. As an aside, if the measure of knowing a technology is understanding its weaknesses, I am certainly getting there. Don suggested that I investigate KeyName.ENTER rather than keyCode .

The KeyName values are string representations of non-string things like the Enter and Escape keys, which are really what I want to test (I used TextEvent to test “real” input). The documentation for KeyName mentions that these values are the things that would be returned from KeyEvent.getKeyboardIdentifier, but there does not seem to be a getKeyBoardIdentifier (or any variation of that name) in KeyEvent or the underlying KeyboardEvent. For now, I latch onto the only string values in KeyboardEvent, which are keyIdentifier in the constructor and the $dom_keyIdentifier getter.

So my testing helpers for hitting the Enter and Escape keys become:
hitEnter()=> type(KeyName.ENTER);
hitEscape()=> type(KeyName.ESC);

type(String key) {
  document.activeElement.dispatchEvent(
    new KeyboardEvent(
      'keyup',
      keyIdentifier: key
    )
  );
}
I now use the KeyName values for Enter and Escape, which are strings, and supply them to the keyIdentifier optional parameter in the KeyboardEvent constructor.

In my application code, based on my earlier unsuccessful attempts to legitimately simulate keyboard events in Dart, I have two helper methods that determine if key presses are Escape and Enter keys:
_isEscapeKey(e) =>
  e.keyCode == 27 || e.$dom_keyIdentifier.codeUnits.first == 27;

_isEnterKey(e) =>
  e.keyCode == 13 || e.$dom_keyIdentifier.codeUnits.first == 13;
The first part the boolean OR statements are the standard keyCode checks. The second part of the booleans were my hack for testing. My hope is that I can actually get rid of the keyCode portion of the boolean and compare the $dom_keyIdentifier with the appropriate KeyName value:
_isEscapeKey(e) =>
  e.$dom_keyIdentifier == KeyName.ESC;

_isEnterKey(e) =>
  e.$dom_keyIdentifier == KeyName.ENTER;
And that actually seems to work. With that change, all 125+ tests in the ICE Code Editor are again passing.

That is not quite the end of the story, however. This only means that I have been able to satisfactorily simulate keyboard events in my tests. What about in real life?

Unfortunately, real life betrays me. When I try this out in Dartium, the Enter key works, but the Escape key does not. Further investigation reveals that the codeUnits (the underlying bytes of the string) of the escape key event, [85, 43, 48, 48, 49, 66], do not match the KeyName code units, [69, 115, 99]. The number of bytes is not even correct—let alone the bytes matching up. Interestingly, the Enter key also has five bytes and, since the event and the KeyName seem to agree, it would seem the KeyName value for Escape is simply wrong.

So, my nice, compact _isEnterKey(e) application method becomes:
_isEscapeKey(e) {
  return e.$dom_keyIdentifier == KeyName.ESC ||
    e.$dom_keyIdentifier == new String.fromCharCodes([85, 43, 48, 48, 49, 66]);
}
With that, I have all tests passing and it works in real life. But I am still not quite done.

Since Dart lacks a keyIdentifier getter for KeyboardEvent objects, I am stuck reaching under the covers with the $dom_keyIdentifier getter. The problem is that not all browsers may support this. So I have to compile my example down into JavaScript to test in FireFox and Internet Explorer.

And, unfortunately, this fails to work in either. I get an error about trying to read get$codeunits from an undefined or null reference because the keyboard event in both FireFox and IE do not support this property. Bugger.

So, in the end, I am forced to settle for a slightly better KeyName-based implementation. Instead of mucking about with codeUnits:
_isEnterKey(e) =>
-  e.keyCode == 13 || e.$dom_keyIdentifier.codeUnits.first == 13;
I can now be explicit about the key in question:
_isEnterKey(e) =>
  e.keyCode == 13 || e.$dom_keyIdentifier == KeyName.ENTER;
As frustrating as this is, it does amount to progress. One has to assume that Dart will support keyIdentifier at some point—in either KeyboardEvent or KeyEvent, normalizing all of this stuff for developers. I do believe that I will surf through bug.dartlang.org bit tonight to see if any of this is in the works and possibly add an issue or two.


Day #796

No comments:

Post a Comment