Configuring Enzyme 3.x Adapter - javascript

I am writing tests for a React app using Jest and Enzyme. Enzyme 3.x introduced Adapters to provide compatibility across different versions of React. The installation documentation gives examples how to set this up:
// setup file
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
What is this "setup file" referred to in the comment? Is this a file which can be run before all tests? Where does this file go in a React project? Does it need a particular name?

It doesn't need a particular name, and yes, it's run before any tests are.
You hook it up in your package.json's jest stanza.
This is an example from a project I'm working on.
"jest": {
// other stuff...
"setupFiles": [
"./js/jest-setup.js"
],
// ....
}
The actual js/jest-setup.js file looks like this (i.e. like your example).
import Enzyme from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
Enzyme.configure({adapter: new Adapter()});

Enzyme has a nifty guide on setup here: https://github.com/airbnb/enzyme/blob/master/docs/guides/jest.md
Though the initial step doesn't say it is the package.json.
Posting the relevant sections (with some minor tweaks) here so we don't lose it:
Configure with Jest
To run the setup file to configure Enzyme and the Adapter with Jest direct setupTestFrameworkScriptFile to literally the string <rootDir> and the path to your setup file.
package.json:
{
"jest": {
"setupTestFrameworkScriptFile": "<rootDir>src/setupTests.js"
}
}
Jest version 15 and up
Starting with version 15, Jest no longer mocks modules by default. Because of this, you no longer have to add any special configuration for Jest to use it with enzyme.
Install Jest, and its Babel integrations, as recommended in the Jest docs. Install enzyme. Then, simply require/import React, enzyme functions, and your module at the top of a test file.
setupTests.js:
import React from 'react';
import { shallow, mount, render } from 'enzyme';
import Foo from '../Foo';

What is this "setup file" referred to in the comment? :
It's a way of running something before your test starting (some config for instance)
If you create your React Project using react-create-app you need or eject your application or pass a command set up your file setupTest.js (The name does not matter), but you need to identify in your command line, like this:
package.json
"scripts": {
"test": "react-scripts test --env=jsdom --setupFiles=./src/test-setup.js",
},
test-setup.js
import './shim';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
shim.js (this is a hack to avoid the warning)
global.requestAnimationFrame = (callback) => {
setTimeout(callback, 0);
};

Jest & enzyme configuration :
Add following code in package.json
"jest": {
"testEnvironment": "jsdom",
"moduleDirectories": [
"src",
"node_modules"
],
"moduleNameMapper": {
"\\.(css|scss)$": "<rootDir>/__mocks__/styleMock.js",
"\\.(jpg|gif|ttf|eot|svg)$": "<rootDir>/__mocks__/fileMock.js"
},
"transform": {
"^.+\\.(js|jsx)$": "babel-jest",
".+\\.(css|styl|less|sass|scss)$": "<rootDir>/node_modules/jest-css-modules-transform"
},
"setupTestFrameworkScriptFile": "<rootDir>/setupTests.js",
"setupFiles": [
"<rootDir>setup.js"
],
"moduleFileExtensions": [
"css",
"scss",
"js",
"json",
"jsx"
],
"testRegex": "\/test\/spec\/.*\\.js$",
"transformIgnorePatterns": [
"/node_modules/(?!test-component).+\\.js$"
]
}
For setup of Enzyme => setup.js
import { configure } from 'enzyme'
import Adapter from 'enzyme-adapter-react-16'
configure({ adapter: new Adapter() })
For setupTestFrameworkScriptFile : setupTests.js
global.fetch = require('jest-fetch-mock')
const { JSDOM } = require('jsdom')
const jsdom = new JSDOM('<!doctype html><html><body></body></html>')
const { window } = jsdom
const exposedProperties = ['window', 'navigator', 'document']
const { document } = new JSDOM('').window
global.document = document
global.window = document.defaultView
global.HTMLElement = window.HTMLElement
global.HTMLAnchorElement = window.HTMLAnchorElement
Object.keys(document.defaultView).forEach(property => {
if (typeof global[property] === 'undefined') {
exposedProperties.push(property)
global[property] = document.defaultView[property]
}
})
global.navigator = {
userAgent: 'node.js',
}

I was just having this same issue and the easiest solution was to simply create a file named exactly setupTests.js, in the src/ directory with the contents:
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
Jest automatically detects it and the error goes away.

With enzyme 3 we need an adapter. Adapter allows us to specify exactly which version of react we are gonna test against. This allows core enzyme library to be smaller. It does not need to have all of the code for all of the various versions of react that supported. Instead you just specify which one you need by installing the adapter that keeps the core library light and keeps your entire code base more manageable.
this is how u configure it:
in tests directory create any .js file:
tests/setupTest.js
import Enzyme from "enzyme";
import Adapter from "enzyme-adapter-react-16";
Enzyme.configure({ adapter: new Adapter() });
in the root directory create jest.config.json file and put this
{
"setupFiles": ["raf/polyfill", "<rootDir>/src/tests/setupTest.js"],
}
NOTE: We also need to install a polyfill module. Polyfill is known as request animation frame. Since we do not have it in test environment, we need to install.
npm i raf --save
finally in package.json :
"test": "jest --config=jest.config.json"

