How can I use jest coverage in Next.js styled-jsx? - reactjs

I'm trying to use test framework Jest with Next.js and styled-jsx.
This is my external css style.
import css from 'styled-jsx/css';
export const TextStyle = css`
p {
font-size: 14px;
color: #a8a8a8;
}
`;
and this is my package.json script. It works well.
"test": "jest --verbose",
I want to use coverage option. so I tried this "test": "jest --coverage --verbose", but it is not working.
ReferenceError: css is not defined
I have read this https://github.com/zeit/styled-jsx/issues/436. However, the issue is still open and doesn't help me.
How can I fix it?

styled-jsx docs mention how to solve it but that didn't work well for me.
I made it work by doing the following:
Set the node env in your package.json test scripts to "test" like so (can add the --verbose flag as well if you want):
"test": "NODE_ENV=test jest",
"test:coverage": "NODE_ENV=test jest --coverage"
In your babel configuration (.babelrc in my case) add babel-test: true to the test env config like so (refer to the Next docs for more details):
{
// this is your original config, it's required if you don't have a babel config yet and are using `Next` since `Next` normally does it under the hood for you:
"presets": [
[
"next/babel",
]
],
// this is what you're adding:
"env": {
"test": {
"presets": [
[
"next/babel",
{
"styled-jsx": {
"babel-test": true
}
}
]
]
}
}
}
Your tests should now pass but may show a warning saying:
styled-jsx/css: if you are getting this error it means that your css tagged template literals were not transpiled.
In that case you should add a jest auto mock for styled-jsx/css by adding a new file with this exact path from the root of your project (the __mocks__ folder has to be a sibling of your node_modules folder) /__mocks__/styled-jsx/css.js:
function css() {
return ""
}
module.exports = css
*Note: what this whole setup does is disable styled-jsx transpilation when you run your tests which means that the generated classes will not be generated in your test components. In my case for example that breaks my tests because I'm relying on the generated classes for hiding some elements and my tests rely on those elements being hidden. I'm now trying to figure out a way around that but it may not be an issue in your case.

Related

After updating the query-string library, test:ci now fails

