Created
September 17, 2021 11:22
-
-
Save kpunith8/51d43ed6adaaa5698e49ed2cab3f514e to your computer and use it in GitHub Desktop.
Jest setup with babel and webpack-5 (React testing library, Jest)
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
const requiredPlugins = [ | |
'@emotion', | |
'@babel/plugin-syntax-dynamic-import', | |
'@babel/plugin-proposal-optional-chaining', | |
] | |
const plugins = { | |
development: requiredPlugins, | |
test: requiredPlugins, | |
production: requiredPlugins, | |
} | |
const requiredPresets = [ | |
[ | |
'@babel/preset-react', | |
{runtime: 'automatic', importSource: '@emotion/react'}, | |
], | |
] | |
const development = [ | |
...requiredPresets, | |
[ | |
'@babel/preset-env', | |
{ | |
targets: 'last 1 chrome version', | |
useBuiltIns: 'usage', | |
corejs: 3, | |
shippedProposals: true, | |
}, | |
], | |
] | |
const presets = { | |
development, | |
test: development, | |
production: [ | |
...requiredPresets, | |
[ | |
'@babel/preset-env', | |
{ | |
targets: 'defaults', | |
useBuiltIns: 'usage', | |
corejs: 3, | |
shippedProposals: true, | |
}, | |
], | |
], | |
} | |
module.exports = api => { | |
const env = api.env() | |
return { | |
plugins: plugins[env], | |
presets: presets[env], | |
sourceMap: true, | |
} | |
} |
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
/* istanbul ignore file */ | |
const fileMock = '' | |
export default fileMock |
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
// Default timezone for testing | |
process.env.TZ = 'UTC' | |
module.exports = { | |
clearMocks: true, | |
collectCoverage: true, | |
coverageDirectory: 'coverage', | |
moduleNameMapper: { | |
'.svg': '<rootDir>/config/jest/mocks/file-mock.js', | |
'\\.css$': 'identity-obj-proxy', // Package | |
}, | |
setupFilesAfterEnv: ['./config/jest/jest.setup.js'], | |
testEnvironment: './config/jest/jest.environment.js', | |
moduleFileExtensions: ['js', 'yml'], | |
transform: { | |
'\\.js?$': 'babel-jest', // Install these packages | |
'\\.yml$': 'yaml-jest', | |
}, | |
transformIgnorePatterns: ['<root-dir>/node_modules(?!/@other-packages|d3-.*)/'], | |
} |
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
/* istanbul ignore file */ | |
const JSDOMEnvironment = require('jest-environment-jsdom') | |
const fs = require('fs') | |
const yaml = require('js-yaml') | |
const I18n = require('i18n-js') | |
const _ = require('lodash') | |
class JestEnvironment extends JSDOMEnvironment { | |
async setup() { | |
await super.setup() | |
// Load I18n translations for Jest, since webpack isn't running | |
I18n.locale = 'en' | |
I18n.translations = yaml.load( | |
fs.readFileSync('./config/locales/en.yml', 'utf8') | |
) | |
this.global.I18n = I18n | |
this.global._ = _ | |
} | |
} | |
module.exports = JestEnvironment |
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
/* istanbul ignore file */ | |
import sinon from 'sinon' | |
import '@testing-library/jest-dom' | |
import { | |
act, | |
configure, | |
fireEvent, | |
prettyDOM, | |
render, | |
waitFor, | |
} from '@testing-library/react' | |
// Sinon should only be used for stubbing object properties | |
global.sinon = sinon | |
// eslint-disable-next-line no-console | |
const consoleError = console.error | |
// eslint-disable-next-line no-undef | |
jest.spyOn(console, 'error').mockImplementation((...args) => { | |
const message = _.head(_.castArray(args)) | |
if ( | |
message.indexOf('Request to server failed with') < 0 && | |
message.indexOf( | |
'React will try to recreate this component tree from scratch using the error boundary you provided' | |
) < 0 && | |
message.indexOf( | |
"Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more: https://reactjs.org/link/switch-to-createroot" | |
) | |
) | |
consoleError(...args) | |
}) | |
// eslint-disable-next-line no-console | |
const consoleWarn = console.warn | |
// eslint-disable-next-line no-undef | |
jest.spyOn(console, 'warn').mockImplementation((...args) => { | |
const message = _.head(_.castArray(args)) | |
if ( | |
// https://github.com/ReactTraining/react-router/issues/7460 | |
message.indexOf( | |
'You should call navigate() in a useEffect, not when your component is first rendered' | |
) < 0 | |
) | |
consoleWarn(...args) | |
}) | |
configure({ | |
getElementError: message => { | |
const error = new Error(message) | |
error.name = 'TestingLibraryElementError' | |
// This stack trace gets drowned out by the tippy style block at the top of | |
// our index.html page, and just adds a ton of noise to our test output. | |
// It'd be nice to look into how we might be able to focus this in on the | |
// relevant part of the DOM, filtering out the boilerplate around it. Maybe | |
// there's something we could do with a component's root element className | |
// to scope the output? | |
// | |
// This shouldn't impact unit tests that aren't using RTL. | |
error.stack = null | |
return error | |
}, | |
}) | |
global.act = act | |
global.fireEvent = fireEvent | |
global.prettyDOM = prettyDOM | |
global.render = (...args) => { | |
const {container, ...rest} = render(...args) | |
const find = container.querySelector.bind(container) | |
const findAll = container.querySelectorAll.bind(container) | |
return {container, find, findAll, ...rest} | |
} | |
global.waitFor = waitFor | |
global.lastCall = fn => fn.mock.calls[fn.mock.calls.length - 1] | |
global.config = { | |
api: '/api' | |
} | |
global.cache = { | |
// Default values for an app | |
} |
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
{ | |
"scripts": { | |
"chk": "check-dependencies", | |
"build": "rm -rf ./public && NODE_ENV=production webpack --config ./webpack.config.prod.js", | |
"start": "npm run chk && rm -rf ./public && NODE_ENV=development webpack serve --config ./webpack.config.dev.js", | |
"test": "npm run chk && npx jest --maxWorkers=50%", | |
"test:silent": "npx jest --maxWorkers=50% --reporters jest-silent-reporter --collectCoverage=false", | |
"test:ci": "npx jest --runInBand", | |
"test:watch": "npx jest --watch", | |
"test:dev": "npx majestic", | |
"test:debug": "node --inspect node_modules/.bin/jest --watch --runInBand" | |
}, | |
"devDependencies": { | |
"@babel/cli": "7.13.0", | |
"@babel/core": "7.13.8", | |
"@babel/plugin-proposal-object-rest-spread": "7.13.8", | |
"@babel/plugin-proposal-optional-chaining": "7.13.8", | |
"@babel/plugin-syntax-dynamic-import": "7.8.3", | |
"@babel/preset-env": "7.13.9", | |
"@babel/preset-react": "7.12.13", | |
"@emotion/babel-plugin": "11.1.2", | |
"terser-webpack-plugin": "5.1.1", | |
"webpack": "5.24.2", | |
"webpack-cli": "4.5.0", | |
"webpack-config-utils": "2.3.1", | |
"webpack-dev-server": "3.11.2", | |
"check-dependencies": "1.1.0", | |
} | |
} |
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
// Webpack Version 5.50 | |
const {resolve} = require('path') | |
const _ = require('lodash') | |
const {copySync, readFileSync} = require('fs-extra') | |
const webpack = require('webpack') | |
const MiniCssExtractPlugin = require('mini-css-extract-plugin') | |
const {getIfUtils} = require('webpack-config-utils') | |
const {mkdirSync, processTemplates} = require('./scripts/common') // https://gist.github.com/kpunith8/0ac775bb3bcf42e0f0b2a5782f656f0c#file-common-js | |
const getPackages = () => | |
JSON.parse(readFileSync(process.env.npm_package_json, 'utf8')) | |
const {ifProduction} = getIfUtils(process.env.NODE_ENV) | |
const publicDir = resolve(__dirname, 'public') | |
const host = process.env.HOST || 'localhost' | |
class ProcessTemplatesPlugin { | |
apply(compiler) { | |
const logger = compiler.getInfrastructureLogger('process-templates-plugin') | |
compiler.hooks.done.tap('ProcessTemplatesPlugin', () => { | |
try { | |
processTemplates(process.env.APP_ENV) | |
} catch (err) { | |
logger.error(err) | |
process.exit(1) | |
} | |
}) | |
} | |
} | |
// This allows us to override @monaco-editor/react's default behavior of loading monaco from the JSDelivr CDN | |
class CopyMonacoPlugin { | |
apply(compiler) { | |
const logger = compiler.getInfrastructureLogger('copy-monaco-plugin') | |
compiler.hooks.done.tap('CopyMonacoPlugin', () => { | |
try { | |
const version = getPackages().dependencies['monaco-editor'] | |
copySync( | |
'./node_modules/monaco-editor/min/vs', | |
mkdirSync(`./public/vs/${version}`) | |
) | |
} catch (err) { | |
logger.error(err) | |
process.exit(1) | |
} | |
}) | |
} | |
} | |
// Builds a webpack compatible alias map that can be used to force webpack to | |
// use our dependencies in lieu of a module's installed dependencies. This is | |
// especially useful when `npm link` is being used. | |
const modulePeersAlias = moduleName => { | |
const modulePkg = require(resolve( | |
__dirname, | |
`node_modules/${moduleName}/package.json` | |
)) | |
return _.transform( | |
modulePkg.peerDependencies, | |
(result, _value, key) => { | |
const depName = _.head(key.split('/')) | |
result[depName] = resolve(__dirname, `node_modules/${depName}`) | |
}, | |
{} | |
) | |
} | |
module.exports = { | |
devServer: { | |
contentBase: publicDir, | |
historyApiFallback: true, | |
host, | |
port: 8181, | |
}, | |
devtool: 'eval-source-map', | |
entry: { | |
application: resolve(__dirname, 'app', 'index.js'), | |
}, | |
mode: 'development', | |
module: { | |
strictExportPresence: true, | |
rules: [ | |
{ | |
test: /\.js$/, | |
exclude: /node_modules/, | |
loader: 'babel-loader', | |
options: {cacheDirectory: true}, | |
}, | |
{ | |
test: /\.css$/, | |
use: [MiniCssExtractPlugin.loader, 'css-loader'], | |
}, | |
{ | |
test: /\.yml$/, | |
use: ['json-loader', 'yaml2json-loader'], | |
}, | |
{ | |
test: /\.ico$/, | |
include: [resolve(__dirname, 'app', 'assets', 'favicons')], | |
loader: 'file-loader', | |
options: { | |
name: 'favicons/[name].[ext]', | |
}, | |
}, | |
{ | |
test: /\.(png|svg)$/, | |
include: [resolve(__dirname, 'app', 'assets')], | |
loader: 'file-loader', | |
options: { | |
name: 'images/[name].[ext]', | |
}, | |
}, | |
{ | |
test: /\.svg$/, | |
exclude: [resolve(__dirname, 'app', 'assets')], | |
loader: 'svg-react-loader', | |
}, | |
{ | |
test: /\.(woff2|ttf)$/, | |
loader: 'file-loader', | |
options: { | |
name: () => | |
ifProduction( | |
'fonts/[name][contenthash].[ext]', | |
'fonts/[name].[ext]' | |
), | |
}, | |
}, | |
], | |
}, | |
output: { | |
filename: 'javascripts/[name].js', | |
path: publicDir, | |
publicPath: '/', | |
}, | |
plugins: _.compact([ | |
new MiniCssExtractPlugin({ | |
filename: ifProduction( | |
'stylesheets/[name].[contenthash].css', | |
'stylesheets/[name].css' | |
), | |
}), | |
new webpack.ProvidePlugin({ | |
I18n: 'i18n-js', | |
_: 'lodash', | |
}), | |
new ProcessTemplatesPlugin(), | |
new CopyMonacoPlugin(), | |
]), | |
resolve: { | |
alias: { | |
...modulePeersAlias('package-name'), | |
}, | |
extensions: ['.js', '.css', '.svg'], | |
fallback: { | |
buffer: false, | |
fs: false, | |
}, | |
modules: [resolve(__dirname, 'app'), 'node_modules'], | |
symlinks: false, | |
}, | |
watchOptions: { | |
ignored: /node_modules(?!\/@local-package)/, // used to link locally for development | |
}, | |
infrastructureLogging: { | |
level: 'verbose', | |
debug: /process-templates-plugin|copy-monaco-plugin/, | |
}, | |
} |
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
const webpack = require('webpack') | |
const {resolve} = require('path') | |
const TerserPlugin = require('terser-webpack-plugin') | |
const {StatsWriterPlugin} = require('webpack-stats-plugin') | |
const webpackDevConfig = require('./webpack.config.dev') | |
const webpackProdConfig = { | |
...webpackDevConfig, | |
output: { | |
chunkFilename: 'javascripts/[id].chunk.[contenthash].js', | |
filename: 'javascripts/[name].[contenthash].js', | |
path: resolve(__dirname, 'public'), | |
publicPath: '/', | |
}, | |
devtool: 'source-map', | |
mode: 'production', | |
optimization: { | |
minimize: true, | |
minimizer: [ | |
new webpack.DefinePlugin({ | |
'process.env.NODE_ENV': JSON.stringify('production'), | |
}), | |
new TerserPlugin({ | |
parallel: true, | |
terserOptions: { | |
output: { | |
ascii_only: true, | |
}, | |
}, | |
}), | |
], | |
}, | |
plugins: [ | |
...webpackDevConfig.plugins, | |
new StatsWriterPlugin({ | |
filename: 'manifest.json', | |
transform: data => | |
JSON.stringify( | |
{ | |
css: data.assetsByChunkName.application[0], | |
js: data.assetsByChunkName.application[1], | |
}, | |
null, | |
2 | |
), | |
}), | |
], | |
} | |
module.exports = [ | |
{ | |
...webpackProdConfig, | |
entry: { | |
application: resolve(__dirname, 'app', 'index.js'), | |
}, | |
}, | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment