Showing posts with label events. Show all posts
Showing posts with label events. Show all posts

Sunday, August 17, 2014

Polymer Track and Holdpulse Events


I confess that, until last night, I was largely unaware of Polymer's normalized events. I had seen them (and even used them) in passing, but had not given them much thought. The Simple Event Example from the Polymer team gives a nice feel for what they are, but I would like to play with them a little more before moving on to other topics.

First, I am curious about the “holdpulse” event. As the name implies, it generates events for as long as the pointer (mouse or touch) is down. But what properties are supported by such an event and how might I use them? To answer those questions, I add a Polymer on-holdpulse handler to a card element:
        <div class="card" layout horizontal
             hero-id="{{selectedTopping}}"
             hero
             on-holdpulse="{{pulse}}"
             on-tap="{{transitionTopping}}">
          <!-- ... -->
        </div>
And, in that bound pulse() method, I simply debug:
Polymer('x-pizza', {
  // ...
  pulse: function(evt) {
    debugger
  },
  // ...
});
After initiating a hold, I find evt in the Scope Variables tab of Chrome's JavaScript console:



Of note in here is that the time is not exactly 200ms. So, if I wanted to send ripples from the current location on some modulo of time, I would have to round the value first:
Polymer('x-pizza', {
  // ...
  pulse: function(evt) {
    //debugger
    if (Math.round(evt.holdTime/100)*100 % 1200 == 0) {
      this.$.ripple.downAction({x: evt.x, y: evt.y});
      var that = this;
      setTimeout(
        function(){
          that.$.ripple.upAction();
        },
        800
      );
    }
  },
  // ...
}
I am also curious about the “track” events. They seem like a cheap way to implement drag events in Polymer elements (among other applications). So I create a dirt simple track icon to test it out:
<polymer-element name="x-pizza">
  <template>
    <!-- ... -->
    <div id=track
         style="position: absolute; top: 50px; right: 50px; width: 25px; height: 25px; background-color: orange; cursor: pointer;"
         on-track="{{track}}"></div>
    <!-- ... -->
  </template>
  <script src="x_pizza.js"></script>
</polymer-element>
I half expected (OK, to be honest, I 100% expected) the X-Y position to be normalized into x and y properties on the event. But they are available on pageX and pageY like normal mouse DOM events:
Polymer('x-pizza', {
  // ...
  track: function(evt) {
    evt.target.style.top = evt.pageY + "px";
    evt.target.style.left = evt.pageX + "px";
  },
  // ...
}
With that, I have a simple drag working.

In the end, these Polymer events work pretty much like a developer might expect. And yes, they work quite well on touch devices as well.


Day #155

Monday, March 17, 2014

Refactoring in the Spirit of Polymer


One of the really solid pieces of advice that I have received on Patterns in Polymer is that the example in the Model Driven View chapter is a tad large.

The MDV example is a simple pizza maker:



The “model” part being a simple object literal comprised of different lists for the toppings:
Polymer('x-pizza', {
  ready: function() {
    this.model = {
      firstHalfToppings: [],
      secondHalfToppings: [],
      wholeToppings: []
    };
  },
  // ...
});
I favor this approach because the model, which is the central piece of the MDV chapter, it relatively small and easily understood. That said, the code that backs these lists is repetitive and long. In other words, the backing class strays from the “Polymer way.” I hate to lose the current, easily understood model, but I also hate to stray from the spirit of Polymer in any of my examples.

I may very well have to introduce an entirely different example. Before I go to that extreme, I try one of the suggestions of moving the model into a new <x-pizza-toppings> Polymer element. This initially means that the <x-pizza> template gets much simpler. It goes from:
<polymer-element name="x-pizza">
  <template>
    <p>
      <select class="form-control" value="{{currentFirstHalf}}">
        <option>Choose an ingredient...</option>
        <option value="{{ingredient}}" template repeat="{{ingredient in ingredients}}">
          {{ingredient}}
        </option>
      </select>
      <button on-click="{{addFirstHalf}}" type="button" class="btn btn-default">
        Add First Half Topping
      </button>
    </p>
    <!-- Nearly identical 2nd half and whole toppings template code... -->
  </template>
  <script src="x_pizza.js"></script>
</polymer-element>
To the much more readable:
<link rel="import" href="x-pizza-toppings.html">
<polymer-element name="x-pizza">
  <template>
    <h2>Build Your Pizza</h2>
    <pre>{{pizzaState}}</pre>
    <x-pizza-toppings id="firstHalfToppings"
                      name="First Half Toppings"
                      ingredients="{{ingredients}}"></x-pizza-toppings>
    <x-pizza-toppings id="secondHalfToppings"
                      name="Second Half Toppings"
                      ingredients="{{ingredients}}"></x-pizza-toppings>
    <x-pizza-toppings id="wholeToppings"
                      name="Whole Toppings"
                      ingredients="{{ingredients}}"></x-pizza-toppings>
  </template>
  <script src="x_pizza.js"></script>
</polymer-element>
The repetitive HTML goes into the template for <x-pizza-toppings> with almost no changes. The backing class of <x-pizza-toppings> then gets the model:
Polymer('x-pizza-toppings', {
  ingredients: [],
  ready: function() {
    this.model = [];
  },
  current: '',
  add: function() {
    this.model.push(this.current);
  }
});
That really is much cleaner. The “model” is now an array, which is a little weird, but I can live with that. The code is much cleaner and much DRYer, so this definitely feels better.

That said, I am more than a little concerned at the number of concepts that I would be introducing with this example. In addition to discussing MDV, I would also have to introduce child Polymer elements. I would even have to mention the data binding of the master ingredients list in <x-pizza> for sharing the list with the child elements. This is not too horrible, especially for a book that is aimed at beyond the introduction. Still, the fewer the concepts, the better.

Speaking of concepts, I am still not quite done here. In the old version, I updated a string representation of the entire pizza whenever a topping change was made. To do that in this version, I need the child <x-pizza-toppings> elements to communicate up to the parent <x-pizza> element whenever a change occurs. That means observing the <x-pizza-toppings> model for changes so that it can fire a custom event:
Polymer('x-pizza-toppings', {
  observe: {
    'model': 'fireChange'
  },
  // ...
  fireChange: function() {
    this.fire('topping-change');
  }
});
The <x-pizza> can then listen for these topping change events, updating the “pizza state” accordingly:
Polymer('x-pizza', {
  // ...
  ready: function() {
    this.addEventListener('topping-change', function(event){
      this.updatePizzaState();
    });
  },
  updatePizzaState: function() {
    var pizzaState = {
      firstHalfToppings: this.$.firstHalfToppings.model,
      secondHalfToppings: this.$.secondHalfToppings.model,
      wholeToppings: this.$.wholeToppings.model
    };
    this.pizzaState = JSON.stringify(pizzaState);
  }
});
With that, I have a fully functional <x-pizza> Polymer element:



I have much less code accomplishing this and it all feels much more approachable. Still...

I will need to think about this approach before pulling it into the book. The ultimate solution may be as simple as only discussing <x-pizza-toppings> and leaving the inclusion in <x-pizza> until a later chapter. That would require some chapter reorganization, but it may be worth it.

Regardless, something needs to change because, thanks to some very helpful feedback, I am much happier with this solution than my previous approach. Improved solutions are always worth the effort, so I have some editing ahead of me!


Day #6


Tuesday, December 10, 2013

Unvexing the Shadow Root


I find my myself slightly vexed. I suppose this is better than being very vexed or in a state of high vexation, but still, I don't care to be even slightly vexed. It irritates the bowels which only increases vexation because there is so much yummy holiday food about. So, you see, a slight vexation simply won't do.

What vexes me today is communication. I still do not believe that I have communication down in Polymer. I continue to struggle with a series of Polymers arranged as follows:
    <store-changes>
      <store-changes-load></store-changes-load>
      <div contenteditable>
        <!-- actual changes occur here -->
      </div>
    </store-changes>
The crux of the communication is the <store-changes-load> element. It is responsible for communicating the initial load value of the parent element, <store-changes> down to the content-editable <div>. The communication down to the <div> is in OK shape. I have to walk the shadow DOM to find where the <div> is projected, but it works. The communication from the parent <store-changes> turns out to be trickier—at least in Dart.

The problem is that <store-changes-load> is in the shadow DOM of <store-changes>. The whole point (well one of the points) of the shadow DOM is to provide strict element encapsulation. Everything from the shadow root down has its own document fragment with no way back out to the “real” element. That's not 100% accurate—it is possible to access the ownerDocument property of a shadow root to get back to the real document, but that still leaves the bother of tracking down the real element in that DOM. Hassle aside, the DOM encapsulation created by the shadow root is a good thing, which should never be broken.

To maintain the integrity of the shadow DOM, I cannot have <store-changes-load> access properties on <store-changes>. The most I can do is interact with the root of <store-changes>'s shadow DOM. Since that has no actual information, I instead resorted to having <store-changes> walk down its own shadow DOM to fire events on <store-changes-load> elements:
@CustomTag('store-changes')
class StoreChangesElement extends PolymerElement {
  // ...
  void _fetchCurrent() {
    stores.forEach((store) {
      store.fetch().then((_r) {
        record = _r;
        shadowRoot.queryAll('store-changes-load').
          forEach((el) {
            fire('store-changes-load', detail: record, toNode: el);
          });
      });
    });
  }
  // ...
}
I am somewhat OK with this apparent coupling of <store-changes> and <store-changes-load>. The names of the elements already suggest that there is coupling. In fact, the <store-changes-load> element is even hard-coded into the template of the <store-changes> element. So there is very explicit coupling taking place. And yet, this still bothers me. It slightly vexes.

What keeps bugging me is that I have to fire my events on the <store-changes-load> element (via toNode). So even though I am rife with coupling everywhere, it is this piling on more coupling that gets to me. The whole point of events is to separate the firer from the listener and here I am doing the opposite: targeting a very specific element.

But what else can I do? The <store-changes-element> cannot gain access to the “real” <store-change>, just its shadow DOM. If I had access to the real element, I could do something as simple as reading properties. But all I can do is access the shadow root, which has precious few properties and none of them custom.

It turns out that the shadow root has two important things going for it. First, the <store-changes> Polymer code has access to it. Second, it has dispatchEvent() and addEventListener() methods. In other words, I can have my <store-changes> Polymer fire the store-changes-load event on its own shadow root, for which the <store-changes-load> Polymer can listen.

So I replace that shadow DOM walking with a dispatchEvent() on the shadow root:
@CustomTag('store-changes')
class StoreChangesElement extends PolymerElement {
  // ...
  void _fetchCurrent() {
    stores.forEach((store) {
      store.fetch().then((_r) {
        record = _r;
        shadowRoot.
          dispatchEvent(new CustomEvent('store-changes-load', detail: record));
        // shadowRoot.queryAll('store-changes-load').
        //   forEach((el) {
        //     fire('store-changes-load', detail: record, toNode: el);
        //   });

      });
    });
  }
  // ...
}
It is a slight bummer that Dart's ShadowRoot class does not have a fire() method of its own. But, as that would only be a very thin wrapper around dispatching a custom event, I am not too fussed. I am not even slightly vexed.

What this means is that the parent Polymer no longer needs to care about the internal structure of its elements. It need only know that it has to communicate a custom event down and do so at the topmost point possible. With that, the elements that care about this event now have to walk up their respective shadow DOMs to find the necessary shadow root for listening. In my case here, I only need to listen to the parent node:
@CustomTag('store-changes-load')
class StoreChangesLoadElement extends PolymerElement {
  // ...
  StoreChangesLoadElement.created(): super.created();
  ready() {
    super.ready();
    parentNode.addEventListener('store-changes-load', (e){
      var current = e.detail['current'];
      editable.innerHtml = current;
    });
  }
  // ...
}
With that, I have my Polymers again communicating, this time without breaking encapsulation.

This might seem a minute distinction, but I think it could be valuable. It is easier for a child element to walk up to a node that for the parent to walk through all descendants to find the child. In other words, this approach should make for some cleaner code. I also appreciate one less point of coupling. For other Polymers that have no natural coupling point, this could make all the difference in the world between spaghetti callbacks and clean, non-vexing code.


Day #961

Friday, November 29, 2013

UI-less Polymer Element for Sane Change Events


Yesterday, I managed to scrape together a Polymer (JS) element that listened for events on the containing document. This was a non-UI element that was just in place to interact with other elements on the page. I think there is a lot of potential for this kind of thing, which is only one of the reasons that Polymer excites me enough to be writing a book on it.

Today, I hope to try the opposite. Instead of listening for events on the web page in which my custom Polymer element is contained, I would like to listen for events on an element inside my custom Polymer element. The idea in this case is to take something that is very hard to listen for events on, say a contenteditable <div>, and normalize them for easier consumption.

Something like this:
    <change-sink>
      <div contenteditable>
        <!-- initial content here... -->
      </div>
    </change-sink>
    <script>
      document.querySelector('change-sink').addEventListener('change', function(e){
        console.log('Was:');
        console.log(e.detail.was);
        console.log('But now it\'s different!');
      });
    </script>
Content editable stuff is a pain to identify when changes occur. It's possible, but far from easy. That's where this new <change-sink> tag comes in.

I start by importing the Polymer definition as usual:
<!DOCTYPE html>
<html lang="en">
  <head>
    <!-- 1. Load Polymer before any code that touches the DOM. -->
    <script src="scripts/polymer.min.js"></script>
    <!-- 2. Load component(s) -->
    <link rel="import" href="scripts/change-sink.html">
  </head>
  <body>
    <change-sink>
      <div contenteditable>
        <!-- initial content here... -->
      </div>
    </change-sink>
  </body>
<script>
  <!-- change-sink listeners here... -->
</script>
</html>
I begin the definition of change-sink.html with a simple entered-view callback that grabs a reference to the element contained within <change-sink> and placing a keyup-console-logger on it:
<polymer-element name="change-sink">
  <script>
    Polymer('change-sink', {
      enteredView: function() {
        var that = this,
            el = this.children[0];
        el.addEventListener('keyup', function(e){
          console.log(el.innerHTML);
        });
      }
    });
  </script>
</polymer-element>
That does the trick as I now see the contenteditable contents change with each keyup:
<h1>Change e!</h1>
<h1>Change Me!</h1>
Instead of simply generating console messages, I want to support the API described earlier. When a change occurs, I should fire a "change" event whose details include the previous values for comparison. That is easy enough:
    Polymer('change-sink', {
      was: undefined,
      enteredView: function() {
        var that = this,
            el = this.children[0];
        el.addEventListener('keyup', function(e){
          that.fire('change', {was: that.was});
          that.was = el.innerHTML;
        });
      }
    });
I support a was property on the Polymer element itself. The previous value is then assigned there whenever a change occurs. Just before that assignment, I fire the change event with the previous value of the contained element.

And that works just fine! When I make my first change, the was value is undefined and, after the second change, the was value is defined properly:
Was:
undefined
But now it's different! 

Was:
<h1>Change Me!</h1>
But now it's different!
OK. That is pretty cool. Cool, but it is not actually firing on changes—just keyup. I'll get change events when I arrow key around my content editable <div>. I'll also get events with every character typed, not with every change. I am not going to see change events when content is pasted. Also, it would be cool if I could "change sink" <input> changes as well as content editable <div> tag changes.

All of that turns out to be pretty easy. I need to listen for a few more event types, use a debounce, and check for content in "value" if it is not in innerHTML:
    Polymer('change-sink', {
      was: undefined,
      child: undefined,
      enteredView: function() {
        this.child = this.children[0];

        var that = this;
        this.child.addEventListener('keyup', function(_){lazyChange(that)});
        this.child.addEventListener('blur',  function(_){lazyChange(that)});
        this.child.addEventListener('paste', function(_){lazyChange(that)});
        this.child.addEventListener('input', function(_){lazyChange(that)});
      }
    });

  var lazyChange = debounce(change, 750);

  function change(_el) {
    var el = _el.child;
    if (el.innerHTML == _el.was) return;

    _el.fire('change', {was: _el.was});
    _el.was = el.innerHTML || el.value;
  }
That does the trick. It now works with <textarea> just as easily as it does with content editable <div> tags. I can paste and see a change event. I can type new content, but only see one event. It is all pretty slick.



The actual heavy lifting comes from Polymer establishing the custom element and its relationship with the child element. Once that is in place, normalizing an otherwise ugly event proves very straight forward. Best of all, this is built for re-use. Anyone with this <change-sink> code now has normalized change events. I am eager to see what else I can do with this stuff!


Day #950

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

Wednesday, November 6, 2013

Upgrading for 1.0 (Wha?)


Dart 1.0 is coming! Dart 1.0 is coming!

OK there may not be an official release date or anything, but it's encouraging to have an actual mention of the until-now-mythical milestone. To get in the spirit, I will take the advice in the announcement and ensure that one of my Pub packages is 1.0 ready.

Since the ctrl-alt-foo package's build is currently failing, I will use this as an opportunity to fix is as well. Before fixing any remaining tests (I actually may already have them shipshape), I upgrade everything to the Dart 1.0 compatible settings. This involves a few changes to the pubspec.yaml file:
name: ctrl_alt_foo
version: 0.3.0
description: For creating simple keyboard shortcuts.
authors:
- Chris Strom <chris@eeecomputes.com>
homepage: https://github.com/eee-c/ctrl-alt-foo
environment:
  sdk: ">=0.8.10+6 <2.0.0"
dev_dependencies:
  unittest: any
I have bumped the version of my own library from 0.2.0 to 0.3.0. This is mostly because I am breaking some of the API in an attempt to use more of the new KeyEvent stuff. The large version bump is not really necessary just for preparing for 1.0.

The announcement specifically mentions that the days of any version constraints has past with the approaching 1.0. I am unsure if that applies to development dependencies like unittest. I rather like using the more recent version of development dependencies. No doubt I will change my mind once multiple builds break on a future library change, but for now I will risk it.

The important change is the sdk restriction to 0.8.10+6 or higher, which will support the 1.0 release. Armed with that change and the most recent version of the SDK, I begin the upgrade process. And quickly hit a roadblock:
➜  ctrl-alt-foo git:(key-event) ✗ pub upgrade   
Resolving dependencies...
Package ctrl_alt_foo requires SDK version >=0.8.10+6 <2.0.0 but the current SDK is 0.8.10+3.r29803.
This is just how much on the bleeding edge I am—I am following announcement instructions from the future! At the time of this writing 29803 is the most recent SDK on the dartlang.org site.

Rather than relaxing the SDK constraint, I will pull down the more recent continuous build from the continuous build archives. With that, I am able to grab the most recent version of unittest and its dependencies:
➜  ctrl-alt-foo git:(key-event) ✗ pub upgrade
Resolving dependencies...............
Downloading unittest 0.9.0 from hosted...
Downloading stack_trace 0.9.0 from hosted...
Downloading path 0.9.0 from hosted...
Dependencies upgraded!
I run into an odd utf-8 issue. Utf-8 that I had placed into a string was no longer coming through as utf-8, but rather the the utf-8 representation of the three bytes in the original utf-8 string. For now, I chalk that up to running the bleeding edge and implement a quick workaround.

After merging my code changes, I am ready to publish to pub.dartlang.org:
➜  ctrl-alt-foo git:(master) pub lish
Publishing "ctrl_alt_foo" 0.3.0 to https://pub.dartlang.org:
|-- .gitignore
|-- LICENSE
|-- README.md
|-- lib
|   |-- helpers.dart
|   |-- key_event_x.dart
|   |-- key_identifier.dart
|   |-- keys.dart
|   '-- shortcut.dart
|-- pubspec.yaml
'-- test
    |-- index.html
    |-- run.sh
    '-- test.dart

Looks great! Are you ready to upload your package (y/n)? y
Uploading.........
ctrl_alt_foo 0.3.0 uploaded successfully.
And that's all there is to getting ready for the big 1.0. At least in this package.

Now if you'll excuse me, I do believe that I need to finish up the next edition of Dart for Hipsters. Quickly.


Day #927

Sunday, November 3, 2013

Still Can't KeyEvent in Dart


I have not been pushing my #pairwithme sessions much of late, in part because Dart's keyboard events were broken for a long time. I don't have tons of time in the upcoming weeks, but I would at least get to a point where #pairwithme is a possibility again, so let's see if I can fix the build in ICE Code Editor.

When I first tried the build with the most recent version of Dart, there were some 130+ failing tests. As has been the case quite often of late, this was due to one or more libraries undergoing some breaking changes—this time the js-interop. Most of those changes were actually rather pleasant—of the remove old, unnecessary method calls variety. After doing that, I am down to 30 or so, which mostly seem to involve my ctrl-alt-foo keyboard shortcut library.

And, unfortunately, there still seem to be the same old bugs in Dart's KeyEvent—namely that I cannot use it to create a wrapped KeyboardEvent with an actual keycode. Even more unfortunately, the static class hack that I devised no longer seems to be working for me.

To review, I cannot dispatchEvent() because there is no way to generate custom keyboard events with data in tests. I cannot add events to CustomStream because the elements on which I am trying to listen for events are dynamically created.

In other words, if I try to listen for Enter events with the onKeyDown property:
  _handleEnter(el) {
    el.onKeyDown.listen((e){
      if (e.keyCode != KeyCode.ENTER) return;
      // Try to do stuff here...
    });
  }
This will not work with my tests because the wrapped KeyboardEvent—which I try to generate in tests—always has a zero keyCode.

And, If I use the EventStreamProvider interface:
  _handleEnter(el) {
    KeyEvent.keyDownEvent.forTarget(el).listen((e) {
      // Try to do stuff here...
    });
  }
Then Dartium generates the following when I try to use the application itself:
Exception: InvalidStateError: Internal Dartium Exception
(with no stacktrace)

Bummer. I think that I have an open bug for the second problem. I really need to make sure that I have the first one in the system. All I can do is wait patiently after that.


Day #924

Sunday, October 27, 2013

Getting Started with Angular.dart's New Testing Features


I probably need to quit while I am ahead with Angular.dart. It is under such active development that I could spend every other night correcting posts that have been made obsolete by new releases. Between the API documentation and the Angular.dart Tutorial, I am very likely polluting the documentation pool with stuff that will be obsolete before long.

Still, it is very exciting that so much is happening with the Dart port of the AngularJS project. I fully intend to come back at some point and before I leave, I cannot resist the chance to play with the test bed that the project uses internally. Yes, I am completely obsessed with testing and, more importantly, finding new and unique ways to test.

Happily testing methods were recently exposed directly in the library, so there is no need for me to do bad things to my local copy of the repository. All of this work is done against the 0.0.6 release of the package.

I start with my test setup, which needs to establish the injector context for the test:
import 'package:unittest/unittest.dart';
import 'package:unittest/mock.dart';
import 'dart:html';
import 'dart:async';

import 'package:angular/angular.dart';
import 'package:angular/mock/module.dart';

import 'package:angular_calendar/calendar.dart';

main(){
  group('Appointment', (){
    setUp(setUpInjector);
    // ...
  });
}
The setUpInjector function is part of the angular mock library, which is already being imported.

Next, I need a module that includes a mock HTTP backend service so that I can set expectation:
    setUp(module((Module module) {
      http_backend = new MockHttpBackend();
      module
        ..value(HttpBackend, http_backend);
    }));
The module() function enables me to inject a fake HTTP backend into the test so that I do not need a real server running in the background. With the setup out of the way, I ought to be able to inject an instance of my backend service for actual testing.

And here, I am a bit stumped. I have a mock Angular module that has already injected a mock Http instance. I want to inject my backend into the test, so I try:
    test('add will POST for persistence', (){
      inject((AppointmentBackend b) { server = b; });

      http_backend.
        expectPOST('/appointments', '{"foo":42}').
        respond('{"id:"1", "foo":42}');

      server.add({'foo': 42});
    });
But that does not work. When I run the test, I get:
ERROR: Appointment Backend add will POST for persistence
  Test failed: Caught Illegal argument(s): No provider found for AppointmentBackend! (resolving AppointmentBackend) at position 0 source:
   (AppointmentBackend b) { server = b; }.
  #0      DynamicInjector.invoke.<anonymous closure> (package:di/dynamic_injector.dart:197:9)
  #1      DynamicInjector.invoke.<anonymous closure> (package:di/dynamic_injector.dart:196:9)
I am not quite sure what this means since I am importing my application code that defines this class. Ah well, I will keep at this tomorrow and hopefully figure out what I am doing wrong.

Update: Figured it out. This error is telling me that I have not injected the class into the fake Angular module that I am trying to use. I need only type(AppontmentBackend) to get the code working. The full version of the test using the new test module() and inject() is then:
  group('Appointment Backend', (){
    var server, http_backend;
    setUp(() {
      setUpInjector();

      module((Module module) {
        http_backend = new MockHttpBackend();
        module
          ..value(HttpBackend, http_backend)
          ..type(AppointmentBackend);
      });
    });

    test('add will POST for persistence', (){
      inject((AppointmentBackend b) { server = b; });

      http_backend.
        expectPOST('/appointments', '{"foo":42}').
        respond('{"id:"1", "foo":42}');

      server.add({'foo': 42});
    });
  });
I am still not sure I have the hang of this, so I will likely follow up more tomorrow.


Day #917

Tuesday, October 15, 2013

Yet Another Awful Hack Needed to Test Keyboard Events in Dart


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:
  1. When used in live code, KeyEvent listeners do not work, throwing “Internal Dartium Errors”
  2. 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
  3. Creating a KeyEvent wraps a corresponding low-level KeyboardEvent that can be dispatched to elements, but it lacks actual keyboard data (keyCode, charCode)
  4. Dispatching the high-level KeyEvent generates Internal Dartium Errors—in test and application code
In a desperate attempt to get my tests passing and useful I have come up with two workarounds. The first addresses #1 and #2. Instead of listening to a stream of 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.

Unfortunately, my hack for #3 works when dispatching only one event to an element. My text input fields tend only to need to listen for an Enter keydown event. If there is any keydown event with keyCode equals zero, I can safely assume that I am trying to dynamically create an Enter keydown. Unfortunately for me, the ICE Code Editor also include an arrow-key navigatiable menu system. My tests had been fairly nice thanks to some nice helper code:
      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.

Day #905

Monday, October 14, 2013

A Perfectly Acceptable, Yet Completely Horrific Compromise


I continue to explore the bleeding edge KeyEvent changes in Dart. Last night, I finally found an appropriate solution for using and testing the new API in my keyboard shortcut library ctrl_alt_foo. Armed with that knowledge, I would like to get the build for the ICE Code Editor passing again for the first time since the last round of changes to keyboard event handling in Dart some months back. To say that I am anxious to have this finished is an understatement.

Simply pointing the ICE Code Editor to a local copy of ctrl_alt_foo with the recent changes fixes a number of failing tests. I am down to 7 problems tests:
153 PASSED, 4 FAILED, 3 ERRORS
I think that most of them suffer from the same problem: hitting the Enter and expecting the UI to change. For instance, in the following test, I expect that hitting the Enter key will select the top-most, matching project and open it:



But the test finds that nothing happens—the “Old” project remains open:
FAIL: Keyboard Shortcuts Open Projects Dialog enter opens the top project
  Expected: 'Old'
    Actual: 'Current'
     Which: is different.
  Expected: Old
    Actual: Current
            ^
The problem point in my test is the hitEnter() method:
      test("enter opens the top project", (){
        helpers.typeCtrl('o');
        helpers.typeIn('old');
        helpers.hitEnter();

        expect(
          editor.content,
          equals('Old')
        );
      });
For better experimentation, I comment out helpers.hitEnter() and replace it with the equivalent dispatch call:
      solo_test("enter opens the top project", (){
        helpers.typeCtrl('o');
        helpers.typeIn('old');
        // helpers.hitEnter();
        var e = new KeyEvent('keydown', keyCode: KeyCode.ENTER);
        print('gonna dispatch: ${e.keyCode}');

        document.
          activeElement.
          dispatchEvent(e);

        expect(
          editor.content,
          equals('Old')
        );
      });
I still get the same error. I cannot dispatch a KeyboardEvent directly—dynamically generated events in the new Dart keyboard reality must come from KeyEvent. But wait! There is the wrapped property on KeyEvent. Perhaps I can grab the wrapped KeyboardBoard event from a dynamically created KeyEvent and dispatch that? Sadly no. I am perfectly capable of doing that, but no matter what keycode I use for KeyEvent, the wrapped KeyboardEvent is always zero:



Dang. I was really hoping that I was onto something there.

If I stick with dispatching the KeyEvent, I get Test failed: Caught InvalidStateError: Internal Dartium Exception. If I dispatch the wrapped KeyboardEvent, I get the same thing, but as an non-halting error. In fact, the KeyboardEvent actually triggers my code's onKeyDown listener. Of course, it hardly matters since keyCode is always zero. Say…

In real life, keyCode is never going to be zero. So it is safe to assume that, if I ever see a zero I am running some test code. And, since this bug is presumably going to be fixed in Dart at some in the very near future... What's the harm in assuming that my guard clauses match when keyCode is zero in addition to my desired value?

In other words, I change my application code to think that it is handling an Enter event when it sees a keyCode of ENTER or zero:
  _handleEnter(el) {
    el.onKeyDown.listen((e){
      if (![KeyCode.ENTER, 0].contains(e.keyCode)) return;
      if (el.value.isEmpty) return;
      query('.ice-menu ul').children.first.click();
    });
  }
I still get that error message about Internal Dartium Errors, but… my test passes:
Exception: InvalidStateError: Internal Dartium Exception 
PASS: Keyboard Shortcuts Open Projects Dialog enter opens the top project 

All 1 tests passed. 
Make no mistake: this is pretty horrible. The goal of testing is robust, accurate, maintainable code. This is none of those things. But what it is, is a minimal stopgap bridge until bleeding edge (and released) Dart fixes the problems of Internal Dartium Exceptions and zero keyCodes. Presumably once all of this is stable the above test and application code will just work—with the allow zero to be removed.

I think I can live with that. I will take some time to get the other 6 problem tests fixed in a similar fashion. And, if this approach works with them, I will move on to other topics. At least until KeyEvent stabilizes.



Day #904

Sunday, October 13, 2013

Reusing Dart KeyEvent Streams for Great Testing


I am not quite satisfied with last night's custom keyboard testing solution for Dart's bleeding edge KeyEvent class. The primary problem that I am trying to work around is that KeyEvent instances have to be added to the same stream that is listening for events. There is no way to test for bubbling events. There is no way to dispatch an event to an element so that any number of streams that are attached can listen.

Last night's solution worked around this by creating a class stream on my key shortcut library's ShortCut class. I think that was probably a reasonable thing to do, but I am less happy with the class-level dispatchEvent():
class ShortCut {
  static var _stream = KeyboardEventStream.onKeyDown(document.body);
  static var _streamController = new StreamController.broadcast();
  static get stream {
    _stream.listen((e) {_streamController.add(new KeyEventX(e));});
    return _streamController.stream;
  }
  static void dispatchEvent(KeyEvent e)=> _stream.add(e);
  // ...
}
This means that listening to events looks like:
ShortCut.stream.listen((e) { /* ... */ });
And dispatching events looks like:
ShortCut.dispatchEvent(
  new KeyEvent('keydown', keyCode: some_kind_of_key_code)
);
This is probably OK for my keyboard shortcut library because none of this will be exposed by the library whose API will feature higher-level calls. What is unsatisfying in this approach is that I am making use to the new KeyEvent feature, but not supporting its interface. It may not matter much in this case, but I would like to have a handle on how to do it for the future.

I start by borrowing heavily from the CustomStream implementations in bleeding edge Dart. OK, I don't borrow. I copy directly, changing only the name to ShortCutStream:
class ShortCutStream<T extends Event> extends Stream<T>
    implements CustomStream<T> {
  StreamController<T> _streamController;
  String _type;

  ShortCutStream(String type) {
    _type = type;
    _streamController = new StreamController.broadcast(sync: true);
  }

  StreamSubscription<T> listen(void onData(T event))
    => _streamController.stream.listen(onData);

  Stream<T> asBroadcastStream()
      => _streamController.stream;

  bool get isBroadcast => true;

  void add(T event) {
    if (event.type == _type) _streamController.add(new KeyEventX(event));
  }
}
I also copy the optional attributes, but omit them above for a little brevity. This, I believe, is what is going to be necessary for any Stream that supports the new CustomStream interface, which mandates only that the class support the add() method. The class needs a stream controller, which is instantiated by the constructor. The regular stream methods are delegated to stream controller's stream property. The add() method can then add() events by adding them to the stream controller. As I said, this seems the only way to support a CustomStream interface, so we might see abstract classes along the lines of the above spring up in the future.

The only thing that makes the above a ShortCutStream instead of a generic custom stream is that the add() method adds KeyEventX instances to the stream instead of the normal KeyEvent objects. KeyEventX decorates KeyEvent with some helpful methods in a shortcut context (e.g. isCtrl, isShift).

To use ShortCutStream, I remove dispatchEvent() (I will add() dynamically created events directly to ShortCut.stream from now on) and define the static stream getter as:
class ShortCut {
  static CustomStream _stream;
  static get stream {
    if (_stream == null) _stream = new ShortCutStream('keydown');
    return _stream;
  }
  // ...
}
What is crazy is I do not need to do anything else to create and dispatch dynamic shortcut events. My test is again passing:
  test("can listen for key events", (){
    ShortCut.stream.listen(expectAsync1((e) {
      expect(e.isKey('A'), true);
    }));

    type('A');
  });
Where the type() helper now added to the same ShortCut stream:
type(String key) {
  ShortCut.stream.add(
    new KeyEvent('keydown', keyCode: keyCodeFor(key))
  );
}
In all honesty, that is somewhat disappointing. I believe that I have used CustomStream and KeyEvent properly here (in the spirit in which it is meant to be used). But all that I have managed to do is verify that, if I create a keyboard stream that could wrap actual keyboard events, then my code would work. Nothing in here actually captures real keyboard events. In other words, all I can do is test a wrapper for keyboard events, but not real keyboard events.

That said, I can workaround the buggy KeyEvent and KeyboardEventStream interfaces to obtains KeyEvent instances. Instead of using the stream providers from those classes (which currently generate Internal Dartium Exceptions in the face of real keyboard input), I can wrap the more standard KeyboardEvent instances in the ShortCutStream constructor:
class ShortCutStream<T extends Event> extends Stream<T>
    implements CustomStream<T> {
  // ...
  ShortCutStream(String type) {
    _type = type;
    _streamController = new StreamController.broadcast(sync: true);

    document.body.onKeyDown.listen((e) {
      KeyEvent wrapped_event = new KeyEvent.wrap(e);
      add(wrapped_event);
    });
  }
  // ...
}
With that, I am able to get all of the tests passing in my keyboard shortcut library (even one that had been mysteriously marked as “skip”). And, in all honesty, I think the code is improved for this change. Where before I had been creating a new stream for every keyboard shortcut, now I am re-using the existing stream. That has to cut down on the overhead of my code—especially when code is creating lots of keyboard shortcuts. It might even cut down on a race condition or two.

That said, I still do not know how this helps to test something along the lines of hitting Enter in a text field. It would seem that a library that caches streams per element is going to be required. As luck would have it, I am faced with just that problem over in the ICE Code Editor, so I will pick back up with that problem tomorrow.


Day #903

Saturday, October 12, 2013

KeyEvents are for Unit Tests


It took me a while, but I think that I have finally realized that the latest KeyEvent changes in Dart are for unit tests, not acceptance tests. I like unit tests for driving new functionality, but rather prefer having acceptance tests around as being better at catching bugs—or at least giving me the confidence that swaths of the application are behaving more or less as designed. Still, unit tests are pretty nice and I hope to put them to use tonight.

Toward that end, I give up (at least temporarily) the hope to get the acceptance tests in the ICE Code Editor working. Instead, I am going to see if I can get the ctrl_alt_foo shortcut library working again.

Actually, working again is not quite right. It works now and the build has never once failed. But it works through an absolutely insane hack involving dynamically created JavaScript code. For the record, I opted for that hack not because Dart lacks keyboard handling, but because it lacked the ability to test it well.

With the JavaScript hack and various Dart hacks that preceded it, I tried my best to stay close to existing Dart APIs. Where Dart has KeyEvent, ctrl_alt_foo has KeyEventX. Where Dart has KeyboardEventStream, ctrl_alt_foo has KeyboardEventStreamX. My sincere hope is that, even though acceptance tests may be out of the question, the recent KeyEvent patched to Dart's bleeding edge are sufficient to remove my “X” versions of Dart core classes.

From what I learned yesterday, I don't think that removing these is an option just yet. For one thing, I need to keep some of the decoration that I add to KeyEvent (e.g. isCtrl, isChar, etc). More pertinent to testing, I need to keep KeyboardEventStreamX because I need it to return the same stream every time.

As I found yesterday, adding custom keyboard events to a stream for testing only works if I am adding to the same stream on which the code is listening. What this means in practice is that it is not (currently) possible to dispatch events to elements in Dart as is possible in JavaScript. Dart limits developers to adding events to streams. If I create multiple streams for an element, then add an event to the last stream, only the last stream will see the event—not all of the streams listening to the same element.

In other words, my existing approach to KeyboardEventStreamX is not going to work because it returns a new stream every time its “on” class methods are invoked:
class KeyboardEventStreamX extends KeyboardEventStream {
  static Stream onKeyDown(EventTarget target) {
    return Element.
      keyDownEvent.
      forTarget(target).
      map((e)=> new KeyEventX(e));
  }
}
Instead, I switch to ShortCut, which needs a way to always use the same stream, but also to return a stream that wraps normal KeyEvent instances in KeyEventX. I settle on returning a static stream controller stream, which will wrap KeyEventX instances and also exposing a static dispatchEvent() method for placing events directly on the single instance of the document.body stream:
class ShortCut {
  // ...
  static var _stream = KeyboardEventStream.onKeyDown(document.body);
  static var _streamController = new StreamController.broadcast();

  static get stream {
    _stream.listen((e) {_streamController.add(new KeyEventX(e));});
    return _streamController.stream;
  }

  static void dispatchEvent(KeyEvent e)=> _stream.add(e);
  // ...
}
I then use this to dispatch events as:
type(String key) {
  ShortCut.dispatchEvent(
    new KeyEvent('keydown', keyCode: keyCodeFor(key))
  );
And can test that the events are working with:
  test("can listen for key events", (){
    _s = ShortCut.stream.listen(expectAsync1((e) {
      expect(e.isKey('A'), true);
    }));

    type('A');
  });
That works:
PASS: can listen for key events 
But I don't know if that makes my life much easier. I am guaranteed one stream for listening to events. The _stream static variable is assigned when ShortCut is loaded. If I limit my testing to dispatching keyboard events through ShortCut.dispatchEvent() that same stream will always be used. So I have side-stepped the multi-stream problem. But the ShortCut.stream value is just a regular Stream, not a CustomStream that supports add(). Forcing events through dispatchEvent() seems likely to cause confusion in the future.

I will call it a night here and think on this approach.


Day #902

Friday, October 11, 2013

Trying to Figure Out What I Can Test with KeyEvent


Still stinging from last night's defeat, I try to pick up the broken pieces of my soul tonight.

I mean, how cool would it have been to get custom keyboard events working in Dart on the 900th day of the current chain? Having dutifully file the requisite bug, I move on tonight to see what I can salvage from last night's effort.

I start by pulling down the latest Dart DSK (Dart VM version: 0.1.2.0_r28552 (Fri Oct 11 14:06:31 2013) on "linux_x64") from the continuous build archive. The first two listeners in the following still generate errors:
import 'dart:html';
main(){
  var subscription = KeyboardEventStream.onKeyDown(document.body).listen(
      (e) => print('KeyboardEventStream listener'));
  var subscription2 = KeyEvent.keyDownEvent.forTarget(document.body).listen(
      (e) => print('KeyEvent listener'));
  var subscription3 = document.body.onKeyDown.listen(
      (e) => print('regular listener'));
}
Only subscription3 from the document.body.onDown stream give me results when I hit a key:
regular listener 
The other two spew multiple Dartium errors:
Exception: InvalidStateError: Internal Dartium Exception 
undefined
As far as I can tell, the first two streams (from KeyboardEventStream and KeyEvent) are both useless in user-facing code. Even if I run the unit tests for the new KeyEvent in Dartium, hitting any key after running the test generates oodles of Internal Dartium Exceptions. But maybe the onKeyDown can be of some use?

Well, no. The onKeyDown property is a _ElementEventStreamImpl. Sadly that is not a subclass of the new CustomStream and therefore does not support the add() method for dispatching events:
Exception: Class '_ElementEventStreamImpl' has no instance method 'add'.

NoSuchMethodError : method not found: 'add'
Receiver: Instance of '_ElementEventStreamImpl@0x3918afae'
Arguments: [Instance of 'KeyEvent']
Bummer. I suppose this is the purpose of the KeyboardEventStream and KeyEvent wrappers. But still… bummer.

Well, it seems that I remain at a dead-end in ICE Code Editor—at least until my bug is addressed. I can write testable code that crashes the browser or non-testable code because the working property does not support dispatching events. In my mind, testing trumps runnable code. At some point the testable code will work—and maybe it already does when compiled to JavaScript.

Even this proves difficult. Consider if I stick with the KeyEvent class and listen for keydown events:
  var subscription2 = KeyEvent.keyDownEvent.forTarget(document.body).listen(
      (e) => print('KeyEvent listener'));

  // Trigger / listen for event on document.body
  var streamDown = KeyEvent.keyDownEvent.forTarget(document.body);
  var subscription4 = streamDown.listen(
      (e) => print('streamDown listener ${e.keyCode}'));
  streamDown.add(new KeyEvent('keydown', keyCode: 4, charCode: 0));

  // Trigger / listen for event on the H1
  var h1Down = KeyEvent.keyDownEvent.forTarget(query('h1'));
  var subscription5 = h1Down.listen(
      (e) => print('h1Down listener ${e.keyCode}'));
  h1Down.add(new KeyEvent('keydown', keyCode: 5, charCode: 0));
Both subscription2 and subscription4 listen for keydown events on document.body. When I add a new keydown KeyEvent to the stream from the second group, I expect to see both event listeners fire. But it turns out that I am adding the KeyEvent to the second stream and only the second stream. I am not generating an event that will be seen by all listeners on document.body—only an event that will be seen by that second stream. Worse, when I add an event to the keydown stream of an <h1>, it is again only to the <h1> tag's stream and it does not bubble up.

I expect the above code to trigger both the document.body listeners twice—both should see the custom keyboard event on document.body and both should see the <h1> event bubble. But I only see the direct events:
streamDown listener 4 undefined:1
h1Down listener 5 
This is of very limited use for me. For instance, if I want to test that a keydown in the ICE project filter shows all the projects that start with “a” I need access to the KeyEvent stream that actually listens to the corresponding text field. For that to work, I would need to expose the filter stream (and every other stream) in the ICE code as a public property. There is no way I am doing that just to support testing.

Bother.

As depressing as all of that is, hope is not completely lost. I do believe that the keyboard shortcut code ought to be somewhat testable. Given what I learned today, I doubt that I will be able to add a custom KeyEvent for Ctrl+O to a stream in ICE to test that the open dialog appears. Still, I think the keyboard shortcut library itself ought to be testable. And perhaps I can expose some high-level testing methods from that same package to support limited keyboard testing in ICE. Something for tomorrow. Hope.


Day #901

Thursday, October 10, 2013

Bleeding Edge Dart Key Events


Please let this work. Please let this work. Please let this work.

For a very long time, I have been trying to get keyboard testing working in Dart. The API has been very much a moving target, but recent changes would seem to suggest that hope is in sight. And possibly already in the builds.

To try the new API out, I start by updating the pubspec.yaml for my ICE Code Editor project:
name: ice_code_editor
# ...
dependencies:
  crypto: any
  js: any
  ctrl_alt_foo:
    path: /home/chris/repos/ctrl-alt-foo
  json: any
dev_dependencies:
  unittest: any
Most of my keyboard shortcut and testing has been extracted out into that ctrl_alt_foo package. By pointing the pubspec.yaml dependency to my local copy, I can edit ctrl_alt_foo code and try the changes out immediately in ICE.

After a quick pub update, I am ready to try some testing. The first test that does some keyboard interaction is verifying that the Escape key closes dialogs:
    test("editor has focus after closing a dialog", (){
      helpers.click('button', text: '☰');
      helpers.click('li', text: 'Make a Copy');
      helpers.hitEscape();

      var el = document.
        query('.ice-code-editor-editor').
        query('textarea.ace_text-input');

      expect(document.activeElement, el);
    });
That helper is exported from the ctrl_alt_foo package. I replace the current, non-working implementation with:
hitEscape() {
  KeyEvent.
    keyDownEvent.
    forTarget(document.activeElement).
    add(
      new KeyEvent('keypress', keyCode: KeyName.ESC)
    );
}
The forTarget() method gives me a custom stream. Dart streams do not expose an add() method—a corresponding stream sink would have that—but the new custom stream does support adding objects directly to the stream. At least this is the hope.

Sadly, my hope is dashed when I try to run the test:
ERROR: Focus editor has focus after closing a dialog
  Test failed: Caught No constructor 'KeyEvent' with matching arguments declared in class 'KeyEvent'.
  
  NoSuchMethodError: incorrect number of arguments passed to method named 'KeyEvent'
  Receiver: Type: class 'KeyEvent'
  Tried calling: KeyEvent("keypress", keyCode: "Esc")
  Found: KeyEvent(KeyboardEvent)
  dart:core-patch/errors_patch.dart                                                                                               NoSuchMethodError._throwNew
  package:ctrl_alt_foo/helpers.dart 22:19                                                                                         hitEscape
  ../full_test.dart 200:24                                                                                                        full_tests.<fn>.<fn>
  package:unittest/src/test_case.dart 111:30
This error is coming from my attempt to create the new instance of a KeyEvent. In other words, the most recent release of Dart does not support this yet.

So it's on to the continuous build for me:
$ wget http://gsdview.appspot.com/dart-editor-archive-continuous/latest/darteditor-linux-64.zip
After installing that, restarting Dartium, and re-running the test… it still fails. But it is a different failure!
FAIL: Focus editor has focus after closing a dialog
  Expected: TextAreaElement:<textarea>
    Actual: InputElement:<input>
It seems that I have succeeded in generating a custom event. At the very least, I am no longer seeing outright failures when I create and dispatch my custom event. Not satisfied by moral victories, it is now time to figure out why that custom event is not having the desired effect.

My first attempt is to update the shortcut code in ctrl_alt_foo. The private _createStream() has always been responsible for creating stream subscriptions. Now I can use the new KeyEvent interface to do so:
  void _createStream() {
    var keyCode = // determine keycode here...
    var stream = KeyEvent.keyDownEvent.forTarget(document.body);
    var subscription = stream.listen((e) {
      if (e.keyCode != keyCode) return;

      if (e.ctrlKey  != isCtrl) return;
      if (e.shiftKey != isShift) return;
      if (e.metaKey  != isMeta) return;

      e.preventDefault();
      cb();
    });

    subscriptions.add(subscription);
  }
None of the other code needs to change here, which is nice. Nice, except my test continues to fail. Worse, when I try the copy dialog (the subject of this test) in the application, I get a lovely stack trace:
Exception: InvalidStateError: Internal Dartium Exception
undefined
Greaaaat.

There are a lot of event listeners in the ICE Code Editor, so I decide to try this in a simple test case. I create a new project with a pubspect.yaml that pulls in the browser compatibility package:
name: test
dependencies:
  browser: any
Next, I add a simple page:
<head>
  <script src="packages/browser/dart.js"></script>
  <script src="main.dart" type="application/dart"></script>
</head>
<h1>Hello</h1>
<div style="width:600px; height: 400px" id="ice"></div>
Finally, I add the referenced main.dart as:
import 'dart:html';
main(){
  var stream = KeyEvent.keyDownEvent.forTarget(document.body);
  var subscription = stream.listen((e) {
    print('yo');
  });
}
When I load the page and hit a key, I still get the same error:



The test code for the new KeyEvent feature exercises three different subscription models. When I try these in my sample page:
main(){
  // var stream = KeyEvent.keyDownEvent.forTarget(document);
  // var subscription = stream.listen((e) {
  //   print('yo');
  // });

  // var subscription = KeyboardEventStream.onKeyDown(document.body).listen(
  //     (e) => print('KeyboardEventStream listener'));
  // var subscription2 = KeyEvent.keyDownEvent.forTarget(document.body).listen(
  //     (e) => print('KeyEvent listener'));
  var subscription3 = document.body.onKeyDown.listen(
      (e) => print('regular listener'));
}
Only the element property stream works. The KeyboardEventStream and KeyEvent streams both generate that same Internal Dartium Exception.

Well, they don't call it bleeding edge for nothing. The new KeyEvent API looks fairly nice and ought to be more or less a drop-in replacement for what I have currently. But it may be a while longer before I can use it.


Day #900

Friday, September 13, 2013

I Don't Understand Drop Events (in any language)


Regardless of whether or not I can test it in Dart, I still need to be able to drop project files into ICE Code Editor. So as much as it pains me to do this… tonight I will add that feature to ICE without a test to guide me.

After last night's vain attempt to TDD the behavior into existence, I have the outline of how this will look:
    Element.
      dropEvent.
      forTarget(document).
      listen((event) {
        event
          ..preventDefault()
          ..stopPropagation();

        var file = event.dataTransfer.files.first;
      });
Nothing too fancy there—in Dart or JavaScript. It establishes an event stream for drop events. When such an event occurs, the listener callback is invoked with the drop event. I prevent the default action (which would be to open the file in the browser) and then get started with reading the contents of the dropped file.

As of last night, I had only gotten as far as getting a reference to the uploaded file. I was unable to populate the dataTransfer property in a test, but it ought to work just fine in real life. The API for file readers in Dart seems to follow the JavaScript exactly. So my next step is to create a FileReader object that reads the dropped file as text:

      // ...
      listen((event) {
        event
          ..preventDefault()
          ..stopPropagation();

        var file = event.dataTransfer.files.first;
        new FileReader()
          ..onLoad((read_event) {
              print(read_event.target.result);
            })
          ..readAsText(file);
      });
If everything is working correctly, when I drop a file into ICE:



I should now see the contents of the file printed to the console.

Instead, I see the contents of the file that I just uploaded:



Arrgh! Is drop just completely broken in Dart?!

Er, no. It's just me.

In the end, I have to boil this down into the simplest JavaScript version that is possible before I realize that it still does not work. It seems that it is not sufficient to prevent the drop event—I also have to prevent the “dragover” event:
<body>
<h1>Hello</h1>
</body>
<script>
document.addEventListener('dragover', function (e) {
  e.preventDefault();
});

document.addEventListener('drop', function(e) {
  e.preventDefault();
  var file = e.dataTransfer.files[0];

  console.log('file.name: ' + file.name);
  console.log('file.type: ' + file.type);

  var reader = new FileReader();
  reader.onload = function (e) {
    console.log(e.target.result);
  };
  reader.readAsText(file);
});
</script>
And, in fact, I need to prevent both the dragover and drop default event behavior so that the dropped file does not open in the browser:



DOM coding: catch the fever!

Comforted in the knowledge that my trouble is with DOM coding, not Dart, I switch back to Dart. There, I also prevent the default dragover event behavior:
    document
      ..onDragOver.
          listen((event) {
            event.preventDefault();
          })
      ..onDrop.
          listen((event) {
            event.preventDefault();

            var file = event.dataTransfer.files.first;
            print('file.name: ${file.name}');
            print('file.type: ${file.type}');

            new FileReader()
              ..onLoad.
                  listen((read_event) {
                    print(read_event.target.result);
                  })
              ..readAsText(file);
          });
With that, I can successfully read the pertinent information from the dropped file:



No doubt there is a very good reason for needing to prevent two different events. I do not really want to know what it is. Really. Mostly I would like Dart or one of its libraries to make this go away. Regardless, I believe that I have a sufficient understanding of how this works to enable me to hook it into ICE—even if I cannot test it. That seems a fine stopping point for tonight.


Day #873

Thursday, September 12, 2013

Trying to Test Drive Drop Events in Dart


OK, I really am going to give up now. I have been banging my head against the custom keyboard event wall in Dart of late. I feel that I have made some real progress on that front, but think it probably best to wait for the new KeyEvent changes to land in Dart proper.

Instead, I switch gears tonight to something different: events in Dart. Well, maybe not that different, but Definitely not keyboard events. Tonight, I will try to add the ability to drop a code file onto the ICE Code Editor, triggering a file reader.

I am going to drive this with tests. So I start with a fixture file in my tests directory:
➜  ice-code-editor git:(master) ✗ cat test/fixtures/file_upload.html 
<body></body>
<script>
console.log("yo");
</script>
Actually…

That probably will not work. The browser and dart:html tends not to have access to the file system. I am starting to get a scary feeling about this. But I plug ahead anyway. I start with the usual setUp() that creates an instance of the full screen editor with single projects whose contents are Test. Then, in my test, I create a drop event, dispatch it to the document, and set my expectation:
    test("can create projects", (){
      var file_upload_event = new MouseEvent('drop');
      document.dispatchEvent(file_upload_event);

      expect(
        editor.content,
        'File Upload Content'
      );
    });
I have no reason whatsoever to expect that dispatching the drop event will change the editor content. I am not setting the “File Upload Content” content anywhere in the event. Even if that content were in the event, there is no code in the editor to process the event. Since I am not sure where to put the content in the event, I start with the code to start a file reader.

I already have a private method to attach mouse handlers in ICE, so this seems a reasonable place to put the drop handler:
  _attachMouseHandlers() {
    // ...
    Element.
      dropEvent.
      forTarget(document).
      listen((event) {
        print('event: ${event}');
        event
          ..preventDefault()
          ..stopPropagation();
        // File reader will go here...
      });
  }
My test still fails, but I see the mouse event in the handler's print statement:
unittest-suite-wait-for-done
event: Instance of 'MouseEvent'
FAIL: File Upload can create projects
  Expected: 'File Upload Content'
    Actual: 'Test'
     Which: is different.
  Expected: File Uploa ...
    Actual: Test
            ^
   Differ at offset 0
  
  package:unittest/src/expect.dart 78:29                                                                                                 expect
  ../full/file_upload_test.dart 27:13 
Dart seems to support the same dataTransfer property from JavaScript, so I add that into my handler:
    Element.
      dropEvent.
      forTarget(document).
      listen((event) {
        print('event: ${event}');
        event
          ..preventDefault()
          ..stopPropagation();

        var file = event.dataTransfer.files.first;
        print('file: $file');
      });
That results in an error because the dataTransfer property on my event is null:
Exception: The null object does not have a getter 'files'.

NoSuchMethodError : method not found: 'files'
Receiver: null
Arguments: [] dart:core-patch/object_patch.dart:20
Object.noSuchMethod dart:core-patch/object_patch.dart:20
Full._attachMouseHandlers.<anonymous closure> package:ice_code_editor/full.dart:229
Node.dispatchEvent /mnt/data/b/build/slave/dartium-lucid64-full-trunk/build/src/out/Release/gen/blink/bindings/dart/dart/html/Node.dart:215
file_upload_tests.<anonymous closure>.<anonymous closure> file_upload_test.dart:25
_run.<anonymous closure>
Unfortunately, I find myself in familiar territory here. The dataTransfer property on Dart's MouseEvent is final—there is no way to update it. Even if there was, I have no means of creating an instance of DataTransfer—it has no constructor (it is only instantiated internally).

Perhaps this is a job for mocks?

I import the mock library:
import 'package:unittest/unittest.dart';
import 'package:unittest/mock.dart';
Then create a mock version of the MouseEvent class:
class MockMouseEvent extends Mock implements MouseEvent {
  MockMouseEvent(String type);
}
If this works, I will add a mock DataTransfer. First, I update my test to dispatch my mock mouse event:
    test("can create projects", (){
      var file_upload_event = new MockMouseEvent('drop');
      document.dispatchEvent(file_upload_event);

      expect(
        editor.content,
        'File Upload Content'
      );
    });
And, when I run my test now, I get:
unittest-suite-wait-for-done undefined:1
ERROR: File Upload can create projects
  Test failed: Caught InvalidStateError: Internal Dartium Exception
  ../../../../../../mnt/data/b/build/slave/dartium-lucid64-full-trunk/build/src/out/Release/gen/blink/bindings/dart/dart/html/Node.dart 215:120  Node.dispatchEvent
  ../full/file_upload_test.dart 29:29                                                                                                            file_upload_tests.<fn>.<fn>
That is disappointing.

I am starting to get the feeling that testing anything involving events in Dart will not work. It is certainly frustrating because I would like to do the right thing for my codebase. One of the reasons that I switched from JavaScript to Dart for ICE was to gain the assurance that comes with testing. And yet I have hit my second testing dead-end.

Bother.


Day #872