Friday, March 14, 2014

Refactoring Polymer Code Under Test


Up tonight, I finish off changes to the changes-from-the-outside chapter of Patterns in Polymer. During my recent hiatus, I got loads of great feedback on this and other chapters. I still have two more bits of advice to put to use in this chapter: better Polymer detection and better node finding.

First, it was pointed out that relying on a “double underscore” property is likely not a recipe for long term success. While waiting on a custom element to be upgraded by Polymer, I had been polling for the __upgraded__ property:
function onPolymerReady(el, fn) {
  if (el.__upgraded__) fn();
  else setTimeout(function(){onPolymerReady(el, fn);}, 10);
}
Although the semantics of the property name fit my need here, it is bad form to use an internal property as indicated by the double underscores. So I instead wait for a significant Polymer property: $.

Happily, the code change is minor:
function onPolymerReady(el, fn) {
  if (el.$) fn();
  else setTimeout(function(){onPolymerReady(el, fn);}, 10);
}
And, even more happily, my tests from last night continue to work so I am good to go with that change.

Next up is a change that reminds me that I really need to get back to Dart: Math.random() testing in JavaScript. The simple <hello-you> Polymer element that is being tested in this chapter can randomly change the color of the <h2> element:



Per some other feedback on the chapter, I need to change how the <h2> element is found. Before I do that, I need a test to verify the existing functionality. But how to test a random number? If this were Dart, I could seed the random number. But, since this is JavaScript, I need to stub Math.random. Thankfully, I stuck with the Karma test runner's default testing framework, Jasmine. That means that I can spy on Math.random and return a value that ensures that I always see the last value in “randomized” list:
  describe('clicking', function(){
    beforeEach(function(){
      spyOn(Math, "random").andReturn(0.99);
      // ...
    });
    // ...
  });
In my case, the color green is the last in the list:
  describe('clicking', function(){
    beforeEach(function(){
      spyOn(Math, "random").andReturn(0.99);
      // ...
    });
    it('randomly changes the title\'s color', function(){
      var button = el.shadowRoot.querySelector('button');

      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');
      expect(h2.style.color).toEqual('green');
    });
  });
I verify that this is actually doing what I think it does by changing the andReturn value from 0.99 to zero. The test then fails because the color is red instead of green. In other words, I have a decent test.

With that, I can simplify the code that randomizes the color from this:
  feelingLucky: function() {
    var num = Math.floor(colors.length*Math.random());
    this.color = colors[num];
    this.
      shadowRoot.
      querySelector('h2').
      style.color = this.color;
  }
Instead, I can give the <h2> an ID and use Polymer's automatic node finding:
  feelingLucky: function() {
    var num = Math.floor(colors.length*Math.random());
    this.color = colors[num];
    this.$.hello.style.color = this.color;
  }
And, since my test continues to pass, I am safe in the knowledge that I have not introduced any bugs into the narrative of the chapter.

One chapter down, 14 to go. Actually, it is not as bad as all that. Now that I am getting in the regression testing groove, I expect the remainder of the chapters to go a little smoother. Of course, some of the chapters have more feedback than others, so I still need to hop to it. Which I do right now...


Day #3

No comments:

Post a Comment