Thursday, March 13, 2014

Watching Polymer Changes in Jasmine


Tonight I would like to add a regression test for my watch-changes-from-the-outside solution in Patterns in Polymer. I continue to use Karma as the test runner and Jasmine as the testing framework for this and all of the chapters in the book.

Unlike most of the tests that I have been writing, I need this one in place for a more immediate reason than to catch future changes to the Polymer platform that might break the approach in the chapter. In this case, I need to refactor the approach slightly based on some recent feedback. So I need to get it right.

The Polymer element that I will observe from the outside is the simple <hello-you>. In the book, I watch for changes to the your_name and color attributes:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="hello-you" attributes="your_name color">
  <template><!-- ... --></template>
  <script src="hello_you.js"></script>
</polymer-element>
The problem with the example code in the book is that it simply logs the changes to the console:
function watchPolymer(el) {
  var observer = new MutationObserver(function(mutations) {
    mutations.forEach(function(mutation) {
      console.log(
        mutation.attributeName +
        ' is now: ' +
        mutation.target[mutation.attributeName]
      );
    });
  });
  observer.observe(el, {attributes: true});
}
So I need to include the watcher.js in the setup code:
// ...
var watcher_script = document.createElement("script");
watcher_script.src = "/base/scripts/watcher.js";
document.getElementsByTagName("head")[0].appendChild(watcher_script);
// ...
With that in place, I need a way to watch for calls to console.log() to ensure that the desired mutation is observed. Since that is the purpose of Jasmine spies, I replace the built-in console.log with a spy in my setup just before invoking the watchPolymer() function that I am trying to test:
describe('watching', function(){
    var el, input;
    beforeEach(function(){
      el = document.querySelector('hello-you');
      input = el.shadowRoot.querySelector('input');

      console.log = jasmine.createSpy("Console Log");
      watchPolymer(el);

      var e = document.createEvent('TextEvent');
      e.initTextEvent('textInput', true, true, null, 'Bob');
      input.dispatchEvent(e);

      waits(0);
    });
    // ....
  });
The expectation for that spy is pretty straight-forward, thanks in part to last night's work. I already know that the rest of that setup will generate a change to the your_name attribute in the form of a new value of “Bob.” So the expectation is:
  describe('watching', function(){
    var el, input;
    beforeEach(function(){ /* ... */ });

    it('sees changes to your_name', function() {
      expect(console.log).
        toHaveBeenCalledWith('your_name is now: Bob');
    });
  });
Of course, the Jasmine reporter does not actually report that this test passes. I'll never admit how long it takes me to figure out why this is the case (5 damn minutes). The fixed, passing test is:
  describe('watching', function(){
    var el, input, original_log;
    beforeEach(function(){
      el = document.querySelector('hello-you');
      input = el.shadowRoot.querySelector('input');
      original_log = console.log;
      console.log = jasmine.createSpy("Console Log");
      watchPolymer(el);

      var e = document.createEvent('TextEvent');
      e.initTextEvent('textInput', true, true, null, 'Bob');
      input.dispatchEvent(e);

      waits(0);
    });

    afterEach(function(){
      console.log = original_log;
    });

    it('sees changes to your_name', function() {
      expect(console.log).
        toHaveBeenCalledWith('your_name is now: Bob');
    });
  });
The problem, of course, is that I overwrote the console.log() that Jasmine uses to report its results. So, when it tried to report that the test passed my Jasmine spy was notified, but nothing else happened. To address the problem, I keep a reference to the original console.log() so that it can be restored in afterEach().

My silly mistake aside, it was rather easy to test that Polymer watcher with Jasmine. That will come in handy tomorrow when I refactor some of the setup in my solution. Yay!


Day #2

No comments:

Post a Comment