Component content


A simple button without any non-text content is everybody's favorite component example, but few real components are that trivial. Most contain at least a few child elements and in this chapter of the tutorial we will figure out how Warhol deals with that.

Adding child elements #

Even ostensibly “simple” buttons tend to have more to them and may contain child nodes with individual styles and may look more like this:

<!-- Definition for a button component with an icon inside -->
<button class="basic-button icon-button">
  <span class="icon">✓</span>
  <span class="text">Accept all cookies</span>
</button>

<style>
  .basic-button {
    color: #eee;
    background: navy;
    font-weight: bold;
    padding: 0px 1em;
  }
  .icon-button .icon {
    font-weight: normal;
  }
  .icon-button .text {
    font-style: italic;
  }
</style>

Once we add the above icon button to the pattern library 

we don't really have to do anything except to add it to the list of components like any other component:

[
  {
    "source": ".basic-button",
    "target": ".btn, .button",
    "name": "Basic button"
  },
  {
    "source": ".basic-button.large",
    "target": ".btn-large, .button-large",
    "name": "Larger button"
  },
  {
    "source": ".basic-button.icon-button",
    "target": ".btn-icon, .button-icon",
    "name": "Button with icon"
  }
]

You don't have to configure anything special to get tests for component content, except to make sure that it's there and that it is visible. Warhol ignores invisible elements (those with display: none) as well as line breaks.

Running tests... with surprising results #

Lets try and find out what Warhol thinks about the following implementation of a button with an icon:

<!-- an icon button implementation with some minor bugs -->
<button class="button button-icon">
  <span class="ico">★</span>
  <span class="text">Button in production</span>
</button>

<style>
  .button {
    color: white; /* should be #EEE */
    background: navy;
    font-weight: bold;
    padding: 0px 1em;
  }
  .button-icon .ico {
    font-weight: 200; /* should be normal */
  }
  /* styles for text are missing */
</style>

Update the pattern library configuration, re-generate snapshots, re-download data into the extension, run the tests and, well...

... it's a mess. The overlay has been deactivated in the above screenshot because there is much wrong in one place that layers and labels do not really help with understanding what is going on. But at least the mess at hand is a properly diagnosed mess: Warhol found all the errors we would expect it to find and there are no false positives. Once we fix all the obvious problems, the reports go away:

But what may be more surprising is that Warhol also does not issue any complaints if there's two icons instead of one:

Or if the icon and the text trade places:

This is not unexpected behavior, but rather how Warhol is supposed to work - at least for now. Warhol, which only analyzes DOM and CSS, can't deduce if having two icons is not allowed just by looking at one single example element in a pattern library. Maybe having two icons on a button is not all that common, but a .genericTable component should probably be able to contain any number of rows. And the difference between a rather flexible table element and a very narrowly defined icon button is just not obvious at all. Also the desired level of flexibility is often not constant inside a component's descendants! A modal dialog should probably only have one header and one main content element (in exactly that order), but valid content inside the main content element may be any combination of headlines, paragraphs and buttons. But all that Warhol can know from DOM and CSS is which elements are allowed at all, and how they are supposed to look.

In the future there will be features that allow you to add metadata to components to tighten or loosen Warhol's testing of component children. The default mode will always be the current, extremely loose mode, but a mode where e.g. the content has to be exactly like in the pattern library is already in the works. This feature will be a new directive and even today we can use an existing directive to make sure that our icon button contains only one icon and one text element, in exactly this order.

Adding directives #

Directives are how you can augment component definitions with additional metadata for Warhol. They are special custom CSS properties that start with --warhol- and they influence Warhol's understanding of a component and its contents. Let's use the must-match/must-not-match directive to add requirements to the icon button's children!

Directives are just CSS properties, so it's up to you if you want to have your Warhol-specific directives right next to your regular CSS or you'd rather have them put away in an extra style sheet. Let's keep with one style element for now and add some directives:

<!-- The icon button as defined in the pattern library -->
<button class="basic-button icon-button">
  <span class="icon">✓</span>
  <span class="text">Accept all cookies</span>
</button>

<style>
  .basic-button {
    color: #EEE;
    background: navy;
    font-weight: bold;
    padding: 0px 1em;
  }
  .icon-button .icon {
    font-weight: normal;
    /* Directives: */
    --warhol-must-match: :first-child;
    --warhol-must-not-match: :last-child;
  }
  .icon-button .text {
    font-style: italic;
    /* Directives: */
    --warhol-must-match: :last-child;
    --warhol-must-not-match: :first-child;
  }
</style>

The directive --warhol-must-match requires the elements affected by the directive to match one or more CSS selectors, while --warhol-must-not-match forbids the element from matching certain selectors. It bears repeating that this information is only required in the pattern library! Warhol stores the required selectors in snapshots, pretty much like it stores all regular styles that it needs to test in production, so there's no need to have directives in your CSS outside the pattern library. Our directives require the icon to be the first and the text to be the last child element inside its parent, while also forbidding both to appear at the other's position... which means that we've essentially removed all flexibility from the icon button's content!

Time to update the pattern library! Adding the directives changes nothing about how the elements look, its just extra data for Warhol:

We can see the extra data in effect once we re-generate the snapshots and let Warhol test the production page once again:

That does not exactly look better, but at least now, Warhol agrees! There is a clear error for the duplicated icon in the second button as well as for the text in the third button, which must never be the first child element according to its must-not-match directive. The third error message is less obvious. “Multiple equally matching child snapshots” is Warhol's way of saying “something's fishy, but I can't say what exactly.” This happens when the element in question is so far removed from anything allowed in the pattern library that Warhol just has to give up. The span.ico element in this example...

  1. ... does not use a selector that appears in that pattern library - it's .ico in production and .icon in the pattern library. This is not a problem, it just makes Warhol's job a little more interesting.
  2. ... does not conform to the rules of any of the component children in the pattern library, so there's no way to just be content with the element conforming to something.
  3. ... is as bad at being a proper icon as it is bad at being a text element (the other valid kind of child element in an icon button). Treating it as one or the other does not change the number of errors the element causes.

At this point, Warhol can't know if this element is meant to be an icon or a text element. All that Warhol does know it that something is wrong and that it's up to us to figure out what exactly. This is almost always straightforward, given that we have a pattern library and, in this case, error messages that tell us how to fix the element's siblings. Once we fix the errors on the icon's previous sibling (the text element), the icon's not-so-helpful error message disappears as well.

Summary #

  1. Adding component children is super-easy, no additional configuration is required. Just update your pattern library and your production web page.
  2. Currently Warhol is not very strict about the order component children appear in. This will change in the future and can be worked around with must-match/must-not-match directives.
  3. The error message “Multiple equally matching child snapshots” means that Warhol can't pinpoint the precise problems with an element, so this becomes our job.

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.