Modal component - onClick event - reactjs

Trying to test the following click event: Using Jest and Enzyme for ReactJS
<Modal isOpen={this.state.descriptionModalOpen} style={descriptionModalStyle}>
<div>
<div className='fullmodal'>
<div className="fullmodal_title">
<div className="fullmodal_title_add">Description</div>
</div>
<div className='sidemodal_addnew_x' id="close-Modal-id" onClick={this.closeModal}>
<FontAwesome name='xbutton' className='fa-times' />
</div>
</div>
{this.getDescription()}
</div>
</Modal>
Node cannot be found. The others click events test passed just fine, but this is the only one inside of a Modal.
Here is part of my test file
beforeEach(() => (wrapper = mount(<MemoryRouter keyLength={0}><Notifications {...baseProps} /></MemoryRouter>)));
it("should check button click events under Modal Component", () => {
baseProps.onClick.mockClear();
wrapper.find('Notifications').setState({
descriptionModalOpen: false,
});
wrapper.update()
wrapper.find('Notifications').find('#close-Modal-id').simulate("click");
});

did you try findWhere?
const yourElement = element.findWhere(node => node.id === "close-Modal-id")
yourElement.simulate('click');
If it doesn't, can you verify if findWhere traverses the node you're targeting?

Related

react-testing-library testing Ant Design modal

I have a React component with Ant Design Modal inside it and I am trying to test that modal gets opened when a button it clicked:
The component:
const ModalComponent = () => {
const [visible, setVisible] = useState(false);
return (
<>
<Button type="primary" onClick={() => setVisible(true)}>
Open Modal
</Button>
<Modal
title="Modal title"
centered
visible={visible}
onOk={() => setVisible(false)}
onCancel={() => setVisible(false)}
>
<p>some contents...</p>
<p>some contents...</p>
<p>some contents...</p>
</Modal>
</>
);
};
Test file:
test('modal opening', async () => {
const { queryByText } = render(<ModalComponent />);
fireEvent.click(queryByText('Open Modal'));
await waitFor(() => expect(queryByText('Modal title')).toBeInTheDocument());
});
The problem is that the modal DOM is never rendered in the test when I try to debug, so the test fails. It could be happening because the modal content is created outside of the component DOM tree right inside the body tag?
There is no test failure that you have given from our side.
A little information from my side on Antd modal component.
Antd Modal during testing renders outside the container. This is because Antd uses the rc-dialog component and that component uses react portal to show modal which is always render outside the root div. In the same way, during testing modal will not render in the container but outside of it.
The test that you have given will pass(modal is present) because the queryByText will search the element in document.body not inside the container.
test('modal opening', async () => {
const { baseElement, queryByText } = render(<ModalComponent />);
fireEvent.click(queryByText('Open Modal'));
expect(baseElement).toMatchSnapshot(); // added snapshot
await waitFor(() => expect(queryByText('Modal title')).toBeInTheDocument());
});
baseElement will show all the elements that are present in the document.body.
I had this exact same use case, with a very similar component.
For some reason, if I triggered the event inside act, then the state of the component wouldn't be updated and the modal would never come up (even if a console.log in the event handler does show up in the console).
The solution was to do userEvent.click outside the act

Reusable Modal Component React Typescript

