Sunday, June 16, 2013

Driving Focus Tests

‹prev | My Chain | next›

I have enjoyed refactoring my ICE Code Editor tests over the last few days. I have an even better understanding of how to test in Dart and how to make those tests as readable and robust as possible. I am sorely tempted to continue mucking about with the test suite itself, but the most pressing issue that I need to answer tonight is an actual feature. More specifically, I need to figure out how to test when different pieces of ICE are visible.

One of the few remaining issues in the so-called 3D Game Programming for Kids milestone is giving ICE focus after dialogs and menus have closed. Since the milestone gets its name because it is blocking a release for the 3D Game Programming for Kids book, it seems worth making it a priority.

I need to start by adding a feature to the core Editor class, which is responsible for synchronizing the ACE code editor with a preview layer for the visualizations. Currently, there is no way to focus anything in the core editor, so I need to drive that feature.

I start with the setup for the new focus group in the editor_test.dart file:
  group("focus", (){
    var editor;

    setUp((){
      document.body.nodes.
        add(new Element.html('<div id=ice-${currentTestCase.id}>'));

      editor = new Editor(
        '#ice-${currentTestCase.id}',
        enable_javascript_mode: false
      );

      return editor.editorReady;
    });
    // Test will go here...
  });
Since this is a core editor test, I have to manually create a <div> to hold the editor. In the full-screen class, this is done automatically, but this is a lower level test, hence the greater manual effort. I add the editor to that element and return a future as I learned last night.

With that, I am ready to test. The first test simply verifies default state—that the code editor is visible:
    test('code is visibile by default', (){
      var el = document.
        query('#ice-${currentTestCase.id}').
        query('.ice-code-editor-editor');

      expect(el.style.visibility, isNot('hidden'));
    });
After some debate, we opted to hide the code editor with CSS visibility rather than via z-index hackery of setting the display to none. I am still not sold on this, but it does make this test easy. And, not unexpectedly, this test passes. In fact, all of the tests for the low-level editor pass. The focus seems to naturally go to the correct location. Well, that and it seems that I had inadvertently copied some focus code from the JavaScript version when I was first getting started on the Dart version:
  showCode() {
    _editor_el.style.visibility = 'visible';
    query('.ace_print-margin').style.visibility = 'visible';

    _ace.renderer.onResize();
    _ace.focus();
  }
(again ACE is the actual code editor piece of ICE, which is incorporated via #pairwithme js-interop)

Once I am done with the core editor, I work my way out to the Full screen IDE-lite class, Full. That actually works as well except in the case in which the code is edited, then hidden before the preview layer has a chance to update itself. Immediately after the code is hidden, the preview has focus, but the updated iframe is enough to lose focus.

I drive the test as usual:
  solo_group("Focus", (){
    var editor;

    setUp((){
      editor = new Full(enable_javascript_mode: false)
        ..store.storage_key = "ice-test-${currentTestCase.id}";

      var preview_ready = new Completer();
      editor.onPreviewChange.listen((e){
        if (preview_ready.isCompleted) return;
        preview_ready.complete();
      });
      return preview_ready.future;
    });
    // tests here
  });
But, instead of writing a test, I add another group with more async setup (I really love that newly discovered feature):
    group("hiding code after update", (){
      setUp((){
        editor.ice.focus();
        editor.content = '<h1>Force Update</h1>';
        helpers.click('button', text: 'Hide Code');

        var preview_ready = new Completer();
        editor.onPreviewChange.listen((e){
          preview_ready.complete();
        });
        return preview_ready.future;
      });

      test("preview has focus", (){
        var el = document.query('iframe');

        expect(document.activeElement, el);
      });
    });
Making that pass is a simple matter of adding a discrete focus() call.

In the end it is a lot of work to verify that most everything is working as desired. Still, it is worth the trouble to have the assurance of a stronger test suite.

I end the night by driving the feature that started this adventure
    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 gets met the expected error:
ERROR: Focus editor has focus after closing a dialog
  Focus editor has focus after closing a dialog: Test setup failed: Expected: TextAreaElement:<textarea>
       But: was BodyElement:<body>.
That may end up being a little trickier than expected since the menu options lack direct access to ICE to manipulate focus. I think it best to leave that for tomorrow. I always appreciate being able to start work with a failing test. It makes it so easy to jump right in.


Day #784

No comments:

Post a Comment