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

3 comments:

  1. The cool thing about your blog is, that you take care of all the dirty stuff for me. ;)
    I am using the ACE Editor on my beta.pshdl.org website as well, and was looking at exactly the same problems, like how can I create keyboard shortcuts or how to upload a file with drag'n'drop. So seeing as you are going to take care of that issue, I will simply wait and see :)
    Thanks a lot for your blog! I really hope that ctrl-alt-foo is becoming operational again soon.

    ReplyDelete
    Replies
    1. Cool! It's encouraging to know that I'm not alone in this barren wilderness of Dart event testing :)

      I'll probably implement drop upload tonight without the benefit of tests. Hopefully there won't be any more surprises there. And yes, I can't wait for ctrl-alt-foo to be operational again as well. ICE is a mess without it working properly. Fingers crossed that the KeyEvent CL will get approved shortly.

      Delete
  2. Ya understand your pain but I do wonder if their might be away to do custom general event without much hassle, why not look at a proxy object, I think since dart::html event are still not so mature, I believe it's better to abstract out all the deep gutter and use the current system but manipulate it using a object that does something special, example is let dart just tell us event occurred n with specifics from the event object, we take that and manipulate it how we feel, and if need be mix dart and js, we shouldn't look just for purity but a bridge that brings both together

    ReplyDelete