Thursday, November 21, 2013

Testing Content Wrapped by Polymer Elements


After last night, I have a setup for testing Polymer elements. I don't know if it is a good setup, let along the setup. Luckily, I have a reasonably involved pair of Polymer elements to try things out with.

As I found yesterday, because of the way that Polymer likes to establish itself and ready itself to do its web component thing, the setup in Jasmine tests was a little awkward. In application code, the <head> of the HTML document needs to look like:
  <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>
The best that I could come up with to simulate this in my Jasmine setup was:
describe('<pricing-plan>', function(){
  var previouslyLoaded = false;

  beforeEach(function(){
    if (!previouslyLoaded) {
      var script = document.createElement("script");
      script.src = "/base/scripts/polymer.min.js";
      document.getElementsByTagName("head")[0].appendChild(script);

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

      document.body.innerHTML = __html__['test/pricing-plan-fixture.html'];
    }

    previouslyLoaded = true;
    waits(100);
  });
  // Tests go here...
});
There is not really a “before all” built into Jasmine which is why I added in the ugly previouslyLoaded conditional.

Anyhow, what I would like to test tonight is that the content that I wrap with my Polymer element winds up being rendered in the proper location. This turns out to be much harder than I expected. The fixture that I am placing into my document.body is pretty simple:
<pricing-plan>pricing plan 01</pricing-plan>
I just need to verify that the string "pricing plan 01" is embedded inside of the Polymer element. If I inspect the element on the debug page, it is definitely there:



So how hard can it be to query programatically? Well, it involves something called distributed nodes. It seems that the text content from my fixture is still in the original element. If I query the element's text content from the JavaScript, I see it:
> document.querySelector('pricing-plan').textContent
  "pricing plan 01"
I see that text, but that is not where the content is "distributed" to be rendered.

In fact, it is "distributed" for rendering in the <content> tag of my Polymer element:
<polymer-element name="pricing-plan" attributes="name type size">
  <template>
    <div class="col-md-{{size}}">
      <div class="panel panel-{{type}}">
        <div class="panel-heading">
          <h3 class="panel-title">{{name}}</h3>
        </div>
        <div class="panel-body">
          <content></content>
        </div>
      </div>
    </div>
  </template>
  <script>
    Polymer('pricing-plan', { /* ... */ });
  </script>
</polymer-element>
And to test for that, I need to go through my Polymer element's shadow DOM. The >content> element resides there in the "shadows" of the real DOM element, my custom <pricing-plan> element. Once I have that, use the getDistributedNode() method to get the first node that was distributed. Hopefully that is my text snippet:
    it('contains the content wrapped by the element', function() {
      var distributed_nodes = el.shadowRoot.
           querySelector('content').
           getDistributedNodes();

      expect(distributed_nodes[0].textContent).toBe('pricing plan 01');
    });
And that actually works.

It may work, but that's pretty awkward. Testing this shadow DOM stuff is much trickier than I expected. But tested it must be. I wonder if that is the last of the surprises. Almost certainly not.


Day #942

No comments:

Post a Comment