Saturday, November 9, 2013

Giving Up on KeyEvents until after Dart 1.0


I think it safe to say that KeyEvent won't be fixed in time for the Dart 1.0 release. This makes me sad, but I understand the various moving parts making this a problem. Since whining won't help, it is time to give up the ghost and move onto workarounds.

I already have a separate keyboard shortcut library that boasts some testing and isolates the untestable features for a later time when KeyEvent is fully implemented. What is still not working (or at least not tested, which is the same thing, right?) in the ICE Code Editor is navigation with arrow kyes, hitting enter in text fields and hitting escape anywhere to hide dialogs. To move on, I think it best to give up trying to generate real or fake KeyEvents.

Instead I am going to fallback to the old standby of hidden buttons. These buttons can be created with style="display:none" so that they will never be seen by humans. They can be clicked by tests and, when clicked, they can call the same methods used by the keyboard handlers. I do this with a heavy heart (OK, so I'm not entirely done whining).

The first test that I would like to get passing verifies what happens after a project list is filtered and then the down arrow key is pressed twice. In the UI, it looks something like:



My test looks like:
      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')
        );
      });
Aside from the minor nuisance that the test does not work, I am rather proud of this acceptance test. It reads well and (ideally) verifies real human behavior.

Since I cannot create an event with the DOWN keyCode, this is a case in which I need a fake button. I will assume that my fake button for the down arrow key will have an ID of fake_down_key. If present, the helpers.arrowDown() method needs to click this fake down button instead of trying to generate a keydown event:
arrowDown([times=1]) {
  // var e = new KeyEvent('keydown', keyCode: KeyCode.DOWN).wrapped;
  var fake_button = document.query('#fake_down_key');
  if (fake_button == null) return;

  new Iterable.generate(times, (i) {
    // document.activeElement.dispatchEvent(e);
    fake_button.click();
  }).toList();
}
My test is still failing because there is no such button in the UI. So I add one:
class OpenDialog extends Dialog implements MenuAction {
  Element menu;
  open() {
    menu = new Element.html(
      '''
      <div class=ice-menu>
      <h1>Saved Projects</h1>
      ${filterField}
      <ul></ul>
      <button id=fake_down_key></button>
      </div>
      '''
    );
    menu.
      queryAll('button').
      forEach((b){ b.style.display = 'none';});
    // ...
    _handleArrowKeys(menu);
  }
  // ...
}
Next, I need to add the same event handler for when the fake button is clicked as when the down arrow key is pressed:
  _handleArrowKeys(el) {
    el.onKeyDown.listen(_handleDown);
    el.onKeyDown.listen(_handleUp);

    // Hacks in lieu of KeyEvent tests
    menu.query('#fake_down_key').onClick.listen(_handleDown);
  }
I can almost live with these changes. I have not changed any functionality, I have only added some useless code from the human perspective. Unfortunately, the test will not work as-is because it includes a check for keycodes, which there will be none for click events. So I have to amend the initial guard clause to only apply to keydown events:
  _handleDown(e) {
    if (e.type == 'keydown' && e.keyCode != KeyCode.DOWN) return;
    // ...
  }
Not ideal, but I have a passing test again:
PASS: Keyboard Shortcuts Open Projects Dialog down arrow key moves forward in list
I am going to have some fun in store for when the Enter key is ambiguous, but I think this will mostly work. It gives me some assurance that keyboard interaction is working. It should be fairly easy to back out once KeyEvent is fixed. Most importantly, it does not affect the existing human-facing functionality.



Day #930

No comments:

Post a Comment