How to test Callback of Material Pagination Element in Functional Component? - reactjs

I'm trying to get full unit test coverage using '#testing-library/react' but am having trouble triggering the callbacks of Material's React Pagination Component. When I simulate a click on the next page button, I still see no coverage of the callback defined in my file. Nor can I replace this callback with a mocked callback because it is defined inside my functional component, so a mocked one cannot be passed in as a parameter. Here's what I've tried so far. Any help is really appreciated.
DataMonitoring.tsx
const DataMonitoring: React.FC<DataMonitoringProps> = ({}: DataMonitoringProps) => {
const handleChangePage = (event: React.MouseEvent<HTMLButtonElement> | null, newPageIndex: number) => {
console.log("it works"); // not begin logged on simulated click
};
const handleChangeRowsPerPage = (event: React.ChangeEvent<HTMLInputElement>) => {
console.log("it works"); // not being logged on simulated click
};
return (
<TablePagination
rowsPerPageOptions={[10, 20, 50]}
component="div"
count={10}
page={0}
onPageChange={handleChangePage}
rowsPerPage={4}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
);
};
DataMonitoring.test.tsx
describe('DataMonitoring Pagination Test', () => {
it('handles page onchange', () => {
const mockPageIndex = 0;
const mockPageSize = 0;
const mockTotalObjects = 0;
const { container } = render(
<DataMonitoring/>
);
const nextPageBtn = container.querySelector('[title="Next page"]');
userEvent.click(nextPageBtn);
}
});

Related

Act Warning When Using MUI Popover

I am having a hard time getting a component to render without any act warnings from react. Here is the component that I am testing. It contains a TextField and a Popover. The idea is that the parent component controls when and what the Popover displays.
const PopoverContainer = (props: TextFieldWithPopoverProps) => {
const [anchorEl, setAnchorEl] = React.useState(null);
const anchorRef = React.useRef(null);
React.useEffect(() => {
setAnchorEl(anchorRef.current);
}, [anchorRef]);
return (
<>
<TextField type="text" ref={anchorRef}/>
<Popover id={props.popperId} open={props.open} sx={{ zIndex: 1001 }} anchorEl={anchorEl}>
<Paper>
{props.renderedChild}
</Paper>
</Popover>
</>
);
};
And here is the test.
test('should open popover', async () => {
const { rerender } = render(<PopoverContainer open={false} popperId="popper-id" renderedChild={renderInnerPopover()} />);
expect(screen.queryByText('Hello World!')).toBe(null);
await userEvent.type(await screen.findByRole('textbox'), 'HELLO');
rerender(<PopoverContainer open={true} popperId="popper-id" renderedChild={renderInnerPopover()} />);
expect(await screen.findByText('Hello World!'));
});
const renderInnerPopover = () => {
return (
<div>
Hello World!
</div>
);
};
And here is an example of a few of the act warnings that are reported.
Warning: An update to ForwardRef(FormControl) inside a test was not wrapped in act(...)
Warning: An update to ForwardRef(Popover) inside a test was not wrapped in act(...)
Act warnings are always on the rerender call. I have tried putting act() around the rerender call and the type event but neither fixes the problem. I have also tried not setting the TextField which does prevent the act warnings but of course that defeats what the component is trying to accomplish.
For anyone else running into a similar issue. I swapped userEvent for fireEvent for the typing event. Not sure if its a bug with the recent changes to userEvent or something that I am doing but it did resolve the problem.
test('should open popover', async () => {
const { rerender } = render(<PopoverContainer open={false} popperId="popper-id" renderedChild={renderInnerPopover()} />);
expect(screen.queryByText('Hello World!')).toBe(null);
// await userEvent.type(await screen.findByRole('textbox'), 'HELLO');
fireEvent.change(await screen.findByRole('textbox'), { target: { value: 'HELLO' } });
rerender(<PopoverContainer open={true} popperId="popper-id" renderedChild={renderInnerPopover()} />);
expect(await screen.findByText('Hello World!'));
});
const renderInnerPopover = () => {
return (
<div>
Hello World!
</div>
);
};

React Highcharts - how to use drillUp from parent component?

I am trying to drillUp from the parent component
This is the father component:
<button onClick={() => {
drillUp();
}}></button>
<TreeMap dataLabel={dataLabel} height={blockHeight} />
And in the child component I am rendering the Highchart
<ReactHighcharts
highcharts={Highcharts}
options={treeConfig}
></ReactHighcharts>
This is my treeConfig
const treeConfig = {
chart: {
height: height,
events: {
render() {
const elements = document.getElementsByClassName(
"highcharts-point highcharts-color-0 highcharts-internal-node-interactive",
);
const drillButton = document.getElementsByClassName(
"highcharts-drillup-button",
)[0];
if (drillButton && drillButton.id !== "drill") {
drillButton.setAttribute("id", "drill");
}
if (elements.length) {
const sortElements = [...elements].sort((a, b) => {
return a.width.baseVal.value * a.height.baseVal.value >
b.width.baseVal.value * b.height.baseVal.value
? -1
: 1;
});
sortElements.forEach((el, i) => {
el.style.setProperty("fill", colorPallete[i], "important");
el.setAttribute("fill", colorPallete[i]);
});
}
}
},
},
I found this post -> Manually Triggering drillup event, highcharts but it's in jQuery and I tried that and it didn't work
How can I cause drillUp event from the parent component in React?
Thanks in advance!
Ok, this was more complicated than I thought because the documentation is dreadful but I was able to get it running on this stackblitz
To be able to call events manually or programmatically on the chart we need to get the reference to the chart object. To get the ref on the child component we can do something like this:
const Child = ({ setChart }) => {
...
const chartComponent = useRef(null);
useEffect(() => {
setChart(chartComponent.current.chart);
}, []);
highchartsDrillDown(Highcharts);
return (
<HighchartsReact
highcharts={Highcharts}
options={options}
ref={chartComponent}
/>
);
}
setChart is a prop from the parent that sets the chart object on the parent
On the parent you can manually call drilldown and drillup like this:
const Parent = () => {
const [chart, setChart] = useState(null);
const handleDrillUp = () => {
chart.drillUp();
};
const handleDrillDown = () => {
chart.series[0].data[0].doDrilldown(); // you can chose the series and data
};
return (
<div>
<button onClick={handleDrillUp}> drill up</button>
<button onClick={handleDrillDown}> drill down</button>
<Child setChart={setChart} />;
</div>
);
};
EDIT:
The previous solution is for the highcharts-react-official lib.
Just noticed you are using a different lib for highcharts than the one I used, so for react-highcharts the only difference would be:
const afterRender = (chart) => {setChart(chart)};
<ReactHighcharts config = {config} callback = {afterRender}>.
</ReactHighcharts>
the only difference is that one uses the useRef hook and the other has a callback prop

Mock react hook and keep existing module

I am using Chakra UI and I have to mock just one hook, because I want to simulate various viewports.
My component is using the hook as following:
export const AuthenticationBase = (props: props) => {
const [isMobile] = useMediaQuery(['(max-width: 768px)']);
return (
<Box textAlign="center" fontSize={isMobile ? 'sm' : 'xl'}>
</Box>
);
}
I tried as following:
// runs before any tests start running
jest.mock("#chakra-ui/react", () => {
// --> Original module
const originalModule = jest.requireActual("#chakra-ui/react");
return {
__esModule: true,
...originalModule,
useMediaQuery: jest.fn().mockImplementation(() => [true]),
};
});
And then I execute my test:
describe('Authentication Component', () => {
it('should load with Login', () => {
const container = mount(<ChakraProvider theme={theme}><AuthenticationBase screen="login" /></ChakraProvider>);
const title = container.find('h1');
expect(title).toBeTruthy();
expect(title.text()).toBe('Login');
container.unmount();
});
}
But I get an error from JEST, somehow the hook is not mock correctly:

Jest testing hook state update with setTimeout

I'm trying to test unmount of a self-destructing component with a click handler. The click handler updates useState using a setTimeout.
However, my test fails whereas I'm expecting it to pass. I tried using jest mock timers such as advanceTimersByTime() but it does not work. If I call setState outside setTimeout, the test passes.
component.js
const DangerMsg = ({ duration, onAnimDurationEnd, children }) => {
const [isVisible, setVisible] = useState(false);
const [sectionClass, setSectionClass] = useState(classes.container);
function handleAnimation() {
setSectionClass(classes.containerAnimated);
let timer = setTimeout(() => {
setVisible(false);
}, 300);
return clearTimeout(timer);
}
useEffect(() => {
let timer1;
let timer2;
function animate() {
if (onAnimDurationEnd) {
setSectionClass(classes.containerAnimated);
timer2 = setTimeout(() => {
setVisible(false);
}, 300);
} else {
setVisible(false);
}
}
if (children) {
setVisible(true);
}
if (duration) {
timer1 = setTimeout(() => {
animate();
}, duration);
}
return () => {
clearTimeout(timer1);
clearTimeout(timer2);
};
}, [children, duration, onAnimDurationEnd]);
return (
<>
{isVisible ? (
<section className={sectionClass} data-test="danger-msg">
<div className={classes.inner}>
{children}
<button
className={classes.btn}
onClick={() => handleAnimation()}
data-test="btn"
>
×
</button>
</div>
</section>
) : null}
</>
);
};
export default DangerMsg;
test.js
it("should NOT render on click", async () => {
jest.useFakeTimers();
const { useState } = jest.requireActual("react");
useStateMock.mockImplementation(useState);
// useEffect not called on shallow
component = mount(
<DangerMsg>
<div></div>
</DangerMsg>
);
const btn = findByTestAttr(component, "btn");
btn.simulate("click");
jest.advanceTimersByTime(400);
const wrapper = findByTestAttr(component, "danger-msg");
expect(wrapper).toHaveLength(0);
});
Note, I'm mocking useState implementation with actual because in other tests I used custom useState mock.
Not using Enzyme but testing-library/react instead so it's a partial solution. The following test is passing as expected:
test("display loader after 1 second", () => {
jest.useFakeTimers(); // mock timers
const { queryByRole } = render(
<AssetsMap {...defaultProps} isLoading={true} />
);
act(() => {
jest.runAllTimers(); // trigger setTimeout
});
const loader = queryByRole("progressbar");
expect(loader).toBeTruthy();
});
I directly run timers but advancing by time should give similar results.

Testing React component built with a function

Experimenting testing for React component built with a function. Before that I was used to do :
const animalsTable = shallow(<Animals/*props*//>);
animalsTable.instance().functionToTest();
ShallowWrapper.instance() returns null for a function so now following Alex Answer I am testing the DOM directly.
Below a simplified version of my React component and some tests :
React component :
import React, {useState, useEffect} from 'react';
import ReactTable from 'react-table';
const AnimalsTable = ({animals}) => {
const [animals, setAnimals] = useState(animals);
const [messageHelper, setMessageHelper] = useState('');
//some functions
useEffect(() => {
//some functions calls
}, []);
return (
<div>
<ReactTable id="animals-table"
//some props
getTrProps= {(state, rowInfo) => {
return {
onClick: (_event) => {
handleRowSelection(rowInfo);
}
};
}}
/>
<p id="message-helper">{messageHelper}</p>
</div>
);
};
export default AnimalsTable;
Test :
//imports
describe('AnimalsTable.computeMessageHelper', () => {
it('It should display the correct message', () => {
const expectedResult = 'Select the correct animal';
const animalsTable = mount(<AnimalsTable //props/>);
const message = animalsTable.find('#message-helper').props().children;
expect(message).to.equal(expectedResult);
});
});
This one is working well.
My issue is how to test a row click on the ReactTable component to test the handleRowSelection method?
My current test is :
describe('AnimalsTable.handleRowSelection', () => {
it('When a selection occurs should change the animal state', () => {
const animalsTable = mount(<AnimalsTable //props/>);
const getTrProps = channelsSelectionTable.find('#animals-table').props().getTrProps;
//what to do from here to trigger onClick() ?
});
});
EDIT :
I think the correct way would be like that but handleRowSelection is not triggered :
const animalsTable= mount(<AnimalsTable //props />);
const rows = animalsTable.find('div.rt-tr-group');
rows.at(0).simulate('click');
I will try to add a simple codeSandBox
Here the solution I found out, I had to inspect the classes names with Chrome then use it to simulate a click on the first row of the table :
it('When an animal selection occurs, it change the checkbox state', () => {
const animalsTable = mount(<AnimalsTable //props/>);
const rows = animalsTable.find('div.rt-tr.-odd');
rows.at(0).simulate('click');
const checkBoxResult = animalsTable.find('input[type="checkbox"]')
.at(0).props().checked;
expect(checkBoxResult).to.equal(true);
});
Might not be the correct way to test it but this one is working.

Resources