Send to Kindle

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

Saturday, August 30, 2014

A Package for Common Polymer Testing


I was unable to get polymer-test-tools to work well with a custom Polymer of my own. The effort was not completely wasted as I learned a ton. The most interesting thing that I learned is that I have some definite opinions on what ought to be included in a general purpose Polymer testing tool. Enough, possibly, to write my own.

My Polymer element setup usually looks something like:
.
├── test
│   ├── PolymerSetup.js
│   └── ClickSinkSpec.js
├── package.json
├── karma.conf.js
├── index.html
└── elements
    └── click-sink.html
I have my element(s) in the elements subdirectory, the tests in the tests subdirectory, a package.json that includes Karma and Jasmine dependencies and a Karma configuration file. Also of note is is that I have very specific test setup that goes into test/PolymerSetup.js to enable testing with Polymer.

For a first pass at a testing tool (generator? template? not sure yet?), I initialize a git and npm repository:
➜  repos  mkdir eee-polymer-tests
➜  repos  cd !$
➜  repos  cd eee-polymer-tests
➜  eee-polymer-tests  git init
Initialized empty Git repository in /home/chris/repos/eee-polymer-tests/.git/
➜  eee-polymer-tests git:(master) npm init
This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sane defaults.

See `npm help json` for definitive documentation on these fields
and exactly what they do.

Use `npm install  --save` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
name: (eee-polymer-tests)
version: (1.0.0) 0.0.1
description: Sane Polymer test setup (including generator)
entry point: (index.js)
test command:
git repository: https://github.com/eee-c/eee-polymer-tests
keywords: polymer testing karma jasmine
author: Chris Strom
license: (ISC) MIT
To build this up, I would like to slowly replace parts in my working tests with this NPM package. Unfortunately, I do not know how to do this quickly with NPM since it does not support local filesystem dependencies. Well, that's not entirely true, NPM does support npm link, but I am unsure how to get that to install dependencies.

And, in the case of eee-polymer-tests, I need to include karma-jasmine and karma-chrome-launcher as peer dependencies. I specify those in my package.json:
{
  "name": "eee-polymer-tests",
  // ...
  "peerDependencies": {
    "karma-jasmine": "~0.2.0",
    "karma-chrome-launcher": ">0.0"
  }
}
As peer dependencies, these will be installed in my Polymer application the same as if I had listed them as direct dependencies (or development dependencies):
{
  "name": "parent-events",
  "devDependencies": {
    "karma-jasmine": "~0.2.0",
    "karma-chrome-launcher": ">0.0"
  }
}
The only way I can get my Polymer application to honor my local package's peer dependencies—at least the only way that I can figure out tonight—is to add my local package as a pre-install script to my Polymer element's package.json:
{
  "name": "parent-events",
  "scripts": {
    "preinstall": "npm install /home/chris/repos/eee-polymer-tests/"
  }
}
That actually seems to work:
$ npm install
> parent-events@ preinstall /home/chris/repos/polymer-book/book/code-js/parent_events
> npm install /home/chris/repos/eee-polymer-tests/
...
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
And...
$ ls node_modules
eee-polymer-tests  karma  karma-chrome-launcher  karma-jasmine
Most importantly, my Karma 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 wzCHNN5LnCOMY2-V4ELH with id 70589859
Chrome 37.0.2062 (Linux): Executed 2 of 2 SUCCESS (0.068 secs / 0.061 secs)
Next up, I need to borrow from a trick I learned from polymer-test-tools. I need to move my common testing setup for karma into the NPM package, but still allow developers a means to override the common stuff. Directly from polyer-test-tools, I create karma-common.conf.js in the eee-polymer-tests NPM package:
exports.mixin_common_opts = function(karma, opts) {
  var all_opts = {
    //  Common settings go here...
  };
  for (var key in opts) {
   all_opts[key] = opts[key];
  }
  return all_opts;
};
The common setting are mostly the defaults from the Karma generator. The ones specific to my preferred Polymer testing setup include the testing framework to use:
    // frameworks to use
    frameworks: ['jasmine'],
A means to facilitate fixure loading if needed:
    /**
     * Compile HTML into JS so that they can be used as templates
     */
    preprocessors: {
      'test/*.html': 'html2js'
    },
