Send to Kindle

Saturday, September 20, 2014

Indirection Clean-up of Polymer Jasmine Tests


While testing my JavaScript Polymer elements, I do this a lot:
      beforeEach(function(done) {
        el.locale = 'fr';
        setTimeout(done, 0); // One event loop for element to update
      });
No, really, a lot:
  beforeEach(function(done){
    container = document.createElement("div");
    container.innerHTML = '';
    document.body.appendChild(container);
    el = document.querySelector('hello-you');

    setTimeout(done, 0); // One event loop for elements to register in Polymer
  });
In fact, I do it so often that I hard-coded it in the generator in eee-polymer-tests:
    beforeEach(function(done){
      // Maybe do some prep work here...
      setTimeout(done, 0); // One event loop for Polymer to process
    });
The problem that I am dealing with is, as the comments indicate, that Polymer waits until the next event loop before doing its thing. This promotes Polymer code and elements that are nice and responsive, but makes for noisy tests. I wonder if I might clean things up some.

Before starting down this road, I should say that I probably will not change anything. The first thing that I will try is a helper function. I hate helper functions. I hate indirection in tests. If I have to debug a test or trace through a test, I guarantee that I will delete that test (and maybe replace it). Still, I seem to do this setTimeout() / done() dance a lot, so...

I start by borrowing some of the terminology from the very excellent sheduled_test package in Dart. I try to schedule a Polymer operation in my tests with:
function schedulePolymer(fn){
  return function(done) {
    fn();
    setTimeout(done, 0); // One event loop for elements to register in Polymer
  };
}
By returning a function, I can supply this directly to Jasmine's (or Mocha's) beforeEach() function:
  beforeEach(
    schedulePolymer(function(){
      container = document.createElement("div");
      container.innerHTML = '<hello-you></hello-you>';
      document.body.appendChild(container);
      el = document.querySelector('hello-you');
    })
  );
That works. Well, my tests all still pass. But I am not really sure that clears up intent of my tests sufficiently to justify the existence of this helper function. And although the word schedule is nice in that implies a synchronous operation, I do not feel like it works well in this case. Perhaps if there was an entire library dedicated to scheduling JavaScript tests serially, this would make more sense.

Maybe if I write a Polymer-specific beforeEach():
function beforeEachPolymer(fn){
  return beforeEach(function(done) {
    fn();
    setTimeout(done, 0); // One event loop for elements to register in Polymer
  });
}
This lets me rewrite my beforeEach() as:
    beforeEachPolymer(function(){
      container = document.createElement("div");
      container.innerHTML = '';
      document.body.appendChild(container);
      el = document.querySelector('hello-you');
    });
This has the added benefit of not seeming out of place with afterEach() calls that I typically place immediately after beforeEach() calls:
  afterEach(function(){
    document.body.removeChild(container);
  });
This beforeEachPolymer() is not a huge win, but it does help when performing minor changes before checking expectations. Last night's i18n tests included a series of beforeEach() calls that needed to wait after changing the locale:
    describe('it can localize to French', function(){
      beforeEach(function(done) {
        el.locale = 'fr';
        setTimeout(done, 0); // One event loop for element to update
      });

      it('(fr)', function(){
        expect(el.shadowRoot.textContent).toContain('Bonjour');
      });
    });

    describe('it can localize to Spanish', function(){
      beforeEach(function(done) {
        el.locale = 'es';
        setTimeout(done, 0); // One event loop for element to update
      });

      it('(es)', function(){
        expect(el.shadowRoot.textContent).toContain('Hola');
      });
    });
That is rather noisy — especially since the setTimeout() / done() lines are so much wider than the code that actually tests my Polymer element. With beforeEachPolymer(), I now have:
    describe('it can localize to French', function(){
      beforeEachPolymer(function() {
        el.locale = 'fr';
      });

      it('(fr)', function(){
        expect(el.shadowRoot.textContent).toContain('Bonjour');
      });
    });

    describe('it can localize to Spanish', function(){
      beforeEachPolymer(function() {
        el.locale = 'es';
      });

      it('(es)', function(){
        expect(el.shadowRoot.textContent).toContain('Hola');
      });
    });
The intent of which is much clearer. But is it clear enough to justify test indirection? I just do not know.