Related

vue.config.js configureWebpack not used in unit tests (vue 3)

In my vue config I have the following:
configureWebpack: {
resolve: {
alias: {
react: path.resolve(__dirname, 'composition/react'),
hooks: path.resolve(__dirname, 'composition'),
},
},
},
In my component I have the following:
import { useEffect, useState } from 'react';
This is so I can re use some of the Vue code in React projects. This works when running and building the project but not when testing. When unit testing I get the error: Cannot find module 'react' from 'useCategories.js', the above import is from useCategories.
The following is in the jest.config.js:
module.exports = {
preset: '#vue/cli-plugin-unit-jest',
transform: {
'^.+\\.vue$': 'vue-jest'
}
}
If unit testing in vue is ignoring values from vue.config.js then how can I set the webpack path resolve values for testing. I would rather not repeat these values but if it's a JavaScript file I guess I can import it from the same file in different configs.
Webpack isn't used in Jest, this would make module testing impractical, so Webpack config cannot affect anything.
As the documentation explains, it's necessary to provide module mapping in Jest config as well, e.g.:
moduleNameMapper: {
'^react$': '<rootDir>/composition/react',
...

Esbuild with ReactJS.NET?

TL;DR How can I use esbuild with ReactJS.NET?
Long version
ReactJS.NET expects the following from a "bundle":
React.Exceptions.ReactNotInitialisedException: 'React has not been loaded correctly: missing (React, ReactDOM, ReactDOMServer). Please expose your version of React as global variables named 'React', 'ReactDOM', and 'ReactDOMServer'
What Webpack actually does is always quite unclear to me, but looking at the ReactJS.NET Webpack tutorial here, this is the setup:
import React from 'react';
import ReactDOM from 'react-dom';
import ReactDOMServer from 'react-dom/server';
import RootComponent from './home.jsx';
global.React = React;
global.ReactDOM = ReactDOM;
global.ReactDOMServer = ReactDOMServer;
global.Components = { RootComponent };
Further at the sample webpack-config here, there is this output-setting:
{
[...]
globalObject: 'this',
}
Mirroring this in esbuild would be using iife and esbuild.globalName = 'this', but it throws an error:
> (global name):1:0: error: Expected identifier but found "this"
1 │ this
Esbuild is quite stern on that it is a bundler (not a transpile-and-concatenator), so how would I tweak Esbuild to have the bundle mutate the global object to what React.NET expects? - And is it even possible?
Thanks,
Flemming
Found a solution.
import { build } from 'esbuild'
const globalName = 'whatever'
build({
[...]
format: 'iife',
globalName,
footer: {
// Important! Assigns the raw export of the bundle to whatever `this` is in the given context
js: `Object.assign(this, ${globalName})`,
},
})

Consider using the "jsdom" test environment

I have this simple test:
import React from 'react'
import { render } from '#testing-library/react'
import Button from '.'
describe('Button', () => {
it('renders button without crashing', () => {
const label = 'test'
render(<Button label={label} />)
})
})
And I have a jest.config.json with this content
{
"setupFilesAfterEnv": [
"<rootDir>/lib/settings/setupTests.ts"
]
}
And on my setupTests.ts I have
import '#testing-library/jest-dom'
When I run npm run test (which just run jest), I got the following error:
The error below may be caused by using the wrong test environment, see
https://jestjs.io/docs/configuration#testenvironment-string.
Consider using the "jsdom" test environment.
What I am doing wrong? This used to work before an upgrade.
In your package.json, or jest.config.js/jest.config.ts file, change the value of the testEnvironment property to jsdom.
package.json
"jest":{
"testEnvironment": "jsdom"
}
jest.config.[js|ts]
module.exports = {
"testEnvironment": "jsdom"
}
Important note for jest >28
If you are using jest 28, you will need to install jest-environment-jsdom separately by either:
npm: npm i jest-environment-jsdom --save-dev
yarn: yarn add -D jest-environment-jsdom
Why?
By default, jest uses the node testEnvironment. This essentially makes any tests meant for a browser environment invalid.
jsdom is an implementation of a browser environment, which supports these types of UI tests.
For Jest version 28 and greater, jest-environment-jsdom was removed from the default jest installation to reduce package size.
Additional reading
jest testEnvironment documentation
Jest 28 breaking changes
This can be solved on a per-test-file basis by adding a #jest-environment docblock to the beginning of your file. For example:
/** #jest-environment jsdom */
import React from 'react'
import { render } from '#testing-library/react'
import Button from '.'
describe('Button', () => {
it('renders button without crashing', () => {
const label = 'test'
render(<Button label={label} />)
})
})
If your project has a mix of UI and non-UI files, this is often preferable to changing the entire project by setting "testEnvironment": "jsdom" within your package.json or Jest config. By skipping initializing the JSDom environment for non-UI tests, Jest can run your tests faster. In fact, that's why Jest changed the default test environment in Jest 27.
by default the value for testEnvironment is node which runs all test cases in node.js envioronment, but js-dom provides browser like enviornment. instead of adding jsdom value, you can even add file specific value like below which will work.
/**
* #jest-environment jsdom
*/
// the above comment helps
test('use jsdom in this test file', () => {
const element = document.createElement('div');
expect(element).not.toBeNull();
});
We can even add test file specific environments, please refer this link.
https://jestjs.io/docs/configuration#testenvironment-string
Try this.
module.exports = {
testEnvironment: 'jsdom',
}

Include full file path in esbuild compiled output

I want to get this file as a es module in my browser.
HelloApp.tsx
import React from "react";
import { render } from "react-dom";
function HelloApp(props: any): React.ReactElement {
return <div>Greetings</div>
}
render(
<HelloApp />,
document.getElementById("app")
);
I'm running npx esbuild ./**/*.tsx --outdir=esbuilt --format=esm
Output:
import React from "react";
import {render} from "react-dom";
function HelloApp(props) {
return /* #__PURE__ */ React.createElement("div", null, "I'm a component");
}
render(/* #__PURE__ */ React.createElement(HelloApp, null), document.getElementById("app"));
These imports aren't usable by the browser. Ideally, the path would be something like import React from "/scripts/react.js"
How do you tell esbuild to write imports like that?
The imports cannot be resolved as they are not included in the bundle. This should work if you add the --bundle argument. Also, you should use a specific file as the entry point for the bundle instead of passing a glob pattern.
Example:
npx esbuild src/app.tsx --bundle --outdir=esbuilt --format=esm
When generating a bundle, instead of --outdir you can use the --outfile argument to define the bundle file name. You probably also want to minify the bundle for the browser with the --minify argument, specify the target environment for the browsers you want to support with --target and you might want to add sourcemap support with --sourcemap.
The build command becomes:
npx esbuild src/app.tsx --bundle --minify --sourcemap --target=chrome58,firefox57,safari11,edge16 --outfile=esbuilt/app.js --format=esm

React, webpack and creating a bundle using a per build json?

We would like to create bundle.js from React and webpack, where a per build configuration is included and made available to the React code, but we are not sure how to go about doing this.
The idea would be to be to do something like:
npm run build -- config="config123.json"
And then have the generated bundle.js include that configuration, such that it could be used by the root container. Something like:
import React from 'react';
import ReactDOM from 'react-dom';
import RootContainer from './containers/main-container';
ReactDOM.render(
<RootContainer config={configPassedByBuildProcess}/>,
document.getElementById('app')
);
Is this possible and if so, how should we approach this?
No, I don't believe that you can pass a file on build, from what I know the only thing that you can pass is a string like an ENV variable. You could import the config123.json file like this
import React from 'react';
import ReactDOM from 'react-dom';
import RootContainer from './containers/main-container';
import config from '/path/to/config123.json'
ReactDOM.render(
<RootContainer config={config}/>,
document.getElementById('app')
);
You may need a JSON loader for importing JSON files
After a bit of experimenting, the solution I have found is to use Webpack's DefinePlugin, since this provides a way of doing string substitution during webpack's build phase.
In my React code I defined:
// Will be substituted by webpack's DefinePlugin. Not an error.
const bundleConfig = BUNDLE_CONFIG;
and then in my webpack.config.js I first load my config (hard coded for now):
// The path can be pulled in via 'process.args' and parsed as appropriate
const bundleConfig = fs.readFileSync('bundle-config/default-config.json', 'UTF-8');
module.exports = {
plugins: [
new webpack.DefinePlugin({
'BUNDLE_CONFIG': bundleConfig
})
]
}
Note that if for some reason we want to build with a missing or undefined bundleConfig, then the right substitution would likely be the string (not the keyword) 'undefined' , to keep the code valid, such that:
const bundleConfig = 'undefined';
module.exports = {
plugins: [
new webpack.DefinePlugin({
'BUNDLE_CONFIG': bundleConfig
})
]
}
Just one extra thing was the configuration of the package.json (scripts section only):
{
"scripts": {
"build": "run() { NODE_ENV=production && webpack -p $1; }; run",
}
}
This allow passing in the path to the webpack.conf.js script, when we call:
npm run build /path/to/config.json

Categories