I am using storybook interactions addon and the play functionality. This is my code:
export default {
component: MyComponent,
title: "My Component",
args: {
someId: "someId",
},
decorators: [
(Story) => (
<QueryClientProvider client={queryClient}>
<Header />
<Box sx={{ float: "right" }}>
<Layout>
<Story />
</Layout>
</Box>
</QueryClientProvider>
),
],
} as ComponentMeta<typeof MyComponent>;
const Template: ComponentStory<typeof MyComponent> = (args: any) => {
return <MyComponent {...args} />;
};
export const MyComponentWithInteraction = Template.bind({});
MyComponentWithInteraction.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const addButton = canvas.getAllByText("+ Add")[0];
await userEvent.click(addButton);
};
My component appears on my storybook, but the interaction tab shows no interactions:
These are the packages related to storybook in my devDependencise
"#storybook/addon-essentials": "6.5.16",
"#storybook/addon-interactions": "6.5.16",
"#storybook/addon-knobs": "^6.4.0",
"#storybook/builder-webpack5": "6.5.16",
"#storybook/jest": "0.0.10",
"#storybook/manager-webpack5": "6.5.16",
"#storybook/react": "^6.5.15",
"#storybook/testing-library": "0.0.13",
"#types/storybook__addon-knobs": "^5.2.1",
"#types/storybook__react": "^5.2.1",
"msw-storybook-addon": "1.7.0",
"tsconfig-paths-webpack-plugin": "4.0.0",
"webpack": "5"
This is my main.ts
module.exports = {
stories: ["../src/**/*.stories.#(mdx|tsx|ts|jsx|js)"],
logLevel: "debug",
addons: [
"#storybook/addon-essentials",
"#storybook/addon-links",
"#storybook/addon-interactions",
"msw-storybook-addon",
],
framework: "#storybook/react",
features: {
interactionsDebugger: true,
},
webpackFinal: async (config, { configType }) => {
config.module.rules.push({
include: path.resolve(__dirname, "../src"),
});
config.resolve.plugins = [
new TsconfigPathsPlugin({
configFile: path.resolve(__dirname, "../tsconfig.json"),
}),
];
// Return the altered config
return config;
},
core: {
builder: "#storybook/builder-webpack5",
},
docs: {
autodocs: true,
},
};
If I add this line of code at the end of my story:
await expect(canvas.getByText("Some text")).toBeInTheDocument();
I see that it fails, because it is comparing it against the initial view before the click. But if I get rid of this line, it shows me the view that is the result of the click. I don't see any running in the control tab of the bottom panel either. This is strange.
I found the issue, although it is not shown in my question, I'm going to answer it here ,maybe I can save someone from spending 3-4 hours on this. my imports looked like this which was wrong:
import { within } from "#testing-library/react";
import userEvent from "#testing-library/user-event";
This is the right import
import { userEvent, within } from "#storybook/testing-library";
Related
I finally managed to get SB working with mui4, but after upgrading the mui5, i can't get it going.
installed with Npx sb init —builder webpack5
updated sb main.js as per
Complained that "compressible' module couldn't be found so installed that.
Ran npm i core-js#3.8.2 as gives out about es-weak-map + others not found.
Added web pack 5 as a dev dependency npm install webpack#5 --save-dev
npx sb#next upgrade --prerelease.
plus literally every other suggestion i came across online, which you can see in these files (leaving some comments in so you can see what else i tried in some cases)
.storybook>main.js
const path = require('path');
const toPath = filePath => path.join(process.cwd(), filePath);
module.exports = {
stories: ['../src/**/*.stories.mdx', '../src/**/*.stories.#(js|jsx|ts|tsx)'],
addons: [
'#storybook/addon-links',
'#storybook/addon-essentials',
'#storybook/addon-interactions',
// 'storybook-addon-designs',
// 'storybook-anima',
// '#storybook/addon-actions',
{
name: '#storybook/addon-docs',
options: {
configureJSX: true,
babelOptions: {},
sourceLoaderOptions: null,
transcludeMarkdown: true,
},
},
'#storybook/builder-webpack5',
],
framework: '#storybook/react',
core: {
// builder: '#storybook/builder-webpack5',
builder: 'webpack5',
},
typescript: { reactDocgen: false },
features: {
emotionAlias: false,
modernInlineRendering: true,
},
webpackFinal: async (config, { configType }) => {
config.resolve.modules = [path.resolve(__dirname, '..', '.'), 'node_modules'];
config.resolve.alias = {
...config.resolve.alias,
'#emotion/core': toPath('node_modules/#emotion/react'),
'emotion-theming': toPath('node_modules/#emotion/react'),
// 'core-js/modules/es.weak-map.js': toPath('core-js/modules/es6.weak-map.js'),
};
return config;
// return {
// ...config,
// resolve: {
// ...config.resolve,
// alias: {
// ...config.resolve.alias,
// '#emotion/core': toPath('node_modules/#emotion/react'),
// 'emotion-theming': toPath('node_modules/#emotion/react'),
// },
// },
// };
},
};
.package.json
{ "dependencies": {
"#auth0/nextjs-auth0": "^1.7.0",
"#emotion/react": "^11.8.2",
"#emotion/styled": "^11.8.1",
"#material-ui/core": "^4.12.0",
"#material-ui/icons": "^4.11.2",
"#material-ui/lab": "^4.0.0-alpha.60",
"#mui/icons-material": "^5.5.1",
"#mui/lab": "^5.0.0-alpha.75",
"#mui/material": "^5.5.3",
"#mui/styles": "^5.5.3",
"core-js": "^3.8.2",
"cors": "^2.8.5",
"next": "^12.1.4",
"react": "latest",
},
"devDependencies": {
"#storybook/addon-actions": "^6.5.0-alpha.59",
"#storybook/addon-essentials": "^6.5.0-alpha.59",
"#storybook/addon-interactions": "^6.5.0-alpha.59",
"#storybook/addon-links": "^6.5.0-alpha.59",
"#storybook/builder-webpack5": "^6.5.0-alpha.59",
"#storybook/manager-webpack5": "^6.5.0-alpha.59",
"#storybook/node-logger": "^6.5.0-alpha.59",
"#storybook/preset-create-react-app": "^4.1.0",
"#storybook/react": "^6.5.0-alpha.59",
"#storybook/testing-library": "^0.0.9",
"#svgr/webpack": "^6.2.1",
"compressible": "^2.0.18",
"webpack": "^5.72.0"
},
"eslintConfig": {
"overrides": [
{"files": ["**/*.stories.*"],
"rules": {"import/no-anonymous-default-export": "off"}
}]
},
"resolutions": {"#storybook/{app}/webpack": "^5"}
}
my Story
// import { ThemeProvider } from '#mui/styles';
import { ThemeProvider as MUIThemeProvider, createTheme } from '#mui/material/styles';
import { ThemeProvider } from 'emotion-theming';
import React from 'react';
//import theme from 'src/themes/theme.js';
// import DuplicateComponent from 'src/components/helpers/DuplicateComponent';
// import TickerTicket from 'src/components/modules/dashboard/TickerTicket';
import DataLanes from '.';
export default {
title: 'components/common/dataDisplay/DataLanes',
component: DataLanes,
};
export const Bare = () => (
// <MUIThemeProvider theme={theme}>
// <ThemeProvider theme={theme}>
<DataLanes borderBottom>
<div>1</div>
<div>2</div>
<div>3</div>
</DataLanes>
// </ThemeProvider>
// </MUIThemeProvider>
);
My Component
import { Box } from '#mui/material';
import clsx from 'clsx';
import React from 'react';
import PropTypes from 'prop-types';
import useStyles from './styles';
export default function DataLanes(props) {
const classes = useStyles();
const { borderBottom, borderTop, className, children, ...others } = props;
return (
<Box
className={clsx(classes.dataLanes, className, {
borderBottom: borderBottom,
borderTop: borderTop,
})}
{...others}
>
{children}
</Box>
);
}
its styling
import makeStyles from '#mui/styles/makeStyles';
const useStyles = makeStyles(theme => ({
dataLanes: {
width: '100%',
background: theme.palette.background.paper,
display: 'flex',
paddingTop: theme.spacing(1),
paddingBottom: theme.spacing(1),
overflowY: 'auto',
'& > * ': {
flexGrow: 1,
borderRight: `1px solid ${theme.palette.grey[300]}`,
},
'&.borderBottom': {
borderBottom: `1px solid ${theme.palette.grey[300]}`,
},
'&.borderTop': {
borderTop: `1px solid ${theme.palette.grey[300]}`,
},
},
}));
export default useStyles;
Any thoughts? It seems like it's the background: theme.palette.background.paper,
in the style.js.
For similar issues I encountered with MUI and storybook this change at the preview.js in the .storybook folder solved it.
.storybook>preview.js
import React from 'react';
import { addDecorator } from '#storybook/react';
import { ThemeProvider } from '#mui/material/styles';
import { theme } from '../src/Theme'; //. Please replace the path for the theme with yours.
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
};
addDecorator(story => <ThemeProvider theme={theme}>{story()}</ThemeProvider>);
So i'm building a design system for this company and the component system is displayed correctly in the design system storybook. But when I import it in the consumer app I get the following error
TypeError: Cannot read property 'width' of undefined in theme.border.width
Design System project:
/// src/components/Button/index.js
import { Wrapper } from './styles'
import React from 'react'
export const Button = (props) => (
<Wrapper {...props}>
{children}
</Wrapper>
)
.
///src/components/Button/styles.js
export const Wrapper = styled.button`
...
border-width: ${({theme}) => theme.border.width};
...
`
.
/// .storybook/preview.js
import React from 'react'
import { ThemeProvider } from 'styled-components'
import { GlobalStyle } from '../src/styles/globalStyles'
import { theme } from '../src/styles/theme'
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' }
}
export const decorators = [
Story => (
<ThemeProvider theme={theme}>
<GlobalStyle />
<Story />
</ThemeProvider>
)
]
.
/// src/index.js
...
export { Button } from 'components/Button'
export { theme } from 'style/theme'
...
.
/// babel.config.js
module.exports = {
presets: [
[
'#babel/env',
{
modules: false
}
],
['#babel/preset-react']
],
plugins: [
[
'styled-components',
{
ssr: true,
displayName: true,
preprocess: false,
minify: false
}
]
]
}
.
///rollup.config.js
import babel from 'rollup-plugin-babel'
import includePaths from 'rollup-plugin-includepaths'
import peerDepsExternal from 'rollup-plugin-peer-deps-external'
export default [
{
input: 'src/index.js',
output: [
{
name: 'rollup-tutorial',
file: 'dist/index.js',
format: 'es'
}
],
plugins: [
babel({
exclude: 'node_modules/**'
}),
includePaths({
paths: ['src'],
extensions: ['.js', '.json', '.html']
}),
peerDepsExternal()
]
}
]
.
/// package.json
{
"name": "design-system",
"main": "dist/index.js"
"files: ["dist"],
"scripts": {
...
"build": "rollup --config"
...
},
"devDependencies": {
"#babel/core": "^7.12.10",
"#babel/preset-react": "^7.12.10",
"#babel/preset-env": "^7.12.13",
"react": "17.0.1",
"react-dom": "17.0.1",
"rollup": "^2.38.4",
"rollup-plugin-babel": "^4.4.0",
"rollup-plugin-includepaths": "^0.2.4",
"rollup-plugin-peer-deps-external": "^2.2.4",
"styled-components": "^5.2.1",
},
"peerDependencies": {
"prop-types": ">= 15.x.x",
"react": ">= 17.x.x",
"styled-components": ">= 5.x.x",
}
}
App consumer NextJs default config
/// .babelrc
{
"presets": ["next/babel"],
"env": {
"development": {
"plugins": [
[
"styled-components",
{
"ssr": true,
"displayName": true
}
]
]
},
"production": {
"presets": [
"next/babel"
],
"plugins": [
[
"styled-components",
{
"ssr": true,
"displayName": false
}
]
]
}
}
}
.
///package.json
{
...
"dependencies": {
...
"design-system": "../design-system/",
...
}
...
}
.
/// pages/_app.js
import { ThemeProvider } from 'styled-components'
import { theme } from 'design-system'
function MyApp({ Component, pageProps }) {
console.log(theme)
return (
<ThemeProvider theme={theme}>
<Component {...pageProps} />
</ThemeProvider>
)
}
.
///pages/index.js
import { Button } 'design-system'
export default function Home() {
return (
<Button />
)
}
Note:
The console.log(theme) prints the expected theme however the button doesnt have access to the theme provided by styled-components.
I guess i'm missing some important configurantion in babel rollup or something
Note 2:
I already tried wrapping up the components in withComponent HOC
the only working solution was the next piece of code
///pages/index.js
import { Button } 'design-system'
import { useTheme } 'styled-components'
export default function Home() {
const theme = useTheme()
return (
<Button theme={theme} />
)
}
But this loses the whole point of using context
Thanks in advance
I've had a similar issue when developing a component library using storybook and emotion/styled-components. The issue was that I was using the styled-components' ThemeProvider from the consumer app, as you did in your _app.js. The solution was to create and export a custom ThemeProvider on the component library, using the library's styled-component package, like so:
import { ThemeProvider } from 'styled-components'
import { theme as defaultTheme } from '../src/styles/theme'
const CustomThemeProvider = ({ theme = defaultTheme, children }) => (
<ThemeProvider theme={theme}>
{children}
</ThemeProvider>
)
export { CustomThemeProvider }
and then use in your consumer app
/// pages/_app.js
import { CustomThemeProvider, theme } from 'design-system'
function MyApp({ Component, pageProps }) {
console.log(theme)
return (
<CustomThemeProvider theme={theme}>
<Component {...pageProps} />
</CustomThemeProvider>
)
}
The styled-components FAQ suggests to not ship your styled-components along with your bundle. This resolved the above mentioned issue for me.
https://styled-components.com/docs/faqs#i-am-a-library-author-should-i-bundle-styledcomponents-with-my-library
When I tried to create custom hook I have faced some issue.
ESLint hasn't suggest me subscribers for useEffect. (fetch hadn't been suggested and i filled it manually) I also tried eslint-plugin-react-hooks, it doesn't work too.
useEffect(() => {
fetch();
}, [fetch]);
Same issue for useCallback
full code of custom hook
import { useState, useEffect, useCallback } from "react";
import { server } from "./server";
interface State<TData> {
data: TData | null;
}
export const useQuery = <TData = any>(query: string) => {
const [state, setState] = useState<State<TData>>({
data: null,
});
const fetch = useCallback(() => {
const fetchApi = async () => {
const { data } = await server.fetch<TData>({ query });
setState({ data });
console.log(`${data} loaded (useQuery hook)`);
};
fetchApi();
}, [query]); // HERE
useEffect(() => {
fetch();
}, [fetch]); // AND HERE
return { ...state, refetch: fetch };
};
ESLint config
{
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:#typescript-eslint/recommended",
"prettier/#typescript-eslint",
"plugin:prettier/recommended"
],
"plugins": ["react", "#typescript-eslint", "prettier"],
"env": {
"browser": true,
"jasmine": true,
"jest": true
},
"rules": {
"prettier/prettier": [
"error",
{
"endOfLine": "auto"
}
]
},
"settings": {
"react": {
"pragma": "React",
"version": "detect"
}
},
"parser": "#typescript-eslint/parser"
}
devDependencies
"devDependencies": {
"#typescript-eslint/eslint-plugin": "^3.0.2",
"#typescript-eslint/parser": "^3.0.2",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-prettier": "^3.1.3",
"prettier": "^2.0.5"
}
● Test suite failed to run
Jest encountered an unexpected token
This usually means that you are trying to import a file which Jest cannot parse, e.g. it's not plain JavaScript.
By default, if Jest sees a Babel config, it will use that to transform your files, ignoring "node_modules".
Here's what you can do:
• To have some of your "node_modules" files transformed, you can specify a custom "transformIgnorePatterns" in your config.
• If you need a custom transformation specify a "transform" option in your config.
• If you simply want to mock your non-JS modules (e.g. binary assets) you can stub them out with the "moduleNameMapper" config option.
You'll find more details and examples of these config options in the docs:
https://jestjs.io/docs/en/configuration.html
Details:
SyntaxError: C:\Projects\myProject\src\__tests__\Event.test.js: Unexpected token (354:4)
352 |
353 | it('renders without errors', (done) => {
> 354 | const { getByTestId } = render(
| ^
355 | <Router history={history}>
356 | <Home />
357 | </Router>
at Object._raise (node_modules/#babel/parser/src/parser/location.js:241:45)
at Object.raiseWithData (node_modules/#babel/parser/src/parser/location.js:236:17)
at Object.raise (node_modules/#babel/parser/src/parser/location.js:220:17)
at Object.unexpected (node_modules/#babel/parser/src/parser/util.js:149:16)
at Object.parseExprAtom (node_modules/#babel/parser/src/parser/expression.js:1144:20)
at Object.parseExprAtom (node_modules/#babel/parser/src/plugins/jsx/index.js:532:22)
at Object.parseExprSubscripts (node_modules/#babel/parser/src/parser/expression.js:539:23)
at Object.parseMaybeUnary (node_modules/#babel/parser/src/parser/expression.js:519:21)
at Object.parseExprOps (node_modules/#babel/parser/src/parser/expression.js:311:23)
at Object.parseMaybeConditional (node_modules/#babel/parser/src/parser/expression.js:263:23)
Here are my config files:
package.json
{
"name": "ant4",
"version": "0.1.0",
"private": true,
"dependencies": {
"antd": "^4.2.0",
"aws-amplify": "^2.3.0",
"axios": "^0.19.2",
"#babel/cli": "^7.2.3",
"#babel/core": "^7.3.4",
"#babel/register": "^7.0.0",
"lodash": "^4.17.15",
"moment": "^2.25.3",
"moment-timezone": "^0.5.27",
"node-jsencrypt": "^1.0.0",
"react": "^16.13.1",
"react-copy-to-clipboard": "^5.0.2",
"react-dnd": "^10.0.2",
"react-dom": "^16.13.1",
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-scripts": "^3.4.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"#testing-library/jest-dom": "^4.2.4",
"#testing-library/react": "^9.5.0",
"#testing-library/user-event": "^7.2.1",
"#testing-library/dom": "^7.5.1",
"axios-mock-adapter": "^1.18.1",
"babel-preset-jest": "^24.3.0",
"babel-preset-env": "^1.7.0",
"jest": "^24.5.0"
},
"babel": {
"babelrc": false,
"presets": [
"es2015"
],
"plugins": [
"transform-class-properties"
]
}
}
jest.config.js
module.exports = {
modulePaths: ["/shared/vendor/modules"],
collectCoverageFrom: ["src/**/*.{js,jsx,mjs}"],
testMatch: ["<rootDir>/src/**/__tests__/**/*.{js,jsx,mjs}", "<rootDir>/src/**/?(*.)(spec|test).{js,jsx,mjs}"],
transform: {
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/fileTransformer.js"
},
transformIgnorePatterns: ["[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs)$"],
moduleDirectories: ["node_modules", "bower_components", "shared"],
moduleFileExtensions: ["js", "jsx", "json"],
verbose: true,
moduleNameMapper
};
webpack.config.js
module.exports = {
module: {
loaders: [
{exclude: ['node_modules'], loader: 'babel', test: /\.jsx?$/},
{loader: 'style-loader!css-loader', test: /\.css$/},
{loader: 'url-loader', test: /\.gif$/},
{loader: 'file-loader', test: /\.(ttf|eot|svg)$/},
],
},
resolve: {
alias: {
config$: './configs/app-config.js',
react: './vendor/react-master',
},
extensions: ['', 'js', 'jsx'],
modules: [
'node_modules',
'bower_components',
'shared',
'/shared/vendor/modules',
],
},
};
I used to have a babel.config.js, but it wasn't doing anything, so I removed it.
fileTransformer.js
const path = require('path');
module.exports = {
process(src, filename, config, options) {
return 'module.exports = ' + JSON.stringify(path.basename(filename)) + ';';
},
};
What am I missing? What needs to be changed? I tried following the jest documentation as best as I could: https://jestjs.io/docs/en/webpack
Any help, much appreciated.
Events.test.js
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // deprecated
removeListener: jest.fn(), // deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
import ReactDOM from "react-dom";
import axios from 'axios';
import Events from "../Components/Events/index";
import Home from "../Components/Home";
import { Router, BrowserRouter, MemoryRouter } from "react-router-dom";
import moment from 'moment-timezone';
import { createMemoryHistory } from "history";
import { Auth } from "aws-amplify";
describe('Event', () => {
jest.mock("../apis");
const path = "/events";
const route = "/events";
const history = createMemoryHistory({ initialEntries: [route] });
let props = {
match: {
path: path,
url: route,
isExact: true,
params: {
...
}
},
location: {
pathname: route
}
};
beforeEach(() => {
Auth.currentSession = jest.fn().mockImplementation(
() => {
return new Promise((resolve) => resolve({...})
);
});
});
afterEach(cleanup);
it('should take a snapshot and match it', (done) => {
let mockUseEffect = jest.spyOn(React, 'useEffect');
const { asFragment } = render(
<Router history={history}>
<Home />
</Router>
);
expect(asFragment(<Router history={history}>
<Home />
done();
});
it('renders without errors', (done) => {
const { getByTestId } = render(
<Router history={history}>
<Home />
</Router>
);
expect(getByTestId('event-index-title')).toHaveTextContent('All Events');
done();
});
});
index.js
import React, { useState, useEffect } from "react";
import {
Layout,
Row,
Table,
Button,
Modal,
Select,
Steps,
message,
} from "antd";
import { withRouter } from "react-router-dom";
import EventForm from "./EventForm";
import { getAllEvents, getAggregatedEvents } from "../../apis";
import spinner from "../helpers/spinner";
import * as moment from "moment";
const { Sider } = Layout;
const { Option } = Select;
const { Step } = Steps;
const Events = (props) => {
const [expId] = useState("abc_123");
const [events, setEvents] = useState([]);
const [aggregatedEvents, setAggregatedEvents] = useState([]);
const [selectedEvent, setSelectedEvent] = useState({ venue: {} });
const [currentStep, setCurrentStep] = useState(0);
const [isFetching, setIsFetching] = useState(true);
const [showAddModal, setShowAddModal] = useState(false);
useEffect(() => {
(async function fetchData() {
await getAllEvents(expId)
.then((events) => setEvents(events))
.catch((err) => message.error("Error retrieving Events"))
.finally(() => setIsFetching(false));
await getAggregatedEvents(expId)
.then((events) =>
setAggregatedEvents(events.sort((a, b) => a.weekId - b.weekId))
)
.catch((err) => message.error("Error retrieving aggregated Events"));
})();
}, [expId]);
const handleCancel = (e) => {
setShowAddModal(false);
};
const sider = (
<Sider
width={210}
style={{
background: "#fff",
padding: "1em",
}}
>
<Button
type="primary"
data-testid="add-btn"
onClick={() => {
setSelectedEvent({ venue: {} });
setShowAddModal(true);
}}
>
+ Add Event
</Button>
</Sider>
);
const columns = [
{
title: "Event Category",
dataIndex: "eventCategory",
key: "event.eventCategory",
sorter: (a, b) => a.eventCategory.localeCompare(b.eventCategory),
},
{
title: "Event Title",
dataIndex: "eventTitle",
key: "event.eventTitle",
sorter: (a, b) => a.eventTitle.localeCompare(b.eventTitle),
},
{
title: "Event Start Date",
dataIndex: "eventStartDateUTC",
key: "event.eventStartDateUTC",
render: (text) =>
text && text.length > 0 ? moment(text).format("MM/DD/YYYY") : "",
defaultSortOrder: "descend",
sorter: (a, b) =>
moment(a.eventStartDateUTC) - moment(b.eventStartDateUTC),
sortDirections: ["descend", "ascend"],
},
{
title: "Description",
dataIndex: "eventCaption",
key: "event.eventCaption",
},
];
const steps = [
{
title: "Select Event",
content: (
<div>
<Row>
<Select
data-testid="select-event"
showSearch
style={{ width: 800 }}
placeholder="Select an event"
optionFilterProp="children"
filterOption={(input, option) =>
option.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
size="large"
onChange={(val) => {
setSelectedEvent(
aggregatedEvents.filter((evt) => evt.id === val)[0]
);
setCurrentStep(1);
}}
>
{aggregatedEvents.map((evt) => (
<Option
key={`${evt.id}`}
value={`${evt.id}`}
>{`Week ${evt.weekId} - ${evt.homeTeam.fullName} vs ${evt.awayTeam.fullName}`}</Option>
))}
</Select>
</Row>
</div>
),
},
{
title: "Save Event",
content: (
<EventForm
data={selectedEvent}
isAddingEvent={true}
closeModal={handleCancel}
/>
),
},
];
return (
<div>
<Layout.Content
style={{
background: "#fff",
minHeight: 280,
borderRadius: 7,
color: "black",
}}
>
<h2
data-testid="event-index-title"
style={{
width: "100%",
backgroundColor: "#F7F7F7",
color: "#334D66",
height: "50px",
lineHeight: "50px",
paddingLeft: "30px",
}}
>
All Events
</h2>
<Layout>
{sider}
<Layout>
<Table
dataSource={events}
columns={columns}
rowKey="id"
loading={{ spinning: isFetching, indicator: spinner }}
onRow={(record) => ({
onClick: (evt) => {
const currentUrl = props.location.pathname;
props.history.push({
pathname: `${currentUrl}/${record.id}`,
});
},
})}
/>
</Layout>
</Layout>
</Layout.Content>
<Modal
title="Add an Event"
visible={showAddModal}
onCancel={handleCancel}
width={880}
footer={[]}
>
<Steps current={currentStep}>
{steps.map((item) => (
<Step key={item.title} title={item.title} />
))}
</Steps>
<br />
{steps[currentStep].content}
</Modal>
</div>
);
};
export default withRouter(Events);
The syntax to render a component and destructure RTL functions can be :
describe('<MyComponent />', () => {
let rtl: RenderResult;
beforeEach(() => {
rtl = render(<MyComponent />);
}
test('something', () => {
// here you can access all RTL functions on rtl object
expect(rtl.getByText('...')).toBeTruthy();
// ...
})
})
If you prefer to stick to your syntax :
({ getByText, getAllByTestId, ... } = render(
<MyComponent />
));
// then in test
expect(getByext('...')).toBeTruthy();
I am using jest and enzyme to test my react component. I am also using blueprint icons as one of the dependency in my react component. As part of my webpack config, following is added:
config.resolve.alias = {
blueprintIcons: path.resolve('./node_modules/#blueprintjs/icons'),
blueprint: path.resolve('./node_modules/#blueprintjs/core')
};
Following is added as part of jest config:
rootDir: '.',
roots: [
'<rootDir>/__test__/'
],
transformIgnorePatterns: [
'<rootDir>/node_modules/'
],
transform: {
'^.+\\.jsx?$': 'babel-jest'
},
testRegex: '(/__tests__/.*|(\\.|/)(test|spec))\\.jsx?$',
moduleDirectories: ['node_modules'],
moduleFileExtensions: [
'js',
'jsx',
'json',
'node'
],
moduleNameMapper: {
'\\.(css|scss)$': 'identity-obj-proxy',
blueprintIcons: '<rootDir>/node_modules/#blueprintjs/core'
blueprint: '<rootDir>/node_modules/#blueprintjs/core'
},
snapshotSerializers: ['enzyme-to-json/serializer']
};
Here is my component:
import React, { Component } from 'react';
import Icon from 'blueprint';
import IconNames from 'blueprintIcons';
class Foo extends Component {
render() {
return (
<div>
<p>Hello Foo</p>
<Icon icon={IconNames.HOME} iconSize={Icon.SIZE_LARGE}/>
</div>
);
}
}
export default Foo;
Here is my foo.test.js
import React from 'react';
import Foo from '../../src/Components/Foo';
import Adapter from 'enzyme-adapter-react-16';
import Enzyme, { mount, shallow } from 'enzyme';
describe('Reviews component', () => {
it('render component when loading in progress', () => {
const mountedComponent = mount(<Foo />);
});
});
When I am trying to test that component, the test fails with
TypeError: Cannot read property 'HOME' of undefined at IconNames.HOME
Here are some packages specified in my package.json
"babel-cli": "6.26.0",
"babel-core": "^6.26.3",
"babel-eslint": "^10.0.1",
"babel-jest": "^23.0.1",
"babel-loader": "7.1.4",
"enzyme": "^3.9.0",
"enzyme-adapter-react-16": "^1.1.1",
"enzyme-to-json": "^3.3.4",
"jest": "^23.1.0",
"jest-html-reporter": "^2.3.0",
"#blueprintjs/core": "^2.3.1",
"react": "16.2.0"
I am using react 16.2.0
I tried mocking it but doesn't work(maybe I am not doing it correctly) but here is the code which I am using:
jest.mock('#blueprintjs/icons', () => (
{ IconNames: {HOME: 'home' }}));
For me, the following solution worked:
In jest config:
moduleNameMapper: {
'^blueprint': '<rootDir>/node_modules/#blueprintjs/core',
'^#blueprintjs/(.*)$': '<rootDir>/node_modules/#blueprintjs/$1'
}
Rest everything remained the same.
I think mocking is a possible solution - Not sure exactly why your code is not working (possibly because it's not within the default key, or the name of the mock is incorrect), but you can try something else.
In your Jest config, add the following:
"setupFiles": [
"./__mocks__/mockIcons.js"
],
Create /__mocks__ folder in your root folder
Create mockIcons.js inside __mocks__ with the following code:
jest.mock("blueprint", () => ({
default: {
Icon: { SIZE_LARGE: 'large' }
}
}))
jest.mock("blueprintIcons", () => ({
default: {
IconNames: { HOME: 'home' }
}
}))
Try to use #blueprintjs/icons as the mock name if nothing else works.