I do not think that switching to promises would really offer much help—especially since Polymer is not much of a promise-driven library at present. I loathe to pull in yet another testing library—I already need a runner, a library, and an assertion library. Do I really need to pull in yet another library that people may or may not look upon as a standard? I have to admit, the old Jasmine 1 waits() and runs() stuff is looking pretty nice at this point. I do not think that is a viable solution to offer in Patterns in Polymer, but I may start using that in my own Polymer projects.


Day #189

Friday, September 19, 2014

Testing Core-Ajax Polymer Elements with Karma


One of the benefits of a test runner like Karma is that it, y'know, runs tests. More accurately, it runs tests on a test web server, which is a detail that I would like to explore testing the internationalization solution in Patterns in Polymer.

As best I can tell, there is not a single solution for i18n with Polymer. Way back when, I played with using the i18next JavaScript package in Polymer and liked it quite a bit. I just do not know if I like it as the solution. Especially since the book is simultaneously written in JavaScript and Dart, I settled on a strategy that allowed swapping implementations.

Tonight, I hope to test the default JavaScript strategy from the book, which is a simple, hand-rolled solution that loads localization files via <core-ajax> tags. Normally if I am testing Ajax in JavaScript, I would want to pull in the Sinon.js testing library to stub out HTTP requests like those that load the l10n files. But, since Karma is running its own web server for testing, I ought to be able to add the l10n files to the list of files being served and actually load the l10n files in my tests.

In fact, when I run the solitary test that currently tests my i18n chapter, I already see 404s when my Polymer element attempts to load the l10n files:
$  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 DjAiWyWkVjQjEfu7_8nN with id 73763037
Chrome 37.0.2062 (Linux): Executed 1 of 1 SUCCESS (0 secs / 0.078 secs)
WARN [web-server]: 404: /locales/en/translation.json
WARN [web-server]: 404: /locales/fr/translation.json
Chrome 37.0.2062 (Linux): Executed 1 of 1 SUCCESS (0.096 secs / 0.078 secs)
First, I need to include the l10n files in the list of files that Karma serves:
    files: [
      'bower_components/platform/platform.js',
      'test/PolymerSetup.js',
      {pattern: 'elements/**', included: false, served: true},
      {pattern: 'locales/**', included: false, served: true},
      {pattern: 'bower_components/**', included: false, served: true},
      'test/**/*Spec.js'
    ],
These are not code files—just JSON—so I do not want them automatically included and evaluated on the test page. I just want them served on demand. Hence included: false (the contents are not added directly to the test page) and served: true (the web server will serve them when requested).

That is just part of the puzzle. When I fire up the tests, I still get 404s for the l10n files:
WARN [web-server]: 404: /locales/en/translation.json
WARN [web-server]: 404: /locales/fr/translation.json
This is due to Karma serving everything from the /base URL path. If index.html is at the top-level of my project, then I would need to access it as /base/index.html.

Thankfully, Karma has a somewhat obscure proxies setting which allows resources to be reverse proxied:
proxies:  {
      '/locales/': '/base/locales/'
    }
With that, and a log-level of debug, I see that I have my l10n files being served and proxied:
...
DEBUG [proxy]: proxying request - /locales/en/translation.json to localhost:9876
DEBUG [proxy]: proxying request - /locales/fr/translation.json to localhost:9876
With that, I can write my tests that expect different localizatons:
    it('defaults to English', function(){
      expect(el.shadowRoot.textContent).toContain('Hello');
    });
Updating the locale is slightly tricky, but only because I have to wait for Polymer to update the element display. It only takes a single event loop, but the only way to accomplish this in Jasmine is with a setTimeout call to the asynchronous done():
    describe('it can localize to French', function(){
      beforeEach(function(done) {
        el.locale = 'fr';
        setTimeout(done, 0); // One event loop for element to update
      });

      it('(fr)', function(){
        expect(el.shadowRoot.textContent).toContain('Bonjour');
      });
    });
But, with that, and a similar test for Spanish:
    describe('it can localize to Spanish', function(){
      beforeEach(function(done) {
        el.locale = 'es';
        setTimeout(done, 0); // One event loop for element to update
      });

      it('(es)', function(){
        expect(el.shadowRoot.textContent).toContain('Hola');
      });
    });
Now I have much more meaningful tests for the book's i18n solution. This will be especially useful as Polymer evolves. Should my approach suddenly not localize with a new version of the Polymer library, I will know immediately.

