Jest / Enzyme not recognizing props - reactjs

I am trying to write a test for a React functional component that uses Redux and Hooks.
I am using Jest with Enzyme for testing.
For Reference:
This is the functional component being tested:
import React from 'react';
import {useDispatch, useSelector} from "react-redux";
import * as actions from '../../actions/actions';
import { Button, Icon } from "#material-ui/core";
export const EditBatchHeaderComponent = (props) => {
const dispatch = useDispatch();
const { selectedBatch } = props;
const { batchName } = selectedBatch;
return (
<div className="edit-header-container">
<Button disableRipple onClick={() => {dispatch(actions.unSelectBatch())} }>
<Icon>arrow_back</Icon>
</Button>
<span>Edit Batch</span>
<span>{batchName}</span>
</div>
);
};
This is component's container:
import React from 'react';
import { BatchHeaderComponent } from './BatchHeaderComponent';
import { BatchTableComponent } from './BatchTableComponent';
import { EditBatchComponent } from './EditBatchComponent';
import {useSelector} from "react-redux";
import {EditBatchHeaderComponent} from "./EditBatchHeaderComponent";
export const BatchManagementComponent = () => {
const { selectedBatch } = useSelector(state => state.batchManagementReducer);
if (selectedBatch.length) {
return (
<div className="component-container">
<EditBatchHeaderComponent selectedBatch={selectedBatch} />
<EditBatchComponent selectedBatch={selectedBatch} />
</div>
);
}
return (
<div className="component-container">
<BatchHeaderComponent />
<BatchTableComponent />
</div>
);
};
This is the default state of the reducer:
{
sorting: {
order: '',
orderBy: ''
},
searchBy: 'batchName',
searchText: '',
filterByStatus: '--',
filterByType: '--',
waiting: false,
batchData: [],
selectedBatch: {
batchName: '',
},
}
This is the test file that is failing to recognize the props:
import React from 'react';
import { EditBatchHeaderComponent } from '../../../components/batchManagement/EditBatchHeaderComponent';
import configureStore from '../../../store';
import {Provider} from "react-redux";
import Enzyme, { mount } from "enzyme";
import Adapter from "enzyme-adapter-react-16";
import {Button} from "#material-ui/core";
Enzyme.configure({ adapter: new Adapter() });
describe('EditBatchHeaderComponent', () => {
it('mounts to the DOM successfully', () => {
const wrapper = mount(<Provider store={configureStore()}>
<EditBatchHeaderComponent />
</Provider>);
expect(wrapper.find(EditBatchHeaderComponent)).toBeDefined();
});
it('deselects the account and closes when the back button is clicked', () => {
const props = {selectedBatch: {batchName: 'INFORM'}, dispatch: jest.fn()};
const obj = {};
const wrapper = mount(
<Provider store={configureStore()}>
<EditBatchHeaderComponent {...props} />
</Provider>
);
console.log(wrapper.find(EditBatchHeaderComponent).props());
wrapper.find(Button).first().simulate('click');
expect(wrapper.find(EditBatchHeaderComponent)).toEqual(obj);
});
});
This is the error text provided by the test suite:
FAIL src/spec/components/batchManagement/EditBatchHeaderComponent.test.js (7.182s)
● EditBatchHeaderComponent › mounts to the DOM successfully
TypeError: Cannot read property 'batchName' of undefined
8 | const dispatch = useDispatch();
9 | const { selectedBatch } = props;
> 10 | const { batchName } = selectedBatch;
| ^
11 | return (
12 | <div className="edit-header-container">
13 | <Button disableRipple onClick={() => {dispatch(actions.unSelectBatch())} }>
I have run a nearly identical test on a similar component that runs and covers the code appropriately.
I can't seem to figure out why the props aren't being recognized.
Any assistance would be greatly appreciated, thanks.

Related

react test component based on conditional component in parent component with react testing library

I'm trying to test component that is wrapped with other component, the parent component has conditional rendering, for explanation:
import React, { FunctionComponent } from 'react'
import { getPermissions, ROLES, PERMISSIONS } from '~/constants/permissions'
import usePermission from '~/hooks/usePermission'
export interface UsePermissionParams {
as: ROLES;
children?: React.ReactNode;
permissions: PERMISSIONS[];
}
// This Component is used to check if the user has the permission to access Specific Component or part of the UI
const CanAccess: FunctionComponent<UsePermissionParams> = ({
as,
children,
permissions,
}) => {
const hasPermission = usePermission({ permissions })
if (as === ROLES.VENDOR) {
const hasAllPermissions = permissions?.every((permission) =>
getPermissions.vendor.includes(permission)
)
if (hasPermission && hasAllPermissions) {
return <>{children}</>
}
} else if (as === ROLES.ADMIN) {
const hasAllPermissions = permissions?.every((permission) =>
getPermissions.admin.includes(permission)
)
if (hasPermission && hasAllPermissions) {
return <>{children}</>
}
}
return <></>
}
export default CanAccess
this is the parent component that wrap the component that I need to test.
and this is the component that I want to test:
import React, { useContext, useState } from 'react'
import { CircularProgress } from '#mui/material'
import { useSelector } from 'react-redux'
import CanAccess from '~/components/CanAccess'
import CreateBox from '~/components/CreateBox'
import { ROLES, PERMISSIONS } from '~/constants/permissions'
import CardHolder from '~/modules/finance/Components/SponsoredProductsTab/Components/CardHolder'
import TopUpModal from '~/modules/finance/Components/TopUpModal'
import { FinanceContext } from '~/modules/finance/FinanceContext'
import * as styles from '~/modules/finance/styles'
import { State } from '~/store'
import { User } from '~/store/user/types'
import { i18n } from '~/translations/i18n'
import { currencySign } from '~/utils/getCurrencySign'
import { ls } from '~/utils/localStorage'
import { separator } from '~/utils/numberSeperator'
const AccountBalance = () => {
const { list, loading } = useContext<any>(FinanceContext)
const [showTopUpModal, setShowTopUpModal] = useState<boolean>(false)
const userRepresentation = useSelector<State, User | null>(
(state) => state.user.data
)
const targetSelected = ls.get('target_code')
const handleOpenTopUpModal = () => {
setShowTopUpModal(true)
}
const onClose = () => {
setShowTopUpModal(false)
}
const renderCreateTopUP = () => (
<CanAccess as={ROLES.VENDOR} permissions={[PERMISSIONS.FO_TOPUP_ACCESS]}>
<CreateBox
label="Top Up"
maxWidth="fit-content"
handleCreateCampaign={handleOpenTopUpModal}
/>
</CanAccess>
)
return (
<>
{showTopUpModal && (
<TopUpModal
onClose={onClose}
advertiserId={
userRepresentation == null
? 0
: userRepresentation?.advertiserId || 0
}
target={targetSelected}
open
/>
)}
<CardHolder
cardHeader="Your account balance:"
cardContent={`${currencySign() || ''} ${
separator(list?.balance) || '0'
}`}
CardBottom={renderCreateTopUP()}
/>
</>
)
}
export default AccountBalance
and this is the test file:
import AccountBalance from '.'
import { render, screen } from '~/test-utils'
describe('AccountBalance', () => {
it('should render the top up button', async () => {
render(<AccountBalance />)
const topUpBtn = await screen.findByText('Top up')
expect(topUpBtn).toBeInTheDocument()
})
})
and I have this error:
<body>
<div>
<div
class="MuiPaper-root MuiPaper-elevation MuiPaper-rounded MuiPaper-elevation1 css-s9smhs-MuiPaper-root"
>
<div>
<p
class="MuiTypography-root MuiTypography-body1 css-1tvvmmg-MuiTypography-root"
>
Your account balance:
</p>
<p
class="MuiTypography-root MuiTypography-body1 css-1hd1hip-MuiTypography-root"
>
0
</p>
<div
class="css-0"
/>
</div>
</div>
</div>
</body>
5 | it('should render the top up button', async () => {
6 | render(<AccountBalance />)
> 7 | const topUpBtn = await screen.findByText('Top up')
| ^
8 | expect(topUpBtn).toBeInTheDocument()
9 | })
10 | })
the problem is: any component that is wrapped with the component CanAccess is not rendering and I have null
so what is the problem

