Send to Kindle

Friday, April 10, 2015

UI-less Polymer Elements as the Model in MVVM


Last night's attempt at MVVM (Model-View-ViewModel) in Polymer did not quite work out the way I hoped.

I am trying to rework the very simple <hello-you> Polymer element used in a couple of Patterns in Polymer chapters:



I had hoped that the simplicity would lead to a quick solution on which I could build something more complex. I found myself stuck on the Model. I think the class is simple enough. To support the element as-is, the most basic of objects ought to suffice:
function Person(name) {
  this.name = name ? name : '';
}
What tripped me up was marking name observable. If a human introduces themselves, the Person object should have its name property updated and the View should be able to observe that change so that it can update itself with the new name.

Last night's solution was to shelve the idea of a Person object and use a plain old Object instead. That works fine in Polymer since Polymer can observe plain old objects out of the box—its properties are automatically observable. That solves my simple case, but what about a more complex object with a prototype and methods?

I can think of at least three approaches that might work for this. Tonight, I try what I hope is the easiest: making the Model a UI-less Polymer element:
Polymer({
  is: 'x-person',
  properties: {
    name: {
      type: String,
      value: '',
      observer: 'nameChanged'
    }
  },

  nameChanged: function(v, old) {
    console.log(v + ' ' + old)
  }
});
This should give me an <x-person> element that I can use inside of <hello-you> to store the name of the Person being greeted: <x-person name="Chris"/>. I declare the UI-less template as:
<polymer-element name="x-person">
  <script src="x_person.js"></script>
</polymer-element>
I import this into the <hello-you> element with a <link> import:
<link rel="import" href="../bower_components/polymer/polymer.html">
<link rel="import" href="x-person.html">
<polymer-element name="hello-you">
  <template>
    <!-- Template HTML here ... -->
  </template>
  <script src="hello_you.js"></script>
</polymer-element>
With that, I should be able to use my <x-person> “Model” in the ViewModel (<hello-you>'s backing class) and the View (<hello-you>'s template).

The easiest (but possibly not the best for this pattern) way to bind <hello-you>'s <input> to the name property of <x-person> is in the template/view:
<polymer-element name="hello-you">
  <x-person name="{{name}}"></x-person>
  <template>
    <h2>Hello <span>{{name}}</span></h2>
    <p>
      <input value="{{name::input}}">
    </p>
    <!-- ... -->
  </template>
  <script src="hello_you.js"></script>
</polymer-element>
That is probably not quite right for MVVM since three items (<x-person>, the <h2> title, and the <input>) bind to a local name variable instead of a ViewModel property. I will worry about that later. For now, I have the ViewModel worrying about presentation (from last night) and a Model worrying about the “business” of naming.

Only this does not work.

When I update the <input>, the change observer that I logged in <x-person> is not seeing any changes. This turns out to be caused by placing <x-person> outside of <hello-you>'s <template> tag. The fix for this is easy enough, I move it inside of the <template>:
  <template>
    <x-person name="{{name}}"></x-person>
    <h2>Hello <span>{{name}}</span></h2>
    <p>
      <input value="{{name::input}}">
    </p>
    <!-- ... -->
  </template>
To complete tonight's efforts, I would like to drive the most basic of business logic in the Model: setting a default value. This is accomplished in Polymer 0.8 with a value property:
Polymer({
  is: 'x-person',
  properties: {
    name: {
      type: String,
      value: 'Bob',
      observer: 'nameChanged'
    }
  },
  // ...
});
But when I load up <hello-you>, I am not seeing “Bob”:



This is due to the lack of change notification from the Model. For a host element like <hello-you> to see changes to attributes of child elements, the child has to specify that the property generates notifications. This is done with the notify property:
Polymer({
  is: 'x-person',
  properties: {
    name: {
      type: String,
      value: 'Bob',
      notify: true,
      observer: 'nameChanged'
    }
  },
  // ...
});
Now, when I load my element, the name defaults to “Bob”:



I am unsure if this binding more properly belongs in the ViewModel than in the View as I have done here. For tonight, I claim a small victory in the form of an observable Model. It is clear that I need to continue my investigation into MVVM as I still do not have a complete handle on it.


Day #24

Thursday, April 9, 2015

Circling Around MVVM in Polymer


