Monday, December 1, 2014

Organizing Polymer Projects for Reuse, Unit Testing, and Smoke Testing


My tests say that the <a-form-input> Polymer element works, but I have my doubts that I have the repository organized properly. The <a-form-input> element is primarily intended to be used as a base element for other Polymer elements that would like to behave like native HTML <form> input elements (something that Polymer does not support out of the box).

My main worry is the element definition, which resides at the top-level of the element's repository:
.
├── a-form-input.html
├── a_form_input.js
├── bower.json
├── index.html
├── karma.conf.js
├── package.json
├── README.md
└── test
    ├── AFormInputSpec.js
    ├── PolymerSetup.js
    └── x-double.html
Specifically, I worry about the first line in the element definition:
<link rel="import" href="bower_components/polymer/polymer.html">
<polymer-element name="a-form-input" attributes="name value">
  <template></template>
  <script src="a_form_input.js"></script>
</polymer-element>
I referenced Polymer via bower_components because that is where Bower installs things locally. This works when performing smoke tests over a simple HTTP server, but seems destined to break when installed elsewhere. And I realize that I have not given this much thought, which is rather strange considering that Patterns in Polymer is already at 1.0.

I'll chalk that up to I already gave it due consideration and have since forgotten the outcome. Yeah, that's the ticket.

Ahem.

I thought initially to start investigation by using this element in the “play” area of the book's repository. I realize now that I ought to check out the Polymer project itself. They have definitely already solved this, so...

I check out the <core-meta> element which serves as the base of all the Core Elements. Like all of the other core-elements, it plays nicely with Bower. Like my <a-form-input>, the <core-meta> element definition resides at the top level of the repository (so at least I got that right). The actual definition, however, does not reference Bower:
<link rel="import" href="../polymer/polymer.html">
<polymer-element name="core-meta" attributes="label type" hidden>
<script>
  <!-- ... -->
</script>
</polymer-element>
That is not going to quite work for me, however. It does resolve the problem that I have yet to even consider—how this element will reference other elements when installed elsewhere. Removing the explicit reference to bower_components makes all the sense in the world in that respect.

But when I run a simple HTTP server to smoke test the element (via an index.html page also in the top-level of the repository), the route to ../polymer will expect to find a top-level polymer directory.

The solution that I wind up adopting is to manually create a symbolic link to my current project inside the project's bower_components directory:
$ cd bower_components
$ rm -rf *
$ ln -s .. a-form-input
$ cd ..
$ ls -l bower_components
total 0
lrwxrwxrwx 1 chris chris 2 Dec  1 23:53 a-form-input -> ..
I will check this symbolic link into source control so that the a-form-input project always contains a reference to itself. This will still allow dependencies to be installed without trouble:
$ bower install
bower polymer#*                 cached git://github.com/Polymer/polymer.git#0.5.1
bower polymer#*               validate 0.5.1 against git://github.com/Polymer/polymer.git#*
bower core-component-page#^0.5.0           cached git://github.com/Polymer/core-component-page.git#0.5.1
bower core-component-page#^0.5.0         validate 0.5.1 against git://github.com/Polymer/core-component-page.git#^0.5.0
bower webcomponentsjs#^0.5.0               cached git://github.com/Polymer/webcomponentsjs.git#0.5.1
bower webcomponentsjs#^0.5.0             validate 0.5.1 against git://github.com/Polymer/webcomponentsjs.git#^0.5.0
bower polymer#^0.5.0                      install polymer#0.5.1
bower core-component-page#^0.5.0          install core-component-page#0.5.1
bower webcomponentsjs#^0.5.0              install webcomponentsjs#0.5.1
...
$ ls -l bower_components
total 12
lrwxrwxrwx 1 chris chris    2 Dec  1 23:53 a-form-input -> ..
drwxr-xr-x 2 chris chris 4096 Dec  1 23:58 core-component-page
drwxr-xr-x 2 chris chris 4096 Dec  1 23:58 polymer
drwxr-xr-x 2 chris chris 4096 Dec  1 23:58 webcomponentsjs
That seems to do the trick, if I fire up my smoke test HTTP server:
$ python -m SimpleHTTPServer 8000
Then my test element continues to work.

This solution does not come without risks, however. The main concern is that I have a link in a subdirectory to a parent directory. This can easily result in infinite recursion. In fact, my Karma files trip over this right away:
    // list of files / patterns to load in the browser
    files: [
      'bower_components/webcomponentsjs/webcomponents.js',
      'test/PolymerSetup.js',

      {pattern: 'bower_components/**', included: false, served: true},
      {pattern: '*.html', included: false, served: true},
      {pattern: '*.js', included: false, served: true},
      {pattern: 'test/*.html', included: false, served: true},
      'test/*Spec.js'
    ],
The bower_components/** glob tries to match all subdirectories and their contents. Since one of the subdirectories is the parent directory, I wind up matching the top-level directory in my repository which includes bower_components and another reference to the top-level directory. Yikes!

So, to support the seemingly benign link-to-current-project in bower_components, I have to get a little fancy with my Karma file patterns:
    files: [
      'bower_components/webcomponentsjs/webcomponents.js',
      'test/PolymerSetup.js',

      {
        pattern: 'bower_components/!(a-form-input)/**',
        included: false,
        served: true},
      {
        pattern: 'bower_components/a-form-input/*.+(html|js)',
        included: false,
        served: true
      },

      {pattern: 'test/*.html', included: false, served: true},
      'test/*Spec.js'
    ],
The first bower_components pattern finds all subdirectories and files in bower_components except those in a-form-input. This avoids the infinite recursion, but I still need to grab the HTML and JavaScript definitions for the current element, which is what the second bower_components pattern does.

With that, I have my Karma tests passing again:
$ karma start --single-run
INFO [karma]: Karma v0.12.28 server started at http://localhost:9876/
INFO [launcher]: Starting browser Chrome
INFO [Chrome 39.0.2171 (Linux)]: Connected on socket lHJe_wkPmox9Vo1egAIe with id 36845514
.....
Chrome 39.0.2171 (Linux): Executed 5 of 5 SUCCESS (0.152 secs / 0.147 secs)
And my smoke test page still works:



I still need to try this out in an actual project, but I will leave that for tomorrow. Hopefully basing this approach on the core-elements approach will make live usage more or less trivial. But something is bound to go wrong...


Day #11

No comments:

Post a Comment