285 lines
8 KiB
Markdown
285 lines
8 KiB
Markdown
---
|
||
title: Testing Tailwind CSS plugins with Jest
|
||
date: 2019-04-29
|
||
excerpt: How to write tests for Tailwind CSS plugins using Jest.
|
||
tags:
|
||
- javascript
|
||
- jest
|
||
- tailwind-css
|
||
- testing
|
||
promoted: true
|
||
---
|
||
|
||
<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
|
||
|
||
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][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.
|
||
|
||
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].
|
||
|
||
More information about plugins for Tailwind CSS themselves can be found on the
|
||
[Tailwind website][tailwind-docs-plugins].
|
||
|
||
## 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][jest-testregex-setting].
|
||
|
||
This is the format for writing test methods:
|
||
|
||
```js
|
||
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.
|
||
|
||
```js
|
||
test('it generates the list reset class', () => {
|
||
generatePluginCss().then(css => {
|
||
expect(css).toMatchCss(`
|
||
.list-reset {
|
||
list-style: none;
|
||
padding: 0
|
||
}
|
||
`);
|
||
});
|
||
});
|
||
```
|
||
|
||
However, there are some additional 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.
|
||
|
||
```js
|
||
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.
|
||
|
||
```js
|
||
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`.
|
||
|
||
```js
|
||
.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][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][jest-css-matcher] needs to be required and added using
|
||
[expect.extend][jest-expect-extend].
|
||
|
||
```js
|
||
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.
|
||
|
||
```js
|
||
generatePluginCss({ variants: ['hover', 'focus'] });
|
||
```
|
||
|
||
Now we can add another test that generates the variant classes too, to ensure
|
||
that also works as expected.
|
||
|
||
```js
|
||
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.
|
||
|
||
```diff
|
||
- "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.](/images/blog/testing-tailwindcss-plugins/running-tests.png)
|
||
|
||
## Running tests automatically with Travis CI
|
||
|
||
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.
|
||
|
||
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]:
|
||
|
||
```yaml
|
||
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>.
|
||
|
||
[jest-css-matcher]: https://www.npmjs.com/package/jest-matcher-css
|
||
[jest-expect-extend]: https://jestjs.io/docs/en/expect#expectextendmatchers
|
||
[jest-testregex-setting]:
|
||
https://jestjs.io/docs/en/configuration#testregex-string-array-string
|
||
[jest]: https://jestjs.io
|
||
[lodash]: https://lodash.com
|
||
[repo]: https://github.com/opdavies/tailwindcss-list-reset
|
||
[tailwind-docs-plugins]: https://tailwindcss.com/docs/plugins
|
||
[tailwindcss-interaction-variants]:
|
||
https://www.npmjs.com/package/tailwindcss-interaction-variants
|
||
[travis-nodejs-example]:
|
||
https://docs.travis-ci.com/user/languages/javascript-with-nodejs
|
||
[travis]: https://travis-ci.org
|
||
[working-on-tailwind-video]: https://www.youtube.com/watch?v=SkTKN38wSEM
|