I am marginally dissatisfied with the overhead of beforeEach() just to setTimeout() / done(). I do not normally care too much about the readability of tests, but this seems to unnecessarily obscure the intent of the test. I have larger concerns (like producing screencasts for the Extras readers), so I may just live with this. Still, it bothers me so I may have a look at this in the upcoming days.


Day #188

Thursday, September 18, 2014

Upgrading Old Polymer Elements to Core Elements


I made it through every single chapter in Patterns in Polymer last night. Except one.

I finally have a super test build for the book project. Since Patterns in Polymer is written simultaneously in JavaScript and Dart, my super test build needs to run tests for each chapter in both Dart and JavaScript. After last night, the super test build runs JavaScripts tests for every chapter save for the internationalization chapter.

There are three reasons that I could not get the i18n chapter tested last night. First, I had written no tests when I originally wrote the chapter (bad developer). The eee-polymer-tests generator / dependency manager is capable of generating a simple test, but... The second reason that I could not get the chapter under test is that I am relying on <core-ajax> elements to load JSON localization files. Last, but not least, I am actually relying on <polymer-ajax> elements (the predecessor to <core-ajax>) to load the 110n files.

In other words, I have my work cut out for me to get this tested.

The approach I take is to get the Polymer library updated while still using the old <polymer-ajax>, then try eee-polymer-tests' default test, then upgrade to the new <core-ajax>. Finally, if all goes well, I will try to add some legitimate tests.

My bower.json does not restrict Polymer versions (to enable easy updates to the latest Polymer for testing):
{
  "name": "i18n",
  // ...
  "dependencies": {
    "polymer": "Polymer/polymer",
    "bootstrap": "~3.1.0",
    "i18next": "~1.7.2",
    "polymer-elements": "~0.2.0"
  }
}
Running bower update does not go smoothly, mainly because polymer-elements is so darn old. But, whenever I run across an incompatibility, I opt for the newer instance:
Unable to find a suitable version for platform, please choose one:
    1) platform#0.2.4 which resolved to 0.2.4 and is required by core-component-page#0.2.4, polymer#0.2.4
    2) platform#^0.4.0 which resolved to 0.4.1 and is required by core-component-page#0.4.1, polymer#0.4.1
Eventually, I get everything installed and up to date. Amazingly, when I fire the internationalized <hello-you> element up in the browser, it still works perfectly:



OK, so step #1 is done. Next, I try eee-polymer-tests' simple test. I add eee-polymer-tests to the NPM package.json dependencies:
{
  "name": "i18n",
  "devDependencies": {
    "grunt": "~0.4.1",
    "grunt-contrib-watch": "~0.5.3",
    "eee-polymer-tests": "eee-c/eee-polymer-tests"
  }
}
Then I run npm install and instruct the generator to create a test for <hello-you>. That too works:
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 ebljUvRurILtOe32-8Gq with id 2242723
Chrome 37.0.2062 (Linux): Executed 1 of 1 SUCCESS (0.093 secs / 0.085 secs)
WARN [web-server]: 404: /locales/en/translation.json
WARN [web-server]: 404: /locales/fr/translation.json
WARN [web-server]: 404: /locales/es/translation.json
The test simply checks that a shadow DOM is present if the <hello-you> element is inserted. It is not much of a test, but it does tell me that Polymer is loading properly for this element, which is especially good to know since the element was written so long ago.

Before writing a real test, this is a good time to upgrade from <polymer-ajax> to <core-ajax>. After removing polymer-ajax from the dependencies in bower.json, I tell bower to replace it with core-ajax:
$ bower install Polymer/core-ajax -S
bower core-ajax#*               cached git://github.com/Polymer/core-ajax.git#0.4.1
bower core-ajax#*             validate 0.4.1 against git://github.com/Polymer/core-ajax.git#*
The -S option saves the dependency in bower.json:
{
  "name": "i18n",
  // ...
  "dependencies": {
    "polymer": "Polymer/polymer",
    "bootstrap": "~3.1.0",
    "i18next": "~1.7.2",
    "core-ajax": "Polymer/core-ajax#~0.4.1"
  }
}
I delete bower_component and re-install to ensure that I am rid of any leftover cruft from the previously very old dependency. But after doing so, the element still works:



And my simple test still passes.

That will suffice as a stopping point for tonight. Some of the book text will need to be updated after these changes, so will get to that now. More importantly, I can add the last chapter to my super build—even if it is only covered by a very simple test. Up tomorrow, I will do something about that very simple test—I think that I can write some useful tests for this chapter.