I think MVVM is overkill for most Polymer elements. So tonight I apply it to an extremely simplistic Polymer element to confirm my bias.

I kid. I kid.

Several Patterns in Polymer readers have inquired about MVVM (Model-View-ViewModel) and I have replied that I think it likely overkill. But I have not really investigated—thoroughly or otherwise. I start with the simple case, not to quickly confirm my bias and move one, but to better help me get a grip on the necessary concepts. I tend to read about MV-whatever patterns and then lump them in the same place in my brain. Hopefully this will help.

The simple element remains the initial <hello-you> element in the book:



I continue to use last night's Polymer 0.8 code, mostly so I can continue to get a feel for the new stuff. I have the feeling this may, inadvertently, be a reasonable choice for MVVM (though it is still overkill). The “Fabulousize Me” button point to a method in the backing class that updates colors in the view. That sounds very much like view logic instead of business logic, which should push the code in a View Model.

But I'm getting ahead of myself...

The view in a Polymer element is simple enough. It is the <template> for the Polymer element. For the <hello-you> element, this is the view code:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="hello-you">
  <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>
  <script src="hello_you.js"></script>
</polymer-element>
The your_name property comes directly from the model. The ViewModel can proxy it, but it is the purview of the Model. The your_name property is part of the business logic of <hello-you>. The feelingLucky() method on the other hand...

The backing class currently looks like:
var colors = ['red', 'blue', 'green'];
Polymer({
  is: 'hello-you',
  your_name: '',

  feelingLucky: function() {
    var num = Math.floor(colors.length*Math.random());
    this.
      querySelector('h2').
      style.color = colors[num];
  }
});
My first impression is that this is a ViewModel, not a Model. Certainly the feelingLucky() method is ViewModel code since it only interacts with the View. But if this is ViewModel, what does the Model look like?

In this case, I think it is nothing more than:
function Person(name) {
  this.name = name;
}
Again, this is a very simplistic Polymer element, so it makes sense that the Model is simple. As the code evolves, I could add first and last name properties, a full name computed method, and more. Before I get to that, how can I get this into the Polymer ViewModel? I kind of want to declare a you property as:
  // ...
  properties: {
    you: {
      type: Person
    }
  },
  //...
