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