Sunday, April 27, 2014

Dynamically Generating Polymer Definitions in Karma (Jasmine)


I now know the trick for dynamically generating Polymer definitions. I just don't know how to get that trick to work in Karma / Jasmine tests.

As I found last night, the trick to dynamically generate Polymer elements in regular web pages is to build the <script> portions of the definition via document.createElement() rather than building the <script> tag via innerHTML assignments. Easy enough, I can live with that.

But when I tried to do the same think in my Jasmine tests, I kept getting an error about Polymer not being defined:
Uncaught ReferenceError: Polymer is not defined
Bleary eyed, I was unable to figure out why this would be the case. With fresh eyes today, I realize that sample page and my tests are not quite the same. In my sample page, I have hard-coded the <script> tag for the Polymer platform:
<!doctype html>
<html>
  <head>
    <script src="bower_components/platform/platform.js"></script>
    <link rel="import" href="../bower_components/polymer/polymer.html">

    <script>
      var double_el = document.createElement('polymer-element');
      double_el.setAttribute('name', 'x-double');
      double_el.setAttribute('attributes', 'in out');
      
      var script = document.createElement('script');
      script.innerHTML = '    Polymer("x-double", { /* ... */ });';
      double_el.appendChild(script);
      document.getElementsByTagName("head")[0].appendChild(double_el);
    </script>
  </head>
  <body>
     <!-- ... --> 
     <x-double in="6"></x-double>
  </body>
</html>
In my tests, I have been dynamically adding those during test setup:
var script = document.createElement("script");
script.src = "/base/bower_components/platform/platform.js";
document.getElementsByTagName("head")[0].appendChild(script);

var link = document.createElement("link");
link.rel = 'import';
link.href = "/base/bower_components/polymer/polymer.html";
document.getElementsByTagName("head")[0].appendChild(link);
And indeed, if switch my sample page to use those dynamically built <script> and <link> tags, then I see the same failure in my browser.

I cannot hard-code those tags in Karma tests -- Karma decides which tags get added first and how. What ends up working is to assign an onload function to the Polymer platform's <script> tag:
var script = document.createElement("script");
script.src = "/base/bower_components/platform/platform.js";
script.onload = defineTestPolymerElements;
document.getElementsByTagName("head")[0].appendChild(script);

var link = document.createElement("link");
link.rel = 'import';
link.href = "/base/bower_components/polymer/polymer.html";
document.getElementsByTagName("head")[0].appendChild(link);
With that, my full test setup for a dynamically defined Polymer element looks like:
var script = document.createElement("script");
script.src = "/base/bower_components/platform/platform.js";
script.onload = defineTestPolymerElements;
document.getElementsByTagName("head")[0].appendChild(script);

var link = document.createElement("link");
link.rel = 'import';
link.href = "/base/bower_components/polymer/polymer.html";
document.getElementsByTagName("head")[0].appendChild(link);

function defineTestPolymerElements() {
  var double_el = document.createElement('polymer-element');
  double_el.setAttribute('name', 'x-double');
  double_el.setAttribute('attributes', 'in out');

  var script = document.createElement('script');
  script.innerHTML = '    Polymer("x-double", {\n'
                   + '      publish: {\n'
                   + '        out: {value: 0, reflect: true}\n'
                   + '      },\n'
                   + '      ready: function(){\n'
                   + '        this.inChanged();\n'
                   + '      },\n'
                   + '      inChanged: function(){\n'
                   + '        this.out = parseInt(this.in) * 2;\n'
                   + '      }\n'
                   + '    });\n';
  double_el.appendChild(script);
  document.getElementsByTagName("head")[0].appendChild(double_el);
}

// 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;});
});
With that, I think I have finally answered all of the little questions that occurred to me while trying to test code that interacts with any Polymer elements. I did not strictly need to answer these questions to get my task done—testing AngularJS interaction in angular-bind-polymer. Even so, it is good to have a better understanding of all of this. Maybe tomorrow I can actually write the angular-bind-polymer test that I started on 7 days ago!



Day #47

No comments:

Post a Comment