Testing our design system components

Avoiding the unforeseen consequences of change

We have lots of components in our Pulsar design system, but for this post I’m going to focus on just one. The button.

A button, you click/tap it, something happens (usually).

Buttons are prolific in our software, they do lots of different things and they need to be consistent, recognisable and predictable. Our buttons can have a handful of classes applied which define the colour (primary, success, danger etc), visual variations (small, outlined, naked) and the state (disabled, active) as well as the normal set of interactive states that a button needs to support (hover, active, focus).

We test lots of things about our humble little button, like:

  • Is the markup generated by the button helper correct?
  • Does the CSS render the button variations correctly?
  • Do all of the colour combinations match what we expect?
  • Has anything else changed that we weren’t expecting?

Part 1: Testing the markup

We use the Twig templating language which gives us a lot of features to play with but the main one we take advantage of are macros, although we call them ‘helpers’ within Pulsar.

Helpers let us provide, for want of a better description, an API for the UI. Instead of using the raw HTML markup for a button in a view, we can call the button helper with a set of options, and the relevant markup will be created for us when the page is viewed in-browser.

This helper:

        'label': 'Foo',
        'class': 'btn--primary'

will spit out the following HTML:

<button class="btn btn--primary">Foo</button>  

I know you’re probably wondering “why is this any better than normal markup” and admittedly, a button is probably the least impressive example I can give (form elements have a good amount of wrapper markup), but we’ll keep it simple with buttons for now.

There’s a few documented options that a button may have. Supply 'type': 'link' and you’ll get <a href="#" class="btn btn--primary">Foo</a> instead of <button>, as well as different markup for input and submit types too. We need to test all of those to make sure the markup created by our helper is correct, valid and that any other supported options like disabled are added as correctly formatted attributes.

We have a suite of fixtures, which consist of a .twig file (the input) and a matching .html file containing the expected output. The Twig fixtures all run through PHPUnit and their output is matched against the ‘expected’ HTML file. The Pulsar repository has hundreds of fixtures being tested in this way, everything from buttons to more complex components like the primary navigation module.

Happy days! Helpers are rending as expected in Twig PHP

As an example, if I intentionally add a newClass class to the helper and run PHPUnit again it will fail any fixtures that weren’t expecting a button with newClass. If the change is expected, I can verify which components are depending on the button helper and may be affected by the change or, if the change to the helper wasn’t expected, I can fix that instead!


Taking the time to write unit tests for our helper markup helps massively when it comes to refactoring and bug fixing, we can quickly run the test suite to confirm that our changes within Twig have not affected the markup in ways we weren’t expecting.

But wait, there’s more!

Twig is a PHP templating language but there’s also the Twig.js javascript implementation. We want Pulsar to be usable in Node projects so we run the exact same set of PHPUnit fixtures through Twig.js to make sure the markup is still fine (when we first did this, the markup was wildly different, but thanks to a couple of PRs being accepted into the twig.js project, it’s fine now).

Woo! Look at it fly!

What next?

So we have this comprehensive fixture suite pulling double-duty, testing our generated markup is correct in both the PHP and JS implementations of Twig. In my next post I’ll show how we use the same fixtures again to test the visual output and let us know if anything changed, right down to the pixel.

comments powered by Disqus