React test vcoverage doesnot fulfill - reactjs

import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { startWorkflow } from 'wf-dbd-react-ui/es/actions'
import Block from 'wf-dbd-react-ui/es/Block'
import BrowserTitle from 'wf-dbd-react-ui/es/BrowserTitle'
import ScrollToTopOnMount from 'wf-dbd-react-ui/es/ScrollToTopOnMount'
import FormMessages from 'wf-dbd-react-ui/es/FormMessages'
import { globals } from 'wf-dbd-react-ui/es/lib'
import withStrings from 'wf-dbd-react-ui/es/withStrings'
import PanelHeader from './panel-header/PanelHeader'
import AppHubTabs from './app-hub-tabs/AppHubTabs'
import RightRail from '../right-rail/RightRail'
import MessageDisplay from '../common/message-display/MessageDisplay'
import AddPayeeMessage from '../common/add-payee-message/AddPayeeMessage'
import { ADD_PAYEE, SEARCH_PAYEE } from '../workflows/add-payee/constants'
import ChangeFundingAccount from '../common/change-funding-account/ChangeFundingAccount'
import EditPaymentHotTaskModalContainer from '../common/hottask-edit-payment-modal/EditPaymentHotTaskModalContainer'
import DismissReminder from '../common/dismiss-reminder-modal/DismissReminder'
import HistoryTabViewPaymentDetailsModal from '../history-tab/group-history-form/history-tab-hot-task-menu/HistoryTabViewPaymentDetailsModal'
import * as actions from '../../lib/store/hub/actions'
import { getPayeeDetails } from '../../lib/store/payee-details/actions'
import { getPaymentDetails } from '../../lib/store/payment-details/actions'
import { resetPayeeTabFilterOption } from '../../lib/store/payees/actions'
import { closeViewPaymentDetailsModal } from '../../lib/store/history-tab/actions'
import { getHistoricalPayments } from '../../lib/selectors/selectors'
import styles from './AppHub.less'
class AppHub extends React.Component {
componentDidMount() {
const { getPayeeDetails, getPaymentDetails } = this.props
// payee details need to be fetched unconditionally. the cache in saga ensures it is not fetched until required
getPayeeDetails()
getPaymentDetails()
// Fix for for wide page layout issue in ie11 - Problem: ie11 nested flex element issue when window is minimized
Iif (!!window.MSInputMethodContext && !!document.documentMode) { // will be true for only for ie11 browser
document.querySelector('body > div > div > div > div').style.flexBasis = '0%'
}
// end of fix for ie11 wide page layout issue
}
componentWillUnmount() {
const { clearMessages, resetPayeeTabFilterOption } = this.props
clearMessages()
//clearing the selected payees-tab filter option
resetPayeeTabFilterOption()
}
handleTabSelect = activeTabIndexNew => {
const { clearMessages, activeTabIndex, setActiveTabIndex } = this.props
if (activeTabIndexNew !== activeTabIndex) {
setActiveTabIndex(activeTabIndexNew)
clearMessages()
}
}
render() {
const { activeTabIndex,
getString,
successMessage,
errorMessage,
ineligibleAccountMessage,
payees,
noOfHistoricalPayments,
payeesLoaded,
startAddPayeeWorkflow,
shouldShowChangeAccount,
reminderDismissalInProgress,
reminderDueDate,
closeViewPaymentDetailsModal,
isViewPaymentDetailsModalOpen,
viewPaymentData,
showEditPaymentSeriesModal
} = this.props
const panelHeaderId = globals().billpayBusinessUser ? 'app.hub.business.bill.pay.header' : 'app.hub.bill.pay.header'
return (
<Block layout={true} horizontal={true}>
<BrowserTitle title={getString('app.title')} />
<ScrollToTopOnMount />
<Block flex={true} relative={true} className={styles.billPayHub}>
<PanelHeader headerId={panelHeaderId} />
{
successMessage &&
<Block className={styles.message}>
<MessageDisplay messages={successMessage} />
</Block>
}
{
errorMessage &&
<Block className={styles.message}>
<MessageDisplay messages={errorMessage} />
</Block>
}
{
ineligibleAccountMessage && globals().enableDisplayPayeesWithIneligibleAccounts &&
<Block className={styles.message}>
<MessageDisplay messages={ineligibleAccountMessage} focusOnMount={true} />
</Block>
}
<Block className={styles.message}>
<FormMessages formId="makePaymentForm" className="formMessages" />
</Block>
{payeesLoaded && payees.size === 0 && <AddPayeeMessage startAddPayeeWorkflow={startAddPayeeWorkflow} />}
{payeesLoaded && (payees.size > 0 || noOfHistoricalPayments > 0) && (
<AppHubTabs
activeTabIndex={activeTabIndex}
onTabSelect={this.handleTabSelect}
/>
)
}
</Block>
<Block relative={true} styles={styles.rightRailContainer}>
<RightRail />
</Block>
{shouldShowChangeAccount && <ChangeFundingAccount />}
{showEditPaymentSeriesModal && <EditPaymentHotTaskModalContainer />}
{reminderDismissalInProgress && <DismissReminder dueDate={reminderDueDate} />}
{isViewPaymentDetailsModalOpen &&
<HistoryTabViewPaymentDetailsModal
isOpen={isViewPaymentDetailsModalOpen}
closeModal={closeViewPaymentDetailsModal}
viewPaymentData={viewPaymentData}
/>
}
</Block>
)
}
}
AppHub.propTypes = {
activeTabIndex: PropTypes.number.isRequired,
payees: PropTypes.object.isRequired,
noOfHistoricalPayments: PropTypes.number.isRequired,
payeesLoaded: PropTypes.bool,
startAddPayeeWorkflow: PropTypes.func.isRequired,
errorMessage: PropTypes.object,
successMessage: PropTypes.object,
shouldShowChangeAccount: PropTypes.bool,
resetPayeeTabFilterOption: PropTypes.func.isRequired,
getPayeeDetails: PropTypes.func.isRequired,
getPaymentDetails: PropTypes.func.isRequired,
clearMessages: PropTypes.func.isRequired,
setActiveTabIndex: PropTypes.func.isRequired,
getString: PropTypes.func.isRequired,
reminderDismissalInProgress: PropTypes.bool,
reminderDueDate: PropTypes.string,
closeViewPaymentDetailsModal: PropTypes.func.isRequired,
isViewPaymentDetailsModalOpen: PropTypes.bool,
viewPaymentData: PropTypes.object,
showEditPaymentSeriesModal: PropTypes.func.isRequired,
ineligibleAccountMessage: PropTypes.object
}
const mapStateToProps = state => ({
activeTabIndex: state.app.hub.activeHubTab,
successMessage: state.app.hub.successMessage,
errorMessage: state.app.hub.errorMessage,
ineligibleAccountMessage: state.app.hub.ineligibleAccountMessage,
payees: state.app.payees.payees,
noOfHistoricalPayments: getHistoricalPayments(state),
payeesLoaded: state.app.payees.payeesLoaded,
shouldShowChangeAccount: state.app.hub.showChangeAccount,
showEditPaymentSeriesModal: state.app.editPayment.editPaymentModal.isModalOpen,
reminderDismissalInProgress: state.app.hub.reminderDismissalInProgress,
reminderDueDate: state.app.hub.reminderDueDate,
isViewPaymentDetailsModalOpen: state.app.historyTab.isViewPaymentDetailsModalOpen,
viewPaymentData: state.app.historyTab.viewPaymentDetails
})
const mapDispatchToProps = dispatch => ({
setActiveTabIndex: index => dispatch(actions.setHubActiveTab(index)),
clearMessages: () => dispatch(actions.clearMessages()),
startAddPayeeWorkflow: () => dispatch(startWorkflow({ name: ADD_PAYEE, startingView: SEARCH_PAYEE })),
getPayeeDetails: () => dispatch(getPayeeDetails()),
getPaymentDetails: () => dispatch(getPaymentDetails()),
resetPayeeTabFilterOption: () => dispatch(resetPayeeTabFilterOption()),
closeViewPaymentDetailsModal: () => dispatch(closeViewPaymentDetailsModal())
})
export default compose(withStrings, connect(mapStateToProps, mapDispatchToProps))(AppHub)
iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.iamtryingtofindasolutionpleasehelp.
import React from 'react'
import { mount } from 'enzyme'
import MockStoreProvider from 'wf-dbd-react-ui/es/MockStoreProvider'
import Immutable from 'immutable'
import AppHub from '../AppHub'
import AppHubTabs from '../app-hub-tabs/AppHubTabs'
import AddPayeeMessage from '../../common/add-payee-message/AddPayeeMessage'
import PanelHeader from '../panel-header/PanelHeader'
jest.mock('../panel-header/PanelHeader', () => () => 'PanelHeader')
jest.mock('../app-hub-tabs/AppHubTabs', () => () => 'AppHubTabs')
jest.mock('../../common/add-payee-message/AddPayeeMessage', () => () => 'AddPayeeMessage')
jest.mock('../../common/message-display/MessageDisplay', () => () => 'MessageDisplay')
jest.mock('../../right-rail/RightRail', () => () => 'RightRail')
jest.mock('../../history-tab/group-history-form/history-tab-hot-task-menu/HistoryTabViewPaymentDetailsModal', () => () => 'HistoryTabViewPaymentDetailsModal')
describe('AppHub', () => {
let wrapper
describe('when rendered without any payees and payments', () => {
beforeEach(() => {
const getString = jest.fn().mockImplementation(() => 'testString')
const appState = {
hub: {
activeHubTab: 0,
successMessage: { SuccessMessageBean: { globalMessages: [{ level: 'confirm', message: 'message' }] } },
errorMessage: {}
},
payees: {
payees: Immutable.List([]),
enableSubmitBar: false,
payeesLoaded: true
},
paymentAccounts: {
paymentAccounts: Immutable.List([]),
enableSubmitBar: false
},
scheduledPayments: {
paymentsLoaded: false
},
historyTab: {
isViewPaymentDetailsModalOpen: true,
viewPaymentDetails: { key: 'value' },
historicalPayments: {
payments: []
}
},
editPayment: {
editPaymentModal: {
isModalOpen: false
}
}
}
wrapper = mount(
<MockStoreProvider appState={appState}>
<AppHub getString={getString} />
</MockStoreProvider>
)
})
it('should be defined', () => {
expect(AppHub).toBeDefined()
})
it('should render a PanelHeader', () => {
expect(wrapper.find(PanelHeader)).toHaveLength(1)
})
it('should render AddPayeeMessage', () => {
expect(wrapper.find(AddPayeeMessage)).toHaveLength(1)
})
})
describe('when rendered without any payees and historical payments are present', () => {
beforeEach(() => {
const getString = jest.fn().mockImplementation(() => 'testString')
const appState = {
hub: {
activeHubTab: 0,
successMessage: { SuccessMessageBean: { globalMessages: [{ level: 'confirm', message: 'message' }] } },
errorMessage: {}
},
payees: {
payees: Immutable.List([]),
enableSubmitBar: false,
payeesLoaded: true
},
paymentAccounts: {
paymentAccounts: Immutable.List([]),
enableSubmitBar: false
},
scheduledPayments: {
paymentsLoaded: false
},
historyTab: {
isViewPaymentDetailsModalOpen: true,
viewPaymentDetails: { key: 'value' },
historicalPayments: {
payments: [{
mockKey: 'mockValue'
}]
}
},
editPayment: {
editPaymentModal: {
isModalOpen: false
}
}
}
wrapper = mount(
<MockStoreProvider appState={appState}>
<AppHub getString={getString} />
</MockStoreProvider>
)
})
it('should render AppHubTabs', () => {
expect(wrapper.find(AppHubTabs)).toHaveLength(1)
})
it('should render AddPayeeMessage', () => {
expect(wrapper.find(AddPayeeMessage)).toHaveLength(1)
})
})
describe('when rendered with payees and there are no historical payments', () => {
beforeEach(() => {
const getString = jest.fn().mockImplementation(() => 'testString')
const appState = {
hub: {
activeHubTab: 0,
successMessage: { SuccessMessageBean: { globalMessages: [{ level: 'confirm', message: 'message' }] } },
errorMessage: {}
},
payees: {
payees: Immutable.List([{ id: 1 }]),
enableSubmitBar: false,
payeesLoaded: true
},
paymentAccounts: {
paymentAccounts: Immutable.List([]),
enableSubmitBar: false
},
scheduledPayments: {
paymentsLoaded: false
},
historyTab: {
isViewPaymentDetailsModalOpen: true,
viewPaymentDetails: { key: 'value' },
historicalPayments: {
payments: []
}
},
editPayment: {
editPaymentModal: {
isModalOpen: false
}
}
}
wrapper = mount(
<MockStoreProvider appState={appState}>
<AppHub getString={getString} />
</MockStoreProvider>
)
})
it('should render AppHubTabs', () => {
expect(wrapper.find(AppHubTabs)).toHaveLength(1)
})
it('should render AddPayeeMessage', () => {
expect(wrapper.find(AddPayeeMessage)).toHaveLength(0)
})
})
})