And the list of files to include for testing or to serve (but not directly execute):
    /**
     * Don't include Polymer HTML and JS because Polymer is very
     * particular about the order in which they are added. Serve them,
     * but defer loading to the test setup. Include test HTML
     * fixtures.
     */
    // list of files / patterns to load in the browser
    files: [
      'test/PolymerSetup.js',
      {pattern: 'elements/**', included: false, served: true},
      {pattern: 'bower_components/**', included: false, served: true},
      'test/**/*Spec.js'
    ],
Back in my Polymer element, the karma.conf.js then becomes simply:
module.exports = function(config) {
  var common = require('./node_modules/eee-polymer-tests/karma-common.conf.js');

  config.set(common.mixin_common_opts(config, {
    // Specialization could go here, but none needed!
  }));
};
I do not need to override any of the default settings. Furthermore, I think the common options would likely suit just about any elements that I have created so far. Famous last words, I am sure.

Anyhow, trying it out, I find:
$ karma start --single-run
ERROR [config]: Error in config file!
 { [Error: Cannot find module './node_modules/eee-polymer-tests/karma-common.conf.js'] code: 'MODULE_NOT_FOUND' }
Error: Cannot find module './node_modules/eee-polymer-tests/karma-common.conf.js'
    at Function.Module._resolveFilename (module.js:338:15)
    ...
This is a definite drawback to the pre-install NPM approach. I have to re-install each time I want to try something new. That is going to be a pain if I need to debug often. Perhaps if that is the case, I can try a hybrid approach that first installs dependencies, but then npm-links my local package. Food for thought another day.

For now, I re-install:
$ rm -rf node_modules
$ npm install
...
eee-polymer-tests@0.0.1 node_modules/eee-polymer-tests
With that:
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 Q1jxhuNWJtv6SKmi2C2L with id 12087020
Chrome 37.0.2062 (Linux): Executed 2 of 2 SUCCESS (0.057 secs / 0.051 secs)
Nice!

I now require only a single testing dependency and zero configuration. That may not be completely reusable by others, but it does afford a certain amount of configuration thanks to the approach from the polymer-test-tools project. Regardless, it is a good start and will help me very much in my efforts to test the remainder of the elements in Patterns in Polymer.

I have not saved myself any files required for testing. I may or may not be able to include the PolymerSetup.js directly in eee-polymer-tests. I have been including the <link> import of the element definition under test in PolymerSetup.js. It is possible that I can move the import elsewhere, but that will take some experimentation. I will always need the equivalent of ClickSinkSpec.js. Now that I think about it, both could be generated from a script in eee-polymer-tests.

I will work on that tomorrow.


Day #168

Friday, August 29, 2014

Testing Custom Polymer Elements with Polymer-Test-Tools


I discovered polymer-test-tools last night, but could not quite get them working. While exploring, I began to suspect that they were meant more to support the Polymer project's infrastructure than to help “regular” programmers build elements. Even so, I would like to get them working with my Polymer element, if only to see what polymer-test-tools can teach me about my own testing approach.

I left off last night with a strange SSL / socket.io error. It seemed like an old node.js, but it turns out that I have a relatively recent version of node installed. And, in fact, I no longer see the error tonight. I swear that I have done nothing that could possibly have fixed this, except… it is fixed. Rather than question fortune, I say a prayer of thanks to the gods of the chain and quickly move on before they change their mind.

Not all is well just yet, but I am quite familiar with the next errors thanks to earlier experiments testing Polymer with mocha and chai. First up the before-each failure on start-up:
$ karma start
INFO [karma]: Karma v0.12.23 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
Chrome 37.0.2062 (Linux)  "before each" hook FAILED
        Error: timeout of 2000ms exceeded
