Wednesday, March 19, 2014

Pretty Polymer Test Helpers


I actually thought testing my Polymer element last night would be pretty darn easy. As usual, I find the most trouble when I think a thing is going to be easy.

The Polymer element in question is a relatively simple pizza building element that I cleverly named <x-pizza>. The element itself is comprised of a <pre> element that holds the current state of the pizza being built and three child Polymer elements that are responsible for adding toppings to the pizza:



I ran into two problems with testing. First, it was hard to get the test to wait for the bound variable inside the <pre> to be updated. The bound variable was not updated on the next event loop as I would have preferred, but some time afterward. My solution was to wait for the <pre> to change, but I would have preferred a solution that did not wait on the same value being checked in the test. My second problem was that testing changes to <select> elements is a pain. I start with that pain tonight.

I wound up selecting the “green peppers” option from the <select> list with the following:
      select.selectedIndex = 3;
      var event = document.createEvent('Event');
      event.initEvent('change', true, true);
      select.dispatchEvent(event);
That's ugly-ish. Normally I would avoid jQuery, but I think it can help clean this up a little. So I add it to the list of Bower dev dependencies:
{
  "name": "mdv_example",
  // ...
  "dependencies": {
    "polymer": "Polymer/polymer"
  },
  "devDependencies": {
    "jquery": ">0"
  }
}
After a bower update, I modify my Karma configuration to pull in jQuery:
module.exports = function(config) {
  config.set({
    // ...
    files: [
      'test/PolymerSetup.js',
      'bower_components/jquery/dist/jquery.js',
      {pattern: 'elements/**', included: false, served: true},
      {pattern: 'bower_components/**', included: false, served: true},
      'test/**/*Spec.js'
    ],
    // ...
  });
};
Finally, I change the custom selectedIndex and manual event creation with:
      jQuery(select).val('green peppers').change();
That is a far more readable way of saying “select green peppers and send a change event.” So naturally, it does not work at all.

Well, it works a little. It does select “green peppers” from the <select> list, but it turns out that jQuery does not always generate native events. Yay.

So I drop jQuery as a development dependency and remove it from the list of Karma files. That is fine by me as I did not really want it in the first place. Also, I like my grapes on the sour side. Anyhow...

I replace my jQuery attempt with a custom written helper:
      helpers.selectOption(select, 'green peppers');
Which I define as:
var helpers = {
  selectOption: function(el, v) {
    var index = -1;
    for (var i=0; i<el.length; i++) {
      if (el.options[i].value == v) index = i;
    }
    el.selectedIndex = index;
    var event = document.createEvent('Event');
    event.initEvent('change', true, true);
    el.dispatchEvent(event);
  }
};
I can live with that.

The other problem that I faced from last night can be rephrased as wondering if I am testing the framework. I am striving for something close to an end-to-end acceptance test here, simulating user actions and checking the resulting changes to the DOM. The thing is, I am setting my expectation on a bound variable in the <x-pizza> template:
<link rel="import" href="../bower_components/polymer/polymer.html">
<polymer-element name="x-pizza">
  <template>
    <h2>Build Your Pizza</h2>
    <pre id="state">{{pizzaState}}</pre>
    <!-- ... -->
  </template>
  <script src="x_pizza.js"></script>
</polymer-element>
So by checking for the contents of the pre#state element, I am testing that Polymer is performing data binding properly. That is, I am testing the framework while running an acceptance test. So I could forgo setting expectations on the resulting DOM and instead set the expectation on the pizzaState property directly.

At least that is how I try to rationalize loosening my acceptance test. Fortunately, it turns out that I do not need to. Waiting for the variable bound <pre> to update is unnecessary in this case. I suspect that frustration made me sloppy last night, making me think I had to watch for a change. Instead, I merely need to wait a single browser event loop before checking my expectation. Thus the entire test of my <x-pizza> update can be written as:
    it('updates the pizza state accordingly', function(){
      var toppings = el.$.wholeToppings,
          select = toppings.$.ingredients,
          button = toppings.$.add;

      helpers.selectOption(select, 'green peppers');
      button.click();

      // Wait a single event loop to allow Polymer to update the element…
      waits(0);
      runs(function(){
        var with_toppings = JSON.stringify({
          firstHalfToppings: [],
          secondHalfToppings: [],
          wholeToppings: ['green peppers']
        });
        expect(el.$.state.textContent).toEqual(with_toppings);
      });
    });
Navigating the shadow DOM of the Polymer element and children at the outset of the test is a little awkward, but that is Polymer life. The rest of the test is quite nice, thanks to a simple, custom-written helper and a little Jasmine asynchronous testing love.

With that, I am much happier than I was at this time last night.


Day #8

1 comment: