Sunday, August 31, 2014

NPM Script to Generate Polymer Test Setup


Up today, I continue my efforts to build something of a testing solution for Polymer elements. The built-in testing Dart solutions have been more than up to the task with Polymer.dart, but the JavaScript testing setup has proved trickier. To resolve the various annoyances, I have what I think is a relatively simple approach, but with some definite opinions. This seems ideal for extracting into a node.js package, if only to ease my own Polymer testing.

Last night, I extracted a bunch of the Karma configuration out into eee-polymer-tests. Now that it is a GitHub project, I can specify it as a development dependency in custom Polymer elements:
{
  "name": "parent-events",
  "devDependencies": {
    "eee-polymer-tests": "eee-c/eee-polymer-tests"
  }
}
After npm install (following the traditional rm -rf node_modules), I have Karma, Jasmine, and other testing requirements as well as settings from eee-polymer-tests:
$ rm -rf node_modules
$ npm install
karma-chrome-launcher@0.1.4 node_modules/karma-chrome-launcher
karma@0.12.23 node_modules/karma 
karma-jasmine@0.2.2 node_modules/karma-jasmine
eee-polymer-tests@0.0.1 node_modules/eee-polymer-tests
$ ls -l node_modules/eee-polymer-tests
total 16
-rw-r--r-- 1 chris chris 2022 Aug 31 01:06 karma-common.conf.js
-rw-r--r-- 1 chris chris 1078 Aug 31 01:06 LICENSE
-rw-r--r-- 1 chris chris 1340 Aug 31 22:02 package.json
-rw-r--r-- 1 chris chris  306 Aug 31 01:06 README.md
Thanks to NPM peer dependencies, the Karma and Jasmine packages are installed for easy use in my element's tests. Thanks to the karma-common.conf.js from last night, my own karma.conf.js is nothing more than:
module.exports = function(config) {
  var common = require('./node_modules/eee-polymer-tests/karma-common.conf.js');

  config.set(common.mixin_common_opts(config, {
    // ...
  }));
};
With no other setup required, my tests still pass:
$ karma start --single-run
INFO [karma]: Karma v0.12.23 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 37.0.2062 (Linux)]: Connected on socket B3i3n_dnKrvjoYei80Jc with id 60115395
Chrome 37.0.2062 (Linux): Executed 2 of 2 SUCCESS (0.076 secs / 0.072 secs)
That is pretty good, but I would like to be able to generate some of the testing code that is currently required to make all of this work:
.
├── bower.json
├── elements
│   └── click-sink.html
├── index.html
├── karma.conf.js
├── package.json
└── test
    ├── ClickSinkSpec.js
    └── PolymerSetup.js
The bower.json file exposes a hidden dependency on Bower. The only real consideration for Bower is that my tests expect Polymer dependencies to be installed in bower_components. I will defer consideration of that until another day.

For now, I would like to write a script that creates the other three files in the list. I start with test/PolymerSetup.js, which is nearly boilerplate except for the inclusion of the main Polymer element at the very end of the file:
// Delay Jasmine specs until Polymer is ready
var POLYMER_READY = false;
beforeEach(function(done) { /* wait for polymer-ready */ });

// 1. Load Polymer before any code that touches the DOM.
var script = document.createElement("script");
script.src = "/base/bower_components/platform/platform.js";
document.getElementsByTagName("head")[0].appendChild(script);

// 2. Load component(s)
var link = document.createElement("link");
link.rel = "import";
link.href = "/base/elements/click-sink.html";
document.getElementsByTagName("head")[0].appendChild(link);
I am unsure if there are any standard templating solutions for Node.js (a quick search did not turn one up), so regular expressions it is!

I create a templates/PolymerSetup.js file in eee-polymer-test that is identical to the current setup from my working tests. The only change is a mustache-like placeholder for the element name:
// Identical up to...
// 2. Load component(s)
var link = document.createElement("link");
link.rel = "import";
link.href = "/base/elements/{{element-name}}.html";
document.getElementsByTagName("head")[0].appendChild(link);
I would prefer that eee-polymer-tests be installed globally (i.e. with npm install -g), but that is not going to be an option because of last night's peer dependencies. Instead, this will be run as:
$ ./node_modules/eee-polymer-tests/cli.js click-sink
Given that execution path, the template path will be ./node_modules/eee-polymer-tests/templates/PolymerSetup.js. There is probably a way to determine this path relative to the currently executing script, but I will leave that for another day.

I just want to be able to read the template and write the template with the element applied. The read should look something like:
#!/usr/bin/env node

var element = process.argv[2];

var fs = require('fs');
fs.readFile('./node_modules/eee-polymer-tests/templates/PolymerSetup.js', 'utf8', function (err,data) {
  if (err) {
    return console.log(err);
  }
  writeSetup(data);
});
The writeSetup() can then do the opposite with writeFile():
function writeSetup(template) {
  var content = template.replace(/\{\{element-name\}\}/g, element);
  fs.writeFile('./test/PolymerSetup.js', content);
}
That takes care of globally replacing the {{element-name}} template placeholder (thanks to the g modified on the regular expression).

And that seems to do the trick. I run that code—overwriting the current PolymerSetup.js—and re-run the tests to find everything still passing.

This seems quite promising for my needs. I am unsure if anyone else will find it similarly useful, but I am definitely filling a need of my own here. I think tomorrow I will continue with this, adding in some overwrite protection. That aside, this is already quite close to what I need.


Day #169

No comments:

Post a Comment