Chrome 37.0.2062 (Linux) SLOW 2.003 secs:  "before each" hook
Chrome 37.0.2062 (Linux): Executed 1 of 2 (1 FAILED) ERROR (2.037 secs / 2.003 secs)
I fix this with the same solution from the last time—test setup that relies on Polymer.whenReady() instead of the too-quick polymer-ready event:
// Delay Jasmine specs until Polymer is ready
var POLYMER_READY = false;
beforeEach(function(done) {
  function waitForPolymer() {
    if (Polymer) {
      Polymer.whenReady(done);
      return;
    }
    setTimeout(waitForPolymer, 1000);
  }
  waitForPolymer();

  if (POLYMER_READY) done();
});
I had expected that to be the last of my woes, but I still have another sadly familiar error message to fix:
$ karma start
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 sgWf2bLuHEiLYy7BxgN0 with id 56442994
Chrome 37.0.2062 (Linux)  element content displays click events FAILED
        ReferenceError: expect is not defined
            at Context.<anonymous> (/home/chris/repos/polymer-book/book/code-js/parent_events/test/ClickSinkSpec.js:23:7)
I ran into this same error when I setup mocha on my own, but am surprised to see it with polymer-test-tools. The problem is that the chai framework is not loaded. I am surprised by this because the chai framework is included directly in the polymer-test-tools source. Why include it in the source if it is not going to be enabled by default? I can only guess that the answer has to do with using polymer-test-tools in the Polymer framework code rather than testing custom built Polymer elements.

Whatever the reason, the solution remains the same—I have to alter my karma.conf.js file to include chai in addition to mocha (which polymer-test-tools does include by default):
module.exports = function(config) {
  var common = require('./bower_components/polymer-test-tools/karma-common.conf.js');

  config.set(common.mixin_common_opts(config, {
    frameworks: ['mocha', 'chai'],
    // ...
  }));
};
Only this time, my problem is still not resolved. Since I overrode polymer-test-tools' plugins setting last night, I also have to add karma-chai to it here:
module.exports = function(config) {
  var common = require('./bower_components/polymer-test-tools/karma-common.conf.js');

  config.set(common.mixin_common_opts(config, {
    frameworks: ['mocha', 'chai'],

    plugins: [
      'karma-mocha',
      'karma-chai',
      'karma-chrome-launcher',
      'karma-script-launcher'
    ],
    // ...
  }));
};
After that, all that remains is to change Jasmine's toEqual() and toContain() to chai's to.equal() and to.contain():
  describe('element content', function(){
    beforeEach(function(done){
      document.body.click();
      setTimeout(done, 0); // One event loop for Polymer to process
    });
    it('displays click events', function(){
      expect(el.shadowRoot.textContent).to.contain('Page clicked');
    });
  });
With that, I have all (both) of my tests passing under polymer-test-tools:
$ karma start
...
Chrome 37.0.2062 (Linux): Executed 2 of 2 SUCCESS (0.196 secs / 0.003 secs)
In the end, this really saved me no work. In fact, I had to do a little extra work to disable settings that were not relevant to my on my Linux machine (e.g. IE, Safari testing support). Since I still have to manually install mocha and chai (and setup chai) despite both being included in the polymer-test-tools repository, I save no effort. In other words, this is not a tool for testing custom Polymer elements. Polymer-test-tools is a place for common framework settings to go.

Still, the effort was hardly a complete waste. It is instructive seeing how the Polymer team tests different platforms and browsers (conditionals depending on the operating system). The overall approach seems similar to what I have been using, though I probably ought to investigate a bit more before claiming confirmation of my approach.

Bottom line: I plan to stick with my current approach in Patterns in Polymer.


Day #167

Thursday, August 28, 2014

Getting Started with Polymer-Test-Tools


I can whine for long periods of time about how much better Dart testing is than JavaScript. Loooong periods. But let's face it, that is hardly constructive and, worse, it ignores the hard work of talented folks that are working to make things better.

Last night, I added some simple Dart tests to one of the chapters in Patterns in Polymer. The primary element in that chapter (both the Dart and JavaScript versions of the book) is <click-sink> which, when embedded in another element, will report on events in the containing element. The Dart tests were easy to add, so tonight I do the same in JavaScript.

Until now, my testing solution for JavaScript Polymer has been Karma and Jasmine. It takes a little more work to get these tests working: package.json to install Karma dependencies via NPM, bower.json to install Polymer dependencies via Bower, Jasmine setup for Polymer, etc. In all honesty, it's not that much harder to setup than in Dart.

