well I a have a setup with Webpack and Jest for testing react and also using Webpack's aliases using the "@" sign for the directory of my project which is "src/app", I know also it must be mapped into jest.config using the "moduleNameMapper" property to properly align with that alias.
the problem is that there's an <Icon />
component which intern imports dynamically .svg icons using ES dynamic import.
this is the Regex for that mapping for jest.config.js
'^@/': '/src/app/$1'
but it breaks ONLY when testing
everything works fine except when testing
my folder structure is the following:
config/
webpack-config/
src/
app/
assets/
img/
some images
svg/
icons/
cart.svg
components/
base/
core/
shared/
...
client/
...
server/
...
this is the Webpack alias I'm using:
module.exports = {
extensions: ['.js', '.mjs', '.json', '.jsx', '.css'],
modules: paths.resolveModules,
alias: {
'@': paths.srcApp, // that just point
}
};
this is my jest.config file:
const paths = require('./config/paths');
module.exports = {
verbose: true,
collectCoverageFrom: ['src/**/*.{js,jsx,mjs}'],
setupFiles: [
'<rootDir>/node_modules/regenerator-runtime/runtime',
'<rootDir>/config/polyfills.js',
],
// A list of paths to modules that run some code to configure or set up the testing framework before each test.
setupFilesAfterEnv: ['<rootDir>config/jest/setup.js'],
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}',
'<rootDir>/src/**/?(*.)(spec|test).{js,jsx,mjs}',
],
testEnvironment: 'node',
testURL: 'http://localhost',
modulePaths: ['src'],
moduleNameMapper: {
'^@[/](.+)': '<rootDir>/src/app/$1',
},
transform: {
'^.+\\.(js|jsx|mjs)$': '<rootDir>/node_modules/babel-jest',
'^.+\\.css$': '<rootDir>/config/jest/cssTransform.js',
'^(?!.*\\.(js|jsx|mjs|css|json|svg)$)': '<rootDir>/config/jest/fileTransform.js',
},
transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$'],
moduleDirectories: paths.resolveModules,
moduleFileExtensions: ['js', 'json', 'jsx', 'node', 'mjs'],
};
this is the component I'm testing
// @flow strict
import React, { useState } from 'react';
import './Icon.scss';
type PropsType = {
name: string,
selected?: string,
size: number | string
};
const Icon = ({ name, size }: PropsType) => {
const [iconPath, setIconPath] = useState('');
(async () => {
const icon = await import(`@/assets/svg/icons/${name}.svg`);
setIconPath(icon.default);
})();
return (
<img
alt={`icon ${name}`}
className="icon"
style={{
height: `${size}px`,
width: `${size}px`,
}}
src={iconPath}
/>
);
};
Icon.defaultProps = {
size: 16,
};
export default Icon;
and this it's test
// @flow strict
import React from 'react';
import {
render,
} from 'react-testing-library';
import Icon from './Icon';
describe('Icon', () => {
it('should have "name" with the path of the icon', () => {
const { container } = render(<Icon name="cart" />);
});
});
and this is the output
Configuration error:
Could not locate module @/assets/svg/icons/${name}.svg mapped as:
/Users/jero/Documents/my-shit/react-shop/src/app/assets/svg/icons/${name}.svg.
Please check your configuration for these entries:
{
"moduleNameMapper": {
"/^@[\/](.+)/": "/Users/jero/Documents/my-shit/react-shop/src/app/$1"
},
"resolver": null
}
the interesting thing is that when I use this regex for jest.config
moduleNameMapper: {
'/^@\/(.*)$/': '<rootDir>/src/app$1'
}
the error is gone about, but it breaks when a component imports other components when testing:
FAIL src/app/components/shared/ImgLoader/ImgLoader.test.js
● Test suite failed to run
Cannot find module '@/components/base/Spinner/Spinner' from 'ImgLoader.js'
However, Jest was able to find:
'./ImgLoader.js'
'./ImgLoader.scss'
'./ImgLoader.test.js'
You might want to include a file extension in your import, or update your 'moduleFileExtensions', which is currently ['js', 'json', 'jsx', 'node', 'mjs'].
See https://jestjs.io/docs/en/configuration#modulefileextensions-array-string
2 | import React, { useState } from 'react';
3 |
> 4 | import Spinner from '@/components/base/Spinner/Spinner';
| ^
5 |
6 | import './ImgLoader.scss';
7 |
at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:230:17)
at Object.<anonymous> (src/app/components/shared/ImgLoader/ImgLoader.js:4:1)
and this is the <ImageLoader />
// @flow strict
import React, { useState } from 'react';
import Spinner from '@/components/base/Spinner/Spinner';
import './ImgLoader.scss';
type PropsType = {
src: string
};
function ImgLoader({ src }: PropsType) {
const [imgObj, setImg] = useState({ img: null, isLoading: true });
const image = new Image();
image.onload = () => setImg({ img: image.src, isLoading: false });
image.src = src;
return (
imgObj.isLoading ? (<Spinner />) : (<img className="imgLoader img-fluid" src={imgObj.img} alt="img" />)
);
}
export default ImgLoader;
and its test offcourse
// @flow strict
import React from 'react';
import {
render,
} from 'react-testing-library';
import ImgLoader from './ImgLoader';
describe('ImgLoader', () => {
it('tests \'src\'', () => {
const src = 'https://static.street-beat.ru/upload/resize_cache/iblock/d69/450_450_1/d699afc7b3428f2f51c2f2de6665b506.jpg';
const { container } = render(<ImgLoader src={src} />);
console.log('container', container);
});
});
I believe is something related with the dynamic import?
here's how I fixed it, the problem is that Jest for some reason, it complains when using template literals inside the dynamic import. so to fix it just use normal string concatenation and that's it.
(async () => {
// $FlowIgnore
const icon = await import('@/assets/svg/icons/' + name + '.svg');
setIconPath(icon.default);
})();
This can also be solved without removing template literals by replacing Jest's --watch
flag with --watchAll
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With