Related

How to test setFieldValue from Formik, in a react-slider component

maybe it is a weird question, but I am new to unit testing, and can't wrap my head around this one.
import { Field, FieldInputProps, FieldMetaProps, FormikHelpers, FormikValues } from 'formik';
import Slider from 'components/elements/slider';
import FormError from 'components/elements/formerror';
import { useEffect, useState } from 'react';
interface IScheduleSlider {
min?: number;
max?: number;
title: string;
name: string;
unitLabel: string;
conversions: {
first: {
//eslint-disable-next-line
convertFct: (x: number) => number;
label: string;
};
second: {
//eslint-disable-next-line
convertFct: (x: number) => number;
label: string;
};
};
showError?: boolean;
}
const ScheduleSlider = ({ min = 0, max = 100, title, name, unitLabel, conversions, showError = false }: IScheduleSlider) => {
const [error, setError] = useState(false);
console.log(conversions);
useEffect(() => {
if (showError) {
setError(true);
}
}, [showError]);
const getRealValue = (val: number) => (val > max ? max : val);
return (
<Field name={name}>
{({
field,
form: { setFieldValue, setFieldTouched },
meta,
}: {
field: FieldInputProps<never>;
form: FormikHelpers<FormikValues>;
meta: FieldMetaProps<never>;
}) => (
<div className={`schedule-slider ${error ? 'error' : ''}`} data-testid="schedule-slider-test">
<div className="schedule-slider__top">
<h4>{title}</h4>
{typeof conversions?.first?.convertFct === 'function' && typeof conversions?.second?.convertFct === 'function' && (
<div>
<div className="schedule-slider__top--conversion">
<h5>{conversions?.first?.convertFct(field.value)}</h5>
<span>{conversions?.first?.label}</span>
</div>
<div className="schedule-slider__top--conversion">
<h5>{conversions?.second?.convertFct(field.value)}</h5>
<span>{conversions?.second?.label}</span>
</div>
</div>
)}
</div>
<div className="schedule-slider__bottom">
<Slider
max={Math.ceil(max)}
min={min}
value={Math.ceil(field.value)}
onChange={(val: number) => {
setFieldValue(field?.name, getRealValue(val));
setFieldTouched(field?.name, true);
setError(false);
}}
labels={{ left: `${min} ${unitLabel}`, right: `${max} ${unitLabel}` }}
/>
<div className="schedule-slider__value">
<div>
<h3 data-testid="field-test-value">{field.value}</h3>
<span>{unitLabel}</span>
</div>
</div>
</div>
{error && <FormError meta={meta} />}
</div>
)}
</Field>
);
};
export default ScheduleSlider;
This is my tsx file with the component, I have a ScheduleSlider component which in itself contains a formik component, and a react-slider, which has the onChange prop where setFieldValue and setFieldTouched is.
/* eslint-disable testing-library/no-container */
/* eslint-disable testing-library/no-node-access */
import ScheduleSlider from './scheduleslider';
import * as Formik from 'formik';
import { cleanup, fireEvent, render, screen, waitFor } from '#testing-library/react';
import React from 'react';
import userEvent from '#testing-library/user-event';
import { renderWithRedux } from 'test-utils';
describe('render ScheduleSlider', () => {
const mockFn = jest.fn();
const useFormikContextMock = jest.spyOn(Formik, 'useFormikContext');
beforeEach(() => {
useFormikContextMock.mockReturnValue({
setFieldValue: jest.fn(),
setFieldTouched: jest.fn(),
} as unknown as never);
});
afterEach(() => {
jest.clearAllMocks();
cleanup();
});
const defaultId = 'schedule-slider-test';
const sliderId = 'slider-test';
const fieldId = 'field-test-value';
it('render component with props', async () => {
const props = {
min: 0,
max: 100,
title: 'Test title',
name: 'POWER',
unitLabel: 'kWh',
conversions: {
first: {
convertFct: jest.fn().mockImplementation((x) => x),
label: '%',
},
second: {
convertFct: jest.fn().mockImplementation((x) => x),
label: 'km',
},
},
showError: false,
};
const { container } = renderWithRedux(
<Formik.Formik initialValues={{}} enableReinitialize onSubmit={mockFn}>
<ScheduleSlider {...props} />
</Formik.Formik>
);
// const slider = screen.queryByRole('slider');
const slider = screen.getByTestId(sliderId);
expect(container).toBeVisible();
props.conversions.first.convertFct.mockReturnValue('10');
props.conversions.second.convertFct.mockReturnValue('30');
// expect(slider).toBeVisible();
// expect(slider).toHaveClass('slider__thumb slider__thumb-0');
if (slider) {
slider.ariaValueMin = '1';
slider.ariaValueMax = '100';
// screen.getByTestId(fieldId).textContent = '80';
fireEvent.keyDown(slider, { key: 'ArrowLeft', code: 'ArrowLeft', charCode: 37 });
}
// expect(useFormikContextMock).toBeCalled();
// console.log(slider?.ariaValueMin);
// console.log(screen.getByTestId(fieldId).textContent);
console.log(props.conversions.second.convertFct.mock.results);
console.log(container.textContent);
});
it('render with default min, max, showError value', () => {
const props = {
min: undefined,
max: undefined,
title: 'test title',
name: 'schedule',
unitLabel: 'test unit',
conversions: {
first: {
convertFct: jest.fn(),
label: '%',
},
second: {
convertFct: jest.fn(),
label: 'km',
},
},
showError: true,
};
render(
<Formik.Formik initialValues={{}} enableReinitialize onSubmit={mockFn}>
<ScheduleSlider {...props} />
</Formik.Formik>
);
expect(screen.getByTestId(defaultId)).toBeVisible();
});
it('checks for onChange values', () => {
const props = {
min: 0,
max: 100,
title: 'test title',
name: 'schedule',
unitLabel: 'test unit',
conversions: {
first: {
convertFct: jest.fn(),
label: '%',
},
second: {
convertFct: jest.fn(),
label: 'km',
},
},
showError: undefined,
};
render(
<Formik.Formik initialValues={{}} enableReinitialize onSubmit={mockFn}>
<ScheduleSlider {...props} />
</Formik.Formik>
);
expect(screen.getByTestId(defaultId)).toBeVisible();
});
});
And this is my test file, I could render the component, and some of the branches, functions, statemnts are covered, but don't know how to test setFieldValue. Tried to fire events, but I am making some errors and can't see where. Anybody has any idea how to start with this. Sorry for the comments, console.log-s but I was tryng all kinds of solutions