I would note that the tests just are not as pretty:
  beforeEach(function(done){
    el = document.querySelector('click-sink');
    setTimeout(done, 0); // One event loop for elements to register in Polymer
  });

  describe('element content', function(){
    beforeEach(function(done){
      document.body.click();
      setTimeout(done, 0); // One event loop for Polymer to process
    });
    it('displays click events', function(){
      expect(el.shadowRoot.textContent).toContain('Page clicked');
    });
  });
I really am partial to scheduled_test's schedules:
  test('displays click events', (){
    var el = query('click-sink');
    schedule(()=> document.body.click());
    schedule(() {
      expect(el.shadowRoot.text, contains('Page clicked'));
    });
  });
But I'm not whining!

Instead, I went in search of some other examples of Polymer testing in JavaScript and stumbled across polymer-test-tools, which is a project from the Polymer team itself that has “common tools for testing Polymer elements.” It uses Karma, but instead of Jasmine, it uses Mocha and Chai. I have already investigated both as testing solutions. I remain open to the switch, so let's see if polymer-test-tools can convince me.

It is non-obvious how to use polymer-test-tools, but it has a bower.json, suggesting that I need to add it to my bower.json development dependencies:
$ bower install -D "Polymer/polymer-test-tools"
...
polymer-test-tools#0.4.0 bower_components/polymer-test-tools
Next, I update my karma.conf.js file to pull in the settings from polymer-test-tools:
module.exports = function(config) {
  var common = require('./bower_components/polymer-test-tools/karma-common.conf.js');

  config.set(common.mixin_common_opts(config, {
    // Override with my setting here...
  }));
};
I retain some of my own settings—especially the list of the test files—but remove most of what is listed in the common karma configuration. But when I run this, I get warnings about plugins that are not installed:
$ karma start --single-run
WARN [plugin]: Cannot find plugin "karma-mocha".
  Did you forget to install it ?
  npm install karma-mocha --save-dev
WARN [plugin]: Cannot find plugin "karma-browserstack-launcher".
  Did you forget to install it ?
  npm install karma-browserstack-launcher --save-dev
WARN [plugin]: Cannot find plugin "karma-ie-launcher".
  Did you forget to install it ?
  npm install karma-ie-launcher --save-dev
WARN [plugin]: Cannot find plugin "karma-ios-launcher".
  Did you forget to install it ?
  npm install karma-ios-launcher --save-dev
WARN [plugin]: Cannot find plugin "karma-safari-launcher".
  Did you forget to install it ?
  npm install karma-safari-launcher --save-dev
WARN [plugin]: Cannot find plugin "karma-crbot-reporter".
  Did you forget to install it ?
  npm install karma-crbot-reporter --save-dev
I have no desire for most of those, so I override them in my settings:
module.exports = function(config) {
  var common = require('./bower_components/polymer-test-tools/karma-common.conf.js');

  config.set(common.mixin_common_opts(config, {
    // ...
    plugins: [
      'karma-mocha',
      'karma-chrome-launcher',
      'karma-script-launcher'
    ]
  }));
};
That still complains about a lack of karma-mocha:
$ karma start --single-run
WARN [plugin]: Cannot find plugin "karma-mocha".
  Did you forget to install it ?
  npm install karma-mocha --save-dev
I had hoped to get this automatically from polymer-test-tools since it includes both mocha and chai:
$ ls bower_components/polymer-test-tools/     
bower.json  chai  htmltest.js  karma-common.conf.js  mocha  mocha-htmltest.js  README.md  tools.html
Alas, this is not the case.

After following the instructions (npm install karma-mocha --save-dev), I run into another problem:
$ karma start
INFO [karma]: Karma v0.12.23 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
ERROR [karma]: [Error: error:24064064:random number generator:SSLEAY_RAND_BYTES:PRNG not seeded]
Error: error:24064064:random number generator:SSLEAY_RAND_BYTES:PRNG not seeded
    at Manager.generateId (/home/chris/repos/polymer-book/book/code-js/parent_events/node_modules/karma/node_modules/socket.io/lib/manager.js:750:12)
    at /home/chris/repos/polymer-book/book/code-js/parent_events/node_modules/karma/node_modules/socket.io/lib/manager.js:805:21
    at Manager.authorize (/home/chris/repos/polymer-book/book/code-js/parent_events/node_modules/karma/node_modules/socket.io/lib/manager.js:931:5)
    at Manager.handleHandshake (/home/chris/repos/polymer-book/book/code-js/parent_events/node_modules/karma/node_modules/socket.io/lib/manager.js:801:8)
    at Manager.handleRequest (/home/chris/repos/polymer-book/book/code-js/parent_events/node_modules/karma/node_modules/socket.io/lib/manager.js:616:12)
    at Server.<anonymous> (/home/chris/repos/polymer-book/book/code-js/parent_events/node_modules/karma/node_modules/socket.io/lib/manager.js:119:10)
    at Server.EventEmitter.emit (events.js:98:17)
    at HTTPParser.parser.onIncoming (http.js:2076:12)
    at HTTPParser.parserOnHeadersComplete [as onHeadersComplete] (http.js:120:23)
    at Socket.socket.ondata (http.js:1966:22)