Day #187


Wednesday, September 17, 2014

Auto-Choosing Xvfb Display Ports


My current plan is to work through every chapter in Patterns in Polymer, getting the code and tests up to date with the latest Polymer and Karma + Jasmine. Once I have a regular build in place, I will revisit each chapter's content to ensure that it too is up-to-snuff, then perhaps add a chapter or two.

As an aside, I have encountered my share of annoyances with minor Polymer breakages but I can accept those—this is, after all an alpha release of the library. More frustrating is the syntax in Jasmine changing with every patch release (e.g. andReturn() becomes and.returnValue() is my latest favorite). Between this and major syntax changes in other testing libraries for no apparent reason, it almost enough to put me off testing.

Despite the various annoyances, things are moving steadily. I have an outside chance of getting all of the JavaScript code and tests fixed tonight. Except the Dart build randomly breaks for no apparent reason.

The book is written in both Dart and JavaScript so I need to run tests for both sample codebases. Unlike JavaScript testing libraries and JavaScript expectation libraries and JavaScript test runners, Dart's testing has been quite stable. So what gives?

Well, Dart tests, like JavaScript tests, require a windowing environment in which to execute browser tests. On Linux, this is normally done with Xvfb and on Ubuntu/Debian I have been using the nifty little xvfb-run run script to execute tests:
    # Run the Karma tests
    xvfb-run karma start --single-run --log-level warn
Unfortunately this turns out to be the cause of my Dart failure:
...
xvfb-run: error: Xvfb failed to start
Build step 'Execute shell' marked build as failure
It turns out that the Dart build, which I delay for unrelated reasons, is conflicting with the windowing display port being used by my JavaScript build.

Ugh. I start dreaming up how I might retry the build with a different port or additional delay or maybe even run only one build at a time. Mercifully, before I get around to attempting to implement any of that, I read the xvfb-run man page (to be honest, I was reading it to figure out how to implement some of those crazy ideas). In addition to making it easy to wrap processes inside a windowing environment, the xvfb-run script also has a flag to do just what I need:
-a, --auto-servernum
Try to get a free server number, starting at 99, or the argument to --server-num.

So in the end, all I need to do is add the -a switch to my tests:
    # Run the Karma tests
    xvfb-run -a karma start --single-run --log-level warn
I can even verify this by running my Dart tests several times during the JavaScript build (did I mention that Dart tests tend to be markedly faster than their JavaScript counterparts?). During most builds, there is a note about RANDR missing from the Xvfb windowing environment (which is perfectly OK):
Xlib:  extension "RANDR" missing on display ":99".
During most of the test runs, I see the message on port :99, but on one I see:
Xlib:  extension "RANDR" missing on display ":100".
In other words, the -a flag detected in this case that the JavaScript tests were currently being run on display port :99 and automatically ran on :100 instead. Cool!

With that, I can get back to fixing tests and code without the worry of seemingly random failures.


Day #186

Tuesday, September 16, 2014

Updating Published Polymer Attributes


If a codebase's tests don't run regularly, the codebase is not tested. A team can spend hundreds of person-hours writing hundreds of tests. But if those tests are not executed at least once a day, the team might just as well delete them. They occupy space with no purpose.

This seems utterly obvious, yet I see it time and time again. And as much as I detest this poor excuse for programming, I am guilty of no less with the Patterns in Polymer project code. As I work through each chapter adding at least rudimentary Karma / Jasmine tests to the custom Polymer elements therein, I am surprised… at how many tests I actually did write.

And so far, the tests have been easy to fix once I updated the element definitions to the latest Polymer library. Until tonight. Tonight, I find that some of the console logging that I expect from a Polymer observer is not being called:
$ 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 wjIrCJXi9b1e1VLgybGG with id 7435382
Chrome 37.0.2062 (Linux)  sees changes to your_name FAILED
        Expected spy Console Log to have been called with [ 'your_name is now: Bob' ] but it was never called.
            at Object.<anonymous> (/home/chris/repos/polymer-book/book/code-js/changes_from_outside/test/HelloYouSpec.js:106:9)
Chrome 37.0.2062 (Linux): Executed 1 of 5 (1 FAILED) ERROR (0.065 secs / 0.061 secs)
At first I assumed than an API had changed since I originally wrote this code and test (it was all the way back in March, after all). Upon closer examination, something else seems to be going wrong. I am hesitant to say that Polymer is broken, but my understanding of it is.

