Last night I was wildly successful in extracting a keyboard interaction library out of my ICE Code Editor codebase. Thanks mostly to Dart being an amazing language, I was able to create a new library in a single night, including writing tests, some documentation, and pushing to both GitHub (ctrl-alt-foo repository) and Dart's Pub package repository (ctrl_alt_foo package). And best of all: the many, many tests in original ICE codebase never broke.
Until I made one last change and pushed everything. Of course I did that.
The failing tests in the original repository all have to do with “special” key events like Enter, Escape and the Arrow keys. It seems that all of the keyboard event helpers in the new library are working through the
type()
method:hitEnter()=> type(KeyName.ENTER); hitEscape()=> type(KeyName.ESC); type(String key) { document.activeElement.dispatchEvent( new KeyboardEvent( 'keydown', keyIdentifier: keyIdentifierFor(key) ) ); }The
keyIdentifier
parameter in the KeyboardEvent
's constructor is an odd duck. Not surprisingly, it is causing the failures. As of this writing, the keyIdentifier
property is the only way to supply data to a keyboard event in Dart. Unfortunately, there is very little support for the keyIdentifier
property in JavaScript or even in Dart itself. This makes it difficult to generate events that are cross-browser compatible or even reliable in Dart. This is the crux of the new ctrl_alt_foo
package: it ensures that keyIdentifier
is cross-browser compatible and reliable in Dart. My problem was the
keyIdentifierFor()
function which prepares the key press into a testable/cross-browser state:String keyIdentifierFor(char) { if (char.codeUnits.length != 1) throw "Don't know how to type “$char”"; // Keys are uppercase (see Event.keyCode) var key = char.toUpperCase(); return 'U+00' + key.codeUnits.first.toRadixString(16).toUpperCase(); }Weirdly enough, the cross-browser state is a string representation of the Unicode value of the character (e.g. "U+0041"). My problem is that this code will only work with simple, single-byte code. It will not work with multi-byte strings, which is what the
KeyName.Enter
string is.But this is a perfect excuse to write another test! Yay!
The
ctrl_alt_foo
package exposes a keyboard stream named KeyboardEventStreamX
. In my test, I establish such a listener, simulate Enter with the hitEnter()
helper, and expect that the resultant event will have the isEnter
predicate method set to true: test("can listen for Enter keys", (){
KeyboardEventStreamX.onKeyDown(document).listen(expectAsync1((e) {
expect(e.isEnter, true);
}));
hitEnter();
});
That fails with the thrown string, just as my ICE tests are currently failing:ERROR: can listen for Enter keys Test failed: Caught Don't know how to type “Enter”The solution to this particular failure turns out to be full of “ugh.”
The problem was mostly mine. I was under the impression that the
KeyName
class in Dart was string representations of the various special keys. That is, I expected that KeyName.ENTER
was a non-printable string of 0x0a
embedded into a string. It turns out that KeyName.ENTER
is the string "Enter"
. That might seem somewhat useless, but it serves as a constant lookup for the values in the KeyboardEvent DOM object.It serves as little use to me in this case, so I have to build my own conversion class:
class KeyIdentifier { static final map = { 'Backspace': 'U+0008', 'Tab': 'U+0009', 'Enter': 'U+000A', 'Esc': 'U+001B', 'Del': 'U+007F', 'Cancel': 'U+0018', 'Spacebar': 'U+0020', 'Tab': 'U+0009', 'Del': 'U+007F', 'Left': 'U+0025', 'Up': 'U+0026', 'Right': 'U+0027', 'Down': 'U+0028' }; static forKeyName(name)=> map[name]; }Using those crazy Unicode values and the conversion facilities from previous nights, I am able to properly generate an Enter keyboard event.
Now that I think about it, I am not entirely certain that these special keyboard events are working cross-browser. I *think* they ought to be OK, but I will double check that tomorrow. If that is working, then it is time to add support for Mac keyboard shortcuts (stupid command key).
Day #841
No comments:
Post a Comment