Yikes. Apparently, this is a known issue with Karma. Unfortunately, it looks like I need to install a new node.js. Sigh.

I will call it a night here and maybe pick back up with this tomorrow. While working through this, I am starting to get the feeling that polymer-test-tools is not a general purpose Polymer testing helper. It seems like it helps quite a bit with testing Polymer internals like Shadow DOM, but it may not be appropriate for coders building and maintaining their own Polymer elements. Even so, I may pick back up with this if, for no other reason, than to learn a little from the smart folks behind Polymer.


Day #166

Wednesday, August 27, 2014

When to TDD and When Not to TDD (an exercise with Polymer.dart)


According to git lslt, it has been six months since I last updated the parent events chapter in Patterns in Polymer. Six months!

I start my review, as always, with my tests:
$ pwd     
/home/chris/repos/polymer-book/book/code-dart/parent_events
$ ls -l test
total 0
lrwxrwxrwx 1 chris chris 11 Dec  7  2013 packages -> ../packages
Son of a… Bad developer! Bad author!

I will not produce another edition without complete test coverage of the examples in the book. I will not worry about the code in the “play” area of the repository—that is mostly meant for spiking new features or exploring new facets of Polymer. But there is no excuse for basing any programming book on top of code without tests. So, I start with…

Running the application. It's silly to start testing without some idea of what things look like, so I double-check that this still runs. I will worry about the tests after the fact (not everything in life requires TDD). First, I clean out the pubspec.yaml dependencies of some early-Polymer cruft, leaving me with simply:
name: parent_events
dependencies:
  polymer: any
dev_dependencies:
  scheduled_test: any
I pub upgrade and find:
$ pub upgrade
Resolving dependencies... (4.4s)
...
> polymer 0.12.0 (was 0.9.0+1) (8 newer versions available)
...
Changed 29 dependencies!
Craziness. I cannot believe that I still have Polymer.dart 0.9 code the book (the 8 newer versions are pre-releases).

I also update the smoke test page in the Dart Pub standard web directory to perform the proper setup:
<!doctype html>
<html lang="en">
  <head>
  <!-- ... -->
    <!-- Load platform polyfills -->
    <script src="packages/web_components/platform.js"></script>
    <script src="packages/web_components/dart_support.js"></script>

    <!-- Load component(s) -->
    <link rel="import" href="packages/parent_events/click-sink.html">

    <!-- Start Polymer -->
    <script type="application/dart">
      export 'package:polymer/init.dart';
    </script>
  </head>
  <!-- ... -->
</html>
That should do it. So I fire up Dart pub's built-in test web server (pub serve), access the page, click around, and… nothing:



If I recall correctly, the <click-sink> element added to the page should listen for page events. With another 6 months of Polymer under my belt, I am no longer sure this is a strong pattern, but I would at least like to get it working.

Saaaaaay... this is a good time to introduce some tests! In the pub standard test directory, I add the text page context:
<!doctype html>
<html>
<head>
  <!-- Load platforms polyfills -->
  <script src="packages/web_components/platform.js"></script>

  <!-- Load component(s) -->
  <link rel="import" href="packages/parent_events/click-sink.html">

  <!-- The actual tests -->
  <script type="application/dart" src="test.dart"></script>
  <script src="packages/unittest/test_controller.js"></script>
</head>
<body>
  <click-sink></click-sink>
