Wednesday, November 27, 2013

Running Polymer.dart Tests with Grunt Instead of Karma


I am a little bummed that I was unable to get Polymer.dart tests working under Karma. It's not a huge deal because I do not need Karma's multi-browser runner—this is Dart code, so write once, run everywhere. Even though I am just testing my Dart code and relying on Dart itself to support multiple browsers, it still would have been nice.

For posterity, I believe that the main problem was dynamically adding the <link> imports of my custom Polymer elements:
importPolymerElements(list) {
  list.forEach((definition) {
    var link = new LinkElement()
      ..rel = "import"
      ..href = "packages/pricing_panels/${definition}";
    query('head').append(link);
  });
}

main() {
  importPolymerElements(['pricing-plans.html', 'pricing-plan.html']);
  // Tests here...
}
Even if I disable Polymer, I still get Dartium errors:
URL must be imported by main Dart script: pricing_plans.dart 
URL must be imported by main Dart script: pricing_plan.dart
Even if I try dynamically creating these in my test without Karma, I still see the same.

Since this is already being run inside an active Dart isolate, the import of those Polymer elements, with <script> tags of their own, is uncool. When those <link> tags exist before Dart spins up, they can be imported along with the backing Polymer classes (and no main() entry point) without conflicting with an existing VM isolate.

There are probably ways around this, but they involve too much reaching under the covers of Polymer.dart and/or Karma for my tastes at this point. All I want is to rerun my tests whenever I make a code change. Grunt ought to be able to do that.

I already have Grunt installed (npm install -g grunt with a recent Node.js), so my next step is to install the grunt-contrib-watch module (to watch for test and code changes) and the grunt-shell module (to run tests in response to those changes). I start with this package.json:
{
  "name": "bootstrap-panels",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-shell": "~0.6.1",
    "grunt-contrib-watch": "~0.5.3"
  }
}
After npm install (with no arguments, npm installs whatever is listed in package.json), I am ready to teach Grunt which files to watch. In Gruntfile.js, I start with:
module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    watch: {
      tests: {
        files: [
          'test/**/*.dart',
          'test/**/*.html',
          'lib/**/*.dart',
          'lib/**/*.html'
        ],
        tasks: ['shell:test']
      }
    }
  });
  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-shell');
  grunt.registerTask('default', ['shell:test']);
};
Most of that is pretty basic Grunt and grunt-contrib-watch configuration. At the bottom, I load my tasks (watch and shell) and register a new shell command that I will use to run my actual tests. Above, I watch for changes to Dart and HTML code in my test and lib directories. If there are any changes, then I invoke the shell:test task.

All that remains is to add my shell:test task:
module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    watch: {
      tests: {
        files: [
          'test/**/*.dart',
          'test/**/*.html',
          'lib/**/*.dart',
          'lib/**/*.html'
        ],
        tasks: ['shell:test']
      }
    },
    shell: {
      test: {
        command: 'content_shell --dump-render-tree test/index.html',
        options: {
          stdout: true
        }
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-shell');
  grunt.registerTask('default', ['shell:test']);
};
That is mercifully simple. When it is invoked, I run content_shell against my test page and display the output to STDOUT. I start the watcher with grunt watch, make a code change and:
➜  dart git:(master) ✗ grunt watch
Running "watch" task
Waiting...OK
>> File "test/packages/pricing_panels/pricing-plan.html" changed.

Running "shell:test" (shell) task
CONSOLE MESSAGE: line 221: unittest-suite-wait-for-done
CONSOLE MESSAGE: line 187: PASS: [defaults] name is "Plan"
CONSOLE MESSAGE: line 187: PASS: [defaults] can embed code
CONSOLE MESSAGE: line 191: 
CONSOLE MESSAGE: line 197: All 2 tests passed.
CONSOLE MESSAGE: line 221: unittest-suite-success
Content-Type: text/plain
layer at (0,0) size 800x600
  RenderView at (0,0) size 800x600
layer at (0,0) size 800x600
  RenderBlock {HTML} at (0,0) size 800x600
layer at (8,8) size 784x584
  RenderBody {BODY} at (8,8) size 784x584
#EOF
#EOF

Done, without errors.
Completed in 1.374s at Wed Nov 27 2013 23:52:01 GMT-0500 (EST) - Waiting...
It works! Yay!

I will probably take some time to add exit code handling, but that seems like it will work quite nicely. I may not have Karma's ability to run the tests in multiple browsers, but with Dart, that is hardly a loss.


Day #948

No comments:

Post a Comment