Regardless, it sure would have been nice to have a regular build running so that I knew exactly when this did break.

First, the problem. I am spying on console.log() which is called by a MutationObserver:
function watchPolymer(el) {
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      console.log(
        mutation.attributeName +
        ' is now: ' +
        mutation.target[mutation.attributeName]
      );
    });
  });
  observer.observe(el, {attributes: true});
}
By virtue of the attributes: true on the observe() method, this MutationObserver should fire whenever an attribute changes. And it does, if I find the <hello-you> Polymer element and update one of its attributes, the MutationObserver fires:
> el = document.querySelector('hello-you')
    <hello-you color=​"greenish">​…​</hello-you>​
> el.attributes.getNamedItem('color').value = 'orange'
    color is now: orange watcher.js:14
    "orange"
But if the value of the color property changes in the element, say when the feelingLucky() button is pressed, nothing happens:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="hello-you" attributes="your_name color" color>
  <template><!-- ... --></template>
  <script>
Polymer('hello-you', {
  your_name: '',
  color: '',

  feelingLucky: function() {
    console.log(this.color);

    var colors = ['red', 'blue', 'green'];
    var num = Math.floor(colors.length*Math.random());
    this.color = colors[num];
    this.$.hello.style.color = this.color;
  }
});
</script>
</polymer-element>
Dang it. I am publishing the color attribute by including it in the space separated list of published attributes in the aptly named attributes attribute of the definition. But no matter what I do, changing the property is not reflected in the element's attribute.

What is even stranger is that, the console.log() on the feelingLucky() does see the initial value of the attribute. If I use this custom <hello-you> element with a “greenish” color:
<hello-you color="greenish"></hello-you>
Then, when I first press the feelingLucky() button, I see greenish logged in the JavaScript console:
color is now: greenish     watcher.js:14
greenish                   hello-you.html:27
It is almost like data binding is one way. That, or Polymer has some kind of internal setting that prevents published attributes from getting updated.

Wait....

I have seen this before. I know I have. Despite numerous searches and banging my head against this code for quite some time, I had no luck identifying this problem. But I know that I either getAttribute() or setAttribute()'d before to work this exact problem. And indeed I did.

I have now learned through bitter experience not once, but twice that there is special undocumented syntax that needs to be applied if one wants attributes of a Polymer element to be updated when the underlying property value is updated. I cannot set the published attributes via the attribute attribute of polymer-element anymore. Instead I have to use the published property and set reflect to true:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="hello-you">
  <template><!-- ... --></template>
  <script>
Polymer('hello-you', {
  publish: {
    your_name: {value: '', reflect: true},
    color: {value: '', reflect: true},
  },
  // ...
});
</script>
</polymer-element>
I was nearing my wits' end with this problem. That I have now been forced to solve this twice is a source of much annoyance. Hopefully the title of this post will ensure that I will have better search luck the next time that I have to do battle with this feature.



Day #185

Monday, September 15, 2014

Why Must I Be A Programmer in Love (with Inheritance)?


Why do I hate code inheritance so much? Possibly because no matter how much I pretend to hate it, it still seems to be my first instinct for extending class behavior. Which only makes me hate it more. Or at least pretend to.

Tonight's case in point comes from an unlikely source: the Karma configuration for one of the chapter projects in Patterns in Polymer. I am (slowly) working through each chapter standardizing on the dependencies and generated structure provided by eee-polmer-tests. One of the peer dependencies of eee-polymer-tests is Karma and eee-polymer-tests is kind enough to include a file with common configuration options.

I use those common configuration options in each Polymer project with a karma.conf.js that looks like:
module.exports = function(config) {
  var common = require('./node_modules/eee-polymer-tests/karma-common.conf.js');

  config.set(common.mixin_common_opts(config, {
    // Override common settings here...
  }));
};
OK, this is not classical class inheritance. This is typical JavaScript prototypical inheritance. My problem remains the same. I see a problem and I want to solve it by putting junk in the object doing the extending. Worse, once I start down that road, I begin inventing ways to make the inheritance smart.