</body>
</html>
That is similar to the regular Polymer.dart page, but to initialize and test Polymer elements, I have found this to be an effective approach. I normally create the Polymer element inside my test setup, but I anticipate these tests to be simple enough that I add <click-sink> directly to the page. I can always change later if the need arises.

In addition to the test page context, I also need to initialize Polymer inside my tests:
library click_sink_test;

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

main() {
  initPolymer();

  setUp((){
    schedule(()=> Polymer.onReady);
  });

  test('displays click events', (){
    expect(false, true);
  });
  test('generates click-sink-click events', (){
    expect(false, true);
  });
}
And look! I have two failing tests already in place. I could access my tests from the pub web server (which spins up a test server as well), but I prefer testing with content_shell for ease of transition to continuous integration testing. The arguments are arcane, but as I avowed Linux user, I am used to it:
$ content_shell --dump-render-tree test/index.html
CONSOLE WARNING: line 12: flushing %s elements
CONSOLE MESSAGE: unittest-suite-wait-for-done
CONSOLE MESSAGE: FAIL: displays click events
  Caught ScheduleError:
  | Expected: <true>
  |   Actual: <false>

CONSOLE MESSAGE: FAIL: generates click-sink-click events
  Caught ScheduleError:
  | Expected: <true>
  |   Actual: <false>

CONSOLE MESSAGE:
CONSOLE MESSAGE: 0 PASSED, 2 FAILED, 0 ERRORS
My first step is right out the TDD playbook: change the (error) message. To do so, I write an actual test:
  test('displays click events', (){
    var el = query('click-sink');
    schedule(()=> document.body.click());
    schedule(() {
      expect(el.shadowRoot.text, contains('Page clicked'));
    });
  });
When I click the document body, I expect that the <click-sink> Polymer element will contain a text message that includes the coordinates of the click. Running this test, I find that the message has indeed changed:
CONSOLE MESSAGE: FAIL: displays click events
  Caught ScheduleError:
  | Expected: contains 'Page clicked'
  |   Actual: '\n'
  |   '    \n'
  |   '      Click Anywhere on the Page\n'
  |   '      \n'
  |   '        \n'
  |   '      \n'
  |   '    \n'
  |   '  '
Now, why on earth is this failing?

It turns out that I am using old syntax for the Polymer attached life cycle method (that tends to happen with a 6+ month old dependency on an alpha state library):
@CustomTag('click-sink')
class ClickSinkElement extends PolymerElement {
  // ...
  ClickSinkElement.created(): super.created();
  enteredView() {
    super.enteredView();
    // ...
  }
}
Both Polymer.dart and JavaScript Polymer have long since standardized on the attached() callback method (called when the Polymer instance is attached to the container document). So the fix is simple enough:
@CustomTag('click-sink')
class ClickSinkElement extends PolymerElement {
  // ...
  ClickSinkElement.created(): super.created();
  attached() {
    super.attached();
    // ...
  }
}
And, just like that, I have my element working and a legitimate test to ensure that something like this never happens again:
CONSOLE MESSAGE: PASS: displays click events
I remain quite disappointed in myself for not having done this already. Hopefully this is a case of better (severely) late than never.


Day #165

Tuesday, August 26, 2014

Git Subcommand to Sort by Last Modified Date


OK, it's time to get serious about updating Patterns in Polymer and finishing the screencasts. But where to start? I know what needs to be done for the screencasts: record them (the upcoming long US weekend should help there). But how best to focus my efforts on the chapters in the book?

Working through each chapter based on last modified date seems the best bet, but all of the chapter files on my filesystem have the same last modified timestamp:
$ ls -l *asc
...
-rw-r--r-- 1 chris chris 13163 Jun 28 10:06 i18n.asc
-rw-r--r-- 1 chris chris  5213 Jun 28 10:06 live_reload.asc
-rw-r--r-- 1 chris chris 10713 Jun 28 10:06 mdv.asc
-rw-r--r-- 1 chris chris  7917 Jun 28 10:06 plain_old_forms.asc
-rw-r--r-- 1 chris chris 12495 Jun 28 10:06 polymer.asc
-rw-r--r-- 1 chris chris   794 Jun 28 10:06 strategy.asc
Either I made a very large commit on June 28 at 10:06 or that was the last time that I checked these files out fresh (either from a new clone or switching from a sparse branch).