The development environment uses next.js 13.
After updating the query-string library to 8.1, test:ci now fails.
It fails at the following point.
before "query-string": "^7.1.0",
after "query-string": "^8.1.0",
error
({"Object.<anonymous>":function(module,exports,require,__dirname,__filename,jest){import * as queryString from './base.js';
^^^^^^
SyntaxError: Cannot use import statement outside a module
Jest encountered an unexpected token
Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.
Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.
By default "node_modules" folder is ignored by transformers.
Changed part.
before
import { stringifyUrl } from 'query-string';
.
. omission
.
stringifyUrl({url})
after
import queryString from 'query-string';
.
. omission
.
queryString.stringifyUrl({ url })
I am very troubled.
If anyone knows how to solve this problem, please let me know.
added
module.exports = {
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!**/*.d.ts'],
moduleDirectories: ['node_modules', 'src'],
moduleNameMapper: {
// Handle CSS imports (with CSS modules)
// https://jestjs.io/docs/webpack#mocking-css-modules
'^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
// Handle CSS imports (without CSS modules)
'^.+\\.(css|sass|scss)$': '<rootDir>/src/__mocks__/styleMock.ts',
// Handle image imports
// https://jestjs.io/docs/webpack#handling-static-assets
'^.+\\.(png|jpg|jpeg|gif|webp|avif|ico|bmp|svg)$/i': `<rootDir>/src/__mocks__/fileMock.ts`,
// Handle ESM packages
'^react-markdown$': '<rootDir>/src/__mocks__/react-markdown.tsx',
},
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
testEnvironment: 'jest-environment-jsdom',
transform: {
// Use babel-jest to transpile tests with the next/babel preset
// https://jestjs.io/docs/configuration#transform-objectstring-pathtotransformer--pathtotransformer-object
'^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }],
},
transformIgnorePatterns: [
'/node_modules/',
'^.+\\.module\\.(css|sass|scss)$',
],
};
The issue is query-string version 8 introduced a breaking to consuming applications because their dependencies upgraded to ESM. See their release notes here:
v8.0.0
Breaking
Require Node.js 14
This package is now pure ESM. Please read this.
Add "module": "node16", "moduleResolution": "node16" to your
tsconfig.json.
(Example)
And more!!!
It appears Jest in trying to use import but it's not configured to do so. Jest also provides some guidance for how to deal with this: https://jestjs.io/docs/ecmascript-modules
Since you're using Next.js, those steps don't really apply.
I'm reproducing and testing in a sandbox... Will update shorty.
For React apps:
Try setting "type": "module" inside package.json.
You should also update your package.json test script to:
"test": "node --experimental-vm-modules ./node_modules/.bin/jest"
Now inside jest.config.js you'll want to export transform: {}:
export default {
transform: {}
}
After all those steps, you should no longer have this issue.

How to exclude some files and directories from sonar coverage report - react, typescript project using jest enzyme

So I have Create React App with typescript template. I have also integrated sonarqube with it.
First When I ran sonar report with below configuration,
scanner(
{
serverUrl: "http://localhost:9000",
token: "*****",
options: {
...
"sonar.tests": "./src",
"sonar.test.inclusions": "**/*.test.tsx,**/*.test.ts, **/*.js",
"sonar.typescript.lcov.reportPaths": "coverage/lcov.info",
"sonar.testExecutionReportPaths": "test-report.xml"
},
},
() => process.exit()
);
The result is as below,
as you can see, it includes following file which is fine.
Now I am changing sonar-scanner.js configuration as below,
const testExclusions = `./src/index.ts,./src/reportWebVitals.ts, ./src/core/constants/**/*,./src/core/interfaces/**/*`;
scanner(
{
serverUrl: "http://localhost:9000",
token: "*****",
options: {
...
"sonar.tests": "./src",
"sonar.test.inclusions": "**/*.test.tsx,**/*.test.ts, **/*.js",
"sonar.test.exclusions": `${testExclusions}` <<=== I don't know if this line plays any role.
"sonar.typescript.lcov.reportPaths": "coverage/lcov.info",
"sonar.testExecutionReportPaths": "test-report.xml"
},
},
() => process.exit()
);
BUT again result is same. there is no change in the report.
Then, I introduced jest configuration in package.json as follow,
"test:report": "npm run test -- --watchAll=false --coverage --testResultsProcessor jest-sonar-reporter",
...
...
"jest": {
"collectCoverageFrom": [
"src/**/*.{js,jsx,tsx,ts}",
"!src/**/*.d.ts",
"!src/reportWebVitals.ts",
"!src/index.ts",
"!src/core/constants/**/*",
"!src/core/interfaces/**/*"
]
}
and I ran npm run test:report and it generated new test-report.xml with below files,
as you can see, it doesn't cover excluded files/directories.
When I ran sonar-scanner again, I believed now it will not show excluded files/directories in sonar coverage report but it is not the case yet. It is still including those files and directories.
What is wrong with this setup ?
x
Make changes as follow,
1) For some reason, need to remove ./ before src as below,
const testExclusions = `src/index.ts,src/reportWebVitals.ts, src/core/constants/**/*,src/core/interfaces/**/*`
2) use: sonar.coverage.exclusions property in sonar options instead of sonar.test.exclusions
"sonar.coverage.exclusions": `${testExclusions}`
Optional:
3) if you want to remove jest configuration from package.json. It is fine.

Getting the error "Nested CSS was detected, but CSS nesting has not been configured correctly" in React app?