That will not work, however since properties can only be native types like String and Object. Well, the most basic model would be just an Object, so I try:
Polymer({
  is: 'hello-you',
  properties: {
    you: {
      type: Object
    }
  },
  // ...
}
In the View, I give direct access to this you Object:
    <h2>Hello <span>{{you.name}}</span></h2>
    <p>
      <input value="{{you.name::input}}">
      <input type=submit
             value="Fabulousize Me!"
             on-click="feelingLucky">
When I try to enter a name in the <input> field, however, I get:
Uncaught TypeError: Cannot set property 'name' of undefined
Hrm... even the simple Object version of the Model is not making this easy. Perhaps I need to initialize the you property?

I try this in the created() callback:
  // ...
  created: function(){
    this.you = {name: ''};
  }
  // ...
But I get the following when loading the element:
Uncaught SyntaxError: Unexpected identifier
I am unsure why that fails. I can get that working by changing to the ready() lifecycle method, but I eventually realize this is not how Polymer wants me to do this. I should be creating a default value in the properties declaration:
var colors = ['red', 'blue', 'green'];
Polymer({
  is: 'hello-you',

  properties: {
    you: {
      type: Object,
      value: {name: ''}
    }
  },

  feelingLucky: function() {
    var num = Math.floor(colors.length*Math.random());
    this.
      querySelector('h2').
      style.color = colors[num];
  }
});
With that, I have something moving toward MVVM. It does not feel like a real MVVM implementation—at least not without a method or two in the Model. Still, it is a baby step in the right direction. I am unsure how I want to get a real Model tomorrow, but I may start by creating another, UI-less Polymer object as the first try. If nothing else, this should make it easier to get observable properties and methods.


Day #23


Wednesday, April 8, 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

Tuesday, April 7, 2015

Replacing Karma with web-component-tester in Polymer Projects


It looks as though I will be replacing Karma with web-component-tester as the testing framework in Patterns in Polymer. I only really need to do the in the two testing chapters, but I'm in for a penny...

To get started, I will switch testing for the very simple <hello-you> Polymer element from chapter 1 of the book, then upgrade to the latest Polymer to see if anything breaks. That seems like a nice little test of things.

I start by simply replacing the karma dependencies in my chapter's package.json with web-component-tester (WCT):
{
  "name": "hello-you",
  "devDependencies": {
    "grunt": "~0.4.5",
    "grunt-contrib-watch": "~0.6.1",
    "grunt-notify": "^0.4.1",
    "web-component-tester": "^2.2.6"
  }
}
I remove the Karma configuration, the test/PolymerSetup.js that was needed in Karma, and the existing node_modules. Then I install the new WCT dependencies:
➜  polymer git:(master) rm -rf karma.conf.js test/PolymerSetup.js node_modules; npm install 
From there, I can convert my tests fairly easily. I had defined the test files in the old karma.conf.js. The web-component-tester expects the test entry point to reside in test/index.html. So I create test/index.html and point it the existing test file:
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <script src="../bower_components/webcomponentsjs/webcomponents.min.js"></script>
  <script src="../../web-component-tester/browser.js"></script>
  <link rel="import" href="../elements/hello-you.html">
</head>
<body>
  <hello-you id="fixture"></hello-you>
  <script src="index.js"></script>
</body>
</html>
I also add a fixture in there for easier access to the Polymer element that I want to test.

The tests themselves nearly work without change. I had been using Jasmine for my tests. The BDD interface supported by Chai is very similar to Jasmine—all I need to change is my assert statements:
describe('<hello-you>', function(){
  var el;

  beforeEach(function(){
    el = document.querySelector('#fixture');
  });
  // ...
  describe('typing', function(){
    beforeEach(function(done){
      var input = el.shadowRoot.querySelector('input');
      var e = document.createEvent('TextEvent');
      e.initTextEvent('textInput', true, true, null, 'Bob');
      input.dispatchEvent(e);
      setTimeout(done, 0); // One event loop for Polymer to process
    });

    it('says a friendly hello', function() {
      var h2 = el.shadowRoot.querySelector('h2');
      assert.equal(h2.textContent, 'Hello Bob');
    });
  });
});
Just like that, I have passing tests:
chrome 41                Beginning tests via http://localhost:59285/polymer/test/index.html?cli_browser_id=0
chrome 41                ✓ index.html » <hello-you> » element content » has a shadow DOM
chrome 41                ✓ index.html » <hello-you> » typing » says a friendly hello
chrome 41                Tests passed
Test run ended with great success

chrome 41 (2/0/0)

Done, without errors.
I have another test that relies on Jasmine spies. WCT supports spies, but Sinon stubs instead of Jasmine spies. In this case, I want to stub the return value of Math.random so that the otherwise random color change is deterministic. Sinon (like Jasmine) makes this easy:
  describe('clicking', function(){
    beforeEach(function(){
      // spyOn(Math, "random").and.returnValue(0.99);
      sinon.stub(Math, "random").returns(0.99);
    });

    it('randomly changes the title\'s color', function(){
      var button = el.shadowRoot.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.shadowRoot.querySelector('h2');
      assert.equal(h2.style.color, 'green');
    });
  });
With that, I again have three passing tests for my simple element:
chrome 41                Beginning tests via http://localhost:37627/polymer/test/index.html?cli_browser_id=0
chrome 41                ✓ index.html »  » element content » has a shadow DOM
chrome 41                ✓ index.html »  » typing » says a friendly hello
chrome 41                ✓ index.html »  » clicking » randomly changes the title's color
chrome 41                Tests passed
Test run ended with great success

chrome 41 (3/0/0)                     

Done, without errors.
My next step is to upgrade the Polymer library to verify that (hopefully) nothing breaks. For the book, I have left Polymer unconstrained in Bower:
{
  "name": "hello-you",
  // ...
  "dependencies": {
    "polymer": ">0.0"
  }
}
So a simple bower update gets me to Polymer 0.8:
$ 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
With that, I re-run my tests and find… that two tests are now failing:
chrome 41                Beginning tests via http://localhost:42253/polymer/test/index.html?cli_browser_id=0
chrome 41                ✖ index.html » <hello-you> » element content » has a shadow DOM

  expected null to not equal null
    <unknown> at   Context.<anonymous> at /polymer/test/index.js:10:0

chrome 41                ✖ 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

chrome 41                Tests failed: 2 failed tests
Test run ended in failure: 2 failed tests

chrome 41 (0/0/2)                     
2 failed tests
At first, I suspect that there is a newer version of the web-component-tester that I need for Polymer 0.8. Then I take a closer look a the 0.8 Migration Guide and… wow. I am in some serious trouble with 0.8. Not only has the API completely changed, but many of the core concepts that have been baked in to Polymer for nearly two years have changed. Shady DOM? Ouch.

With not _too_ much effort, I am able to get my very simple element migrated to 0.8 and the tests passing again. But I need to give some very serious consideration to how I plan to handle 0.8 in the book. Given that it is only a preview release at this point, I may push to get the latest version of the book done sooner rather than later then deal with the repercussions of 0.8 after the new API stabilizes. Ugh. And I was having such a nice time with web-component-tester.


Day #21

Monday, April 6, 2015

The Debugger and Breakpoints with web-component-tester


At this point, I am just looking for an excuse not to use web-component-tester. And so far, I have not found one. The web-component-tester (WCT) testing library for Polymer elements seems quite solid. It eliminates a lot of the overhead required for Polymer testing without sacrificing much in the way of configurability or extendability.

But, thanks to a question from Jim Simon in response to last night's post, I might just have the excuse for which I have been looking.

OK, in reality, I am not looking for a reason to dislike WCT. I am simply casting a very critical eye at it before I switch testing solutions in Patterns in Polymer to use it. Until Jim's question, I had yet to find a reasonable objection. Until Jim's question...

So here it is, how do you debug tests in WCT? With Karma, which is the current Patterns in Polymer recommendation for JavaScript, you click the Debug button on the page. Setting breakpoints and using debugger statements work as expected.

I would be satisfied that debugging was solid in WCT if I could simply get a debugger statement to work. So I add one to my Page Objects test from a couple of nights ago:
  describe('adding a whole topping', function(){
    beforeEach(function(done){
      xPizza.addWholeTopping('green peppers', done);
    });

    it('updates the pizza state accordingly', function(){
      var with_toppings = JSON.stringify({
        firstHalfToppings: [],
        secondHalfToppings: [],
        wholeToppings: ['green peppers']
      });

      debugger

      assert.equal(
        xPizza.currentPizzaStateDisplay(),
        with_toppings
      );
    });
  });
This seems a useful point to set a debugger statement. The element would have been established by virtue of a fixture and WCT's baked-in wait for Polymer.onReady. In this case, I use the page object's addWholeTopping() method to interact with the element, wait for changes to be done, then wait for the debugger statement to kick-in.

This will not work with my file-watcher setup because debugger statements, like breakpoints, only work if the JavaScript console is open in Chrome. Without that, my tests merrily continue to pass:
Running "wct-test:local" (wct-test) task
Starting Selenium server for local browsers
Selenium server running on port 52328
Web server running on port 40424 and serving from /home/chris/repos/polymer-book/play/web-component-tester
chrome 41                Beginning tests via http://localhost:40424/js/test/index.html?cli_browser_id=0
chrome 41                ✓ index.html »  » can add toppings to the whole pizza
chrome 41                ✓ index.html »  » defaults » has no toppings anywhere
chrome 41                ✓ index.html »  » adding a whole topping » updates the pizza state accordingly
chrome 41                ✓ simple_tests_01.html »  (1) » starts with no toppings
chrome 41                ✓ simple_tests_02.html »  (2) » starts with no toppings
chrome 41                Tests passed
Test run ended with great success

chrome 41 (5/0/0)                     

Done, without errors.
My first thought at this point is that I need to make use of WCT's persistent option:
$ wct --help

Usage: wct [options]

Options:
   -p, --persistent        Keep browsers active (refresh to rerun tests).
   --root                  The root directory to serve tests from.
   --plugin NAME           Plugins that should be loaded.
   --skip-plugin NAME      Configured plugins that should _not_ be loaded.
   --expanded              Log a status line for each test run
...
I give that a try:
$ wct -p
Starting Selenium server for local browsers
Selenium server running on port 43088
Web server running on port 56994 and serving from /home/chris/repos/polymer-book/play/web-component-tester
chrome 41                Beginning tests via http://localhost:56994/js/test/index.html?cli_browser_id=0
chrome 41                ✓ index.html » <x-pizza> » can add toppings to the whole pizza
chrome 41                ✓ index.html » <x-pizza> » defaults » has no toppings anywhere
chrome 41                ✓ index.html » <x-pizza> » adding a whole topping » updates the pizza state accordingly
chrome 41                ✓ simple_tests_01.html » <x-pizza> (1) » starts with no toppings
chrome 41                ✓ simple_tests_02.html » <x-pizza> (2) » starts with no toppings
...
The tests continue to pass (and not break for debugging). But the test browser remains. I open the JavaScript console:



Then I reload the page, and... the dang page closes the JavaScript console before re-running the tests. My guess is that WCT is doing this because this browser session is still attached to the backend WCT session. When I reload the browser, the WCT session again reports:
...
chrome 41                Beginning tests via http://localhost:56994/js/test/index.html?cli_browser_id=0
chrome 41                ✓ index.html » <x-pizza> » can add toppings to the whole pizza
chrome 41                ✓ index.html » <x-pizza> » defaults » has no toppings anywhere
chrome 41                ✓ index.html » <x-pizza> » adding a whole topping » updates the pizza state accordingly
chrome 41                ✓ simple_tests_01.html » <x-pizza> (1) » starts with no toppings
chrome 41                ✓ simple_tests_02.html » <x-pizza> (2) » starts with no toppings
But what would happen if I start a new window at the same URL, but without that cli_browser_id query string parameter?

There is an easy way to find the answer to that. I open a new Chrome window, open its JavaScript console, and point it to my current WCT server (currently for me at http://localhost:56994/js/test/index.html). With that, I see:



Yay! Debugging.

I can do anything that I normally would in there: look at local variables, check the call stack, step through execution, even interact with objects in the console:



This works just as well with debugger statements in the actual Polymer code and with breakpoints set in the Sources tab. This seems exactly what I need (and hopefully what Jim needs as well).

I think that I will continue to run a file watcher WCT instance during the course of normal development. When things go really wrong, I will stop the WCT instance and bring up a --persistent instance of WCT and a completely separate Chrome window in which I can do the debugging thing. Once I resolve the problem, I can close the persistent WCT session and browser windows, then return to my regularly scheduled file watcher session.

I am rapidly running out of reasons to dislike WCT. But there is always hope for tomorrow.


Day #20

Sunday, April 5, 2015

Better Reporting and Errors in web-component-tester


The sad path with web-component-tester is a little worrisome. So far, web-component-tester (WCT) has handled pretty much everything I had to throw at it with aplomb. It seems every bit a well thought out and implemented test framework for Polymer. But I have mostly worked with successful tests—the happy path. On occasion, something unexpected went wrong and I had trouble figuring out where. That bothered me enough to note, but at the time, I focused on the task at hand.

Now, I look closer at failures—the sad path.

I start by breaking a test intentionally so that I can take a closer look at what WCT reports:
Running "wct-test:local" (wct-test) task
Starting Selenium server for local browsers
Selenium server running on port 33554
Web server running on port 36678 and serving from /home/chris/repos/polymer-book/play/web-component-tester
firefox 37               Beginning tests via http://localhost:36678/js/test/index.html?cli_browser_id=0
firefox 37               ✖ index.html » <x-pizza> » adding a whole topping » updates the pizza state accordingly

  expected '{"firstHalfToppings":[],"secondHalfToppings":[],"wholeToppings":["green peppers"]}' to equal '{"firstHalfToppings":[],"secondHalfToppings":[],"wholeToppings":["red peppers"]}'

firefox 37               Tests failed: 1 failed tests
Test run ended in failure: 1 failed tests

firefox 37 (4/0/1)                    
1 failed tests
I have 5 tests scattered across 4 test files. Which one broke? I know the answer in this case because I intentionally broke it, but had I been merrily coding a separate feature when greeted by this there is no indication of where the failure occurred. My only option would be to hunt for the test description. That would be incredibly tedious if I have multiple tests with the same description (e.g. in different contexts).

I note that using Chrome instead of Firefox will report the bad line number:
chrome 41                ✖ index.html » <x-pizza> » adding a whole topping » updates the pizza state accordingly

  expected '{"firstHalfToppings":[],"secondHalfToppings":[],"wholeToppings":["green peppers"]}' to equal '{"firstHalfToppings":[],"secondHalfToppings":[],"wholeToppings":["red peppers"]}'
    <unknown> at   Context.<anonymous> at /js/test/page_objects.js:79:0
That can be easy to miss—especially if I am running more than one browser at a time. So I hope to get richer information from WCT with all browser.

WCT's --verbose option is not the answer. That is a swamp of output that, if anything, makes it harder to find the error. Instead, I try the --expanded option by adding it to my project's wct.cong.js file:
module.exports = {
  expanded: true,
  plugins: {
    local: {
      browsers: ['firefox']
    }
  }
};
Not surprisingly, this results in expanded output:
Running "wct-test:local" (wct-test) task
Starting Selenium server for local browsers
Selenium server running on port 57440
Web server running on port 53285 and serving from /home/chris/repos/polymer-book/play/web-component-tester
firefox 37               Beginning tests via http://localhost:53285/js/test/index.html?cli_browser_id=0
firefox 37               ✓ index.html » <x-pizza> » can add toppings to the whole pizza
firefox 37               ✓ index.html » <x-pizza> » defaults » has no toppings anywhere
firefox 37               ✖ index.html » <x-pizza> » adding a whole topping » updates the pizza state accordingly

  expected '{"firstHalfToppings":[],"secondHalfToppings":[],"wholeToppings":["green peppers"]}' to equal '{"firstHalfToppings":[],"secondHalfToppings":[],"wholeToppings":["blue peppers"]}'

firefox 37               ✓ simple_tests_01.html » <x-pizza> (1) » starts with no toppings
firefox 37               ✓ simple_tests_02.html » <x-pizza> (2) » starts with no toppings
firefox 37               Tests failed: 1 failed tests
Test run ended in failure: 1 failed tests

firefox 37 (4/0/1)                    
1 failed tests
That is of some use, but is not entirely satisfactory. I had not realized earlier that index.html in the output referred to the HTML context of the output. That is the entry point for all of my tests, so I assumed that any failure would be reported from there. In fact, alternate HTML contexts, such as simple_tests_01.html are reported as distinct from the JS tests.

All of my tests are loaded from separate files in index.html:
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <script src="../bower_components/webcomponentsjs/webcomponents.min.js"></script>
  <script src="../../web-component-tester/browser.js"></script>
  <link rel="import" href="../elements/x-pizza.html">
</head>
<body>
  <x-pizza id="fixture"></x-pizza>
  <script>
    WCT.loadSuites([
      'simple_tests_01.html',
      'adding_toppings.js',
      'page_objects.js',
      'simple_tests_02.html',
    ]);
  </script>
</body>
</html>
The JS tests are all reported as coming from index.html while the HTML tests are reported as coming from their respective HTML files. With that understanding, I think I can live with this behavior.

Between knowing that I can find the actual line number failure in the output from Chrome and that all browsers will report the correct HTML context, I ought to be fine. For the most part, I imagine that I will just run Chrome tests for development (to keep the runs shorter) and run multiple browsers in continuous integration.

Furthermore, I think it very unlikely that I will ever use multiple JS test suites for a single HTML context like I have done here. I prefer my JS in a separate file, so it is likely that for each simple_tests_01.html and simple_tests_02.html, each will load a corresponding simple_tests_01.js and simple_tests_02.js. So if a test reports failure from a given HTML context, I will know where to start looking. I doubt that I wound ever use two JS contexts as I have done here. As I found last night, doing this makes it easy to confuse your test environment.

So one JS suite for each HTML context will likely resolve my objections. Another potential objection to WCT gone. One way or another, I am definitely sticking with the expanded option. The resulting output may not be as nice as Mocha's spec reporter, but it is close enough for my tastes:
firefox 37               ✓ index.html » <x-pizza> » can add toppings to the whole pizza
firefox 37               ✓ index.html » <x-pizza> » defaults » has no toppings anywhere
firefox 37               ✓ index.html » <x-pizza> » adding a whole topping » updates the pizza state accordingly
firefox 37               ✓ simple_tests_01.html » <x-pizza> (1) » starts with no toppings
firefox 37               ✓ simple_tests_02.html » <x-pizza> (2) » starts with no toppings
firefox 37               Tests passed
Test run ended with great success
It is looking more and more like I need to update a few chapters in Patterns in Polymer.


Day #19

Saturday, April 4, 2015

Page Objects and web-component-tester


I am a Page Objects fan. I like page objects for testing web pages, applications, and even Polymer. Tonight, I try to use them with the web-component-tester (WCT).

I typically eschew code reuse in my tests. That is not quite strong enough. More accurately, indirection in tests inspires feelings of anger, bordering on rage. I prefer my test code to be right there in the tests, not scattered around a bunch of files. When something is misbehaving in tests, I loathe searching test code to find the problem.

I make an exception in the case of page objects because they are built primarily around user interactions with applications—code reuse is simply a (tolerable) byproduct. Putting myself in the frame of mind of a human interacting with my applications is where I find value.

I start by adding a new suite to last night's multi-suite WCT for the tasty <x-pizza> Polymer element:
<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <script src="../bower_components/webcomponentsjs/webcomponents.min.js"></script>
  <script src="../../web-component-tester/browser.js"></script>
  <link rel="import" href="../elements/x-pizza.html">
</head>
<body>
  <x-pizza id="fixture"></x-pizza>
  <script>
    WCT.loadSuites([
      'simple_tests_01.html',
      'adding_toppings.js',
      'page_objects.js',
      'simple_tests_02.html',
    ]);
  </script>
</body>
</html>
I have every reason to expect that page objects will work with WCT. Happily, I am not disappointed. I mostly copy my Karma and Jasmine implementation of Page Objects for this element.

The constructor takes a single element argument:
function XPizzaComponent(el) {
  this.el = el;
}
The methods supported for this element facilitate adding toppings as a human might and reading the results:
XPizzaComponent.prototype = {
  currentPizzaStateDisplay: function() {
    if (this.el.$ === undefined) return '';
    return this.el.$.state.textContent;
  },

  addWholeTopping: function(v, cb) {
    // Select topping from drop-down and click a button here...
    return this.flush(cb);
  },

  flush: function(cb) {
    if (cb === undefined) cb = function(){};
    this.el.async(cb);
  }
};
The most important part of this is the flush() method, which accepts a callback parameter. It invokes Polymer's async() method to ensure that all bound variables are updated and that the UI is updated. Once everything has changed, the supplied callback is invoked. It is useful to invoke flush() directly from tests, but even more useful to invoke it from tests that update the UI—as the addWholeToppings() method does.

This is important for asynchronous testing of Polymer elements. I can supply Mocha's done callback to flush() so that subsequent tests will not run until the element has finished updating itself. So, to test that I can update whole toppings, I call the addWholeTopping() method of the page object and supply the done callback:
  describe('adding a whole topping', function(){
    beforeEach(function(done){
      xPizza.addWholeTopping('green peppers', done);
    });

    it('updates the pizza state accordingly', function(){
      var with_toppings = JSON.stringify({
        firstHalfToppings: [],
        secondHalfToppings: [],
        wholeToppings: ['green peppers']
      });

      assert.equal(
        xPizza.currentPizzaStateDisplay(),
        with_toppings
      );
    });
  });
The test itself will not run until the element has completely updated its properties, bound variables, and UI. This is perfect because the test is trying to assert that the element has been updated to include pepperoni.

This works exactly as desired:
Running "wct-test:local" (wct-test) task
Starting Selenium server for local browsers
Selenium server running on port 58383
Web server running on port 39429 and serving from /home/chris/repos/polymer-book/play/web-component-tester
chrome 41                Beginning tests via http://localhost:39429/js/test/index.html?cli_browser_id=0
chrome 41                Tests passed
firefox 37               Beginning tests via http://localhost:39429/js/test/index.html?cli_browser_id=1
firefox 37               Tests passed
Test run ended with great success

chrome 41 (5/0/0)                       firefox 37 (5/0/0)                    

Done, without errors.
I am growing fond of WCT. It supports the various kinds of testing that I might want. I still worry that there might be some deficiencies that might give me pause for using it today in Patterns in Polymer, so I will continue to explore tomorrow.


Day #18