While converting some old tests over to this new approach, I realized that I was missing two things: the html2js Karma preprocessor and the inclusion of HTML fixtures in the configuration. The fix is simple enough:
module.exports = function(config) {
  var common = require('./node_modules/eee-polymer-tests/karma-common.conf.js');

  config.set(common.mixin_common_opts(config, {
    // Override common settings here...
    preprocessors: {
      'test/*.html': 'html2js'
    },

    files: [
      'test/PolymerSetup.js',
      {pattern: 'elements/**', included: false, served: true},
      {pattern: 'bower_components/**', included: false, served: true},
      'test/**/*Spec.js',
      'test/*.html'
    ]

  }));
};
I have added the html2js preprocessor (I am also required to npm install karma-html2js-preprocessor --save-dev). I have copied the list of files from eee-polymer-test's common Karma configuration and added the test/*.html fixtures to the list.

And this is enough to trigger my Pavlovian inheritance response: “I should add preprocessors by default, I should change the mixin_common_opts() so that I can add the one entry to the list rather than copying and pasting, I should support special attribute that are added to existing values instead of overwriting them.”

Maybe this is why inheritance is so insidious. Because each of those things is small enough that they make sense. I would not be violating YAGNI—I would use them immediately. Each would legitimately solve a problem for me. So why not just give in? Why not just implement the configuration like this and be done with it?

There are at least two reasons. The first is a nagging sense that I would be in violation of POLS—that I (or someone else using this code) would find the inheritance confusing or non-obvious. The second reason is that I am jumping to implementation without really considering the problem.

And here's the problem. Here's my dirty little secret. I don't use fixtures in my Polymer testing. In my very first Polymer tests, I used them mostly as I was trying to understand how best to approach testing. The Polymer fixtures worked, but since then I have mostly dynamically created the elements right in my tests. So maybe this is a case of YAGNI after all. But it's not that I don't need the code—this is a situation where I don't need the solution.

As a nod to my struggles, I will likely add the list of files from the common configuration into the generated karma.conf.js as a comment. That way, if I do run into this again, I will have all of the information that I need right in the configuration file.

In the end, I nearly gave into my seemingly natural reflex to add some form of inheritance to everything. Thankfully, I was able to listen to my better nature. Maybe someday that will be my first instinct.


Day #184

Sunday, September 14, 2014

NPM and Polymer Puzzles for Great Coding Fun


I love coding because of the puzzles.

I confess that I am not much of a 1000-piece picture-puzzle person. That just seems like so much tedium. Every piece has one exact place to go. There is only one solution. And you can get to that single solution if you mindlessly try to find pieces that fit together for an hour or two. There is no challenge, just diversion.

Which is why I like coding so much. There is never a single solution. Heck there might not even be a solution. But it sure is fun trying to find these solutions. Maybe that is why I like testing my code so much. As much as I appreciate the assurance and maintainability that testing gives me, it is yet another challenge. It also presents challenges of setting up tests properly, writing tests that actually test something and, of course, getting the actual code to work.

I am working through the project chapters in Patterns in Polymer tonight, hoping to get as much of it covered by tests as possible. This is aided my new eee-polymer-tests test generator / common setup NPM package. Except when it does not work.

I am working on the AngularJS chapter which describes using custom Polymer elements in Angular projects. I have some tests in there, but I have the feeling that they are rather old. So my thinking is to run the test generator on the chapter project and overwrite everything (it is all in Git, so I can always get a test back if it proves useful).

The problem? When I run the generator with the --force flag, it seems to overwrite my existing test files and karma configuration, but... they are not affected:
$ ./node_modules/eee-polymer-tests/generator.js x-pizza --force

Generating test setup for: x-pizza
Force overwrite: karma.conf.js.
Force overwrite: test/PolymerSetup.js.
Force overwrite: test/XPizzaSpec.js.
Force overwrite: bower.json.
Done!

$ git status
On branch master
Changes not staged for commit:
  (use "git add ..." to update what will be committed)
  (use "git checkout -- ..." to discard changes in working directory)

        modified:   bower.json
        modified:   karma.conf.js
        modified:   package.json
So what gives? Also, I know this was working in my other projects. What is it about this project that is preventing the force-overwrite option from behaving as desired?

Since this is working for some of the generators, but not others, it seems likely that there is a difference in implementation between the different generators. That or it's a permissions issue. It's usually a permissions issue. But not this time.

