Putting everything together


For the final example, of this tutorial, we are going to build a pattern library for a simplified version of the account settings page in Warhol's web app and then let the browser extension verify that everything looks right. This is going to require a whole bunch of component definitions and directives!

For simplicity's sake we skip the page header, the footer and also won't bother with color palettes and typography rules in this example. We are also going to ignore breakpoints and responsive design, as neither changes the allover workflow and would just make the individual components more complex. And remember: you can skip (ignore) Warhol's reports at any time if you think even more simplification is called for.

Defining cards and buttons #

Let's start by defining the card components that form the little boxes around every section on the settings page:

<div class="card">
  <div class="card__content">
    <h2 class="card__content__title">Card title</h2>
    <p>
      Content
    </p>
  </div>
</div>

<div class="card card--warning">
  <div class="card__content">
    <h2 class="card__content__title">Warning card title</h2>
    <p>
      This is important!
    </p>
  </div>
</div>

This example uses the BEM naming convention for CSS classes, which makes the distinction between components and elements (quite important for working with Warhol) obvious in an instant. Now let's add the styles...

.card {
  background: #FFF;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2),
              0 1px 1px 0 rgba(0, 0, 0, 0.14),
              0 2px 1px -1px rgba(0, 0, 0, 0.12);
  border-radius: 2px;
  margin: 1em 0;
}

.card--warning {
  background: #ffb;
}

.card__content {
  padding: 16px 32px;
}

.card__content__title {
  font-weight: normal;
  font-size: 1.5em;
  margin: 16px 0;
}

and we instantly run into a conceptual hurdle. The card components are meant to be extremely versatile. In production, they will not only appear in all sizes and shapes but also in different contexts. This means they will sometimes have to have slightly different margins and, if they appear as children of flex- or grid-containers, will have to have to support very different values for properties like flex, grid-row and so on. Probably cards can even be floated or subjected to absolute positioning. For this example, cards appear in the main content as well as in the sidebar.

To allow flexible positioning we can use the directive --warhol-layout-mode and relax the rules around the component's dimensions and positioning:

.card {
  background: #fff;
  box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.2),
              0 1px 1px 0 rgba(0, 0, 0, 0.14),
              0 2px 1px -1px rgba(0, 0, 0, 0.12);
  border-radius: 2px;
  margin: 1em 0;
  /* prevent all tests on dimensions, positioning and the like */
  --warhol-layout-mode: any-offset any-context any-dimensions;
}

Note that in HTML, the card's warning variant is composed from the modifier class .card--warning and the base .card class, which means that the warning card can be positioned and sized freely as well.

Buttons are stand-alone components, just like cards are. As such, they require their own component definition. Components can be used as example content in other components, but for their own canonical definition they need their own, stand-alone, non-nested example:

<button class="button">Button</button>
.button {
  color: #13191e;
  border: 2px solid #13191e;
  display: inline-block;
  outline: 0;
  padding: 0.5rem 1rem;
  font-size: 0.875rem;
  background: #fff;
  font-weight: bold;
  line-height: 1.3;
  text-transform: uppercase;
}

Buttons can of course have loads of interactive states like :hover and :focus, but as those are not really supported in Warhol right now, this is just another problem to ignore right now. Instead, let's consider this our pattern library, version 1.0:

With cards and buttons properly defined in our pattern library, we can add them to our Warhol component configuration! Our target page has not been built with BEM in mind, so we have to define different selectors for source and target:

[
  {
    "source": ".button",
    "target": "button",
    "name": "Button"
  },
  {
    "source": ".card",
    "target": ".box",
    "name": "Card"
  },
  {
    "source": ".card.card--warning",
    "target": ".box.warn",
    "name": "Card, Warning variant"
  }
]

