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>
);
};
Related
For context, I have a react app with a tree explorer. When the user hovers a node, I show a menu for 5 seconds using a setTimeout inside a useEffect. I want to be able to set a ref on a file input element in the menu but the ref is always null. How can I update the ref after it has been mounted? The issue it seems is that the input is not mounted until the tree node is hovered but the component the input node is created in has already been mounted.
------------- EDIT -----------
The following code snippet works. The key seems to be the e.stopPropagation(). I thought a ref would have been a better choice but it did not seem to work.
// component
const [showControls, setShowControls] = useState(false);
useEffect(() => {
const timer = setTimeout(() => {
setShowControls(false);
}, 5000);
return () => clearTimeout(timer);
}, [showControls]);
const handleChange = (e) => {
console.log('handleChange...');
}
const onButtonClick = (e) => {
e.stopPropagation();
e.preventDefault();
const el = document.querySelector("input[type=file]");
el.click();
};
// some other code...
return (
//...other jsx
{ showControls && (
<>
<IconButton onClick={onButtonClick} />
<input
id="hiddenFileInput"
type="file"
onClick={(e) => e.stopPropagation()}
onChange={handleChange}
style={{display: 'none'}}
/>
</>
)}
);
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
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);
}
});
Using React Testing Library to test a dialog provider. I can get it to open, and assert on it appearing — but for some reason I can't get it to close in the test. Do I need to rerender or something?
test('await the closing or confirming of the modal', async () => {
const { debug, getByText, queryByText } = render(
<DialogProvider>
<Test />
</DialogProvider>,
);
const openDialogButton = getByText(/click me/i);
fireEvent.click(openDialogButton);
await wait(() => getByText(/ok/i));
fireEvent.click(getByText(/ok/i));
debug();
});
function Test() {
const confirm = useConfirmation();
return (
<button
onClick={() => {
confirm({ variant: 'info' });
}}
>
click me
</button>
);
}
Apparently, the following seemed to work
await waitForElement(() => getByText(/ok/i));
fireEvent.click(getByText(/ok/i));
await waitForElementToBeRemoved(() => queryByText(/ok/i));
expect(queryByText(/ok/i)).toBeNull();
Unable to make the following test pass :
Using React JS / enzyme and jest
I already asked a similar question and try to apply the same method, but its not going through. Any reason ?? Substitute shallow = mount ? or add a dive() ?
file.test.js -
// jest mock functions (mocks this.props.func)
const updateSelectedFormJSON = jest.fn();
const closeModal = jest.fn();
const onClick = jest.fn();
const onSaveClick = jest.fn();
// defining this.props
const baseProps = {
selectedFormJSON :{
FORM_COLUMN:[],
},
updateSelectedFormJSON,
closeModal,
onClick,
onSaveClick,
describe('SpecifyBodyModal Test', () => {
let wrapper;
let tree;
beforeEach(() => wrapper = mount(<SpecifyBodyModal {...baseProps} />));
it('should call closeModal functions on button click', () => {
baseProps.closeModal.mockClear();
wrapper.setProps({
updateSelectedFormJSON :null
});
wrapper.find('.add-custom-field-close').at(0).simulate('click')
expect(baseProps.closeModal).toHaveBeenCalled();
});
the 2nd test is not passing: error Method “simulate” is meant to be run on 1 node. 0 found instead.
it('should call onSaveClick functions on button click', () => {
baseProps.onSaveClick.mockClear();
wrapper.setProps({
closeModal :null
});
wrapper.find('.tran-button specify-body-continue').at(1).simulate('click')
expect(baseProps.onSaveClick).toHaveBeenCalled();
here is the render file js.
onSaveClick = () => {
let json = Object.assign({}, this.props.selectedFormJSON);
for(let i in json.FORM_COLUMN) {
json.FORM_COLUMN[i].IncludeInBody = this.state[json.FORM_COLUMN[i].Name];
}
this.props.updateSelectedFormJSON(json);
this.props.closeModal();
render() {
return (
<div className='specify-grid-modal'>
<div className='fullmodal'>
<div className='fullmodal_title'>Specify Body</div>
<div title="Close Window" className="add-custom-field-close" onClick={() => this.props.closeModal()}><FontAwesome name='xbutton' className='fa-times preview-close' /></div>
</div>
<button className='tran-button specify-body-continue' onClick={() => {this.onSaveClick()}} >
Continue
</button>
<div className='specify-body-wrapper'>
{this.renderColumns()}
</div>
</div>
)
}
The error means that there are no matches for className.add-custom-field-close selector.
className is prop name and shouldn't be included into the selector:
wrapper.find('.add-custom-field-close').at(0).simulate('click')
The selector of to find the element looks wrong. Its className.add-custom-field-close but should be .add-custom-field-close
Thanks for the help
it('should call onSaveClick functions on button click', () => {
baseProps.closeModal.mockClear();
wrapper.setProps({
updateSelectedFormJSON :null
});
wrapper.find('.add-custom-field-close').at(0).simulate('click')
expect(baseProps.closeModal).toHaveBeenCalled();
});