I've been upgrading my CRA project to TailwindCSS 3, but now CSS nesting no longer works. Upon starting the server, the console spits out:
(8:3) Nested CSS was detected, but CSS nesting has not been configured correctly.
Please enable a CSS nesting plugin *before* Tailwind in your configuration.
See how here: https://tailwindcss.com/docs/using-with-preprocessors#nesting
However, I don't see what must be done to correct this. I've tried setting up a plain CRA project with Tailwind (following this guide) just to make sure I have no conflicts, and still no success.
postcss.config.js:
module.exports = {
plugins: {
"tailwindcss/nesting": {},
tailwindcss: {},
autoprefixer: {},
},
};
As you can see, I have added the nesting plugin before Tailwind. It appears to me as if the plugin isn't being detected whatsoever. I've also tried replacing it with postcss-nesting with same outcome.
Note: I've also tried using the array syntax with require('tailwind/nesting') like the guide suggests.
Interestingly, removing all plugins from postcss.config.js (or using a require that fails to resolve) still outputs the same error, implying that this file isn't needed to get Tailwind to load. Maybe I am missing something that causes the whole postcss.config.js file to not be loaded in the first place?
index.js:
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
ReactDOM.render(
<React.StrictMode>
<div className="a">
aaa
<div className="b">bbb</div>
</div>
</React.StrictMode>,
document.getElementById("root")
);
index.css:
#tailwind base;
#tailwind components;
#tailwind utilities;
.a {
#apply text-blue-500;
.b {
#apply text-green-500;
}
}
package.json: (omitted things for brevity)
{
"name": "tailwindtest",
"dependencies": {
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "5.0.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"devDependencies": {
"autoprefixer": "^10.4.2",
"postcss": "^8.4.5",
"tailwindcss": "^3.0.12"
}
}
This is mostly just bad news.
Create React App's Tailwind support means that they will detect tailwind.config.js in the project and add tailwindcss to their existing postcss configuration. Source in CRA
The guide that Tailwind offers on their site creates a dummy postcss.config.js - Making changes in this file does not change the actual postcss configuration. (misleading if anything)
This is a known issue currently - Github discussion on Tailwind support PR between Adam Wathan (Tailwind founder) and Ian Sutherland (CRA maintainer). But it does not seem like there is an intention to be fixed soon.
If you want to use nesting (or any PostCSS plugin really) is to eject from CRA using:
npm run eject
After ejecting you can find CRA's postcss configuration in config/webpack.config.js - look for postcss-loader. Editing the configuration there can enable any postcss features.
PS: Look out for postcss-preset-env in the default configuration while enabling nesting. Tailwind requires you to edit configuration if this is present.
I use CRA and to fix the issue I used postinstall to run a script after npm install or yarn. The script is changing the web pack config of CRA after all dependencies are installed(a temporary solution of cause).
You can find the web pack config in node_modules/react-scripts/config/webpack.config.js.
The script adds my postcss packages to the actual CRA web pack config.
WHY? CRA does not respect any postcss config in your repo
Have also a look at this comment to see how you should use postinstall https://github.com/facebook/create-react-app/issues/2133#issuecomment-347574268.
I also added tailwindcss/nesting before tailwindcss because tailwind is throwing a warning when it sees any nested css. The warning was blocking my CI since CI=true in CRA means all warnings are treated as errors.
Here is the script that is running in my repo.
FILE="node_modules/react-scripts/config/webpack.config.js"
function replace {
TARGET_FILE=$1
PATTERN_TO_FIND=$2
VALUE_FOR_REPLACEMENT=$3
OLD_FILE_CONTENT=$(cat "$TARGET_FILE") # we need to collect the content of the file so we can overwrite it in the next command
echo "$OLD_FILE_CONTENT" | sed -e "s/$PATTERN_TO_FIND/$VALUE_FOR_REPLACEMENT/g" > "$TARGET_FILE"
}
# add postcss-nesting
replace "$FILE" "'postcss-flexbugs-fixes'," "'postcss-flexbugs-fixes','postcss-nesting',"
# add tailwind/nesting
replace "$FILE" "'tailwindcss'," "'tailwindcss\/nesting', 'tailwindcss',"
acording to #aricma answer, is easier if you create a script.js file on parent directory (same as package.json) and add this on package.json
"scripts": {
"postinstall": "node script.js",
...
}
and this on script.js
const fs = require('fs');
fs.readFile('node_modules/react-scripts/config/webpack.config.js', 'utf8', (err, data) => {
if (err) {
return console.log(err);
}
const result = data.replace("'postcss-flexbugs-fixes',", "'postcss-flexbugs-fixes','postcss-nesting',").replace("'tailwindcss',", "'tailwindcss/nesting', 'tailwindcss',");
fs.writeFile('node_modules/react-scripts/config/webpack.config.js', result, 'utf8', (err) => {
if (err) {
return console.log(err);
}
return console.log(true);
});
return console.log(true);
});

using jest vs react-scripts test