React Testing Error : TestingLibraryElementError: Unable to find an element with the text

I am using React Testing Library for first time and it seems very confusing. I am stuck on the error since long. I will be very grateful if someone proposes a solution to this problem
Here is my testfile:
import React from 'react';
import { cleanup, fireEvent, screen } from '#testing-library/react';
import { act } from 'react-dom/test-utils';
import { render } from '../../../test-util';
import EmailEditor from '../../../../components/commonComponents/EmailInput/EmailChip';
const mockFn = jest.fn();
const props={
salutation: '',
signature: '',
emailBody: '',
onChangeEmailBody: () => { },
onFocus: () => { },
onBlur: () => { },
disabled: false,
showSalutation: true,
showSignature: true,
allowVariables: false,
showPersonalize: false,
customParams: [],
recommendImgSize: '',
autoFocus: false,
allowFonts: true
}
describe('Email Editor Test cases', () => {
afterEach(cleanup);
it('Determining the ', async () => {
act(() => {
render(<EmailEditor {...props} /> );
});
const OnclickElement = screen.getByText(/Select Personalization Parameter/i)
expect(OnclickElement).toBeInTheDocument();
});
it('should render the button', async () => {
act(() => {
render(<EmailEditor {...props} /> );
});
const OnclickElement = screen.getByTestId(/custom/i)
expect(OnclickElement).toBeInTheDocument();
});
Here are some of the elements which are associated in index.js
<Header style={{ fontSize: window.innerWidth > MOBILE_WIDTH ? '1.15vw' : 12.15 }}>Select Personalization Parameter</Header>
<CustomToolbar key="customToolbar" show={allowVariables && showPersonalize} uniqueKey={uniqueKey} recommendImgSize={recommendImgSize} allowFonts={allowFonts} data-testid = "custom"/>
Please help me with this.

Adding a Modal Instead of "prompt" in react app

Adding a Modal Instead of "prompt" in react app
Hi Iam creating a menu builder with react-sortable-tree. I want to add a Modal when clicked on Add or Edit Task. prompt is working fine here but i want Modal to be opened. I have created state and finctions for modal but unable to render on UI when clicked. anybody help
import React, { Component } from "react";
import "bootstrap/dist/css/bootstrap.css";
import "react-sortable-tree/style.css";
import { Button } from "react-bootstrap";
import "./MenuBuilder.css";
import { Modal } from "react-responsive-modal";
import "react-responsive-modal/styles.css";
import treeData from "./MenuBuilderData";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { faTrashAlt, faPlus, faPen } from "#fortawesome/free-solid-svg-icons";
import SortableTree, {
toggleExpandedForAll,
getNodeAtPath,
addNodeUnderParent,
removeNode,
changeNodeAtPath,
} from "react-sortable-tree";
const maxDepth = 5;
export default class MenuBuilder extends Component {
constructor(props) {
super(props);
this.state = {
treeData: treeData,
searchString: "",
searchFocusIndex: 0,
searchFoundCount: null,
openModal: false,
};
}
onOpenModal = (e) => {
e.preventDefault();
this.setState({ openModal: true });
};
onCloseModal = () => {
this.setState({ openModal: false });
};
handleTreeOnChange = (treeData) => {
this.setState({ treeData });
};
selectPrevMatch = () => {
const { searchFocusIndex, searchFoundCount } = this.state;
this.setState({
searchFocusIndex:
searchFocusIndex !== null
? (searchFoundCount + searchFocusIndex - 1) % searchFoundCount
: searchFoundCount - 1,
});
};
selectNextMatch = () => {
const { searchFocusIndex, searchFoundCount } = this.state;
this.setState({
searchFocusIndex:
searchFocusIndex !== null
? (searchFocusIndex + 1) % searchFoundCount
: 0,
});
};
toggleNodeExpansion = (expanded) => {
this.setState((prevState) => ({
treeData: toggleExpandedForAll({
treeData: prevState.treeData,
expanded,
}),
}));
};
getNodeKey = ({ treeIndex: number }) => {
if (number === -1) {
number = null;
}
return number;
};
handleSave = () => {
console.log(JSON.stringify(this.state.treeData));
};
editTask = (path) => {
let editedNode = getNodeAtPath({
treeData: this.state.treeData,
path: path,
getNodeKey: ({ treeIndex }) => treeIndex,
ignoreCollapsed: true,
});
let newTaskTitle = prompt("Task new name:", editedNode.node.title);
if (newTaskTitle === null) return false;
editedNode.node.title = newTaskTitle;
let newTree = changeNodeAtPath({
treeData: this.state.treeData,
path: path,
newNode: editedNode.node,
getNodeKey: ({ treeIndex }) => treeIndex,
ignoreCollapsed: true,
});
// console.log(newTree);
this.setState({ treeData: newTree });
};
addTask = (path) => {
let parentNode = getNodeAtPath({
treeData: this.state.treeData,
path: path,
getNodeKey: ({ treeIndex }) => treeIndex,
ignoreCollapsed: true,
});
let newTaskTitle = parentNode.node.children ? prompt("Task name:", "default") && prompt("form ID:", "default") : prompt("Task name:", "default") // let newFormId = prompt("Form Id:", "");
if (newTaskTitle === null) return false;
let NEW_NODE = { title: newTaskTitle };
// let NEW_ID = { id: newFormId };
let parentKey = this.getNodeKey(parentNode);
let newTree = addNodeUnderParent({
treeData: this.state.treeData,
newNode: NEW_NODE,
// newId: NEW_ID,
expandParent: true,
parentKey: parentKey,
getNodeKey: ({ treeIndex }) => treeIndex,
});
this.setState({ treeData: newTree.treeData });
};
removeTask = (path) => {
let newTree = removeNode({
treeData: this.state.treeData,
path: path,
ignoreCollapsed: true,
getNodeKey: ({ treeIndex }) => treeIndex,
});
this.setState({ treeData: newTree.treeData });
};
renderTasks = () => {
const { treeData, searchString, searchFocusIndex } = this.state;
return (
<>
<SortableTree
treeData={treeData}
onChange={this.handleTreeOnChange}
maxDepth={maxDepth}
searchQuery={searchString}
searchFocusOffset={searchFocusIndex}
canDrag={({ node }) => !node.noDragging}
canDrop={({ nextParent }) => !nextParent || !nextParent.noChildren}
searchFinishCallback={(matches) =>
this.setState({
searchFoundCount: matches.length,
searchFocusIndex:
matches.length > 0 ? searchFocusIndex % matches.length : 0,
})
}
isVirtualized={true}
generateNodeProps={(taskInfo) => ({
buttons: [
<Button
variant="link"
onClick={() => this.editTask(taskInfo.path)}
>
<FontAwesomeIcon icon={faPen} color="#28a745" />
</Button>,
<Button
variant="link"
onClick={() => this.addTask(taskInfo.path)}
>
<FontAwesomeIcon icon={faPlus} color="#007bff" />
</Button>,
<Button
variant="link"
onClick={() => this.removeTask(taskInfo.path)}
>
<FontAwesomeIcon icon={faTrashAlt} color="#dc3545" />
</Button>,
],
})}
/>
<Button style={{ width: "100px" }} onClick={this.handleSave}>
save
</Button>
</>
);
};
render() {
return (
<>
<div className="wrapper">{this.renderTasks()}</div>
{/* <div>
<button onClick={this.onOpenModal}>Click Me</button>
<Modal open={this.state.openModal} onClose={this.onCloseModal}>
<input type="text" />
</Modal>
</div> */}
</>
);
}
}
treeData.js
const treeData = [
{
expanded: true,
title: "Contact HR",
children: [
{
expanded: true,
title: "Build relationships"
},
{
expanded: true,
title: "Take a test assignment"
}
]
},
{
expanded: true,
title: "Complete a test assignment",
children: [
{
expanded: true,
title: "Send link to this doc through LinkedIn"
}
]
},
{
expanded: true,
title: "Discuss Proposal details",
children: [
{
expanded: true,
title: "Prepare list of questions."
},
{
expanded: true,
title: "Other coming soon..."
}
]
},
{
expanded: true,
title: "Make an appointment for a technical interview",
children: [
{
expanded: true,
title: "Discuss details of the technical interview"
},
{
expanded: true,
title: "Prepare to Technival Interview"
}
]
},
{
expanded: true,
title: "Accept or Decline Offer"
}
];
export default treeData;

How to test react-dropzone with Jest and react-testing-library?

I want to test onDrop method from react-dropzone library in React component. I am using Jest, React Testing Library. I'm creating mock file and I'm trying to drop this files in input, but in console.log files are still equal to an empty array. Do you have any ideas?
package.json
"typescript": "^3.9.7",
"#testing-library/jest-dom": "^5.11.4",
"#testing-library/react": "^11.0.4",
"#types/jest": "^26.0.13",
"jest": "^26.4.2",
"ts-jest": "^26.3.0",
"react-router-dom": "^5.1.2",
"react-dropzone": "^10.1.10",
"#types/react-dropzone": "4.2.0",
ModalImportFile.tsx
import React, { FC, useState } from "react";
import { Box, Button, Dialog, DialogContent, DialogTitle, Grid } from "#material-ui/core";
import { useDropzone } from "react-dropzone";
import AttachFileIcon from "#material-ui/icons/AttachFile";
import DeleteIcon from "#material-ui/icons/Delete";
interface Props {
isOpen: boolean;
}
interface Events {
onClose: () => void;
}
const ModalImportFile: FC<Props & Events> = props => {
const { isOpen } = props as Props;
const { onClose } = props as Events;
const [files, setFiles] = useState<Array<File>>([]);
const { getRootProps, getInputProps, open } = useDropzone({
onDrop: (acceptedFiles: []) => {
setFiles(
acceptedFiles.map((file: File) =>
Object.assign(file, {
preview: URL.createObjectURL(file),
}),
),
);
},
noClick: true,
noKeyboard: true,
});
const getDragZoneContent = () => {
if (files && files.length > 0)
return (
<Box border={1} borderRadius={5} borderColor={"#cecece"} p={2} mb={2}>
<Grid container alignItems="center" justify="space-between">
<Box color="text.primary">{files[0].name}</Box>
<Box ml={1} color="text.secondary">
<Button
startIcon={<DeleteIcon color="error" />}
onClick={() => {
setFiles([]);
}}
/>
</Box>
</Grid>
</Box>
);
return (
<Box border={1} borderRadius={5} borderColor={"#cecece"} p={2} mb={2} style={{ borderStyle: "dashed" }}>
<Grid container alignItems="center">
<Box mr={1} color="text.secondary">
<AttachFileIcon />
</Box>
<Box color="text.secondary">
<Box onClick={open} component="span" marginLeft="5px">
Download
</Box>
</Box>
</Grid>
</Box>
);
};
const closeHandler = () => {
onClose();
setFiles([]);
};
return (
<Dialog open={isOpen} onClose={closeHandler}>
<Box width={520}>
<DialogTitle>Import</DialogTitle>
<DialogContent>
<div data-testid="container" className="container">
<div data-testid="dropzone" {...getRootProps({ className: "dropzone" })}>
<input data-testid="drop-input" {...getInputProps()} />
{getDragZoneContent()}
</div>
</div>
</DialogContent>
</Box>
</Dialog>
);
};
export default ModalImportFile;
ModalImportFile.test.tsx
import React from "react";
import { render, screen, fireEvent } from "#testing-library/react";
import ModalImportFile from "../../components/task/elements/ModalImportFile";
const props = {
isOpen: true,
onClose: jest.fn(),
};
beforeEach(() => jest.clearAllMocks());
describe("<ModalImportFile/>", () => {
it("should drop", async () => {
render(<ModalImportFile {...props} />);
const file = new File([JSON.stringify({ ping: true })], "ping.json", { type: "application/json" });
const data = mockData([file]);
function dispatchEvt(node: any, type: any, data: any) {
const event = new Event(type, { bubbles: true });
Object.assign(event, data);
fireEvent(node, event);
}
function mockData(files: Array<File>) {
return {
dataTransfer: {
files,
items: files.map(file => ({
kind: "file",
type: file.type,
getAsFile: () => file,
})),
types: ["Files"],
},
};
}
const inputEl = screen.getByTestId("drop-input");
dispatchEvt(inputEl, "dragenter", data);
});
}
With the rokki`s answer (https://stackoverflow.com/a/64643985/9405587), I rewrote the test component for easier understanding.
ModalImportFile.test.tsx
import React from "react";
import { render, screen, fireEvent } from "#testing-library/react";
import ModalImportFile from "../../components/task/elements/ModalImportFile";
const props = {
isOpen: true,
onClose: jest.fn(),
};
beforeEach(() => jest.clearAllMocks());
describe("<ModalImportFile/>", () => {
it("should drop", async () => {
render(<ModalImportFile {...props} />);
window.URL.createObjectURL = jest.fn().mockImplementation(() => "url");
const inputEl = screen.getByTestId("drop-input");
const file = new File(["file"], "ping.json", {
type: "application/json",
});
Object.defineProperty(inputEl, "files", {
value: [file],
});
fireEvent.drop(inputEl);
expect(await screen.findByText("ping.json")).toBeInTheDocument();
}
How about changing fireEvent(node, event); to fireEvent.drop(node, event);.
References:
https://jestjs.io/docs/jest-object#jestrequireactualmodulename
requireActual
Returns the actual module instead of a mock, bypassing all checks on whether the module should receive a mock implementation or not.
let dropCallback = null;
let onDragEnterCallback = null;
let onDragLeaveCallback = null;
jest.mock('react-dropzone', () => ({
...jest.requireActual('react-dropzone'),
useDropzone: options => {
dropCallback = options.onDrop;
onDragEnterCallback = options.onDragEnter;
onDragLeaveCallback = options.onDragLeave;
return {
acceptedFiles: [{
path: 'sample4.png'
},
{
path: 'sample3.png'
}
],
fileRejections: [{
file: {
path: 'FileSelector.docx'
},
errors: [{
code: 'file-invalid-type',
message: 'File type must be image/*'
}]
}],
getRootProps: jest.fn(),
getInputProps: jest.fn(),
open: jest.fn()
};
}
}));
it('Should get on drop Function with parameter', async() => {
const accepted = [{
path: 'sample4.png'
},
{
path: 'sample3.png'
},
{
path: 'sample2.png'
}
];
const rejected = [{
file: {
path: 'FileSelector.docx'
},
errors: [{
code: 'file-invalid-type',
message: 'File type must be image/*'
}]
}];
const event = {
bubbles: true,
cancelable: false,
currentTarget: null,
defaultPrevented: true,
eventPhase: 3,
isDefaultPrevented: () => {},
isPropagationStopped: () => {},
isTrusted: true,
target: {
files: {
'0': {
path: 'FileSelector.docx'
},
'1': {
path: 'sample4.png'
},
'2': {
path: 'sample3.png'
},
'3': {
path: 'sample2.png'
}
}
},
timeStamp: 1854316.299999997,
type: 'change'
};
dropCallback(accepted, rejected, event);
onDragEnterCallback();
onDragLeaveCallback();
expect(handleFiles).toHaveBeenCalledTimes(1);
});

fireEvent is calling Found multiple elements by: data-testid error in react-testing-library

I'm calling a function by finding the button with the data-testid with "show_more_button"
<OurSecondaryButton test={"show_more_button"} onClick={(e) => showComments(e)} component="span" color="secondary">
View {min !== -1 && min !== -2 ? min : 0} More Comments
</OurSecondaryButton>
showComments
const showComments = (e) => {
e.preventDefault();
if (inc + 2 && inc <= the_comments) {
setShowMore(inc + 2);
setShowLessFlag(true);
} else {
setShowMore(the_comments);
}
};
which renders this
const showMoreComments = () => {
return filterComments.map((comment, i) => (
<div data-testid="comment-show-more" key={i}>
<CommentListContainer ref={ref} comment={comment} openModal={openModal} handleCloseModal={handleCloseModal} isBold={isBold} handleClickOpen={handleClickOpen} {...props} />
</div>
));
};
and upon executing fireEvent the function gets called which is good but, im getting the error:
TestingLibraryElementError: Found multiple elements by:
[data-testid="comment-show-more"]
(If this is intentional, then use the `*AllBy*` variant of the query (like `queryAllByText`, `getAllByText`, or `findAllByText`)).
There is only one data-testid with "comment-show-more", i doubled checked, this function must be getting triggered multiple times in the same test i guess, Im not sure..
CommentList.test.tsx
it("should fire show more action", () => {
const { getByTestId } = render(<CommentList {...props} />);
const showMore = getByTestId("show_more_button");
fireEvent.click(showMore);
expect(getByTestId("comment-show-more").firstChild).toBeTruthy();
});
CommentList.test.tsx (full code)
import "#testing-library/jest-dom";
import React, { Ref } from "react";
import CommentList from "./CommentList";
import { render, getByText, queryByText, getAllByTestId, fireEvent } from "#testing-library/react";
import { Provider } from "react-redux";
import { store } from "../../../store";
const props = {
user: {},
postId: null,
userId: null,
currentUser: {},
ref: {
current: undefined,
},
comments: [
{
author: { username: "barnowl", gravatar: "https://api.adorable.io/avatars/400/bf1eed82fbe37add91cb4192e4d14de6.png", bio: null },
comment_body: "fsfsfsfsfs",
createdAt: "2020-05-27T14:32:01.682Z",
gifUrl: "",
id: 520,
postId: 28,
updatedAt: "2020-05-27T14:32:01.682Z",
userId: 9,
},
{
author: { username: "barnowl", gravatar: "https://api.adorable.io/avatars/400/bf1eed82fbe37add91cb4192e4d14de6.png", bio: null },
comment_body: "fsfsfsfsfs",
createdAt: "2020-05-27T14:32:01.682Z",
gifUrl: "",
id: 519,
postId: 27,
updatedAt: "2020-05-27T14:32:01.682Z",
userId: 10,
},
{
author: { username: "barnowl2", gravatar: "https://api.adorable.io/avatars/400/bf1eed82fbe37add91cb4192e4d14de6.png", bio: null },
comment_body: "fsfsfsfsfs",
createdAt: "2020-05-27T14:32:01.682Z",
gifUrl: "",
id: 518,
postId: 28,
updatedAt: "2020-05-27T14:32:01.682Z",
userId: 11,
},
],
deleteComment: jest.fn(),
};
describe("Should render <CommentList/>", () => {
it("should render <CommentList/>", () => {
const commentList = render(<CommentList {...props} />);
expect(commentList).toBeTruthy();
});
it("should render first comment", () => {
const { getByTestId } = render(<CommentList {...props} />);
const commentList = getByTestId("comment-list-div");
expect(commentList.firstChild).toBeTruthy();
});
it("should render second child", () => {
const { getByTestId } = render(<CommentList {...props} />);
const commentList = getByTestId("comment-list-div");
expect(commentList.lastChild).toBeTruthy();
});
it("should check comments", () => {
const rtl = render(<CommentList {...props} />);
expect(rtl.getByTestId("comment-list-div")).toBeTruthy();
expect(rtl.getByTestId("comment-list-div")).toBeTruthy();
expect(rtl.getByTestId("comment-list-div").querySelectorAll(".comment").length).toEqual(2);
});
// it("should match snapshot", () => {
// const rtl = render(<CommentList {...props} />);
// expect(rtl).toMatchSnapshot();
// });
it("should check more comments", () => {
const { queryByTestId } = render(<CommentList {...props} />);
const commentList = queryByTestId("comment-show-more");
expect(commentList).toBeNull();
});
it("should fire show more action", () => {
const { getByTestId } = render(<CommentList {...props} />);
const showMore = getByTestId("show_more_button");
fireEvent.click(showMore);
expect(getByTestId("comment-show-more").firstChild).toBeTruthy();
});
});
Try to clean up the DOM after each test
import { cleanup } from '#testing-library/react'
// Other import and mock props
describe("Should render <CommentList/>", () => {
afterEach(cleanup)
// your test
}
Note: You have filterComments.map so make sure filterComments have one item.
use
getAllByTestId
example:
await waitFor(() => userEvent.click(screen.getAllByTestId('serviceCard')[0]));
Kinda late but this may be helpful for somebody:
I can see that you are using a iterator that might return multiple children, if you want to solve differently, add a literals key for each child when defining your data-testid tag:
const showMoreComments = () => {
return filterComments.map((comment, i) => (
<div data-testid={`comment-show-more-test-key-${i}`} key={i}>
<CommentListContainer ref={ref} comment={comment} openModal={openModal} handleCloseModal={handleCloseModal} isBold={isBold} handleClickOpen={handleClickOpen} {...props} />
</div>
));
};
It can be solved by use getAllByTestId.
it("should fire show more action", () => {
const { getAllByTestId, getByTestId } = render(<CommentList {...props} />);
const showMore = getAllByTestId("show_more_button")[0];
fireEvent.click(showMore);
expect(getByTestId("comment-show-more").firstChild).toBeTruthy();
});

Resources