Save the configuration (don't forget the pattern lib URL!), create snapshots, load the example target, fire up the extension, load, activate, and then let's do a comparison of what Warhol thinks is right with what it thinks is wrong.

Interpreting results from the first test run #

Well, it could be worse!

Warhol confirmed that all our cards, card headlines, card paragraphs and buttons are in working order, which is not bad at all for a first attempt at retrofitting a pattern library to a brownfield project.

The complaints about the <li> elements inside the <nav> element and the <p> elements inside the <form> boil down to there simply not being any <nav> and <form> elements in our pattern library! Warhol does not just give up when it encounters unexpected elements, but tries hard to match them to something it knows from the pattern library. After all, any unknown element might just turn out to be a creative (or buggy) interpretation of something from the pattern library! And this is just what happened for the <nav> element:

  • Warhol identified the <nav> element as an attempt at implementing the <div class="card__content"> element in the Card component, which is understandable: a <nav> works mostly like a <div> and the styles are not wildly different. The padding is wrong when compared to the pattern library's <div class="card__content">, but at least there is some!
  • Based on that, Warhol had the choice between treating the <ul> element inside the <nav> as either <h2 class="card__content__title"> or <p>, the only two elements the pattern library allows inside <div class="card__content">. The difference between the actual and expected element are much more pronounced in that case, but Warhol was still able to convince itself that the <ul> is probably meant to be a <h2 class="card__content__title">. Because Warhol was not quite sure about this, it issued a debug notice about an unsure match, but still soldiered on.
  • Only when Warhol encountered <li> elements in what Warhol thought was a <h2>, it gave up and reported an unexpected child element.

Warhol's flexibility is a blessing and a curse. You are not required to use the exact same classes and tags across production and pattern library, but in turn you do have to deal with tests sometimes taking a wrong turn. This is not that big of a deal if we read Warhol's reports carefully and keep an eye on the source element selector that accompany reports on child elements:

The highlighted selectors indicate which pattern library elements Warhol thinks it is dealing with, which is obviously wrong in this case. But unless we define a navigation element, there is also no way to be right, so let's do that next!

Adding a navigation element #

The navigation element is probably not just some random card content, but rather warrants its own component definition:

<nav class="navigation">
  <ul class="navigation__list">
    <li class="navigation__list__item">
      <a class="navigation__list__item__link" href="#">Foo</a>
    </li>
    <li class="navigation__list__item">
      <a class="navigation__list__item__link" href="#">Bar</a>
    </li>
    <li class="navigation__list__item">
      <a class="navigation__list__item__link" href="#">Baz</a>
    </li>
    <li class="navigation__list__item">
      <a class="navigation__list__item__link" href="#">Bah</a>
    </li>
  </ul>
</nav>

<style>
  .navigation {
    padding: 0.25rem 1rem;
  }

  .navigation__list {
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .navigation__list__item {
    margin: 0;
    padding: 0;
    list-style: none;
    border-bottom: 1px solid #d8e1e7;
  }

  .navigation__list__item__link {
    display: block;
    padding: 1rem 0;
  }
</style>

BEM does indeed require a lot of classes! But at least it makes sure that we always have unique component selectors to extend our component definition with:

[
  {
    "source": ".button",
    "target": "button",
    "name": "Button"
  },
  {
    "source": ".card",
    "target": ".box",
    "name": "Card"
  },
  {
    "source": ".card.card--warning",
    "target": ".box.warn",
    "name": "Card, Warning variant"
  },
  {
    "source": ".navigation",
    "target": ".box > nav",
    "name": "Navigation"
  }
]

Should be good, right? Let's save the configuration, create snapshots, reload the example target, fire up the extension, load, activate, and 

there is something wrong with the last element's border. In this case, where we retrofit a pattern library to an existing page, we probably forgot to define this as an exception in the pattern library. And while we fix this, why not add a must-match directive to the last item?

<style>
  /* ... */
  .navigation__list__item--is-last {
    border-bottom: none;
    --warhol-must-match: :last-child;
  }
</style>

<nav class="navigation">
  <ul class="navigation__list">
    ...
    <li class="navigation__list__item  navigation__list__item--is-last">
      <a class="navigation__list__item__link" href="#">Bah</a>
    </li>
  </ul>
</nav>

The exception in the updated pattern library is implemented with a class, but it would also work with :last-child. In both cases adding the must-match directive is worthwhile, because the production web page also has the choice between :last-child and a class – and the latter can be misplaced, leading to visible bugs. Adding the extra list item with modified styles just tells Warhol that list items are allowed to look like this and you need the directive to nail that list items with these styles must be the last item in a list.

The interactive states on links and inputs (like :hover and :invalid) are currently invisible to Warhol. Every test works as if no interactive pseudo class exists, which also extends to states like :not(:visited). Support for interactive states is on our roadmap, but the implementation is not quite there yet.

Defining forms #

Forms are now the only thing left to fix:

Warhol is confused about <p> elements inside the <form> element, which for lack of alternatives is identified as .card > div.card__content > p. Fixing this is rather straightforward: all we need is some forms in our pattern library. But why does Warhol think a <form> element's styles are equal to those of a <p> element, when the latter clearly has a margin the former is missing?

Warhol can currently only reason about author style sheets. The default style sheets that are built into every browser are not visible to regular JavaScript running in a browser window (which is what Warhol is), so Warhol can only compare the styles that were manually defined in the pattern library with what has been manually defined in production. But the effects of everything that has not been defined (such as the default margins on <p> elements) are out of Warhol's reach, at least for the time being.

Again: when Warhol's error messages appear to make no sense, one of the probable causes is an exceedingly large difference between production and the pattern library, in this case because the pattern library does not define forms at all. But this is easy to fix:

<form class="form">
  <p class="form__field">
    <label for="name" class="form__field__label">Label</label>
    <input class="form__field__input" id="name" type="text" />
  </p>
  <p class="form__field">
    Some additional text.
  </p>
  <p class="form__field">
    <input
      class="form__field__input form__field__input--is-button"
      type="submit"
      value="Submit"
    />
  </p>
</form>

<style>
  .form__field__label {
    display: block;
    margin-bottom: 0.25rem;
  }
  .form__field__input {
    color: #13191e;
    width: 100%;
    border: 2px solid #13191e;
    padding: 0.5rem;
    font-size: 0.875rem;
    font-family: inherit;
    line-height: 1.5;
  }
  .form__field__input--is-button {
    width: auto;
    display: inline-block;
    outline: 0;
    padding: 0.5rem 1rem;
    background: white;
    font-weight: bold;
    line-height: 1.3;
    text-transform: uppercase;
  }
</style>

Now Warhol's flexibility works in our favor: even though the production pages uses <input type="text"> and <input type="email">, we only have to provide one example input element and can leave it to Warhol to prove that the design is right. But is it? Let's check by adding the form to our component definitions 

[
  {
    "source": ".button",
    "target": "button",
    "name": "Button"
  },
  {
    "source": ".card",
    "target": ".box",
    "name": "Card"
  },
  {
    "source": ".card.card--warning",
    "target": ".box.warn",
    "name": "Card, Warning variant"
  },
  {
    "source": ".navigation",
    "target": ".box > nav",
    "name": "Navigation"
  },
  {
    "source": "form",
    "name": "Form"
  }
]

and then re-generating the snapshots and running the tests once again!

Almost perfect! Warhol keeps complaining about the <a> element inside the form, and rightly so: the element is after all not defined in the form component in the pattern library. As there is currently no way to jump out of strict component checking and back into only verifying the theme (which might be useful for generic content containers) we can only either extend the pattern library or just skip the error.

And that's it! #

Without too much work we managed to test and verify every component in our somewhat complex example page!

Depending on your perspective we either made sure that our production page looks like it should or we managed to build a pattern library that covers all use cases and can serve as a basis for the rest of the project. There is still more that we could do to improve the pattern library and Warhol's error checking. We can add a theme, define breakpoints and there is always room for more components. Warhol itself will also be able to do much more in the future. Stay tuned for automated tests, support for more browsers and tests for interactive styles!

Join our Slack Community

Any question not answered yet? You want to get the latest information on Warhol and discuss new features? Join our Slack Community.