Tuesday, May 14, 2013

Better DOM Tests in Dart

‹prev | My Chain | next›

Last night I and Santiago Arias (my #pairwithme pair) made a decent start on the full-screen, mini-IDE version of the Dart-flavor ICE Code Editor. We are finding that the test suite is growing messy—primarily, I suspect, due to the manner in which all of the tests run asynchronously (i.e. at once). With so many elements all creating an instance of the ICE Code Editor and tearing down the DOM elements used to build it, tests are stomping on each other. Tonight, I hope to fix that up a bit.

Dart provides facilities to run groups of tests serially, but I do not think that I need make use of that—or at least I think that I can address most of my problems without doing that. To illustrate the point, most of the errors are along the lines of:
TypeError {} ace.js:5464
Could not load worker ace.js:5463
TypeError {} ace.js:5464
Uncaught TypeError: Cannot read property 'ace/ace' of undefined ace.js:114
Could not load worker ace.js:5463
TypeError {} ace.js:5464
Uncaught TypeError: Cannot read property 'ace/ace' of undefined ace.js:114
Could not load worker ace.js:5463
TypeError {} 
My initial thought is that my setup and teardown are being overwritten by async tests all writing to the <div> tag with the same ID:
  group("content", () {
    var el;
    setUp(() {
      el = new Element.html('<div id=ice>'); document.body.nodes.remove(el));

    test("can set the content", () {
      var it = new Editor('#ice');
      // ...
    });
  });
There are more tests that do the same, so I try creating unique IDs using the test-case ID:
group("content", () {
    setUp(() {
      var el = new Element.html('<div id=ice-${currentTestCase.id}>');
      document.body.nodes.add(el);
    });
    tearDown(() {
      var el = document.query('#ice-${currentTestCase.id}');
      document.body.nodes.remove(el);
    });

    test("can set the content", () {
      var it = new Editor('#ice-${currentTestCase.id}');
      // ...
    });
  });
If I am right, then using unique <div> tags ought to clean up the test cases.

It turns out that I am wrong. I still see the same errors. So instead, I work with Damon Douglas, tonight's #pairwithme pair, to get to the bottom of this. The bottom turns out to be the ACE JavaScript editor that is being loaded via js-interop. Loading ACE once is fine. Loading it several times as in test cases: not so fine.

The solution is to move the dynamic creation of the <script> tags for the ACE editor into a one-time only static method in the ICE.Editor class:
  static _attachScripts() {
    if (_isAceJsAttached) return;

    var script_paths = [ /* ... */ ];

    var scripts = script_paths.
      map((path) {
        var script = new ScriptElement()
          ..async = false
          ..src = path;
        document.head.nodes.add(script);
        return script;
      }).
      toList();

    return _scripts = scripts;
  }
If the private method indicating that the ACE JavaScript is already loaded returns true, then there is no need to dynamically create new <script> elements that load ACE again and again.

I really seem to be paying the price for dynamically creating those ACE <script> tags. It was a pain to get working in the first place and it is a pain to be able to test with them in the way. And yet, I cannot bring myself to get rid of them. From the outside world, a developer should rarely need to worry about this. Even if it is a problem, there is a single Future, editorReady, that completes when everything is in place. All of this makes for a web page that contains no more than:
<head>
  <script src="packages/browser/dart.js"></script>
  <script type="application/dart">
    import 'package:ice_code_editor/ice.dart' as ICE;
    main()=> new ICE.Full();
  </script>
</head>
With just that, all of the JavaScript is loaded, along with the Dart that controls it. This still seems a worthy goal. And, since I have my tests in better shape thanks to Damon, I will stick to this path for a little while longer.


Day #751

No comments:

Post a Comment