React - useContext is returning undefined

I trying to use React context to manage state in my project, but I cannot seem to figure out why it's returning undefined. I copied the example from another project I was working on, and it seems as if everything should be working properly. I just can't seem to pin down why it is not.
Here is where I am creating the context.
import React, { useState } from "react";
const initialTodos = [
{
id: 1,
title: "Setup development environment",
completed: true,
},
{
id: 2,
title: "Develop website and add content",
completed: false,
},
{
id: 3,
title: "Deploy to live server",
completed: false,
},
];
export const TodoContext = React.createContext({
todos: initialTodos,
onChange: () => {},
});
const TodoContextProvider = (props) => {
const [todos, setTodos] = useState(initialTodos);
const handleChange = () => {
console.log("clicked");
};
return (
<TodoContext.Provider value={{ todos: todos, onChange: handleChange }}>
{props.children}
</TodoContext.Provider>
);
};
export default TodoContextProvider;
Here is where I am wrapping the app.
import React from "react";
import ReactDOM from "react-dom";
import TodoContainer from "./components/TodoContainer";
import TodoContextProvider from "./context/TodoContext";
ReactDOM.render(
<TodoContextProvider>
<TodoContainer />
</TodoContextProvider>,
document.getElementById("root")
);
Here is my TodoContainer.
import React, { useContext } from "react";
import TodosList from "./TodosList";
import Header from "./Header";
import TodoContext from "../context/TodoContext";
const TodoContainer = (props) => {
const todoContext = useContext(TodoContext);
console.log("todoContext", todoContext);
return (
<div>
<Header />
<TodosList />
</div>
);
};
export default TodoContainer;
And here is where I am attempting to use the context.
import React, { useContext } from "react";
import TodoItem from "./TodoItem";
import TodoContext from "../context/TodoContext";
const TodosList = (props) => {
const todoContext = useContext(TodoContext);
console.log(todoContext);
return (
<div>
{todoContext.todos.map((todo) => (
<TodoItem key={todo.id} todo={todo.title} />
))}
</div>
);
};
export default TodosList;
Finally, here is the error I receive.
TypeError: Cannot read property 'todos' of undefined
TodosList
C:/Users/Stacey/repos/simple-todo-app/src/components/TodosList.js:11
8 | console.log(todoContext);
9 |
10 | return (
> 11 | <div>
| ^ 12 | {todoContext.todos.map((todo) => (
13 | <TodoItem key={todo.id} todo={todo.title} />
14 | ))}
You are importing the TodoContext as a default import but it must be a named import.
change in the TodosList.js:
import TodoContext from "../context/TodoContext";
to:
import { TodoContext } from "../context/TodoContext";
and it will work

TypeScript Property 'value' does not exist on type 'HTMLElement'. React Jest Testing

Currently without TypeScript this code is working, but now it is not working unfortunately. It gave me the following error: Property 'value' does not exist on type 'HTMLElement'. Not sure what is wrong with this. Seems it is nagging about the value. In this case I am using Jest testing and React. Not sure if I can ignore this error or should fix this in order to avoid weird bugs in the future.
import React from 'react';
import axios from 'axios';
import { useDispatch } from "react-redux";
import { getData } from '../../../actions/index';;
export const SearchInput : React.FC = () => {
const dispatch = useDispatch();
let input: any;
const getInputValue = (value: string):void => {
let url = `https://api.tvmaze.com/search/shows?q=${value}`
}
return (
<div className="container">
<h1>Keyword</h1>
<form className="form display-inline-flex"
onSubmit={e => {
e.preventDefault()
if(!input.value.trim()) return;
getInputValue(input.value);
}}>
<input className="form-input-field disable-outline display-inline"
ref={node => (input = node)}
placeholder="Search catalog"
aria-label="search-input"
/>
<button type="submit" className="btn btn-grey white-color display-inline">
Search
</button>
</form>
</div>
)
}
export default SearchInput;
// Jest testing
import React from "react"
import { render, fireEvent } from "#testing-library/react";
import { SearchInput } from "./SearchInput";
import { Provider } from "react-redux";
import { store } from "../../../Store";
const setup = () => {
const utils = render(
<Provider store={store}>
<SearchInput/>
</Provider>);
const input = utils.getByLabelText("search-input");
return {
input,
...utils
}
}
test("It should check if input field get the value passed", () => {
const { input } = setup();
fireEvent.change(input, { target: { value: "search-bar-test" } })
expect(input.value).toBe("search-bar-test")
});
You should be good to go if you add a type assertion like:
const input = utils.getByLabelText("search-input") as HTMLInputElement;

Warning after mount a component in jest, test was not wrapper in the act

Hello I am writing test for a component and and I mount the component successfully. It gives me warning like
console.error src/setupTests.js:17
Warning: An update to ReactFinalForm inside a test was not wrapped in act(...).
When testing, code that causes React state updates should be wrapped into act(...):
act(() => {
/* fire events that update state */
});
/* assert on the output */
This ensures that you're testing the behavior the user would see in the browser. Learn more at
in ReactFinalForm (at SalesTaxForm.component.js:53)
in div (at Modal.component.js:41)
in Transition (created by ForwardRef(Fade))
in ForwardRef(Fade) (at Modal.component.js:40)
in TrapFocus (created by ForwardRef(Modal))
in div (created by ForwardRef(Modal))
in ForwardRef(Portal) (created by ForwardRef(Modal))
in ForwardRef(Modal) (at Modal.component.js:27)
in Component (at SalesTaxForm.component.js:52)
in SalesTaxForm (at salesTaxForm.test.js:36)
in I18nextProvider (at salesTaxForm.test.js:35)
in Provider (created by WrapperComponent)
in WrapperComponent
Below is the my code:
Component.test.js
import React from 'react';
import { mount } from 'enzyme';
import { Provider } from 'react-redux';
import { act } from 'react-dom/test-utils';
import { I18nextProvider } from 'react-i18next';
import i18n from '../../../i18n';
import SalesTaxForm from './SalesTaxForm.component';
import taxTypes from '../../../mockData/taxTypes.json';
import configureStore from '../../../setUpMockStore';
const initialState = {};
const initialRecord = {
id: 194,
taxRate: 1.29,
taxType: 5,
taxNo: "87681 1522 RT0001",
startDate: new Date(2011, 10, 30),
endDate: new Date(2035, 10, 30)
};
const store = configureStore(initialState);
jest.mock('../../../store');
let wrapper;
const open = true;
const record = initialRecord;
const reloadData = jest.fn();
const setOpen = jest.fn(() => { });
const taxTypesMock = taxTypes;
const mountComponent = () => {
return mount(
<Provider store={store}>
<I18nextProvider i18n={i18n}>
<SalesTaxForm open={open} record={record} reloadData={reloadData}
setOpen={setOpen} taxTypes={taxTypesMock} />
</I18nextProvider>
</Provider>
);
};
describe('SalesTaxForm', () => {
it('should render the salesTaxForm', async () => {
wrapper = mountComponent();
});
});
Componnet.js
import React from 'react';
import PropTypes from 'prop-types';
import { useDispatch } from 'react-redux';
import clsx from 'clsx';
import DateFnsUtils from '#date-io/date-fns';
import { MuiPickersUtilsProvider } from '#material-ui/pickers';
import { useTranslation } from 'react-i18next';
import { Form } from 'react-final-form';
import createDecorator from 'final-form-calculate';
import Modal from '../../../components/UI/Modal/Modal.component';
import { defaultStyles } from '../../../commonStyles';
import { salesTaxOperations } from '../../../store/salesTaxes';
import validate from '../../../services/validation';
import Footer from '../../UI/Tab/Footer/Footer.component';
import { labels, schema } from './validationHandler';
import SalesTaxFields from './SalesTaxFields.component';
import styles from './SalesTaxForm.styles';
let taxTypesList = [];
const calculator = createDecorator({
field: 'taxType',
updates: {
taxNo: val => {
const indx = taxTypesList.findIndex(el => el.id === val);
if (indx > -1) {
return taxTypesList[indx].taxNo;
}
},
},
});
const SalesTaxForm = ({ open, record, reloadData, setOpen, taxTypes }) => {
const classes = styles();
const applyDefaultStyle = defaultStyles();
const { t } = useTranslation();
const dispatch = useDispatch();
taxTypesList = taxTypes;
async function saveHandler(data) {
await dispatch(salesTaxOperations.updateSalesTaxData(data));
reloadData();
setOpen(false);
}
const closeHandler = () => {
setOpen(false);
};
return (
<Modal title={t('SALESTAX_EDITOR')} open={open} showBtns={false}>
<Form
onSubmit={saveHandler}
initialValues={record}
decorators={[calculator]}
validate={values => validate(schema, values, labels)}
render={({ form, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<MuiPickersUtilsProvider utils={DateFnsUtils}>
<div
className={clsx(
classes.container,
classes.modalWidth,
classes.modalContainer,
applyDefaultStyle.modalinnergrid
)}
>
<SalesTaxFields
classes={classes}
record={record}
taxTypes={taxTypes}
/>
<Footer
closeHandler={closeHandler}
saveHandler={form.submit}
customStyle={classes.footer}
/>
</div>
</MuiPickersUtilsProvider>
</form>
)}
/>
</Modal>
);
};
SalesTaxForm.propTypes = {
open: PropTypes.bool.isRequired,
record: PropTypes.object.isRequired,
reloadData: PropTypes.func.isRequired,
setOpen: PropTypes.func.isRequired,
taxTypes: PropTypes.array.isRequired,
};
export default SalesTaxForm;
In my code there is validate function is asynchronous that is causing the problem. Anyone can help in the resolve this problem ? Thanks in advance.

React - frontend component test with Jest

I've just written test file for my component, at the moment it's very rudimentary.. I'm quite inexperience in written test for frontend. I ran yarn test to this test file and it failed miserably..
Here is the message:
Unable to find an element with the text: Please review your billing details...
This is what I have so far for my test:
import React from 'react';
import { render, cleanup, waitForElement } from 'react-testing-library';
// React Router
import { MemoryRouter, Route } from "react-router";
import Show from './Show';
test('it shows the offer', async () => {
const { getByText } = render(
<MemoryRouter initialEntries={['/booking-requests/20-A1-C2/offer']}>
<Route
path="/booking-requests/:booking_request/offer"
render={props => (
<Show {...props} />
)}
/>
</MemoryRouter>
);
//displays the review prompt
await waitForElement(() => getByText('Please review your billing details, contract preview and Additions for your space. Once you’re happy, accept your offer'));
//displays the confirm button
await waitForElement(() => getByText('Confirm'));
});
and this is the component:
// #flow
import * as React from 'react';
import i18n from 'utils/i18n/i18n';
import { Btn } from '#appearhere/bloom';
import css from './Show.css';
import StepContainer from 'components/Layout/DynamicStepContainer/DynamicStepContainer';
const t = i18n.withPrefix('client.apps.offers.show');
const confirmOfferSteps = [
{
title: t('title'),
breadcrumb: t('breadcrumb'),
},
{
title: i18n.t('client.apps.offers.billing_information.title'),
breadcrumb: i18n.t('client.apps.offers.billing_information.breadcrumb'),
},
{
title: i18n.t('client.apps.offers.confirm_pay.title'),
breadcrumb: i18n.t('client.apps.offers.confirm_pay.breadcrumb'),
},
];
class Show extends React.Component<Props> {
steps = confirmOfferSteps;
renderCtaButton = (): React.Element<'Btn'> => {
const cta = t('cta');
return <Btn className={css.button} context='primary'>
{cta}
</Btn>
};
renderLeftContent = ({ isMobile }: { isMobile: boolean }): React.Element<'div'> => (
<div>
<p>{t('blurb')}</p>
{!isMobile && this.renderCtaButton()}
</div>
);
renderRightContent = () => {
return <div>Right content</div>;
};
render() {
const ctaButton = this.renderCtaButton();
return (
<StepContainer
steps={this.steps}
currentStep={1}
ctaButton={ctaButton}
leftContent={this.renderLeftContent}
rightContent={this.renderRightContent}
footer={ctaButton}
/>
);
}
}
export default Show;
what am I missing? Suggestions what else to add to my test file would be greatly appreciated!

Resources