Wednesday, May 8, 2013

BDDing Dart Gzip with js-interop

‹prev | My Chain | next›

I have been neglecting the test suite in the Dart version of the ICE Code Editor. I still have the original four specs that I wrote for it and they still pass. The code still passes dart_analyzer. So it ought to be in pretty decent shape. Still, I need to go back to add some tests to catch regressions and the like.

But I'll leave that for another day. Maybe even for a #pairwithme session.

Tonight I am going to start working through new code, but I hope to be better about driving it with tests. The next feature that I want to add to ICE is the ability to interface with localStorage. Before I do that, I need to be able to deflate and inflate compressed data that will be stored therein. A little while back, I spiked some exploratory code to find that Dart could use its Zlib package to work with this data. Unfortunately, I also found that it was only capable of doing so on the server. Since this needs to work in the browser, I need to js-interop with the legacy JavaScript.

From the JavaScript version, I know how the string "Howdy, Bob!" should look when it is encoded (gzip'd and mime64 encoded). This makes it easy to write two tests:
import 'package:unittest/unittest.dart';
import 'package:ice_code_editor/store.dart';
import 'dart:html';

main() {
  group("gzipping", () {
    test("it can encode text", (){
      expect(Store.encode("Howdy, Bob!"), equals("88gvT6nUUXDKT1IEAA=="));
    });

    test("it can decode as text", (){
      expect(Store.decode("88gvT6nUUXDKT1IEAA=="), equals("Howdy, Bob!"));
    });
  });
}
From there, the tests drive me. First up, I need to change the message indicating that I do not have a store.dart file:
Failed to load a file file:///home/chris/repos/ice-code-editor/test/packages/ice_code_editor/store.dart store_test.dart:-1
GET file:///home/chris/repos/ice-code-editor/test/packages/ice_code_editor/store.dart  
After fixing that, my tests tell me that I need a Store class, then that I need static methods encode and decode. This leaves me with:
library ice;

class Store {
  static String encode(String string) {
  }
  static String decode(String string) {
  }
}
At this point, the message that I need to change is:
FAIL: gzipping it can encode text
  Expected: '88gvT6nUUXDKT1IEAA=='
       but: expected String:'88gvT6nUUXDKT1IEAA==' but was null:<null>.
Finally, to make that pass, I use a bit of knowledge left over from the spike. The RawDeflate.deflate() call from Dart looks like:js.context.RawDeflate.deflate(string) thanks to the magic of js-interop. That returns a string of gzip'd data. I then use CryptoUtils.bytesToBase64() from the dart:crypto package (why is base64 encoding in dart:crypto, but zlib is not?) package to base64 encode the gzip compressed data. The end result that make my test pass is:
library ice;

import 'dart:crypto';
import 'package:js/js.dart' as js;

class Store {
  static String encode(String string) {
    var gzip = js.context.RawDeflate.deflate(string);
    return CryptoUtils.bytesToBase64(gzip.codeUnits);
  }
  static String decode(String string) {
  }
}
To support decoding, I implement the reverse as:
library ice;

import 'dart:crypto';
import 'package:js/js.dart' as js;

class Store {
  static String encode(String string) {
    var gzip = js.context.RawDeflate.deflate(string);
    return CryptoUtils.bytesToBase64(gzip.codeUnits);
  }
  static String decode(String string) {
    var bytes = CryptoUtils.base64StringToBytes(string);
    var gzip = new String.fromCharCodes(bytes);
    return js.context.RawDeflate.inflate(gzip);
  }
}
With that, I have two passing tests that will let me work with existing data stored from the JavaScript version of the code editor. This seems like a fine stopping point for tonight. At least until my next #pairwithme session!


Day #745

No comments:

Post a Comment