Tuesday, April 7, 2015

Upgrading a Polymer Element to Polymer 0.8


Tonight, I break a rule. Sort of.

Normally in these chains, I think of a question to which I do not know the answer then do my best to answer it. During the course of exploring web-component-tester (WCT) last night, I was quite taken aback at the difficulty involved in upgrading a project from Polymer 0.5 to the new 0.8 (even with the very nice migration guide). I was able to get it working, but without much thought.

So tonight, I revert all of my code changes and start again:
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

Changes not staged for commit:
  (use "git add/rm ..." to update what will be committed)
  (use "git checkout -- ..." to discard changes in working directory)

        modified:   code-js/polymer/elements/hello-you.html
        modified:   code-js/polymer/elements/hello_you.js
        modified:   code-js/polymer/index.html
        deleted:    code-js/polymer/karma.conf.js
        deleted:    code-js/polymer/test/HelloYouSpec.js
        deleted:    code-js/polymer/test/PolymerSetup.js

no changes added to commit (use "git add" and/or "git commit -a")
$ git checkout .
$ git status
On branch master
Your branch is up-to-date with 'origin/master'.

nothing to commit, working directory clean
I can get rid of the Karma tests as I am likely switching to WCT one way or another, but I leave them for now since my CI build expects them.

I will do this work in a new polymerjs-0_8 branch in the Patterns in Polymer book repository. After some thought (and discussion), I think it best for the book to target PolymerJS 0.5 for the time being. Targeting 0.8/0.9/1.0 can wait for the next edition. Still, I need to start playing with it, so let's try to repeat last night's success...

First up, I remove the "polymer": "<0.8" constraint that I added earlier today to my project's bower.json. In this branch, I will again target whatever the latest (semi-)stable version of Polymer is available:
{
  "name": "hello-you",
  // ...
  "dependencies": {
    "polymer": ">0.0"
  }
}
A Bower update and I am ready to roll:
bower update
....
polymer#0.8.0 bower_components/polymer
├── core-component-page#0.5.5
└── webcomponentsjs#0.6.0

webcomponentsjs#0.6.0 bower_components/webcomponentsjs
My tests are failing, but before I even start with that, I update both my sample HTML page and my WCT test page to point to the new webcomponentjs:
    <!-- 1. Load the polymer platform. -->
    <script
       src="bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
    <!-- 2. Load component(s) -->
    <link rel="import" href="elements/hello-you.html">
My tests are still failing all the same, but this is the most obvious external change required, so it makes sense to start with that.

Next, I start with making sure that my element compiles. I have a sanity test for just this sort of thing. In Polymer 0.5, I would check that my Polymer element had a shadow root element. In 0.8, I need to check for the existence of a "shady" root:
  describe('element content', function(){
    it('has a shady DOM', function(){
      assert.isDefined(el.root);
    });
  });
It does not:
chrome 41                ✖ index.html » <hello-you> » element content » has a shady DOM

  expected undefined to not equal undefined
    <unknown> at   Context.<anonymous> at /polymer/test/index.js:10:0
If I look closer in a persistent WCT browser session, I find:
Uncaught TypeError: undefined is not a function     
desugar                                            polymer.html:37 
window.Polymer                                     polymer.html:21
(anonymous function)                               hello_you.js:2
This is because I can no longer declare my elements in Polymer with the name of the element as the first argument to the Polymer() function:
// 0.5 style:
Polymer('hello-you', {
  // Element definition here...
});
Instead, the element name ("hello-you" in this case) goes in the definition as the is property:
Polymer({
  is: 'hello-you',
  // The rest of the element definition here...
});
With that, I have my sanity test passing:
✓ index.html » <hello-you> » element content » has a shady DOM
One hurdle cleared, but another of my tests is not even registering and the last, which tests clicks, is failing:
✖ index.html » <hello-you> » clicking » randomly changes the title's color

  Cannot read property 'querySelector' of null
    <unknown> at   Context.<anonymous> at /polymer/test/index.js:44:0
