2019-04-29 07:52:29 +00:00
---
title: Testing Tailwind CSS plugins with Jest
2020-03-08 14:32:13 +00:00
date: 2019-04-29
2019-04-29 07:52:29 +00:00
excerpt: How to write tests for Tailwind CSS plugins using Jest.
tags:
2020-03-08 17:52:59 +00:00
- javascript
- jest
- tailwind-css
- testing
2019-04-29 07:52:29 +00:00
promoted: true
---
2020-03-08 17:52:59 +00:00
2019-04-29 07:52:29 +00:00
< div class = "note" markdown = "1" >
**Note:** The content of this post is based on tests seen in Adam Wathan’ s ["Working on Tailwind 1.0" video][working-on-tailwind-video], the Jest documentation website, and existing tests for other Tailwind plugins that I’ ve used such as [Tailwind CSS Interaction Variants][tailwindcss-interaction-variants].
< / div >
## Preface
2020-03-08 17:52:59 +00:00
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.
2019-04-29 07:52:29 +00:00
2020-03-08 17:52:59 +00:00
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.
2019-04-29 07:52:29 +00:00
2020-03-08 17:52:59 +00:00
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][repo]
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.
2019-04-29 07:52:29 +00:00
2020-03-08 17:52:59 +00:00
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][jest].
2019-04-29 07:52:29 +00:00
2020-03-08 17:52:59 +00:00
More information about plugins for Tailwind CSS themselves can be found on the
[Tailwind website][tailwind-docs-plugins].
2019-04-29 07:52:29 +00:00
## Add dependencies
2020-03-08 17:52:59 +00:00
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.
2019-04-29 07:52:29 +00:00
2020-03-08 17:52:59 +00:00
We also need to add `tailwindcss` and `postcss` so that we can use them within
the tests.
2019-04-29 07:52:29 +00:00
```plain
yarn add -D jest jest-matcher-css postcss tailwindcss@next
```
This could be done with `yarn add` or `npm install` .
## Writing the first test
2020-03-08 17:52:59 +00:00
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][jest-testregex-setting].
2019-04-29 07:52:29 +00:00
This is the format for writing test methods:
```js
test('a description of the test', () => {
// Perform tasks and write assertions
2020-03-08 17:52:59 +00:00
});
2019-04-29 07:52:29 +00:00
```
2020-03-08 17:52:59 +00:00
The first test is to ensure that the correct CSS is generated from the plugin
using no options.
2019-04-29 07:52:29 +00:00
2020-03-08 17:52:59 +00:00
We do this by generating the plugin’ s CSS, and asserting that it matches the
expected CSS within the test.
2019-04-29 07:52:29 +00:00
```js
test('it generates the list reset class', () => {
2019-05-01 19:01:36 +00:00
generatePluginCss().then(css => {
2019-04-29 07:52:29 +00:00
expect(css).toMatchCss(`
.list-reset {
list-style: none;
padding: 0
}
2020-03-08 17:52:59 +00:00
`);
});
});
2019-04-29 07:52:29 +00:00
```
2020-01-05 20:15:35 +00:00
However, there are some additional steps needed to get this working.
2019-04-29 07:52:29 +00:00
### Generating the plugin’ s CSS
2020-03-08 17:52:59 +00:00
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.
2019-04-29 07:52:29 +00:00
```js
2020-03-08 17:52:59 +00:00
const plugin = require('./index.js');
const postcss = require('postcss');
const tailwindcss = require('tailwindcss');
2019-04-29 07:52:29 +00:00
```
Now we need a way to generate the CSS so assertions can be written against it.
2020-03-08 17:52:59 +00:00
In this case, I’ ve created a function called `generatePluginCss` that accepts
some optional options, processes PostCSS and Tailwind, and returns the CSS.
2019-04-29 07:52:29 +00:00
```js
const generatePluginCss = (options = {}) => {
2020-03-08 17:52:59 +00:00
return postcss(tailwindcss())
.process('@tailwind utilities;', {
from: undefined,
})
.then(result => result.css);
};
2019-04-29 07:52:29 +00:00
```
2020-03-08 17:52:59 +00:00
Alternatively, to test the output of a component, `@tailwind utilities;` would
be replaced with `@tailwind components` .
2019-04-29 07:52:29 +00:00
```js
.process('@tailwind components;', {
from: undefined
})
```
2020-03-08 17:52:59 +00:00
Whilst `from: undefined` isn’ t required, if it’ s not included you will get this
message:
2019-04-29 07:52:29 +00:00
2020-03-08 17:52:59 +00:00
> 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.
2019-04-29 07:52:29 +00:00
### Configuring Tailwind
2020-03-08 17:52:59 +00:00
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.
2019-04-29 07:52:29 +00:00
As of Tailwind 1.0.0-beta5, this can be done as follows:
```
tailwindcss({
corePlugins: false,
plugins: [plugin(options)]
})
```
2020-03-08 17:52:59 +00:00
In prior versions, each plugin in `corePlugins` needed to be set to `false`
separately.
2019-04-29 07:52:29 +00:00
2020-03-08 17:52:59 +00:00
I did that using a `disableCorePlugins()` function and [lodash][lodash], using
the keys from `variants` :
2019-04-29 07:52:29 +00:00
```
const _ = require('lodash')
// ...
const disableCorePlugins = () => {
return _.mapValues(defaultConfig.variants, () => false)
}
```
### Enabling CSS matching
2020-03-08 17:52:59 +00:00
In order to compare the generated and expected CSS, [the CSS matcher for
Jest][jest-css-matcher] needs to be required and added using
[expect.extend][jest-expect-extend].
2019-04-29 07:52:29 +00:00
```js
const cssMatcher = require('jest-matcher-css')
...
expect.extend({
toMatchCss: cssMatcher
})
```
2020-03-08 17:52:59 +00:00
Without it, you’ ll get an error message like _"TypeError: expect(...).toMatchCss
is not a function"_ when running the tests.
2019-04-29 07:52:29 +00:00
## The next test: testing variants
2020-03-08 17:52:59 +00:00
To test variants we can specify the required variant names within as options to
`generatePluginCss` .
2019-04-29 07:52:29 +00:00
For example, this is how to enable `hover` and `focus` variants.
```js
2020-03-08 17:52:59 +00:00
generatePluginCss({ variants: ['hover', 'focus'] });
2019-04-29 07:52:29 +00:00
```
2020-03-08 17:52:59 +00:00
Now we can add another test that generates the variant classes too, to ensure
that also works as expected.
2019-04-29 07:52:29 +00:00
```js
test('it generates the list reset class with variants', () => {
2019-05-01 19:01:36 +00:00
generatePluginCss({ variants: ['hover', 'focus'] }).then(css => {
2019-04-29 07:52:29 +00:00
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
}
2020-03-08 17:52:59 +00:00
`);
});
});
2019-04-29 07:52:29 +00:00
```
## Running tests locally
Now that we have tests, we need to be able to run them.
2020-03-08 17:52:59 +00:00
With Jest included as a dependency, we can update the `test` script within
`package.json` to execute it rather than returning a stub message.
2019-04-29 07:52:29 +00:00
```diff
- "test": "echo \"Error: no test specified\" && exit 1"
+ "test": "jest"
```
2020-03-08 17:52:59 +00:00
This means that as well as running the `jest` command directly to run the tests,
we can also run `npm test` or `yarn test` .
2019-04-29 07:52:29 +00:00
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. ](/images/blog/testing-tailwindcss-plugins/running-tests.png )
## Running tests automatically with Travis CI
2020-03-08 17:52:59 +00:00
As well as running the tests locally, they can also be run automatically via
services like [Travis CI][travis] when a new pull request is submitted or each
time new commits are pushed.
2019-04-29 07:52:29 +00:00
2020-03-08 17:52:59 +00:00
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][travis-nodejs-example]:
2019-04-29 07:52:29 +00:00
```yml
language: node_js
node_js:
- '8'
cache:
directories:
- node_modules
before_install:
- npm update
install:
- npm install
script:
- npm test
```
2020-03-08 17:52:59 +00:00
With this in place, the project can now be enabled on the Travis website, and
the tests will be run automatically.
2019-04-29 07:52:29 +00:00
2020-03-08 17:52:59 +00:00
For this plugin, you can see the results at
< https: / / travis-ci . org / opdavies / tailwindcss-list-reset > .
2019-04-29 07:52:29 +00:00
[jest-css-matcher]: https://www.npmjs.com/package/jest-matcher-css
[jest-expect-extend]: https://jestjs.io/docs/en/expect#expectextendmatchers
2020-03-08 17:52:59 +00:00
[jest-testregex-setting]:
https://jestjs.io/docs/en/configuration#testregex-string-array-string
2019-04-29 07:52:29 +00:00
[jest]: https://jestjs.io
[lodash]: https://lodash.com
[repo]: https://github.com/opdavies/tailwindcss-list-reset
[tailwind-docs-plugins]: https://tailwindcss.com/docs/plugins
2020-03-08 17:52:59 +00:00
[tailwindcss-interaction-variants]:
https://www.npmjs.com/package/tailwindcss-interaction-variants
[travis-nodejs-example]:
https://docs.travis-ci.com/user/languages/javascript-with-nodejs
2019-04-29 07:52:29 +00:00
[travis]: https://travis-ci.org
[working-on-tailwind-video]: https://www.youtube.com/watch?v=SkTKN38wSEM