In fact, the last time that I updated the i18n chapter was back in April (yikes!):
$ git log i18n.asc | head -5
commit 030968dff38794ba7e20fa3ae839fc62a999eee2
Author: Chris Strom 
Date:   Sun Apr 20 23:39:09 2014 -0400

    Fix up i18n copy and code
So how do I go about sorting by the last-modified-in-git date? That turns out to be tricky. Thankfully, there is a nice Stack Overflow post on the subject.

The date timestamp from that article will not help sorting, but ISO 8601 will. Happily, git-log supports logging with ISO 8601:
$ git log -1 --date=iso --format="%ad" i18n.asc 
2014-04-20 23:39:09 -0400
It have the feeling that I would like to use this again, so I will create a git subcommand: git-lslt (similar to the usual ls -lt). Git subcommands are trivial—I need only a script prefixed with git-. So I create $HOME/bin/git-lslt as:
#!/bin/sh

ls | \
  while read filename; do
    echo "$(git log -1 --date=iso --format="%ad" -- $filename) $filename"
  done | \
  grep '^2' | \
  sort
Most of that is from the Stack Overflow article. The sort is self-explanatory. The grep '^2' selects only entries with date stamps—meaning that files not tracked by git will be ignored.

The output looks like:
$ git lslt
2013-11-16 16:26:38 -0500 about.asc
2013-11-16 16:26:38 -0500 chapter_1.asc
2013-11-16 17:51:48 -0500 getting_help.asc
2014-01-14 19:58:38 -0500 book_dart.asc
2014-01-14 19:58:38 -0500 book_js.asc
2014-02-12 02:52:49 -0500 copyright.asc
...
Most of that is boilerplate code that never needs an update. I suspect that the "about" file has since been absorbed into the introduction and can be deleted.

After grepping the output a bit more, I find a few surprises:
$ git lslt | \
   grep .asc | \
   grep -v 'chapter_1\|contents\|copyright\|book.*.asc\|bibliography'
...
2014-02-14 01:13:27 -0500 parent_events.asc
2014-02-15 01:16:04 -0500 parent_child.asc
2014-02-15 01:16:04 -0500 svg.asc
2014-02-15 16:12:16 -0500 configuration.asc
...
I am surprised that the parent events and parent-child chapters have not been updated. I could have sworn that I got (and incorporated) feedback on at least one of those. The configuration chapter has since been removed (possibly permanently), so that is no surprise. But I have learned a ton about SVG since February—I am quite surprised that I have not included any of that knowledge in that chapter.

My new git lslt subcommand has already paid some nice dividends. I know where I need to focus my initial update/rewrite efforts. Starting tomorrow.


Day #164

Monday, August 25, 2014

At the Bottom of the Rabbit Hole, I Find Polyfill-Next-Selector


The more I investigate styling and theming Polymer elements, the less a grasp I feel like I have on it. The core-style continues to vex. Beyond that, I am also having difficulty applying consistent CSS styles on my elements.

I am unsure if my current problems are with Polymer or with the Core Elements and Paper Elements built on top of Polymer. The most recent issue that I find is that the <paper-button> inside of a sample Polymer element that I am trying to create do not share styles. Specifically, the border is not included on all paper-buttons:



Both the top buttons and the dialog buttons are <paper-button> elements. Both should be covered by my core-style CSS:
<polymer-element name="lorem-dialog">
  <template>
    <paper-button label="Bottom" on-tap="{{toggleDialog}}"></paper-button>
    <paper-button label="Center" on-tap="{{toggleDialog}}"></paper-button>

    <paper-dialog heading="Bottom Dialog" transition="paper-dialog-transition-bottom">
      <!-- ... -->
      <paper-button label="Accept" affirmative autofocus></paper-button>
    </paper-dialog><
    <!-- ... -->
    <core-style id="theme">
      :host /deep/ paper-button {
        color: {{g.primaryColor}};
        border: 5px solid {{g.secondaryColor}};
        padding: 2px 5px;
      }
    </core-style>
    <core-style ref="theme"></core-style>
  </template>
  <script>
    Polymer('lorem-dialog', {/* ... */});
  </script>
