Tuesday, September 3, 2013

Completing the Same Way


In many ways, I believe that Dart is exceptionally well positioned to play nicely with low-latency devices. Making Futures and first-class concept in the language from the outset is an acknowledgement that things do not always happen when programmers expect them to happen.

I am already making some good of this in the ICE Code Editor, which waits for the various ACE JavaScript libraries to load before doing its thing. Even nicer is that Dart has extremely strong asynchronous testing built-in from the outset:
    test("starts an ACE instance", (){
      var it = new Editor('#ice-${currentTestCase.id}');
      it.editorReady.then(
        expectAsync1((_) {
          expect(document.query('.ace_content'), isNotNull);
        })
      );
    });
This means that programmers can test actual asynchronous behavior rather than work hard to make inherently asynchronous actions behave serially just to make them testable. Embracing the async web at this level goes a long way toward ensuring that web applications will work when latency pushes asynchronous actions beyond “normal” conditions.

Of course, this is only true if programmers actually test these things.

Unfortunately, there is a use-case in ICE that I did not test in such a fashion. Naturally, it is a fairly important use-case. When a new programmer wants to share her work, ICE generates a link that gzip encodes the entire contents of the code editor. This link can be shared (or shortened) with someone else that has not run ICE before (and may not be aware of ICE at all).

Herein lies the problem: even while ICE is patiently waiting for all of the ACE JavaScript files to load to start the editor and preview, it is already trying to decode that gzip encoded string. Unfortunately, that gzip encoding and decoding is done entirely in JavaScript. If my Dart code tries to use the gzip JavaScipt code, the unsuspecting recipient sees nothing—only a JavaScript error in the console.

But that's terrible! If an aspiring programmer wanted to share her work with her friends or family, I have effectively created a chilling effect on the excitement that prompted the share. So I must fix this. Not only do I need to fix this, but I also need to test this.

The actual unit testing of this is very easy. The tests are already waiting for and editorReady Future to complete. Instead of waiting on the editor being ready, I need to switch to wait for the gzip libraries to be ready:
group("Opening Shared Link", (){
    var editor, store;

    setUp((){
      window.location.hash = 'B/88gvT6nUUXDKT1IEAA==';
      editor = new Full()
        ..store.storage_key = "ice-test-${currentTestCase.id}";
      return editor.gzipReady;
    });
    test("shared content is opened in the editor", (){
      expect(editor.content, 'Howdy, Bob!');
    });
    // ...
  });
That fails because there is no gzipReady getter yet. I add one in much the same way that the still useful editorReady Future functions. In the constructor, the completers are instantiated:
class Editor {
  Completer _waitForAce, _waitForGzip;
  Editor(this._el) {
    this._waitForAce = new Completer();
    this._waitForGzip = new Completer();
    // ...
  }
  // ...
}
Later, these completers are completed when the appropriate script element generates an event on its onLoad stream:
      var scripts = _attachScripts();
      scripts.first.onLoad.listen((_)=> _startJsAce());
      scripts.last.onLoad.listen((_)=> this._waitForGzip.complete());
Lastly, I need to expose a getter for my tests to latch onto:
  Future get editorReady => _waitForAce.future;
  Future get gzipReady =>  _waitForGzip.future;
I am seeing a bunch of code duplication in there. So, with my tests passing again, I think it best to call it a night here. I will pick back up tomorrow trying to extract this out into something a bit DRYer. Once I have that in place, then I will try a little latency testing.



Day #863

No comments:

Post a Comment