Hi guys,
If you use require.context
within a React component the storyshots will fail with TypeError: require.context is not a function
.
Just write require.context()
in any of the component.
This is mine:
import React from 'react';
import PropTypes from 'prop-types';
import DefaultProps from '../../helpers/default-props';
const lineIcons = require.context('../../assets/icons/Line', true, /.+\.svg$/);
const solidIcons = require.context('../../assets/icons/Solid', true, /.+\.svg$/);
const requireAll = requireContext => requireContext.keys().map(requireContext);
const toObjectNames = (state, icon) => ({ ...state, [icon.default.id]: icon.default.id });
const icons = {
Line: requireAll(lineIcons).reduce(toObjectNames, {}),
Solid: requireAll(solidIcons).reduce(toObjectNames, {}),
};
const Icon = ({
glyph, type = 'Line', width = 14, height = 14, className = 'icon', fill = 'currentColor', ...rest
}) => (
<svg {...rest} className={className} width={width} fill={fill} height={height}>
<use xlinkHref={`#${type} ${glyph}`} />
</svg>
);
Icon.propTypes = {
...DefaultProps,
/** icon name, just exactly how the file is named */
glyph: PropTypes.string.isRequired,
/** main folder where lays this file, could be `Line | Solid` */
type: PropTypes.oneOf(['Line', 'Solid']),
/** width, which is set to <svg> */
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** height, which is set to <svg> */
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** fill (color), which is set to <svg> */
fill: PropTypes.string,
};
export default Icon;
export {
icons,
};
Any OS any build.
// content of storyshots.test.js
import initStoryshots from '@storybook/addon-storyshots';
global.window = global;
window.addEventListener = () => {};
window.requestAnimationFrame = () => {
throw new Error('requestAnimationFrame is not supported in Node');
};
initStoryshots({ /* configuration options */ });
require.context
is a webpack-specific feature, so it doesn't work in jest. You can try to mock it somehow
Yes I tried this mock, but then it gives syntax error of just running start-storybook -p 9001 -c .storybook
. Some incorrect left-hand assignment. I prefer to keep main job working, tests are something additional.
If you can help mocking it in different way, that main storybook could be working, would be great.
You could try something like this:
https://github.com/mzgoddard/jest-webpack
it gives syntax error of just running
start-storybook -p 9001 -c .storybook
That's weird. Mocks should only be applied when running tests
@AlexanderTserkovniy Did you try to apply the mock in the storyshots.test.js
file?
Yes, I have tried quite much approaches. Let me show you what I get.
1) If I put it in the .storybook/storyshots.test.js
it just fails the same way. Basically this code is not get invoked before the component code is run.
import initStoryshots from '@storybook/addon-storyshots';
global.window = global;
window.addEventListener = () => {};
window.requestAnimationFrame = () => {
throw new Error('requestAnimationFrame is not supported in Node');
};
// This condition actually should detect if it's an Node environment
if (typeof require.context === 'undefined') {
const fs = require('fs');
const path = require('path');
require.context = (base = '.', scanSubDirectories = false, regularExpression = /\.js$/) => {
const files = {};
function readDirectory(directory) {
fs.readdirSync(directory).forEach((file) => {
const fullPath = path.resolve(directory, file);
if (fs.statSync(fullPath).isDirectory()) {
if (scanSubDirectories) readDirectory(fullPath);
return;
}
if (!regularExpression.test(fullPath)) return;
files[fullPath] = true;
});
}
readDirectory(path.resolve(__dirname, base));
function Module(file) {
return require(file);
}
Module.keys = () => Object.keys(files);
return Module;
};
}
initStoryshots({ /* configuration options */ });
> jest && echo 'use jest after this is resolved https://github.com/storybooks/storybook/issues/2487'
FAIL .storybook/storyshots.test.js
โ Test suite failed to run
TypeError: require.context is not a function
at Object.<anonymous> (src/components/Icon/Icon.js:5:25)
at Object.<anonymous> (.storybook/stories/Icon.js:3:13)
at node_modules/@storybook/addon-storyshots/dist/require_context.js:39:24
at Array.forEach (<anonymous>)
at requireModules (node_modules/@storybook/addon-storyshots/dist/require_context.js:34:9)
at Function.newRequire.context (node_modules/@storybook/addon-storyshots/dist/require_context.js:90:5)
at evalmachine.<anonymous>:14:19
at runWithRequireContext (node_modules/@storybook/addon-storyshots/dist/require_context.js:102:3)
at testStorySnapshots (node_modules/@storybook/addon-storyshots/dist/index.js:105:35)
at Object.<anonymous> (.storybook/storyshots.test.js:45:31)
at Generator.next (<anonymous>)
at new Promise (<anonymous>)
at Generator.next (<anonymous>)
at <anonymous>
at process._tickCallback (internal/process/next_tick.js:188:7)
console.info node_modules/@storybook/react/dist/server/babel_config.js:73
=> Loading custom .babelrc
Test Suites: 1 failed, 1 total
Tests: 0 total
Snapshots: 0 total
Time: 3.978s
Ran all test suites.
2) When I put it into Icon
component:
import React from 'react';
import PropTypes from 'prop-types';
import DefaultProps from '../../helpers/default-props';
// This condition actually should detect if it's an Node environment
if (typeof require.context === 'undefined') {
const fs = require('fs');
const path = require('path');
require.context = (base = '.', scanSubDirectories = false, regularExpression = /\.js$/) => {
const files = {};
function readDirectory(directory) {
fs.readdirSync(directory).forEach((file) => {
const fullPath = path.resolve(directory, file);
if (fs.statSync(fullPath).isDirectory()) {
if (scanSubDirectories) readDirectory(fullPath);
return;
}
if (!regularExpression.test(fullPath)) return;
files[fullPath] = true;
});
}
readDirectory(path.resolve(__dirname, base));
function Module(file) {
return require(file);
}
Module.keys = () => Object.keys(files);
return Module;
};
}
const lineIcons = require.context('../../assets/icons/Line', true, /.+\.svg$/);
const solidIcons = require.context('../../assets/icons/Solid', true, /.+\.svg$/);
const requireAll = requireContext => requireContext.keys().map(requireContext);
const toObjectNames = (state, icon) => ({ ...state, [icon.default.id]: icon.default.id });
const icons = {
Line: requireAll(lineIcons).reduce(toObjectNames, {}),
Solid: requireAll(solidIcons).reduce(toObjectNames, {}),
};
const Icon = ({
glyph, type = 'Line', width = 14, height = 14, className = 'icon', fill = 'currentColor', ...rest
}) => (
<svg {...rest} className={className} width={width} fill={fill} height={height}>
<use xlinkHref={`#${type} ${glyph}`} />
</svg>
);
Icon.propTypes = {
...DefaultProps,
/** icon name, just exactly how the file is named */
glyph: PropTypes.string.isRequired,
/** main folder where lays this file, could be `Line | Solid` */
type: PropTypes.oneOf(['Line', 'Solid']),
/** width, which is set to <svg> */
width: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** height, which is set to <svg> */
height: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/** fill (color), which is set to <svg> */
fill: PropTypes.string,
};
export default Icon;
export {
icons,
};
then tests are passed PASS .storybook/storyshots.test.js. And command jest
just works.
BUT if I run the app (start-storybook -p 9001 -c .storybook
), I see this:
@storybook/react v3.2.16
=> Loading custom .babelrc
=> Loading custom addons config.
=> Loading custom webpack config (extending mode).
webpack built 1657d28027f144d8ba93 in 15220ms
Hash: 1657d28027f144d8ba93
Version: webpack 3.8.1
Time: 15220ms
Asset Size Chunks Chunk Names
92bbabfda96fb9e73100d90404d5383a.ttf 136 kB [emitted]
816c43ce4c83ecd53572f8ec92d85bc2.ttf 143 kB [emitted]
05e056450eab95f6c22a802ac56658be.svg 1.8 kB [emitted]
6e7eb8f5ed815b0f3865005bba91ccda.svg 721 bytes [emitted]
static/preview.bundle.js 5.83 MB 0 [emitted] [big] preview
static/manager.bundle.js 3.64 MB 1 [emitted] [big] manager
static/preview.bundle.js.map 5.11 MB 0 [emitted] preview
static/manager.bundle.js.map 4.5 MB 1 [emitted] manager
[65] (webpack)/buildin/module.js 495 bytes {0} {1} [built]
[97] (webpack)/buildin/harmony-module.js 572 bytes {0} [built]
[98] ./node_modules/@storybook/react/dist/client/index.js 1.63 kB {0} [built]
[255] ./node_modules/@storybook/react/dist/server/config/polyfills.js 113 bytes {0} {1} [built]
[664] ./node_modules/@storybook/ui/dist/index.js 2.42 kB {1} [built]
[727] multi ./node_modules/@storybook/react/dist/server/config/polyfills.js ./.storybook/addons.js ./node_modules/@storybook/react/dist/client/manager/index.js 52 bytes {1} [built]
[728] ./.storybook/addons.js 171 bytes {1} [built]
[730] ./node_modules/@storybook/addon-links/register.js 30 bytes {1} [built]
[731] ./node_modules/@storybook/addon-options/register.js 131 bytes {1} [built]
[734] ./node_modules/@storybook/addon-knobs/register.js 28 bytes {1} [built]
[1002] ./node_modules/@storybook/react/dist/client/manager/index.js 404 bytes {1} [built]
[1189] multi ./node_modules/@storybook/react/dist/server/config/polyfills.js ./node_modules/@storybook/react/dist/server/config/globals.js (webpack)-hot-middleware/client.js?reload=true ./.storybook/config.js 64 bytes {0} [built]
[1190] ./node_modules/@storybook/react/dist/server/config/globals.js 105 bytes {0} [built]
[1191] (webpack)-hot-middleware/client.js?reload=true 7.04 kB {0} [built]
[1203] ./.storybook/config.js 505 bytes {0} [built]
+ 3307 hidden modules
WARNING in ./src/components/Icon/Icon.js
12:11-18 Critical dependency: require function is used in a way in which dependencies cannot be statically extracted
@ ./src/components/Icon/Icon.js
@ ./.storybook/stories/Icon.js
@ ./.storybook/stories .js$
@ ./.storybook/config.js
@ multi ./node_modules/@storybook/react/dist/server/config/polyfills.js ./node_modules/@storybook/react/dist/server/config/globals.js (webpack)-hot-middleware/client.js?reload=true ./.storybook/config.js
WARNING in ./src/components/Icon/Icon.js
16:2-9 Critical dependency: require function is used in a way in which dependencies cannot be statically extracted
@ ./src/components/Icon/Icon.js
@ ./.storybook/stories/Icon.js
@ ./.storybook/stories .js$
@ ./.storybook/config.js
@ multi ./node_modules/@storybook/react/dist/server/config/polyfills.js ./node_modules/@storybook/react/dist/server/config/globals.js (webpack)-hot-middleware/client.js?reload=true ./.storybook/config.js
WARNING in ./src/components/Icon/Icon.js
42:13-26 Critical dependency: the request of a dependency is an expression
@ ./src/components/Icon/Icon.js
@ ./.storybook/stories/Icon.js
@ ./.storybook/stories .js$
@ ./.storybook/config.js
@ multi ./node_modules/@storybook/react/dist/server/config/polyfills.js ./node_modules/@storybook/react/dist/server/config/globals.js (webpack)-hot-middleware/client.js?reload=true ./.storybook/config.js
ERROR in ./src/components/Icon/Icon.js
Module not found: Error: Can't resolve 'fs' in '/ui-components/src/components/Icon'
@ ./src/components/Icon/Icon.js 13:11-24
@ ./.storybook/stories/Icon.js
@ ./.storybook/stories .js$
@ ./.storybook/config.js
@ multi ./node_modules/@storybook/react/dist/server/config/polyfills.js ./node_modules/@storybook/react/dist/server/config/globals.js (webpack)-hot-middleware/client.js?reload=true ./.storybook/config.js
and in browser it shows this: Uncaught ReferenceError: Invalid left-hand side in assignment
http://prntscr.com/hnt9mu.
ReferenceError: http://prntscr.com/hnta5c
Compiled code:
// This condition actually should detect if it's an Node environment
if (typeof !(function webpackMissingModule() { var e = new Error("Cannot find module \".\""); e.code = 'MODULE_NOT_FOUND'; throw e; }()).context === 'undefined') {
var fs = __webpack_require__(!(function webpackMissingModule() { var e = new Error("Cannot find module \"fs\""); e.code = 'MODULE_NOT_FOUND'; throw e; }()));
var path = __webpack_require__(1307);
!(function webpackMissingModule() { var e = new Error("Cannot find module \".\""); e.code = 'MODULE_NOT_FOUND'; throw e; }()).context = function () {
var base = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : '.';
var scanSubDirectories = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
var regularExpression = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : /\.js$/;
var files = {};
function readDirectory(directory) {
fs.readdirSync(directory).forEach(function (file) {
var fullPath = path.resolve(directory, file);
if (fs.statSync(fullPath).isDirectory()) {
if (scanSubDirectories) readDirectory(fullPath);
return;
}
if (!regularExpression.test(fullPath)) return;
files[fullPath] = true;
});
}
readDirectory(path.resolve(__dirname, base));
function Module(file) {
return !(function webpackMissingModule() { var e = new Error("Cannot find module \".\""); e.code = 'MODULE_NOT_FOUND'; throw e; }());
}
Module.keys = function () {
return Object.keys(files);
};
return Module;
};
}
// Fails on !(function webpackMissingModule() { var e = new Error("Cannot find module \".\""); e.code = 'MODULE_NOT_FOUND'; throw e; }()).context = function () {
And I wish to keep such polyfills outside the component, so anyway this is bad way to fix.
Would you help?
The problem is that require.context
is not actually a runtime function, it's rather a hint for webpack that gets replaced by an actual context object.
Did you try the first answer? Maybe combining the separate file approach from it with the stub function from second one
https://stackoverflow.com/questions/38332094/how-can-i-mock-webpacks-require-context-in-jest/42439030#42439030
Will try and return with results, thanks. Just remember that it was not so neat to try it.
BTW @ndelangen (but thanks).
You could try something like this:
https://github.com/mzgoddard/jest-webpack
Fails on https://github.com/mzgoddard/jest-webpack/issues/21.
By the way, how does your story for icon look like?
import React from 'react';
import { storiesOf } from '@storybook/react';
import * as Icon from '../../src/components/Icon/Icon';
import Colors from '../../src/common-styles/colors.pcss';
storiesOf('Icon', module)
.add('Icons set', () => {
const LineIcons = Object.keys(Icon.icons.Line)
.map(iconName => <Icon.default type='Line' width='36' height='36' glyph={iconName.replace(/Line /, '')} />);
const SolidIcons = Object.keys(Icon.icons.Solid)
.map(iconName => <Icon.default type='Solid' width='36' height='36' glyph={iconName.replace(/Solid /, '')} />);
return (
<section style={{ display: 'flex' }}>
<section style={{ flex: 1 }}>
<h2>Line icons</h2>
<ul>
{LineIcons.map(icon =>
<li key={Math.random()} style={{ display: 'flex', alignItems: 'center' }}>
{icon} {icon.props.glyph}
</li>)}
</ul>
</section>
<section style={{ flex: 1 }}>
<h2>Solid icons</h2>
<ul>
{SolidIcons.map(icon =>
<li key={Math.random()} style={{ display: 'flex', alignItems: 'center' }}>
{icon} {icon.props.glyph}
</li>)}
</ul>
</section>
</section>
)
})
.add('Default', () => <Icon.default glyph='Duck' />)
.add('With params', () => <Icon.default glyph='Servers' fill='red' width='34' height='34' />)
.add('With custom class', () => <Icon.default glyph='Barrel' className={Colors.G300} width='64' height='64' />)
.add('Solid icon', () => <Icon.default glyph='Barrel' className={Colors.G300} width='64' height='64' type='Solid' />);
So, icons.Line
is an object of this form?
{
'Line arrow': 'Line arrow',
'Line circle': 'Line circle',
...
}
Yes
You can mock it like this then:
https://gist.github.com/Hypnosphi/043d6f138c3dd2ad88472ea74f7bc12f
icons.js
gets executed in browser, and __mocks__/icons.js
in node
And you'll need to add this line to your Icon.js
file:
export { default as icons } from './icons';
Thanks, looks good. But I believe I need require.context for webpack to actually bundle all svgs. So if I go with fs, webpack will not add all svg files to the bundle.
files under __mocks__
directory are only for jest. You shouldn't require them yourself, so they shouldn't affect webpack in any way
Yes, got it, I mean this part require.context('../../assets/icons/Line', true, /.+\.svg$/)
is used by webpack to actually add files to bundle.
Sure, and it's preserved in icons.js
BTW you actually add those SVGs to bundle only when you call requireAll
For storyshots you don't need it because they don't perform actual browser rendering
Hi everyone! Seems like there hasn't been much going on in this issue lately. If there are still questions, comments, or bugs, please feel free to continue the discussion. Unfortunately, we don't have time to get to every issue. We are always open to contributions so please send us a pull request if you would like to help. Inactive issues will be closed after 60 days. Thanks!
Based on this stackOverflow answer, i created a transformer that add the polyfill only in files that use require.context
https://gist.github.com/ezidio/f64c59d46b19a3fe671a9ded6441de18
It work's for me
Jest v22.4.2
Hi @ezidio at the glance looks good. Thanks!
You can use this package to resolve the issue:
https://github.com/smrq/babel-plugin-require-context-hook
Here's how we are using it:
https://github.com/storybooks/storybook/blob/babel-7/scripts/jest.init.js#L10
@ndelangen I just had same issue, but it just suddenly happned without a indicator how. also your 2nd link is broken. only happen when I tried to start setup storyshot with jest and storybook
I had the same issue. Using CRA v3 (not ejected) with ts and I could not figure a way out to get it to work with the babel plugin or anything. Followed various solutions on different git issues but nothing worked for me, so I hacked it with these 3 lines.
This seems to solve it. No .babelrc or jest.config or setupTest.js or anything. I just add the above to replace the original line in .storybook/config.js
BEFORE
// .storybook.config.js
const req = require.context('../src/components', true, /.stories.tsx$/)
AFTER
// .storybook.config.js
import registerRequireContextHook from 'babel-plugin-require-context-hook/register';
registerRequireContextHook();
const req = global.__requireContext(__dirname, '../src/components', true, /.stories.tsx$/)
The tutorial at https://storybook.js.org/docs/testing/structural-testing/ helped me.
First run
yarn add --dev babel-plugin-macros
in terminal.
Then add this code in .babelrc
{
"plugins": ["macros"]
}
In .storybook/config.js, update to
import { configure } from '@storybook/react';
import requireContext from 'require-context.macro'; // <- add this
import '../src/index.css';
const req = requireContext('../src/components', true, /\.stories\.js$/); // <- change to this
function loadStories() {
req.keys().forEach(filename => req(filename));
}
configure(loadStories, module);
I stumbled upon require-context.macro
a few days after my initial post up. Like @Hongbo-Miao said, that also works, but requires a .babelrc file inside .storybook
The tutorial at https://storybook.js.org/docs/testing/structural-testing/ helped me.
First run
yarn add --dev babel-plugin-macros
in terminal.
Then add this code in .babelrc
{ "plugins": ["macros"] }
In .storybook/config.js, update to
import { configure } from '@storybook/react'; import requireContext from 'require-context.macro'; // <- add this import '../src/index.css'; const req = requireContext('../src/components', true, /\.stories\.js$/); // <- change to this function loadStories() { req.keys().forEach(filename => req(filename)); } configure(loadStories, module);
I think there's no way to add a .babelrc
into CRA
Most helpful comment
I had the same issue. Using CRA v3 (not ejected) with ts and I could not figure a way out to get it to work with the babel plugin or anything. Followed various solutions on different git issues but nothing worked for me, so I hacked it with these 3 lines.
This seems to solve it. No .babelrc or jest.config or setupTest.js or anything. I just add the above to replace the original line in
.storybook/config.js
BEFORE
AFTER