</polymer-element>
The core-style CSS is applied with the /deep/ CSS modifier. My understanding of /deep/ is that my styles should penetrate any shadow DOMs that are under the current element. And it kinda/sorta does what I expect. The primary color, themed from the containing page, is set to orange and it does apply to all paper-button elements. But darn it, why are the borders not in place for the paper-buttons in the dialogs?

My first thought is that perhaps this is a native shadow DOM vs. polyfill issue, so I load the demo page up in Firefox. This only makes things worse:



The borders are applied to all paper-buttons, but none of the core-style global settings are in place. Bleh.

Taking a step back, I know from last night that core-style does not like the way that I am using it. I take that out of the equation by removing the id/ref core-style pair of tags, replacing them with a straight forward <style> tag:
<polymer-element name="lorem-dialog">
  <template>
    <!-- ... -->
    <style>
      :host /deep/ paper-button {
        color: orange;
        border: 5px solid green;
        padding: 2px 5px;
      }
    </style>
  </template>
  <script>
    Polymer('lorem-dialog', {/* ... */});
  </script>
</polymer-element>
That solve my Firefox problem. The polyfills now correctly style my paper-button elements:



But this remains unchanged in Chrome (stable through unstable). On the surface, it would seem that Polymer styles are just broken at this point, but I refuse to indulge in select-is-broken thoughts. At least not yet.

If I peek at one of the paper-button elements in the Chrome inspector, I find that my border style is being overridden:



But what is that double-colon content thingy? I have no idea how that applies to my paper-button elements, let alone is more specific (in a CSS selector sense) than my /deep/ paper-button selector. Furthermore, I am hindered by the odd lack of file and line number for this selector. How can I understand and work around the darn thing if I cannot even find it?

To the ack machine Robin!

Amazingly, that actually finds something:
$ ack -l ::content                                                  
bower_components/platform/platform.js
bower_components/platform/platform.js.map
bower_components/paper-dialog/paper-dialog.css
bower_components/core-style/elements.html
bower_components/core-component-page/core-component-page.html
Only one of those is a CSS file and just so happens to be the element (paper-dialog) that is wrapping my misbehaving paper-buttons. Taking a look at the offending CSS, I find:
polymer-next-selector { content: ':host > *'; }
::content > * {
  font: inherit;
  border: 0;
}
That is exactly the definition of the culprit that I found in the DOM inspector. A quick review of the other matching files reveals that none of them have the same style definition. So the question becomes… what the heck is a polymer-next-selector? More importantly, how do I override it?

The answer to the first question seems to be that polymer-next-selector is also known as polyfill-next-selector. It seems to be a way to apply styles in the polyfills, but which work natively with the shadow DOM if present. Only, the precedence on these things is crazy high.

Try as I might, I cannot target the distributed nodes of the paper-dialog from my custom element that tries to use paper-dialog. I am surprised that targeting the distributed nodes of paper-dialog has no effect:
<polymer-element name="lorem-dialog">
  <template>
    <!-- ... -->
    <style>
      :host /deep/ paper-button {
        color: orange;
        border: 5px solid green;
        padding: 2px 5px;
      }

      paper-dialog::content > * {
        border: 5px dashed blue;
      }
    </style>
  </template>
  <script>
    Polymer('lorem-dialog', {/* ... */});
  </script>
</polymer-element>
In the end, I give up trying to work with ::content and simply add an !important to the border style:
<polymer-element name="lorem-dialog">
  <template>
    <!-- ... -->
    <style>
      :host /deep/ paper-button {
        color: orange;
        border: 5px solid green !important;
        padding: 2px 5px;
      }
    </style>
  </template>
  <script>
    Polymer('lorem-dialog', {/* ... */});
  </script>
</polymer-element>
That does the trick:



But it feels like a cheat.

I cannot figure out why the ::content distributed node selector has such high precedence or why my attempts to influence it failed. I am probably overlooking some selector combinator that would allow me to target paper-button distributed into paper-dialog, but I wish it were easier to find that combinator. And if there is no easier way to do this, then why is paper-dialog using such a high precedence selector to ensure that none of its nodes have borders?

I have a solution, but I have more questions than answers to go along with that solution. Which makes this feel like a cheat.




Day #163