I start with the failing test because I can actually see it and, if last night's spike is any guide, the other test will magically reappear after this one is resolved. This is easy enough. I had been looking for child elements in my Polymer element's shadow DOM. There is no shadow DOM anymore (at least not by default), so I can query a little more directly in my tests now:
    it('randomly changes the title\'s color', function(){
      var button = el.querySelector('input[type=submit]');

      var e = document.createEvent('MouseEvent');
      e.initMouseEvent(
        "click", true, true, window,
        0, 0, 0, 80, 20,
        false, false, false, false, 0, null
      );
      button.dispatchEvent(e);

      var h2 = el.querySelector('h2');
      assert.equal(h2.style.color, 'green');
    });
My test still fails, but with a different message:
✖ index.html » <hello-you> » clicking » randomly changes the title's color

  expected '' to equal 'green'
    <unknown> at   Context.<anonymous> at /polymer/test/index.js:56:0
I think I have a legitimate Polymer 0.8 test now, but it does not appear to be interacting with the randomizer button. Checking in the WCT persistent window, I find the following warning:
[input].[click]: event handler [{{feelingLucky}}] is null in scope
It would be nice if warnings like that showed up in the WCT console output because this tells me exactly what the problem is: I can no longer bind methods with the double-curly braces like that. Instead, I have to update my randomizing button to point to the feelingLucky() method with no braces at all:
<polymer-element name="hello-you">
  <template>
    <h2>Hello {{your_name}}</h2>
    <p>
      <input value="{{your_name}}">
      <input type=submit
             value="Fabulousize Me!"
             on-click="feelingLucky">
    </p>
    <p class=help-block>
      <content></content>
    </p>
  </template>
  <script src="hello_you.js"></script>
</polymer-element>
After that my test still fails to pass, but that is a simple code fix—the feelingLucky() method was also pointing to the shadowRoot. After removing that reference, I have two passing tests and... the last test is still missing. I am unsure why that would be, but it seems like a bug in WCT. If I check the console in the WCT persistent browser, I see that I am again using shadowRoot. After removing that from the test, I now get the failure that I desire:
✖ index.html » <hello-you> » typing » says a friendly hello

  expected 'Hello {{your_name}}' to equal 'Hello Bob'
    <unknown> at   Context.<anonymous> at /polymer/test/index.js:33:0
Part of this is easy to solve. Bound variables now have to reside entirely inside of tag. So I update the Polymer template to place {{your_name}} inside a <span>:
  <template>
    <h2>Hello <span>{{your_name}}</span></h2>
    <p>
      <input value="{{your_name}}">
      <input type=submit
             value="Fabulousize Me!"
             on-click="feelingLucky">
    </p>
    <p class=help-block>
      <content></content>
    </p>
  </template>
The other thing that needs to change is the binding of the your_name property inside the <input> element—{{your_name}} by itself will no longer work. In 0.8, I have to append the kind of event that triggers an update to the property. In this case, I want your_name updated when an input event occurs:
  <template>
    <h2>Hello <span>{{your_name}}</span></h2>
    <p>
      <input value="{{your_name::input}}">
      <input type=submit
             value="Fabulousize Me!"
             on-click="feelingLucky">
    </p>
    <p class=help-block>
      <content></content>
    </p>
  </template>
With that, I have all three of my tests for this simple element passing:
✓ index.html » <hello-you> » element content » has a shady DOM
✓ index.html » <hello-you> » typing » says a friendly hello
✓ index.html » <hello-you> » clicking » randomly changes the title's color
And, more importantly, the <hello-you> element again appears to be working:



That was a lot saner than last night. The only thing that I did yesterday, but not today, was converting the <polymer-element> to <dom-element>. I was under the impression that it was required, but it would seem not.


Day #22

2 comments:

  1. We ran into the issue of console errors not showing up in WCT and not causing any sort of failure either. We ended up writing a quick script that we load right after browser.js and polymer in every test HTML file. The script registers window.onerror handler that has a test("has no console errors", ...) and then assert.fail it with the error message. This causes our test suite to fail when a console error occurs before the rest of the suite starts running.

    This might be what's happening with your missing test. The script errors out before the test runner even begins running the test. We had the same problem and found 10 tests that weren't even running.

    Hope this helps!

    ReplyDelete
    Replies
    1. That's super helpful. I'm going to give that a try in a few days. Your explanation feels right based on what I was seeing. If so, this seems like something that ought to be baked into WCT.

      Thanks!

      Delete