Testing Tailwind CSS plugins with Jest

Note: The content of this post is based on tests seen in Adam Wathan’s "Working on Tailwind 1.0" video, the Jest documentation website, and existing tests for other Tailwind plugins that I’ve used such as Tailwind CSS Interaction Variants.

Preface

In Tailwind 0.x, there was a list-reset utility that reset the list style and padding on a HTML list, though it was removed prior to 1.0 and moved into Tailwind’s base styles and applied by default.

However, on a few projects I use Tailwind in addition to either existing custom styling or another CSS framework, and don’t use @tailwind base (formerly @tailwind preflight) so don’t get the base styles.

Whilst I could re-create this by replacing it with two other classes (list-none and p-0), I decided to write my own Tailwind CSS plugin to re-add the list-reset class. This way I could keep backwards compatibility in my projects and only need to add one class in other future instances.

In this post, I’ll use this as an example to show how to write tests for Tailwind CSS plugins with a JavaScript testing framework called Jest.

More information about plugins for Tailwind CSS themselves can be found on the Tailwind website.

Add dependencies

To start, we need to include jest as a dependency of the plugin, as well as jest-matcher-css to perform assertions against the CSS that the plugin generates.

We also need to add tailwindcss and postcss so that we can use them within the tests.

yarn add -D jest jest-matcher-css postcss tailwindcss@next

This could be done with yarn add or npm install.

Writing the first test

In this plugin, the tests are going to be added into a new file called test.js. This file is automatically loaded by Jest based on it’s testRegex setting.

This is the format for writing test methods:

test('a description of the test', () => {
  // Perform tasks and write assertions
})

The first test is to ensure that the correct CSS is generated from the plugin using no options.

We do this by generating the plugin’s CSS, and asserting that it matches the expected CSS within the test.

test('it generates the list reset class', () => {
  generatePluginCss().then(css => {
    expect(css).toMatchCss(`
      .list-reset {
        list-style: none;
        padding: 0
      }
    `)
  })
})

However, there are some additonal steps needed to get this working.

Generating the plugin’s CSS

Firstly, we need to import the plugin’s main index.js file, as well as PostCSS and Tailwind. This is done at the beginning of the test.js file.

const plugin = require('./index.js')
const postcss = require('postcss')
const tailwindcss = require('tailwindcss')

Now we need a way to generate the CSS so assertions can be written against it.

In this case, I’ve created a function called generatePluginCss that accepts some optional options, processes PostCSS and Tailwind, and returns the CSS.

const generatePluginCss = (options = {}) => {
  return postcss(
    tailwindcss()
  )
  .process('@tailwind utilities;', {
    from: undefined
  })
  .then(result => result.css)
}

Alternatively, to test the output of a component, @tailwind utilities; would be replaced with @tailwind components.

.process('@tailwind components;', {
  from: undefined
})

Whilst from: undefined isn’t required, if it’s not included you will get this message:

Without from option PostCSS could generate wrong source map and will not find Browserslist config. Set it to CSS file path or to undefined to prevent this warning.

Configuring Tailwind

In order for the plugin to generate CSS, it needs to be enabled within the test, and Tailwind’s core plugins need to be disabled so that we can assert against just the output from the plugin.

As of Tailwind 1.0.0-beta5, this can be done as follows:

tailwindcss({
  corePlugins: false,
  plugins: [plugin(options)]
})

In prior versions, each plugin in corePlugins needed to be set to false separately.

I did that using a disableCorePlugins() function and lodash, using the keys from variants:

const _ = require('lodash')

// ...

const disableCorePlugins = () => {
  return _.mapValues(defaultConfig.variants, () => false)
}

Enabling CSS matching

In order to compare the generated and expected CSS, the CSS matcher for Jest needs to be required and added using expect.extend.

const cssMatcher = require('jest-matcher-css')

...

expect.extend({
  toMatchCss: cssMatcher
})

Without it, you’ll get an error message like "TypeError: expect(...).toMatchCss is not a function" when running the tests.

The next test: testing variants

To test variants we can specify the required variant names within as options to generatePluginCss.

For example, this is how to enable hover and focus variants.

generatePluginCss({ variants: ['hover', 'focus'] })

Now we can add another test that generates the variant classes too, to ensure that also works as expected.

test('it generates the list reset class with variants', () => {
  generatePluginCss({ variants: ['hover', 'focus'] }).then(css => {
    expect(css).toMatchCss(`
      .list-reset {
        list-style: none;
        padding: 0
      }

      .hover\\:list-reset:hover {
        list-style: none;
        padding: 0
      }

      .focus\\:list-reset:focus {
        list-style: none;
        padding: 0
      }
    `)
  })
})

Running tests locally

Now that we have tests, we need to be able to run them.

With Jest included as a dependency, we can update the test script within package.json to execute it rather than returning a stub message.

- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "jest"

This means that as well as running the jest command directly to run the tests, we can also run npm test or yarn test.

After running the tests, Jest will display a summary of the results:

A screenshot of the Jest output after running the tests, showing 1 passed test suite and 2 passed tests, as well as the test run time.

Running tests automatically with Travis CI

As well as running the tests locally, they can also be run automatically via services like Travis CI when a new pull request is submitted or each time new commits are pushed.

This is done by adding a .travis-ci.yml file to the repository, like this one which is based on the JavaScript and Node.js example:

language: node_js

node_js:
  - '8'

cache:
  directories:
    - node_modules

before_install:
  - npm update

install:
  - npm install

script:
  - npm test

With this in place, the project can now be enabled on the Travis website, and the tests will be run automatically.

For this plugin, you can see the results at https://travis-ci.org/opdavies/tailwindcss-list-reset.

Questions? Comments? I’m @opdavies on Twitter.

Tags

About the Author

Picture of Oliver

Oliver Davies is a Full Stack Web Developer and System Administrator based in the UK. He is a Senior Software Engineer and Technical Team Lead at Inviqa and a part-time freelancer specialising in Drupal, Symfony and Laravel development and Linux systems administration.