-
Notifications
You must be signed in to change notification settings - Fork 20
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit b0f71d1
Showing
7 changed files
with
645 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
node_modules |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2018 Dominik Ferber | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
# jest-transform-css | ||
|
||
A Jest transfomer which enables importing CSS into Jest's `jsdom`. | ||
|
||
**If you are not here for Visual Regression Testing, but just want to make your tests work with CSS Modules, then you are likley looking for https://github.com/keyanzhang/identity-obj-proxy/.** | ||
|
||
> ⚠️ **This package is experimental.** | ||
> It works with the tested project setups, but needs to be tested in more. | ||
> If you struggle to set it up properly, it might be the fault of this package. | ||
> Please file an issue and provide reproduction, or even open a PR to add support. | ||
> | ||
> The document is also sparse at the moment. Feel free to open an issue in case you have any questions! | ||
> | ||
> I am not too familiar with PostCSS and Jest, so further simplification of | ||
> this plugin might be possible. I'd appreciate any hints! | ||
> | ||
> If this approach is working for you, please let me know on Twitter ([@dferber90](https://twitter.com/dferber90)) or by starring the [GitHub repo](https://github.com/dferber90/jest-transform-css). | ||
> | ||
> I am looking for contributors to help improve this package! | ||
## Description | ||
|
||
When you want to do Visual Regression Testing in Jest, it is important that the CSS of components is available to the test setup. So far, CSS was not part of tests as it was mocked away by `identity-obj-proxy`. | ||
|
||
`jest-transform-css` is inteded to be used in an `jsdom` environment. When any component imports CSS in the test environment, then the loaded CSS will get added to `jsdom` using [`style-inject`](https://github.com/egoist/style-inject) - just like the Webpack CSS loader would do in a production environment. This means the full styles are added to `jsdom`. | ||
|
||
This doesn't make much sense at first, as `jsdom` is headless (non-visual). However, we can copy the resulting document markup ("the HTML") of `jsdom` and copy it to a [`puppeteer`](https://github.com/googlechrome/puppeteer/) instance. We can let the markup render there and take a screenshot there. The [`jsdom-screenshot`](https://github.com/dferber90/jsdom-screenshot) package does exactly this. | ||
|
||
Once we obtained a screenshot, we can compare it to the last version of that screenshot we took, and make tests fail in case they did. The [`jest-image-snapshot`](https://github.com/americanexpress/jest-image-snapshot) plugin does that. | ||
|
||
## Installation | ||
|
||
```bash | ||
yarn add jest-transform-css --dev | ||
``` | ||
|
||
## Setup | ||
|
||
### Setup - adding `transform` | ||
|
||
Open `jest.config.js` and modify the `transform`: | ||
|
||
``` | ||
transform: { | ||
"^.+\\.js$": "babel-jest", | ||
"^.+\\.css$": "./jest-transform-css" | ||
} | ||
``` | ||
|
||
> Notice that `babel-jest` gets added as well. | ||
> | ||
> The `babel-jest` code preprocessor is enabled by default, when no other preprocessors are added. As `jest-transform-css` is a code preprocessor, `babel-jest` gets disabled when `jest-transform-css` is added. | ||
> | ||
> So it needs to be added again manually. | ||
> | ||
> See https://github.com/facebook/jest/tree/master/packages/babel-jest#setup | ||
### Setup - removing `identity-obj-proxy` | ||
|
||
If your project is using CSS Modules, then it's likely that `identity-obj-proxy` is configured. It needs to be removed in order for the styles of the `jest-transform-css` to apply. | ||
|
||
So, remove these lines from `jest.config.js`: | ||
|
||
```diff | ||
"moduleNameMapper": { | ||
- "\\.(s?css|less)$": "identity-obj-proxy" | ||
}, | ||
``` | ||
|
||
## Further setup | ||
|
||
There are many ways to setup styles in a project (CSS modules, global styles, external global styles, local global styles, CSS in JS, LESS, SASS just to name a few). How to continue from here on depends on your project. | ||
|
||
### Further Setup - PostCSS | ||
|
||
If your setup is using `PostCSS` then you should add a `postcss.config.js` at the root of your folder. | ||
|
||
You can apply certain plugins only when `process.env.NODE_ENV === 'test'`. Ensure that valid CSS can be generated. It might be likely that more functionality (transforms) are required to make certain CSS work (like background-images). | ||
|
||
### Further Setup - css-loader | ||
|
||
If your setup is using `css-loader` only, without PostCSS then you should be fine. When components import CSS in the test environment, then the CSS is transformed through PostCSS's `cssModules` plugin to generate the classnames. It also injects the styles into `jsdom`. | ||
|
||
## Known Limitations | ||
|
||
At the moment I struggled to get CSS from `node_modules` to transpile, due to the `jest` configuration. I might just be missing something obvious. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
const fs = require("fs"); | ||
const crossSpawn = require("cross-spawn"); | ||
const stripIndent = require("common-tags/lib/stripIndent"); | ||
|
||
module.exports = { | ||
process: (src, filename, config, options) => { | ||
// The "process" function of this Jest transform must be sync, | ||
// but postcss is async. So we spawn a sync process to do an sync | ||
// transformation! | ||
// https://twitter.com/kentcdodds/status/1043194634338324480 | ||
const postcssRunner = `${__dirname}/postcss-runner.js`; | ||
const result = crossSpawn.sync("node", [ | ||
"-e", | ||
stripIndent` | ||
require("${postcssRunner}")( | ||
${JSON.stringify({ | ||
src, | ||
filename | ||
// config, | ||
// options | ||
})} | ||
) | ||
.then(out => { console.log(JSON.stringify(out)) }) | ||
` | ||
]); | ||
|
||
// check for errors of postcss-runner.js | ||
const error = result.stderr.toString(); | ||
if (error) throw error; | ||
|
||
// read results of postcss-runner.js from stdout | ||
let css; | ||
let tokens; | ||
try { | ||
// we likely logged something to the console from postcss-runner | ||
// in order to debug, and hence the parsing fails! | ||
parsed = JSON.parse(result.stdout.toString()); | ||
css = parsed.css; | ||
tokens = parsed.tokens; | ||
if (Array.isArray(parsed.warnings)) | ||
parsed.warnings.forEach(warning => { | ||
console.warn(warning); | ||
}); | ||
} catch (error) { | ||
// we forward the logs and return no mappings | ||
console.error(result.stderr.toString()); | ||
console.log(result.stdout.toString()); | ||
return stripIndent` | ||
console.error("transform-css: Failed to load '${filename}'"); | ||
module.exports = {}; | ||
`; | ||
} | ||
|
||
// Finally, inject the styles to the document | ||
return stripIndent` | ||
const styleInject = require('style-inject'); | ||
styleInject(${JSON.stringify(css)}); | ||
module.exports = ${JSON.stringify(tokens)}; | ||
`; | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
{ | ||
"name": "jest-transform-css", | ||
"description": "Jest transfomer to import CSS into Jest's `jsdom`", | ||
"version": "0.0.1", | ||
"main": "index.js", | ||
"author": "Dominik Ferber <[email protected]> (http://dferber.de/)", | ||
"license": "MIT", | ||
"dependencies": { | ||
"common-tags": "1.8.0", | ||
"cross-spawn": "6.0.5", | ||
"postcss": "7.0.2", | ||
"postcss-load-config": "2.0.0", | ||
"postcss-modules": "1.3.2", | ||
"style-inject": "0.3.0" | ||
}, | ||
"keywords": [ | ||
"postcss-runner", | ||
"jest", | ||
"css" | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
const postcss = require("postcss"); | ||
const postcssrc = require("postcss-load-config"); | ||
const cssModules = require("postcss-modules"); | ||
|
||
// This script is essentially a PostCSS Runner | ||
// https://github.com/postcss/postcss/blob/master/docs/guidelines/runner.md#postcss-runner-guidelines | ||
module.exports = ({ src, filename }) => { | ||
const ctx = { | ||
// Not sure whether the map is useful or not. | ||
// Disabled for now. We can always enable it once it becomes clear. | ||
map: false, | ||
// To ensure that PostCSS generates source maps and displays better syntax | ||
// errors, runners must specify the from and to options. If your runner does | ||
// not handle writing to disk (for example, a gulp transform), you should | ||
// set both options to point to the same file" | ||
// https://github.com/postcss/postcss/blob/master/docs/guidelines/runner.md#21-set-from-and-to-processing-options | ||
from: filename, | ||
to: filename | ||
}; | ||
let tokens = {}; | ||
return postcssrc(ctx) | ||
.then( | ||
config => ({ ...config, plugins: config.plugins || [] }), | ||
error => { | ||
// Support running without postcss.config.js | ||
// This is useful in case the webpack setup of the consumer does not | ||
// use PostCSS at all and simply uses css-loader in modules mode. | ||
if (error.message.startsWith("No PostCSS Config found in:")) { | ||
return { plugins: [], options: { from: filename, to: filename } }; | ||
} | ||
throw error; | ||
} | ||
) | ||
.then(({ plugins, options }) => { | ||
return postcss([ | ||
cssModules({ | ||
// Should we read generateScopedName from options? | ||
// Does anybody care about the actual names? This is test-only anyways? | ||
// Should be easy to add in case anybody needs it, just pass it through | ||
// from jest.config.js (we have "config" & "options" in css.js) | ||
generateScopedName: "[path][local]-[hash:base64:10]", | ||
getJSON: (cssFileName, exportedTokens, outputFileName) => { | ||
tokens = exportedTokens; | ||
} | ||
}), | ||
...plugins | ||
]) | ||
.process(src, options) | ||
.then( | ||
result => ({ | ||
css: result.css, | ||
tokens, | ||
// Display result.warnings() | ||
// PostCSS runners must output warnings from result.warnings() | ||
// https://github.com/postcss/postcss/blob/master/docs/guidelines/runner.md#32-display-resultwarnings | ||
warnings: result.warnings().map(warn => warn.toString()) | ||
}), | ||
// Don’t show JS stack for CssSyntaxError | ||
// PostCSS runners must not show a stack trace for CSS syntax errors, | ||
// as the runner can be used by developers who are not familiar with | ||
// JavaScript. Instead, handle such errors gracefully: | ||
// https://github.com/postcss/postcss/blob/master/docs/guidelines/runner.md#31-dont-show-js-stack-for-csssyntaxerror | ||
error => { | ||
if (error.name === "CssSyntaxError") { | ||
process.stderr.write(error.message + error.showSourceCode()); | ||
} else { | ||
throw error; | ||
} | ||
} | ||
); | ||
}); | ||
}; |
Oops, something went wrong.