js and react newbie...playing around with testing frameworks...here's the code:
import React from 'react';
// import CheckboxWithLabel from '../CheckboxWithLabel';
import {shallow} from 'enzyme'; //not installed...
//var x = require ('../CheckboxWithLabel.js');
test('CheckboxWithLabel changes the text after click', () => {
const checkbox = shallow(
<CheckboxWithLabel labelOn="On" labelOff="Off" />
);
expect(checkbox.text()).toEqual('Off');
checkbox.find('input').simulate('change');
expect(checkbox.text()).toEqual('On');
});
The react-scripts test error is:
Cannot find module 'enzyme' from 'checkboxWithLabel-test.js'
While the jest error is:
Jest encountered an unexpected token
SyntaxError: /Users/shriamin/Development/js_prj_react_django_etc/jest_react_demo/my-app/src/__tests__/checkboxWithLabel-test.js: Unexpected token (12:4)
10 | test('CheckboxWithLabel changes the text after click', () => {
11 | const checkbox = shallow(
> 12 | <CheckboxWithLabel labelOn="On" labelOff="Off" />
| ^
13 | );
14 | expect(checkbox.text()).toEqual('Off');
15 | checkbox.find('input').simulate('change');
i have no idea why jest would throw this error...react-scripts test makes sense to me since enzyme is not installed....please tell me does jest suck or am i doing something wrong configuring jest (installed via npm and update package.json).
NOTE: i don't have babel installed...i don't know what that is yet.
thanks
You arrived at the answer yourself. To use jest your tests need to go through babel for the runner to understand react syntax. take a look at the babel-doc to understand it at greater detail. it's just a transformation tool that transforms fancy syntax into something javascript understands. install the following plugins and presets.
Presets
npm i --save #babel/preset-env
npm i --save #babel/preset-react
Plugins
npm install --save babel-plugin-transform-export-extensions
in your .babelrc add the following lines:
{
"env": {
"test": {
"presets": [
"#babel/preset-env",
"#babel/preset-react"
],
"plugins": [
"transform-export-extensions",
],
"only": [
"./**/*.js",
"node_modules/jest-runtime"
]
}
}
}
Now try running jest on the command-line from your project directory to make sure your tests are configured correctly.
react-scripts is a preconfigured set of commands that come out of the box with create-react-app if you want to use that instead of jest command, check here.
react-scripts expects your tests folder location to follow a certain convention.
this is probably why the tests weren't getting fetched when the react-scripts test command was run out of the box.
in package.json change
"scripts": {
"test": "jest",
},
to the following:
"scripts": {
"test": "react-scripts test",
},
i.e. don't change to jest in the first place
The error described here seem to be jsx that isn't interpreted, isn't your test file extension js instead of jsx ?

How to use jest with webpack?

I use webpack to develop a React component. Here is a simple version of it:
'use strict';
require('./MyComponent.less');
var React = require('react');
var MyComponent = React.createClass({
render() {
return (
<div className="my-component">
Hello World
</div>
);
}
});
module.exports = MyComponent;
Now, I would like to test this component using jest. Here is the relevant bit from my package.json:
"scripts": {
"test": "jest"
},
"jest": {
"rootDir": ".",
"testDirectoryName": "tests",
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"unmockedModulePathPatterns": [
"react"
]
}
When running npm test, I get the following error:
SyntaxError: /Users/mishamoroshko/react-component/src/tests/MyComponent.js: /Users/mishamoroshko/react-component/src/MyComponent.js: /Users/mishamoroshko/react-component/src/MyComponent.less: Unexpected token ILLEGAL
Looks like webpack needs to process require('./MyComponent.less') before jest can run the test.
I wonder if I need to use something like jest-webpack. If yes, is there a way to specify multiple scriptPreprocessors? (note that I already use babel-jest)
The cleanest solution I found for ignoring a required module is to use the moduleNameMapper config (works on the latest version 0.9.2)
The documentation is hard to follow. I hope the following will help.
Add moduleNameMapper key to your packages.json config. The key for an item should be a regex of the required string. Example with '.less' files:
"moduleNameMapper": { "^.*[.](less|LESS)$": "EmptyModule" },
Add a EmptyModule.js to your root folder:
/**
* #providesModule EmptyModule
*/
module.exports = '';
The comment is important since the moduleNameMapper use EmptyModule as alias to this module (read more about providesModule).
Now each require reference that matches the regex will be replaced with an empty string.
If you use the moduleFileExtensions configuration with a 'js' file, then make sure you also add the EmptyModule to your 'unmockedModulePathPatterns'.
Here is the jest configuration I ended up with:
"jest": {
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"moduleFileExtensions": ["js", "json","jsx" ],
"moduleNameMapper": {
"^.*[.](jpg|JPG|gif|GIF|png|PNG|less|LESS|css|CSS)$": "EmptyModule"
},
"preprocessorIgnorePatterns": [ "/node_modules/" ],
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-addons-test-utils",
"<rootDir>/EmptyModule.js"
]
}
I ended up with the following hack:
// package.json
"jest": {
"scriptPreprocessor": "<rootDir>/jest-script-preprocessor",
...
}
// jest-script-preprocessor.js
var babelJest = require("babel-jest");
module.exports = {
process: function(src, filename) {
return babelJest.process(src, filename)
.replace(/^require.*\.less.*;$/gm, '');
}
};
But, I'm still wondering what is the right solution to this problem.
I just found that it's even simpler with Jest's moduleNameMapper configuration.
// package.json
"jest": {
"moduleNameMapper": {
"^.+\\.scss$": "<rootDir>/scripts/mocks/style-mock.js"
}
}
// style-mock.js
module.exports = {};
More detail at Jest's tutorial page.
I recently released Jestpack which might help. It first builds your test files with Webpack so any custom module resolution/loaders/plugins etc. just work and you end up with JavaScript. It then provides a custom module loader for Jest which understands the Webpack module runtime.
From Jest docs:
// in terminal, add new dependency: identity-obj-proxy
npm install --save-dev identity-obj-proxy
// package.json (for CSS Modules)
{
"jest": {
"moduleNameMapper": {
"\\.(css|less)$": "identity-obj-proxy"
}
}
}
The snippet above will route all .less files to the new dependency identity-obj-proxy, which will return a string with the classname when invoked, e.g. 'styleName' for styles.styleName.
I think a less hacky solution would be to wrap your preprocessor in a conditional on the filename matching a javascript file:
if (filename.match(/\.jsx?$/)) {
return babelJest.process(src, filename);
} else {
return '';
}
This works even if you don't explicitly set the extension in the require line and doesn't require a regex substitution on the source.
I have experienced similar issue with such pattern
import React, { PropTypes, Component } from 'react';
import styles from './ContactPage.css';
import withStyles from '../../decorators/withStyles';
#withStyles(styles)
class ContactPage extends Component {
see example at https://github.com/kriasoft/react-starter-kit/blob/9204f2661ebee15dcb0b2feed4ae1d2137a8d213/src/components/ContactPage/ContactPage.js#L4-L7
For running Jest I has 2 problems:
import of .css
applying decorator #withStyles (TypeError: <...> (0 , _appDecoratorsWithStyles2.default)(...) is not a function)
First one was solved by mocking .css itself in script preprocessor.
Second one was solved by excluding decorators from automocking using unmockedModulePathPatterns
module.exports = {
process: function (src, filename) {
...
if (filename.match(/\.css$/)) src = '';
...
babel.transform(src, ...
}
}
example based on https://github.com/babel/babel-jest/blob/77a24a71ae2291af64f51a237b2a9146fa38b136/index.js
Note also: when you working with jest preprocessor you should clean cache:
$ rm node_modules/jest-cli/.haste_cache -r
Taking inspiration from Misha's response, I created an NPM package that solves this problem while also handling a few more scenarios I came across:
webpack-babel-jest
Hopefully this can save the next person a few hours.
If you're using babel, you can strip unwanted imports during the babel transform using something like https://github.com/Shyp/babel-plugin-import-noop and configuring your .babelrc test env to use the plugin, like so:
{
"env": {
"development": {
...
},
"test": {
"presets": [ ... ],
"plugins": [
["import-noop", {
"extensions": ["scss", "css"]
}]
]
}
}
}
We had a similar problem with CSS files. As you mentioned before jest-webpack solves this problem fine. You won't have to mock or use any module mappers either. For us we replaced our npm test command from jest to jest-webpack and it just worked.
Webpack is a great tool, but I don't need to test it's behavior with my Jest unit tests, and adding a webpack build prior to running unit tests is only going to slow down the process. The text-book answer is to mock non-code dependencies using the "moduleNameMapper" option
https://facebook.github.io/jest/docs/webpack.html#handling-static-assets

Resources