● 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();
Related
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";
I am looking to create a test for a login page. The workflow should be as follows:
- Enter email in textbox, click Continue button.
- Button greys out and a "Processing..." loading Spinner appears
- Enter password in textbox, click Sign in button
SignIn.tsx
import { useState} from 'react'
import { Stack, TextField, PrimaryButton, Spinner, DefaultButton, Dropdown, IDropdownOption } from '#fluentui/react';
import { useFormik } from "formik";
import * as yup from 'yup';
import { CredsI, OrganizationI, TokenI } from '../Lib/Interfaces/signin.interface';
import { JwtManager, sourceToken } from '../Classes/tokensManager';
import { RocklandCall } from '../Utils/apiUtils';
import ApiResponseMessage from '../Components/ApiResponseMessage';
import { ResponseTypeEnum } from '../Lib/Enum/responseTypeEnum';
import { signInTokenStyles, signInTextStyles } from '../componentStyling';
import { NotificationManager } from '../Classes/NotificationManager';
import { NotificationDismissEnum } from '../Lib/Enum/NotificationDismissEnum';
import { useMutation, useQuery } from 'react-query';
interface SignInProps {
onSuccess: () => void;
};
/*
* Login panel styling + layout JSX
* API call using axios
*/
function SignIn(props: SignInProps) {
//React boilerplate to keep track of errors
const [loginWaiting, setLoginWaiting] = useState(false);
const [organizationWaiting, setOrganizationWaiting] = useState(false);
const [email, setEmail] = useState("");
const [organizations, setOrganizations] = useState<OrganizationI>({names: []});
const [selectedOrganization, setSelectedOrganization] = useState({key: 0, text: ""});
const [messageBar, setMessageBar] = useState({
show: false,
type: ResponseTypeEnum.NONE,
message: ""
});
const [showFirstAuthentication, setShowFirstAuthentication] = useState(true)
const [showOrganization, setShowOrganization] = useState(false)
const closeErrorMsg = () => setMessageBar({...messageBar, show: false});
// Formik to manage email validation
const emailValidationSchema = yup.object({
email: yup.string().email('Enter a valid email').required('Email is required')
});
const emailFormik = useFormik({
initialValues: { email: "" },
validationSchema: emailValidationSchema,
onSubmit: (values) => origanization(values.email)
});
const origanization = (email: string) => {
setEmail(email);
ApiOrganizations()
}
// Formik to manage password validation
const loginValidationSchema = yup.object({
password: yup.string().required('Password is required')
});
const loginFormik = useFormik({
initialValues: { password: "" },
validationSchema: loginValidationSchema,
onSubmit: (values) => apiLogin(email, values.password)
});
const formikEmailError = (_: string) => emailFormik.errors.email ? emailFormik.errors.email : '';
const formikPasswordError = (_: string) => loginFormik.errors.password ? loginFormik.errors.password: '';
const onBackButton = () => {
setEmail("")
setShowFirstAuthentication(true)
}
const getDropdownOptions = () => organizations.names.map((o, index) => {
return {
key: index,
text: o
} as IDropdownOption});
/*
* Functions for the buttons
* developer credentials:
* username: jp#rocklandscientific.com
* password: access.$1729Rop
*/
const onSuccessLogin = (data: TokenI) => {
let token = data.token;
let expiry = data.tokenExpiry;
JwtManager.setToken(token, expiry);
if (data.message !== "") {
NotificationManager.setMessage(data.message, ResponseTypeEnum.WARNING, NotificationDismissEnum.NONE);
}
//error in unlikely case of token being null (no token so can't log to api unfortunately but we need a UI message)
if (token==null) {
setMessageBar({
show: true,
type: ResponseTypeEnum.ERROR,
message: "Login failed"
})
} else {
//the login was successful, so do whatever parent component wants us to do (such as redirect to dashboard)
props.onSuccess();
}
setLoginWaiting(false)
}
const onError = (error: any, setLoading: any) => {
let message = error.response?.data?.message ?? "";
setMessageBar({
show: true,
type: ResponseTypeEnum.ERROR,
message: message
})
setLoading(false)
}
const getSinginToken = async(creds: CredsI) => {
return await RocklandCall.signInReturnToken(creds)
}
const {mutate: apiLoginQuery} = useMutation(getSinginToken,{
onSuccess: (data) => {
onSuccessLogin(data)
},
onError: (error) => {
onError(error, setLoginWaiting)
}
});
const apiLogin = async (username: string, password: string) => {
setLoginWaiting(true);
let creds: CredsI = { username: username, password: password, organizationName: selectedOrganization.text, sourceToken: sourceToken};
apiLoginQuery(creds) //mutate
}
const onSuccessOrganizationQuery = (data: OrganizationI) => {
setOrganizations(data);
if (data.names.length > 0) setSelectedOrganization({key: 0, text: data.names[0]});
if (data.names.length > 1) setShowOrganization(true)
setOrganizationWaiting(false);
setShowFirstAuthentication(false)
}
const organizationQuery = async(email: string) => {
return await RocklandCall.organizations(email)
}
const {refetch: organizationAPI, status} = useQuery(['organinzation', email], () => organizationQuery(email), {
onSuccess: (data) => {
onSuccessOrganizationQuery (data)
},
onError: (error) => {
onError(error, setOrganizationWaiting)
},
refetchOnWindowFocus: false,
cacheTime: 0,
enabled: false
})
const ApiOrganizations = () => {
setOrganizationWaiting(true);
organizationAPI() //refetch alias name
}
return (
<div className="Sign-in-panel">
<Stack tokens={signInTokenStyles()} style={{marginLeft: 10, marginRight: 10}} >
<Stack.Item align="start" >
<h2>Welcome to AIRDROP</h2>
</Stack.Item>
{messageBar.show &&
<ApiResponseMessage
message={messageBar.message}
type={ResponseTypeEnum.ERROR}
close={closeErrorMsg} />}
{showFirstAuthentication && // The following is the first step of authentication
<form onSubmit={emailFormik.handleSubmit}>
<Stack tokens={signInTokenStyles()}>
<Stack.Item align="start" >
<TextField label="Email" id="email" name="email"
disabled={email !== ""}
required styles={signInTextStyles()} onGetErrorMessage={formikEmailError}
value={emailFormik.values.email} onChange={emailFormik.handleChange} validateOnFocusOut
/>
</Stack.Item>
<Stack.Item align="start" >
<Stack className="Submit-button" horizontal tokens={signInTokenStyles()}>
<PrimaryButton type="submit" disabled={organizationWaiting} >Continue</PrimaryButton>
{ organizationWaiting && <Spinner label="Processing..." ariaLive="assertive" labelPosition="left"/> }
</Stack>
</Stack.Item>
</Stack>
</form>
||
<form onSubmit={loginFormik.handleSubmit}>
{showOrganization && <Dropdown
placeholder="Select organization"
label="Organization"
options={getDropdownOptions()}
selectedKey = {selectedOrganization.key}
id="type"
onChange={(event, option) => setSelectedOrganization({key: option?.key as number ?? 0, text: option?.text ?? ""})}/>
}
<Stack.Item align="start" >
<TextField label="Password" id="password" name="password"
type="password" required styles={signInTextStyles()} onGetErrorMessage={formikPasswordError}
value={loginFormik.values.password} onChange={loginFormik.handleChange} validateOnFocusOut
/>
</Stack.Item>
<Stack horizontal tokens={signInTokenStyles} >
<Stack.Item className="Submit-button" >
<Stack horizontal gap={10}>
<DefaultButton onClick={onBackButton} disabled={loginWaiting} >Back</DefaultButton>
<PrimaryButton type="submit" disabled={loginWaiting} >Sign in</PrimaryButton>
{ loginWaiting && <Spinner label="Signing you in..." ariaLive="assertive" labelPosition="left"/> }
</Stack>
</Stack.Item>
</Stack>
</form>
}
</Stack>
</div>
);
}
export default SignIn;
SignIn.test.tsx
import { QueryClient, QueryClientProvider } from 'react-query';
import SignIn from '../SignIn';
import { setupServer } from 'msw/node'
import { render, screen, fireEvent, waitFor } from '#testing-library/react';
import "#testing-library/jest-dom"
import { rest } from 'msw';
// set up server to mock API login requests
let server = setupServer();
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
const queryClient = new QueryClient();
const onSubmit = jest.fn();
beforeEach(() => {
onSubmit.mockClear();
render(
<QueryClientProvider client={queryClient}>
<SignIn onSuccess={onSubmit} />
</QueryClientProvider>
);
})
describe('SignIn Domain', () => {
test('Successful login', async () => {
server.use(
rest.post('https://localhost:5001/api/Auth/Login', (_, res, ctx) => {
return res(ctx.json({ token: 'myToken' }));
}),
);
await waitFor(() => {
fireEvent.change(getEmail(), {
target: {
value: ""
}
})
});
// click the button
await waitFor(() => {
fireEvent.click(screen.getByText('Continue'));
});
const processing = await screen.getByText('Processing...');
// await waitFor(() => {
// expect(onSubmit).toHaveBeenCalledTimes(1);
// });
// await waitFor(() => {
// expect(processing).not.toBeIntheDocument()
// })
// const password = await screen.getByRole('textbox', {
// name: /password/i
// });
await waitFor(() => {
fireEvent.change(getPassword(), {
target: {
value: "dummyPassword"
}
})
})
// click the sign in button
await waitFor(() => {
fireEvent.click(screen.getByText('Sign in'));
});
await waitFor(() => expect(onSubmit).toHaveBeenCalled());
})
})
function getEmail() {
return screen.getByRole('textbox', {
name: /email/i
})
}
function getPassword() {
return screen.getByRole('textbox', {
name: /password/i
});
}
1st attempt:
In a synchronous manner, I tried to:
- get the email and fill it in (successfully)
- click the continue button (successfully)
- get the password and fill it in (unsuccessful)
error:
TestingLibraryElementError: Unable to find an accessible element with the role "textbox" and name `/password/i`
2nd attempt:
Since the re-render isn't instant, I decided to confirm that the "Processing..." Spinner appears, and then wait until it disappears. (unsuccessful)
const processing = await screen.getByText('Processing...');
[..]
await waitFor(() => {
expect(processing).not.toBeIntheDocument()
})
error:
TypeError: The "options.agent" property must be one of Agent-like Object, undefined, or false. Received an instance of Object
TypeError: expect(...).not.toBeIntheDocument is not a function
In case this is needed:
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"types": ["node", "jest", "#testing-library/jest-dom"]
},
"include": [
"src"
]
}
package.json
{
"name": "airdrop-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"#fluentui/react": "^8.104.5",
"#fluentui/react-icons": "^1.1.145",
"#fluentui/react-icons-mdl2": "^1.3.29",
"#testing-library/jest-dom": "^5.16.5",
"#testing-library/react": "^11.2.7",
"#testing-library/user-event": "^12.8.3",
"#types/d3": "^6.7.5",
"#types/file-saver": "^2.0.5",
"#types/filesaver": "^0.0.30",
"#types/geojson": "^7946.0.10",
"#types/mapbox-gl": "^2.7.10",
"#types/node": "^12.20.55",
"#types/react": "^17.0.52",
"#types/react-dom": "^17.0.18",
"#types/react-motion": "0.0.29",
"#types/react-router-dom": "^5.3.3",
"axios": "^0.21.4",
"d3": "^6.7.0",
"d3-hexbin": "^0.2.2",
"d3-svg-legend": "^2.25.6",
"enzyme": "^3.11.0",
"file-saver": "^2.0.5",
"fluent": "^0.13.0",
"formik": "^2.2.9",
"mapbox-gl": "^2.12.0",
"msw": "^0.28.2",
"react": "^17.0.2",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-map-gl": "^6.1.21",
"react-motion": "^0.5.2",
"react-query": "^3.39.2",
"react-rnd": "^10.4.1",
"react-router-dom": "^5.3.4",
"react-scripts": "5.0.1",
"sprintf-js": "^1.1.2",
"typescript": "^4.9.4",
"web-vitals": "^1.1.2",
"yup": "^0.32.11"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "jest",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"#types/d3-hexbin": "^0.2.3",
"#types/enzyme": "^3.10.12",
"#types/enzyme-adapter-react-16": "^1.0.6",
"#types/sprintf-js": "^1.1.2",
"enzyme-adapter-react-15": "^1.4.4",
"enzyme-adapter-react-16": "^1.15.7",
"jest": "^29.3.1",
"jest-environment-jsdom": "^29.3.1",
"ts-jest": "^29.0.3"
}
}
jest.config.js
/** #type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
verbose: false,
preset: 'ts-jest',
testEnvironment: 'jsdom',
testEnvironmentOptions: {
url: 'http://localhost/3000'
}
};
Looking forward to discussing any possible solutions.
Thank you.
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>);
I have '#testing-library/jest-dom' installed so it should be there. Also have the below imports, anything wrong with my config?
TypeError: expect(...).toBeInTheDocument is not a function
52 | renderIt(onSubmit);
53 |
> 54 | expect(screen.getByText(/alberta/i)).toBeInTheDocument();
My test:
import { render, screen } from '#testing-library/react';
import { Formik, Form } from 'formik';
import { Select } from '.';
const options = [
'Alberta',
'British Columbia',
'Manitoba',
'New Brunswick',
'Newfoundland & Labrador',
'Northwest Territories',
'Nova Scotia',
'Nunavut',
'Ontario',
'Prince Edward Island',
'Quebec',
'Saskatchewan',
'Yukon',
];
const renderIt = (onSubmit: () => void) => render(
<Formik
initialValues={{ province: '' }}
onSubmit={onSubmit}
>
<Form>
<Select name="province" options={options} />
</Form>
</Formik>,
);
describe('Select component', () => {
test('It renders', () => {
const onSubmit = jest.fn();
renderIt(onSubmit);
expect(screen.getByText(/alberta/i)).toBeInTheDocument();
});
});
My src/setupTests.ts file:
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '#testing-library/jest-dom';
import '#testing-library/jest-dom/extend-expect';
package.json:
"devDependencies": {
"#testing-library/dom": "8.11.3",
"#testing-library/jest-dom": "5.16.2",
"#testing-library/react": "^13.0.0-alpha.6",
"#testing-library/react-hooks": "7.0.2",
"#testing-library/user-event": "13.5.0",
"#types/jest": "27.4.1",
"jest": "27.5.1",
"ts-jest": "27.1.3",
"typescript": "4.6.2"
},
jest.config.js:
module.exports = {
testEnvironment: 'jsdom',
preset: 'ts-jest',
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/__mocks__/fileMock.js',
'\\.(css|less)$': 'identity-obj-proxy',
},
};
You should declare the setupTests.ts in you jest configuration using the setupFilesAfterEnv property.
jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
preset: 'ts-jest',
moduleNameMapper: {
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$':
'<rootDir>/__mocks__/fileMock.js',
'\\.(css|less)$': 'identity-obj-proxy',
},
};
I'm new to this and I get this error and can't figure out why please advice:
Looks like the Error is inside Uppy something.
I follow Uppy Tutorial docs like .use(Dashboard, {... and it was working but suddenly this error I try to back track but no luck
I add files from My Device and and then error happens no breakpoint are hit anywhere what I'm a missing
Here is my simple Uppy:
import React from "react";
import "#uppy/core/dist/style.css";
import "#uppy/status-bar/dist/style.css";
import "#uppy/drag-drop/dist/style.css";
import "#uppy/progress-bar/dist/style.css";
import "#uppy/dashboard/dist/style.css";
import "./styles.css";
const Uppy = require("#uppy/core");
// const Dashboard = require("#uppy/dashboard");
const GoogleDrive = require("#uppy/google-drive");
const Dropbox = require("#uppy/dropbox");
const Instagram = require("#uppy/instagram");
const Webcam = require("#uppy/webcam");
const Tus = require("#uppy/tus");
const {
Dashboard,
DashboardModal,
DragDrop,
ProgressBar,
} = require("#uppy/react");
class DashboardUppy extends React.Component {
constructor(props) {
super(props);
this.form = React.createRef();
this.state = {
showInlineDashboard: false,
open: false,
};
this.uppy = new Uppy({
id: "uppy1",
autoProceed: false,
debug: true,
allowMultipleUploads: true,
showSelectedFiles: true,
restrictions: {
maxFileSize: 1000000,
maxNumberOfFiles: 100,
minNumberOfFiles: 1,
allowedFileTypes: ['image/*', 'video/*'],
},
onBeforeFileAdded: (currentFile, files) => {
console.log(files);
const modifiedFile = Object.assign({}, currentFile, {
name: currentFile + Date.now(),
});
if (!currentFile.type) {
// log to console
this.uppy.log(`Skipping file because it has no type`);
// show error message to the user
this.uppy.info(`Skipping file because it has no type`, "error", 500);
return false;
}
return modifiedFile;
},
})
.use(Tus, { endpoint: "https://master.tus.io/files/" })
.use(GoogleDrive, { companionUrl: "https://companion.uppy.io" })
.use(Dropbox, {
companionUrl: "https://companion.uppy.io",
})
.use(Instagram, {
companionUrl: "https://companion.uppy.io",
})
.use(Webcam, {
onBeforeSnapshot: () => Promise.resolve(),
countdown: false,
modes: ["video-audio", "video-only", "audio-only", "picture"],
mirror: true,
facingMode: "user",
locale: {
strings: {
// Shown before a picture is taken when the `countdown` option is set.
smile: "Smile!",
// Used as the label for the button that takes a picture.
// This is not visibly rendered but is picked up by screen readers.
takePicture: "Take a picture",
// Used as the label for the button that starts a video recording.
// This is not visibly rendered but is picked up by screen readers.
startRecording: "Begin video recording",
// Used as the label for the button that stops a video recording.
// This is not visibly rendered but is picked up by screen readers.
stopRecording: "Stop video recording",
// Title on the “allow access” screen
allowAccessTitle: "Please allow access to your camera",
// Description on the “allow access” screen
allowAccessDescription:
"In order to take pictures or record video with your camera, please allow camera access for this site.",
},
},
})
.on("file-added", (file) => {
const { setFiles } = props;
setFiles(file);
})
}
componentWillUnmount() {
this.uppy.close();
}
render() {
this.uppy.on("complete", (result) => {
console.log(
"Upload complete! We’ve uploaded these files:",
result.successful
);
});
return (
<div>
<div>
<Dashboard
uppy={this.uppy}
plugins={["GoogleDrive", "Webcam", "Dropbox", "Instagram"]}
metaFields={[
{ id: "name", name: "Name", placeholder: "File name" },
]}
open={this.state.open}
target={document.body}
onRequestClose={() => this.setState({ open: false })}
/>
</div>
</div>
);
}
}
export default DashboardUppy;
And I use it like this nothing special:
import React, { useState } from "react";
import FileList from "./FileList";
import FileForm from "./FileForm";
import DashboardUppy from "./DashboardUppy";
import { Container, Grid } from "#material-ui/core";
const CreateContent = () => {
const [file, setItems] = useState();
const [show, showDashboardUppy] = useState(true);
return (
<Container maxWidth="lg">
{show ? (
<DashboardUppy showDashboardUppy={showDashboardUppy}/>
) : (
<Grid container spacing={3}>
<Grid item lg={4} md={6} xs={12}>
<FileList setItems={setItems} />
</Grid>
<Grid item lg={8} md={6} xs={12}>
<FileForm file={file} />
</Grid>
</Grid>
)}
</Container>
);
};
export default CreateContent;
Here is package.json
{
"name": "react-firestore-crud",
"version": "0.1.0",
"private": true,
"dependencies": {
"#uppy/core": "1.0.2",
"#uppy/dropbox": "latest",
"#uppy/form": "^1.3.23",
"#uppy/google-drive": "1.0.2",
"#uppy/instagram": "1.0.2",
"#uppy/react": "^1.0.2",
"#uppy/status-bar": "latest",
"#uppy/tus": "1.0.2",
"#uppy/webcam": "latest",
"#uppy/xhr-upload": "^1.6.8",
"#material-ui/core": "^4.11.2",
"#material-ui/icons": "^4.11.2",
"#material-ui/lab": "^4.0.0-alpha.57",
"#material-ui/styles": "^4.11.2",
"#testing-library/jest-dom": "^4.2.4",
"#testing-library/react": "^9.3.2",
"#testing-library/user-event": "^7.1.2",
"bootstrap": "^4.5.2",
"clsx": "^1.1.1",
"firebase": "^7.23.0",
"moment": "^2.29.1",
"prop-types": "^15.7.2",
"react": "^16.8.4",
"react-dom": "^16.8.4",
"react-perfect-scrollbar": "^1.5.8",
"react-router-dom": "^5.2.0",
"react-scripts": "^3.4.0",
"uuid": "^8.3.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"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"
]
}
}
I hade the wrong Uppy version in package.json hmmm