Wednesday, May 1, 2013

Dart Behavior Driven Develop with JS-InterOp

‹prev | My Chain | next›

Up tonight, I am going to use a little behavior driven development to (re-)introduce the ACE code editor (JavaScript) back into the ICE Code Editor (Dart). I already have the basics of the testing and code in place. Hopefully this will be straight-forward.

I have no intention of testing the implementation details of ACE, but I need some way to verify that I can set ACE up with Dart. The easiest way to accomplish that is to check for the existence of an ACE-specific class like ace_content. Something like this should do:
    test("starts an ACE instance", (){
      var it = new Editor('ice');
      expect(document.query('.ace_content'), isNotNull);
Since I am BDDing here, I expect this to fail with a message to the effect that querying for the ace_content CSS class is null. And, indeed, I do get an error. Just not the error that I expected:
unittest-suite-wait-for-done undefined:1
Exception: 'file:///home/chris/repos/ice-code-editor/test/packages/unittest/unittest.dart': Error: line 763 pos 28: type 'ExpectException' is not loaded
    String message = (e is ExpectException) ? e.message : 'Caught $e';
malformed type used.
Stack Trace: #0      _registerException (file:///home/chris/repos/ice-code-editor/test/packages/unittest/unittest.dart:763:28)

It turns out that ExpectException was removed a while back. So why is unittest complaining about it? That is because I force downgraded unittest yesterday so that I could get headless testing working again. I am back to running tests in the Dartium browser today, and I would like decent error messages. So I temporarily peg my app to the latest unittest via an update to pubspec.yaml:
name: ice_code_editor
version: 0.0.1
description: Code Editor + Preview
author: Chris Strom <>
  unittest: any
  js: any
A quick pub update, a reload of the test page, and I have a useful error message:
FAIL: defaults starts an ACE instance undefined:1
  Expected: not null
       but: was <null>.

With that fixed, I am ready for the familiar change-the-message or make-it-pass cycle of BDD. I start by editing the Editor class to create an ACE instance via js-interop:
import 'dart:html';
import 'package:js/js.dart' as js;

class Editor {
  bool edit_only, autoupdate;
  String title;

  Editor(el, {this.edit_only:false, this.autoupdate:true, this.title}) {
    var context = js.context;
After reloading, I have changed the message. I now get:
FAIL: defaults starts an ACE instance undefined:1
  Caught NoSuchMethodError : method not found: 'ace'
  Receiver: Instance of 'Proxy'
  Arguments: [] 
This is because I have not loaded the ACE JavaScript sources on the page. The easiest way to accomplish this is to go back and edit the test page itself to include ace.js:
  <title>ICE Test Suite</title>
  <script src="packages/ice_code_editor/ace/ace.js" type="text/javascript" charset="utf-8"></script>
  <script type="application/dart" src="editor_test.dart"></script>
  <script src="packages/browser/dart.js"></script>


Now, when I reload, I find that I have made the test pass:
PASS: defaults defaults to auto-update the preview
PASS: defaults defaults to disable edit-only mode
PASS: defaults starts an ACE instance

All 3 tests passed.
It is pretty cool that I can bundle JavaScript with my Dart package. I can install the package, then source main.dart:
  <script src="packages/ice_code_editor/ace/ace.js" type="text/javascript" charset="utf-8"></script>
  <script src="packages/browser/dart.js"></script>
  <script src="main.dart" type="application/dart"></script>

<div style="width:600px; height: 400px" id="ace"></div>
Best of all, I can dart2js that main.dart file:
➜  public git:(master) ✗ dart2js -omain.dart.js main.dart
With that, I can load the ICE Code Editor—ACE and all—in Chrome, FireFox, and even Internet Explorer:

Still, I wonder how much effort it would be to eliminate the need for the web page to source the ACE code. It would seem better to source the Dart code only, and then make the Dart code responsible for sourcing the appropriate JavaScript libraries before kicking in the js-interop stuff. I wonder if that's even feasible.

Something for tomorrow.

Day #738

No comments:

Post a Comment