I have a component which has a button within it, like so -
<Button variant="primary" disabled={checkAccepted} onClick={openModal}>Send</Button>
I would like this button to, when it is active, to open up a modal when clicked. I am unsure how to do this and have been messing around with props but can't seem to figure it out. I also want the modal to be reusable so that any content can be passed in the modal body.I am thinking how do I open up the modal from within my openModal function?
I tried returning it like so -
const openModal = () => {
return (
<Modal>
<ModalBody>*Pass in swappable content here*</ModalBody>
</Modal>
)
}
But that doesn't seem to work. I am sure I am missing something.
You can't return components from an event handler. The way to handle events in react is almost always to alter the state of your application which triggers a re-render. In your case you need to keep track of the open state of your modal.
This can be done either in a controlled way (you keep track of the open state yourself and pass it to your <Modal> component as a prop) or in an uncontrolled way (the <Modal> component manages the open state itself). The second approach requires that you provide e.g. an element to render to your Modal component that acts as a trigger:
const MyModal = ({ children, trigger }) => {
const [modal, setModal] = useState(false);
const toggle = () => setModal(!modal);
return (
<div>
{React.cloneElement(trigger, { onClick: toggle })}
<Modal isOpen={modal} toggle={toggle}>
<ModalBody>{children}</ModalBody>
</Modal>
</div>
);
};
Then you can use it like that:
<MyModal trigger={<Button variant="primary">Send</Button>}>
<p>This is the content.</p>
</MyModal>
Or you can implement it in a controlled way. This is more flexible as it allows you to render the triggering element anywhere:
const MyModal = ({ children, isOpen, toggle }) => (
<div>
<Modal isOpen={isOpen} toggle={toggle}>
<ModalBody>{children}</ModalBody>
</Modal>
</div>
);
Usage Example:
function App() {
const [isOpen, setIsOpen] = useState(false);
const toggle = () => setIsOpen(!isOpen);
return (
<div className="App">
<Button variant="primary" onClick={toggle}>
Send
</Button>
<MyModal isOpen={isOpen} toggle={toggle}>
<p>This is the content.</p>
</MyModal>
</div>
);
}
You should pass the function which triggers the modal to your <Button /> component as prop. Then, in your component, you want to add the onClick event. You can't set an onClick event to the <Button />. It will think of onClick as a prop being passed to <Button />. Within <Button /> you can set the onClick event to an actual <button> element, and use the function which was passed in as a prop on that event.
You can use state to keep track of when the modal button is clicked. Your function can look like: (I am using class based components here, but you can do the same thing with functional components)
buttonClickedHandler = () => {
this.setState({isModalButtonClicked: !this.state.isModalButtonClicked});
}
Then, you can set the Modal component,
<Modal isShow={this.state.isModalButtonClicked} modalButton={this.buttonClickedHandler}>
<div> ...set contents of modal</div>
</Modal>
<button onClick={this.buttonClickedHandler}>Show Modal</button>
So, within the Modal component, you can have something like this:
<React.Fragment>
<Backdrop showModal={this.props.isShow} clicked={this.props.modalButton}/>
{this.props.children}
</React.Fragment>
Backdrop is basically the greyed out background. You can also set an onClick event to listen to when the backdrop is clicked.

Office Fluent UI / Fabric UI Modal - how can I close it from the body component?

I'm using the Modal component from the fluent-ui-react
https://developer.microsoft.com/en-us/fluentui#/controls/web/modal
like this:
function ModalExtended(props) {
const [isModalOpen, { setTrue: showModal, setFalse: hideModal }] = useBoolean(
false
);
const isDraggable = useBoolean(false);
const titleId = useId("title");
return (
<div>
<DefaultButton onClick={showModal} text={props.buttonText} />
<Modal
titleAriaId={titleId}
isOpen={isModalOpen}
onDismiss={hideModal}
isBlocking={false}
containerClassName={contentStyles.container}
>
<div className={contentStyles.header}>
<span id={titleId}>{props.gridHeader}</span>
<IconButton
styles={iconButtonStyles}
iconProps={cancelIcon}
ariaLabel="Close popup modal"
onClick={hideModal}
/>
</div>
<div className={contentStyles.body}>{props.body}</div>
</Modal>
</div>
);
}
Then i call the ModalExtended component from other components like this:
<ModalExtended
buttonText="Open modal button text"
gridHeader="Modal header text"
body={
<GenericTreeGridContainer/>
}
/>
In the body prop i send another component (GenericTreeGridContainer) that i would like to render when the Modal opens.
In this body component i have a click event which, when it finishes its work should also close the Modal.
Is there a way to pass the hideModal function from the ModalExtended components to my body component so i can close the Modal from the body component?
Define a parent component with isModalOpen and hideModal, and pass them into both the modal and body as props. You also might be able to render the {props.body} instead like <props.body hideModal={...} /> but I haven't tried that to see how good of a pattern it is.

