Tuesday, April 22, 2014

A Woeful Approach to Dynamically Defining Polymer Elements


Some posts really get away from me. Like last night's fiasco.

Invariably, the posts that get away from me suffer from the same problem: me trying to do too much at once. Last night I tried setting up a Karma test suite from scratch, to test an AngularJS directive with a Polymer web component. Individually, I can do any of those things relatively quickly (as long as I remember that Angular tests need angular-mock, sheesh). Put them together, however, and 2 or 3 three things that don't quite work might as well be 20.

So tonight, I do what I really ought to know by now: bite off smaller chunks. Even though I have my Angular tests running again, there seems to be something not quite right with my Polymer tests. Thanks to my work in Patterns in Polymer, I have a fairly reliable Karma, Jasmine, and Polymer setup, so this annoys me. To eliminate this annoyance, I focus solely on it.

I comment out the various setup blocks from my tests that initialize Angular and the stuff that gets injected to work with directives. I update my karma.conf.js so that only the tests and Polymer setup are loaded on the test page:
    // ...
    // list of files / patterns to load in the browser
    files: [
      'test/PolymerSetup.js',
      // 'bower_components/angular/angular.js',
      // 'bower_components/angular*/angular-*.js',
      // 'angular_bind_polymer.js',
      {pattern: 'bower_components/**', included: false, served: true},
      'test/*Spec.js'
    ],
    // ...
I will pull back in the Angular stuff once I have Polymer sorted out. As for Polymer, my setup remains as it had been in Patterns in Polymer. I dynamically add a <script> tag to load in the polymer platform and then wait for the polymer-ready event before proceeding with tests in the test/*Spec.js files:
script.src = "/base/bower_components/platform/platform.js";
document.getElementsByTagName("head")[0].appendChild(script);

// Delay Jasmine specs until polymer-ready
var POLYMER_READY = false;
beforeEach(function(done) {
  window.addEventListener('polymer-ready', function(){
    POLYMER_READY = true;
  });
  waitsFor(function(){return POLYMER_READY;});
});
I assume that works since I copied and pasted it from book tests that still pass. What I am unsure about is how to add dynamic Polymer element definitions to the page. When testing real polymer elements, I can dynamically create the <link> import in PolymerSetup (after the platform is initialized):
var link = document.createElement("link");
link.rel = "import";
link.href = "/base/elements/x-pizza.html";
document.getElementsByTagName("head")[0].appendChild(link);
I do not have a real element in this case. I just need a dummy Polymer element against which I can run my data-binding test. So instead of putting those files in a separate Polymer definition file and dynamically creating the <link> import element, I try to dynamically add the definition right to the page:
var double_el = document.createElement('polymer-element');
double_el.setAttribute('name', 'x-double');
double_el.setAttribute('attributes', 'in out');
double_el.innerHTML = '<script>\n'
                       + 'Polymer("x-double", {\n'
                       + '  in: 13,\n'
                       + '  ready: function(){ '
                       + '    console.log("+++++")'
                       + '    this.inChanged();'
                       + '    console.log("+++++")'
                       + '  },\n'
                       + '  inChanged: function(){\n'
                       + '    console.log("u0000000");\n'
                       + '    this.out = parseInt(this.in) * 2;\n'
                       + '  }\n'
                       + '});\n'
                       + '</script>\n';
document.body.appendChild(double_el);
For good measure, I also pull in the Polymer library itself (only the platform was loaded previously):
var link = document.createElement("link");
link.rel = 'import';
link.href = "/base/bower_components/polymer/polymer.html";
document.getElementsByTagName("head")[0].appendChild(link);
With that, I get… nothing. No console statements printed and no errors.

Still, that tells me something. Since there are no errors, the call to Polymer inside the <script> tags is working. The element definition even shows up in the DOM inspector:



Rather than try to solve multiple issues tonight, I really am just going to do the simplest thing that should work: moving the Polymer element definition out into a separate file. First, I will serve this fixture definition in Karma (but not load it by default):
    // ...
    // list of files / patterns to load in the browser
    files: [
      'test/PolymerSetup.js',
      // 'bower_components/angular/angular.js',
      // 'bower_components/angular*/angular-*.js',
      // 'angular_bind_polymer.js',
      {pattern: 'bower_components/**', included: false, served: true},
      {pattern: 'test/*.html', included: false, served: true},
      'test/*Spec.js'
    ],
    // ...
Then, I create my dummy Polymer element as test/x-double.html:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="x-double" attributes="in out">
  <script>
    Polymer("x-double", {
      in: 13,
      ready: function(){
        console.log("+++++")
        this.inChanged();
        console.log("+++++")
      },
      inChanged: function(){
        console.log("u0000000");
        this.out = parseInt(this.in) * 2;
      }
    });
  </script>
</polymer-element>
And that work just fine. All of those debug console statements pass. My test checking the doubling passed. Everything.

So it seems that something about the way that I am dynamically adding the element definition to my test page is wrong. Armed with the knowledge that the Polymer definition itself is sound, I am ready to try the next small steps.

Tomorrow.


Day #42

No comments:

Post a Comment