oliverdavies.uk/source/_posts/testing-tailwind-css-plugins-jest.md

285 lines
8 KiB
Markdown
Raw Normal View History

2021-06-30 07:00:00 +00:00
---
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 Wathans ["Working on Tailwind 1.0" video][working-on-tailwind-video], the Jest documentation website, and existing tests for other Tailwind plugins that Ive 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
Tailwinds 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 dont use `@tailwind base` (formerly
`@tailwind preflight`) so dont 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, Ill 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.
```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
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 its [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 plugins 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 plugins CSS
Firstly, we need to import the plugins 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, Ive 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` isnt required, if its 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 Tailwinds 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, youll 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]:
```yml
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