How do I close a dialog in another component from Material-UI in React?

I have a modal that have a component inside.
I want to close the modal from that component.
I got a component that have:
<Modal open={modalClients} onClose={handleCloseModalClients}>
<div className={classes.paper}>
<ClientsTable />
</div>
</Modal>
It is possible to close the modal inside ClientsTable?
It looks like handleCloseModalClients is what closes the modal, so you should just need to pass it into ClientsTable and call it somehow. For example, if you have a button in ClientsTable:
<Modal open={modalClients} onClose={handleCloseModalClients}>
<div className={classes.paper}>
<ClientsTable onCloseModal={handleCloseModalClients} />
</div>
</Modal>
const ClientsTable = (props) => (
// Not sure what the inside of ClientsTable looks like, but
// it should have something you can attach the handler to:
<div>
<button onClick={props.onCloseModal}>Close Modal</button>
</div>
)
According to https://material-ui.com/api/modal/ , you can control opening and closing the modal through the open prop in the component.
Therefore, you can define a state variable in your modal component
state = { open: false }
and a function to close it closeModal = () => { this.setState({ open: false }); }
and you can pass closeModal function as a prop to the child component inside the modal
<ClientsTable closeModal={closeModal} />. Then you can trigger the closeModal function wherever you want inside the <ClientsTable> component.
Here is an example: https://codesandbox.io/s/material-demo-7nc1p
As you are already using open={modalClients}, and assuming your modalClients must be in a state. You can set this state to false to close the modal from your ClientsTable component like,
const ClientsTable = props => (
<div>
<button onClick={props.hideModal}>Hide</button>
</div>
);
And your modal should look like,
<Modal open={this.state.modalClients}>
<div className="">
<ClientsTable hideModal={() => this.setState({ modalClients: false })} />
</div>
</Modal>
Demo

React.js unit test click event with Enzyme and Sinon

I'm trying to attach a spy to a click event on my react component. I am using Enzyme with Mocha and Chai but I'm having trouble getting the following test to pass:
it('Handles a Click Event', () => {
let rootId = Bigtree['rootId'];
const spy = sinon.spy();
const render = shallow(<EnvH tree={smalltree.objects}
node={smalltree.objects[rootId]} root={rootId}/>);
render.find('.toggler').simulate('click');
expect(spy.called).to.be.true;
});
Here is the component I am testing:
class EnvH extends React.Component {
return (
<div className='top' key={Math.random()}>
<div className={'tableRow row'} id={node.id} style={rowStyle} key={Math.random()}>
<div style={nodeStyle} className={'root EnvHColumn ' + classNames(classObject)} key={Math.random()}>
//Here is the actual Click Item in question
<span className={'toggler'} onClick={this.toggleCollapseState.bind(this)}>{node.type} - {node.name}</span>
</div>
<ColumnContent columnType={'description'} nodeInfo={node.description} />
<ColumnContent columnType={'createdOn'} nodeInfo={node.createdAt} />
<ColumnContent columnType={'updatedOn'} nodeInfo={node.updatedAt} />
</div>
<div className={'Children'} style={childrenStyle} key={Math.random()}>
{childNodes}
</div>
</div>
);
Here is the function that gets called when the span gets clicked:
toggleCollapseState() {
this.setState({collapsed: !this.state.collapsed});
};
Thank you in advance for any help on this one. I should also mention that the test is not passing stating that it is expecting true but found false.
Your test is not passing because the spy function is not given to the span.toggler element, so it's never called by it.
To make the test pass you should instead write
sinon.spy(EnvH.prototype, 'toggleCollapseState')
and then
expect(EnvH.prototype.toggleCollapseState.calledOnce).to.be.true

Resources