In this case, the problem is my Node.js code—it is too Node.js-y. Sidenote: it is hard adding an “-y” to the end of a proper noun that includes punctuation. The difference between the generators is that some are synchronized file system operations while the problem ones are asynchronous—as is typical in Node.js-land (hrm… that doesn't seem any better). The Nodey (bleh) version looks like:
function generatePolymerSetup() {
  var template = __dirname + '/templates/PolymerSetup.js';
  fs.readFile(template, 'utf8', function (err, data) {
    if (err) {
      console.log(err);
      process.exit(1);
    }

    var content = data.replace(/\{\{element-name\}\}/g, element);
    fs.writeFile(POLYMER_SETUP, content);
  });
}
The lack of the word “sync” in there is the giveaway that this is asynchronous code. Node opens a stream to read then merrily executes whatever other code is ready to be evaluated / run outside of this function. Eventually the filesystem responds with the contents of the file and, if Node is not busily executing code elsewhere, it will invoke the callback function with any file content or error information that it might have. And again, when Node tries to write the content to the POLYMER_SETUP file, it will open a write file descriptor then move on to some other business until the filesystem responds that writing is ready. Unless it never gets the chance.

And herein lies my problem. While Node is waiting on the filesystem, it continues to work through the other generator business with which it is tasked until there is no more work. And unlike a Node.js server, this generator script just stops without giving the filesystem operations a chance to complete.

Once the problem has been identified as too much async, the solution is to use the synchronous versions of file system operations:
function generatePolymerSetup() {
  var template = __dirname + '/templates/PolymerSetup.js';
  var content = fs.
    readFileSync(template, 'utf8').
    replace(/\{\{element-name\}\}/g, element);

  fs.writeFileSync(POLYMER_SETUP, content);
}
Node.js will block (not do anything else) until the readFileSync() operation returns. Similarly, it will not do anything else until the new setup file is written and writeFileSync() returns. And once both are done—and only once both are done—Node.js is free it move onto the next generator. In other words, there is no way to accidentally skip any generated files.

Is this the best solution? For a command-line operation, it probably is. There might a more Node.js solution (that kind of works)—especially if I am more careful about tracking open file descriptors. But this seems a good enough solution.

I am a little unsure why I am seeing this problem now. It may be that the filesystem has more work to do before signaling back to Node that streams are ready. When initially testing, I may have manually deleted some of these files before running the generator.

Solving that little puzzle is a nice win, but I am not rewarded with a passing test. Even the skeleton Jasmine test created by my generator is failing when run with Karma:
$ 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 K5_WrlH4x_H2n31y3Iz6 with id 85813120
Chrome 37.0.2062 (Linux) <x-pizza> element content has a shadow DOM FAILED
        Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
Chrome 37.0.2062 (Linux): Executed 1 of 1 (1 FAILED) ERROR (5.072 secs / 5.066 secs)
I would not unhappy if all of this just worked, but the harder it is to solve, the more satisfying the solution.

In this case, my PolymerSetup.js is failing, not the actual test. In the setup, I poll for Polymer to be fully initialized before allowing the tests to run. In Jasmine (2.0+), this is done by calling the done() callback:
// 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();
});
The problem turns out to be that whenReady() has been renamed as whenPolymerReady() in the Polymer project. Fun. It takes me a bit to track that one down, including a trip into the Karma debugger and the Chrome debugger:



So, even though Polymer.whenPolymerReady() seems a little redundant, that would seem to be the solution du jour:
// Delay Jasmine specs until Polymer is ready
var POLYMER_READY = false;
beforeEach(function(done) {
  function waitForPolymer() {
    if (Polymer && Polymer.whenReady) {
      Polymer.whenReady(done);
      return;
    }
    if (Polymer && Polymer.whenPolymerReady) {
      Polymer.whenPolymerReady(done);
      return;
    }
    setTimeout(waitForPolymer, 100);
  }
  waitForPolymer();

  if (POLYMER_READY) done();
});
Yeah, I think I'll hedge my bets on that one by including both whenReady() and whenPolymerReady(). Unless the name is changed in another unique way, I should be covered.

And, thankfully, this time I am rewarded. Not only does the generated test pass, but so does the test that used to be in there:
$ karma start
INFO [karma]: Karma v0.12.23 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [watcher]: Changed file "/home/chris/repos/polymer-book/book/code-js/angular/test/XPizzaSpec.js".
Chrome 37.0.2062 (Linux): Executed 2 of 2 SUCCESS (0.063 secs / 0.057 secs)
So I got to solve fun puzzles and have as my reward some code that is more maintainable and future proofed. For a coder like me, that is a good day.


Day #183