I really want to be done with this. But at the same time, I cannot leave code broken when is likely to sit for a while.
To summarize: it is currently impossible to generate custom keyboard events in Dart. This is a problem because it is quite difficult to test applications that involve keyboard interaction. This makes me sad.
Hope is not all lost. Recent changes to the bleeding edge of Dart have begun to reintroduce the ability to dynamically create keyboard events. More than just generate keyboard events, the
KeyEvent
class promises to normalize keyboard behavior across browsers. But since this is bleeding edge, there are problems:- When used in live code,
KeyEvent
listeners do not work, throwing “Internal Dartium Errors”
- Even in test code, tests relying on
KeyEvent
streams need access to the same event stream used by the application—dispatching to elements is not sufficient
- Creating a
KeyEvent
wraps a corresponding low-levelKeyboardEvent
that can be dispatched to elements, but it lacks actual keyboard data (keyCode, charCode)
- Dispatching the high-level
KeyEvent
generates Internal Dartium Errors—in test and application code
KeyEvent
, I listen to Element
“on” properties like onKeyDown
. I still get Internal Dartium Errors, but somehow these do not halt execution. Instead they are seen as warnings, allowing the code to proceed. I still need to accommodate #2, but I do so by caching a single stream in a class. And where I have used that workaround, my code is actually better for it. Reusing a single stream rather than creating new ones each time a listener is needed is a help. But there are times that I need to dispatch to dynamically created elements, like menu systems. For that, I can use #3 above, though the application code needs to accept zero valued keyCode
properties on events. That is not too horrible. The structure of the code remains unaffected for now and once KeyEvent
wrapped keyboard events support setting values—I only need remove checks for zero values. I had hoped this would be the end of it until Dart's KeyEvent
class stabilized.
test("down arrow key moves forward in list", (){
helpers.typeCtrl('o');
helpers.typeIn('project 1');
// project 11
// project 10 *
// project 1
helpers.arrowDown(2);
expect(
document.activeElement.text,
equals('Project 10')
);
});
And so, finally, I believe that I am out of luck keeping things somewhat sane. Because the menu is dynamically generated, my test cannot easily gain access to a stream to add custom events. So my workaround for #1 and #2 is out. Because I need to distinguish between up and down events, my workaround for #3 is out. I really, really want to stop working on this, but I cannot leave the tests failing. I do not want to skip tests and hope that I come back to fix them later. I need to record the last dynamically created keycode somewhere. If I cannot do that on the event being dispatched, then I will set it in a common class:class Keys { static int _lastKeyCode; static set lastKeyCode(v) { _lastKeyCode = v; } static get lastKeyCode { print('Horrible hack. FIXME ASAP!!!!'); return _lastKeyCode; } // ... }I use a private, static variable,
_lastKeyCode
, to hold the value. I define a static getter and setter that wrap this private variable, making it seem like the Keys
class has a lastKeyCode
property. But in there, I print out a FIXME message that I am not going to ignore for long. To complete my horrid hack, my helpers need to set the “property”:arrowDown([times=1]) { var e = new KeyEvent('keydown', keyCode: KeyCode.DOWN).wrapped; Keys.lastKeyCode = KeyCode.DOWN; new Iterable.generate(times, (i) { document.activeElement.dispatchEvent(e); }).toList(); }And my application code needs to honor it:
_handleDown(e) {
if (e.keyCode != KeyCode.DOWN && Keys.lastKeyCode != KeyCode.DOWN) return;
// ...
}
Yup, that's pretty ugly. But I have my keyboard handling tests passing again for the first time in two months. I am in no rush to push this to production, but hopefully I am better prepared for the stabilization of KeyEvent
. All in all, this is a tough problem to solve—especially given that Dart needs to compile to JavaScript that normalizes behavior across browsers. Light is at the end of the tunnel and I think I have enough duct tape to keep this thing running until the end is reached.
No comments:
Post a Comment