Thursday, June 19, 2014

Testing JavaScript Polymers… With Dart


Look, this isn't that hard. In order to test my <apply-author-styles> Polymer element, I need a test page that:
  1. holds a test Polymer element that uses <apply-author-styles>
  2. Can run in a server so that XHR request can be made
  3. Has custom styles that can be pulled from the page into the test custom element
Try as I might with grunt-contrib-jasmine and Karma, I cannot do this. grunt-contrib-jasmine and Polymer do not get along. Karma refuses to allow me to add custom CSS to the test context—at least not statically.

So you know what? I give up. I'm testing with Dart.

I already have Grunt setup in this project, so I stick with it here. In this case, I want a simple static server against which to run my tests (there is a simple Ajax component). I already have that in the form of grunt-contrib-connect. Now I need a way to run Dart tests against this JavaScript element (yes, I'm serious).

Since there is no grunt-contrib-dart yet (why not?!), I install grunt-shell:
$ npm install --save-dev grunt-shell
Then I add this to the imported tasks in my Gruntfile.js and define my test_runner.sh shell task that will run my Dart tests:
module.exports = function(grunt) {
  // Project configuration.
  grunt.initConfig({
    pkg: grunt.file.readJSON('package.json'),
    // ...
    connect: {
      server: {
        options: {
          port: 8100,
          keepalive: false
        }
      }
    },
    shell: {
      test: {
        command: 'test/test_runner.sh'
      }
    }
  });

  grunt.loadNpmTasks('grunt-contrib-watch');
  grunt.loadNpmTasks('grunt-shell');
  grunt.loadNpmTasks('grunt-contrib-connect');

  grunt.registerTask('default', ['connect', 'shell:test']);
};
The test_runner.sh script is a simple fork of content_shell that comes bundled with Dart for headless testing—but real headless testing with an actual browser. The test_runner.sh script looks like:
#!/bin/bash

# Run a set of Dart Unit tests
results=$(content_shell --dump-render-tree http://localhost:8100/test/ 2>&1)
echo -e "$results"

# check to see if DumpRenderTree tests
# fails, since it always returns 0
if [[ "$results" == *"Some tests failed"* ]]
then
    exit 1
fi

echo
echo "Looks good!"
OK with that, I have my runner in place. Grunt will start grunt-contrib-connect as part of the default task and content_shell will run my test at http://localhost:8100/test/. Now I just need to define the test.

I start with a test context page that *I* can write:
<head>
  <!-- Load Polymer -->
  <script src="/bower_components/platform/platform.js"></script>

  <!-- Load component(s) -->
  <link rel="import" href="x-foo.html">

  <!-- Style that will apply to both page and Polymer elements -->
  <style>
    button { border: 5px solid orange; }
  </style>
</head>
<body>
  <div id=test><!-- Test element -->
    <x-foo></x-foo>
  </div>
  <div id=page><!-- Page element -->
    <button type=button>Test in Main Page</button>
  </div>
</body>
In there, I have the page style—orange buttons—that I want to apply to my <x-foo> test element that makes use of <apply-author-styles>. The <x-foo> element was defined a couple of days ago as:
<link rel="import" href="polymer/polymer.html">
<link rel="import" href="apply-author-styles/apply-author-styles.html">
<polymer-element name="x-foo" noscript>
  <template>
    <apply-author-styles></apply-author-styles>
    <button id=test type=button>Test</button>
  </template>
</polymer-element>
That <apply-author-styles> should change the style of the button inside the element (something the old, deprecated applyAuthorStyles Polymer property used to do). I don't need to actually test that the button is orange. I may try that another day, but for now, I just need to know that that page style has been copied to the shadow DOM of my element. Or, in test form:
library main_test;

import 'package:scheduled_test/scheduled_test.dart';
import 'dart:html';

main(){
  group("[styles]", (){
    test('the app returns OK', (){
      var xFoo = query('x-foo');
      expect(xFoo.shadowRoot.text, contains('orange'));
    });
  });
}
Which does the trick:
CONSOLE MESSAGE: unittest-suite-wait-for-done
CONSOLE MESSAGE: PASS: [styles] the app returns OK
CONSOLE MESSAGE: All 1 tests passed.
CONSOLE MESSAGE: unittest-suite-success
That is not quite perfect—there is some discrepancy between running it in the browser and from content_shell that bears some investigation. Still, it is so very nice to have a testing framework that runs in the browser, can test easily against a custom page context, and actually passes.

As crazy as it might seem to run a Dart test runner for JavaScript code, the crazier thing might be fighting ever more obscure JavaScript testing frameworks. So I may end up sticking with this.


Day #98

1 comment: