Friday, November 22, 2013

Jasmine Setup of Polymer Tests


Testing my fairly simple Polymer elements has been an unexpected adventure. But I think that I may have cracked it.

Although last night's excursion into testing the test Shadow DOM will no doubt be of use at some point, it feels more like testing the framework than serving to test something of real value. I think that the tests that I wrote previously capture that spirit. And, as I found when I tried my second test, I suspect that adding a second specification to the same test suite will teach me.

I continue to test a couple of Boostrap web components: <pricing-plans> and its child <pricing-plan. The idea is to convert the mess of <div> tags that usually build something like:



With Polymer web components, these can be expressed as the much more readable:
<pricing-plans>
  <pricing-plan name="Multi-Language">
    <ul><!-- ... --></ul>
  </pricing-plan>
  <pricing-plan name="I Only Like One">
    <ul><!-- ... --></ul>
  </pricing-plan>
  <pricing-plan name="Extras" type="primary">
    <ul>
      <li>Get the multi-language pack plus…</li>
      <li><strong>Screencast</strong> of <span title="Test Driven
        Development">TDDing</span> a polymer element.</li>
      <li>The <strong>warm feeling</strong> of helping
        produce the book.</li>
      <li>Inclusion in the <strong>supporters</strong> section of the
        book.</li>
      <li>Chris's undying <strong>love</strong>.</li>
      <li>More...?</li>
    </ul>
  </pricing-plan>
</pricing-plans>
I might quibble over the difficulty in distinguishing between the parent and child tag names, but that aside, this is more more readable than slogging through a bunch of <div class="col-md-10 col-sm-4"> tags in order to find my actual content.

What I have come to understand as I have explored testing the child <pricing-plan> tags is that the hardest thing to get right is the way that Polymer wants to be loaded—first:
  <head>
    <!-- ... -->
    <!-- 1. Load Polymer before any code that touches the DOM. -->
    <script src="scripts/polymer.min.js"></script>
    <!-- 2. Load component(s) -->
    <link rel="import" href="scripts/pricing-plans.html">
    <link rel="import" href="scripts/pricing-plan.html">
  </head>
In the spec for the <pricing-plan> tag, I had been doing that with a “before all” test setup block. Before all specs, I dynamically inserted the Polymer library in the test page's <head> so that it would be loaded before anything else. And that worked.

But it raised the question: what happens if there is a second specification file with its own “before all”? Should the second “before all” detect the presence of the Polymer library from the first? Should both detect Polymer so that their order can be changed? Bother that.

Instead, I think I can lean on my testing framework (Jasmine) and my test runner (Karma) to fix this. I start with the later, where I make the first included file a new PolymerSetup.js:
    // ...
    /**
     * 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: 'scripts/**/*.html', included: false},
      {pattern: 'scripts/**/*.js', included: false},
      {pattern: 'scripts/**/*.js.map', included: false},
      'test/**/*Spec.js',
      'test/*.html'
    ],
    // ...
In that setup, I load Polymer and the Polymer elements that I am trying to test:
// 1. Load Polymer before any code that touches the DOM.
var script = document.createElement("script");
script.src = "/base/scripts/polymer.min.js";
document.getElementsByTagName("head")[0].appendChild(script);

// 2. Load component(s)
var link = document.createElement("link");
link.rel = "import";
link.href = "/base/scripts/pricing-plan.html";
document.getElementsByTagName("head")[0].appendChild(link);

link = document.createElement("link");
link.rel = "import";
link.href = "/base/scripts/pricing-plans.html";
document.getElementsByTagName("head")[0].appendChild(link);
What I like about this approach is that it mimics the HTML inclusion order in the normal application code. First polymer is loaded, then the Polymer elements.

This then allows the Jasmine specs to just worry about the fixtures that directly affect its tests:
describe('<pricing-plan>', function(){

  var container;

  beforeEach(function(){
    container = document.createElement("div");
    container.innerHTML = __html__['test/pricing-plan-fixture.html'];
    document.body.appendChild(container);
    waits(0); // One event loop for elements to register in Polymer
  });
  afterEach(function(){
    document.body.removeChild(container);
  });
  // Tests here...
});
Best of all, this appears to work for both set of test files.

UPDATE In order to ensure that Polymer was ready, I found that I needed a Jasmine beforeEach() back in the PolymerSetup.js that waits for CustomElements to be ready (the same as waiting for the WebComponentsReady event to fire):
// Delay Jasmine specs until WebComponentsReady
beforeEach(function(){
  waitsFor(function(){
    if (typeof(CustomElements) == 'undefined') return false;
    return CustomElements.ready;
  });
});


Day #943

No comments:

Post a Comment