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
Awesome!!!
ReplyDelete