I have build a component in react based on reactstrap then using jest and Enzyme I am unable to test the content of modal. Let's see what I have tried so far:
import React from 'react';
import { Button, Modal, ModalHeader, ModalBody, ModalFooter } from 'reactstrap';
class ModalExample extends React.Component {
constructor(props) {
super(props);
this.state = {
modal: false
};
this.toggle = this.toggle.bind(this);
}
toggle() {
this.setState({
modal: !this.state.modal
});
}
render() {
return (
<div className="modal-testing">
<Button color="danger" onClick={this.toggle}>{this.props.buttonLabel}</Button>
<Modal isOpen={this.state.modal} toggle={this.toggle} className={this.props.className}>
<ModalHeader toggle={this.toggle}>Modal title</ModalHeader>
<ModalBody className="inside">
I just want this to show up in unit test
Name: <input type="text" />
</ModalBody>
<ModalFooter>
<Button color="primary" onClick={this.toggle}>Do Something</Button>{' '}
<Button color="secondary" onClick={this.toggle}>Cancel</Button>
</ModalFooter>
</Modal>
</div>
);
}
}
export default ModalExample;
And I have its unit tests as follow:
import React from 'react';
import {mount, ReactWrapper} from "enzyme";
import ModalExample from "./ModalExample";
const wrapper = mount(
<ModalExample isOpen={isOpen} toggle={toggle} />
);
const button = wrapper.find('Button').at(0);
button.simulate('click');
// What I have tried
expect(wrapper.text()).toBe('I just want this to show up in unit test');
expect(wrapper.find('input')).toHaveLength(1); // it gets failed
By the way, I have already tried this method, but it didn't worked:
Similar issue posted on stackoverflow
But I don't know what am I doing wrong, could anyone spot any error or correct me if something not at proper way?
After a lot of research, I found that this was actually reactstrap library issue as I was using an old version i.e. "reactstrap": "5.0.0-beta", so I just updated reactstrap library to its latest version: "reactstrap": "^6.5.0" and it worked.
Thank you #Herman for you time.
Your ModalExample does not render it's children prop, so <p>Hello world</p> is essentially ignored. Why are you passing it in?
In case you are using an older version of Enzyme/ReactStrap, you can pass the container element to mount where you want your Modal to be rendered, like this:
Actual Code:
------------
import React from 'react'
import { Modal } from 'reactstrap'
export default MyModal = () => {
return (
<Modal isOpen={props.isOpen}>
<ModalHeader>Header</ModalHeader>
<ModalBody>Body</ModalBody>
</Modal>
);
}
Unit Test:
----------
import React from 'react'
import MyModal from './MyModal'
import { mount } from 'enzyme'
describe(() => {
let wrapper;
beforeEach(() => {
const container = document.createElement("div");
document.body.appendChild(container);
wrapper = mount( <MyModal isOpen={true}/> , {attachTo: container});
});
it('renders correctly', () => {
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('ModalHeader')).toHaveLength(1);
expect(wrapper.find('ModalBody')).toHaveLength(1);
});
});
Related
I have an application which prompts a user to add an entry via a modal. However, I can't figure out how to call the modal from the function and neither can I figure out how to pass the id to the modal.
So far I've been able to get the two parts written up and compiled but not working together
'Add Entry' button
import React from 'react';
import '../../App.css'
import VideoModal from '../components/VideoModal'
function NewEntry ({event}){
return (
<span><i class="fa fa-plus-circle" aria-hidden="true"></i></span>
<VideoModal entry={event}
);
}
export default NewEntry;
Note that this doesn't call the modal component. I've attempted variations of onClick={this.closeModal} for the <I> tag to no avail, but this obviously won't work. And I've included the modal as a component.
I know, I know this isn't the right way of doing this, I just haven't yet found examples I can wrap my head around.
The Modal
import React, { Component } from "react";
import { Modal, Button } from "react-bootstrap";
import '../../App.css'
class VideoModal extends Component {
state = {
isOpen: false
};
openModal = () => this.setState({ isOpen: true });
closeModal = () => this.setState({ isOpen: false });
render() {
return (
<>
<Modal show={this.state.isOpen} onHide={this.closeModal}>
<Modal.Header closeButton>
<Modal.Title>Modal heading</Modal.Title>
</Modal.Header>
<Modal.Body>Woohoo, you're reading this text in a modal!</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={this.closeModal}>
Close
</Button>
</Modal.Footer>
</Modal>
</>
);
}
}
export default VideoModal;
First of all, the VideoModal tag is not closed, pay attention to it.
Then you can move the open/close logic outside the modal, inside NewEntry:
import React, { Component } from 'react';
import '../../App.css'
import VideoModal from '../components/VideoModal'
class NewEntry extends Component {
state = {
isModalOpen: false
};
openModal = () => this.setState({ isModalOpen: true });
closeModal = () => this.setState({ isModalOpen: false });
render() {
return (
<span onClick={() => this.openModal()}><i class="fa fa-plus-circle" aria-hidden="true"></i></span>
<VideoModal closeModal={() => this.closeModal()} isOpen={this.state.isOpen} />
);
}
}
export default NewEntry;
And use VideoModal as a pure component:
import React from "react";
import { Modal, Button } from "react-bootstrap";
import '../../App.css'
function VideoModal (props) {
const { isOpen, closeModal } = props;
return (
<>
<Modal show={isOpen} onHide={closeModal}>
<Modal.Header closeButton>
<Modal.Title>Modal heading</Modal.Title>
</Modal.Header>
<Modal.Body>Woohoo, you're reading this text in a modal!</Modal.Body>
<Modal.Footer>
<Button variant="secondary" onClick={closeModal}>
Close
</Button>
</Modal.Footer>
</Modal>
</>
);
}
export default VideoModal;
Hi I'm new to React and building few things in React and this may seem a very generic question.
I want to show a table on click of button. Below is my code.
import React from 'react';
import { Link }
import Button from 'react-bootstrap/lib/Button';
import Panel from 'react-bootstrap/lib/Panel';
import Grid from 'react-bootstrap/lib/Grid';
import Row from 'react-bootstrap/lib/Row';
import Col from 'react-bootstrap/lib/Col';
import ButtonGroup from 'react-bootstrap/lib/ButtonGroup';
import FormGroup from 'react-bootstrap/lib/FormGroup';
this.state = {
showSubmit: false,
};
submitForm = () => {
window.alert('test');
}
toggleSubmitForm = () => {
this.setState({
showSubmit: !this.state.showSubmit
});
window.alert('test2');
}
export default (props) => {
return (
<AppLayout title="Table Con" activeModules={props.activeModules}>
<Protected>
<div className="container-fluid">
<h4>
Welcome to the page
!
</h4>
</div>
<Button
className="btn btn-secondary"
bsSize="small"
onClick={this.toggleSubmitForm}
>
Show Table
</Button>
{this.state.showSubmit && (
<div className="container-fluid well" id="submitT">
<form onSubmit={this.submitForm}>
<Grid>
<Row>
<Col xs={12}>
<div>
<h3>HERE</h3>
</div>s
<br />
<br />
</Col>
</Row>
</Grid>
<Button type="submit" bsStyle="success" bsSize="large">
Submit
</Button>
</form>
</div>
)}
</Protected>
</AppLayout>
);
};
But when onClick is called, nothing is happening.
I'm not sure where I'm failing.
Also, if i want to call a mongo collection and render the table after I click on Show Table button. What are the changes to be made ?
As #noitse pointed out, you are mixing statefull and stateless component features.
However, React added a new alternative if you want to keep your component as a function, Hooks. Here's what you code will look like as a hook :
import { useState } from 'react'
export default props => {
const[showSubmit, setShowSubmit] = useState(false)
return (
<AppLayout title="Table Con" activeModules={props.activeModules}>
<Protected>
<div className="container-fluid">
<h4>Welcome to the page !</h4>
</div>
<Button className="btn btn-secondary" bsSize="small" onClick={setShowSubmit(true)}>
Show Table
</Button>
{showSubmit && /* Your table*/}
</Protected>
</AppLayout>
);
};
You are combining functional and class component features.
Functional components do not have access to the state unless you are using useState feature (16.3 update). Any "this." is basically undefined in your code.
Rewrite your component like this:
import React, {Component} from 'react' // or PureComponent
// ...other imports
class YourComponent extends Component {
state = {
showSubmit: false
}
submitForm = () => { /* what ever */}
toggleSubmitForm = () => {
this.setState({showSubmit: !this.state.showSubmit})
}
render(){
return(
... your render code
)
}
}
export default YourComponent
I am using bootstrap modal and on click event on an element its opened but cant closed it when i click the "x" on the right corner of the modal.
The problem is that i do succeed to pass the state by props from parent to child but when i invoke the function "lgClose" inside the child, its goes to the parent component but doesnt actually changing the state position to "false".
I can see its go inside the function because i put "console.log('im in the function')" and i can see it. Why the state doesnt changed to false in parent component ?
import React, { Component } from 'react';
import { Link } from 'react-router-dom'
import { connect } from 'react-redux';
import { getAllUsers,getMessages } from './../actions';
import { bindActionCreators} from 'redux';
import { Button, Modal } from 'react-bootstrap';
import ContainerModal from './../components/popup_modal';
// parent component
class MainContainer extends Component {
constructor(props, context) {
super(props, context);
this.state = {
lgShow: false
};
}
componentWillMount(){
this.props.getAllUsers();
}
renderList = ({list}) => {
if(list){
return list.map((item)=>{
return(
<div key={item._id} className="item-list" onClick={() => this.setState({lgShow: true})}>
<div className="title">{item.firstName} {item.lastName}</div>
<div className="body">{item.age}</div>
<div>
<ContainerModal lgShow={this.state.lgShow} lgClose={this.lgClose}/>
</div>
</div>
)
})
}
}
lgClose = () => {
this.setState({lgShow:false});
console.log('im in the function');
}
render() {
console.log(this.state);
return (
<div className="App">
<div className="top">
<h3>Messages</h3>
<Link to="/form">Add</Link>
</div>
<div className="messages_container">
{this.renderList(this.props.users)}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
messages:state.messages,
users:state.users
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({getAllUsers,getMessages},dispatch);
}
export default connect(mapStateToProps,mapDispatchToProps)(MainContainer);
import React from 'react';
import { Button, Modal } from 'react-bootstrap';
// child component
const ContainerModal = (props) => {
return (
<div>
<Modal
size="lg"
show={props.lgShow}
onHide={ props.lgClose }
aria-labelledby="example-modal-sizes-title-lg"
>
<Modal.Header closeButton>
<Modal.Title id="example-modal-sizes-title-lg">
Large Modal
</Modal.Title>
</Modal.Header>
<Modal.Body>item</Modal.Body>
</Modal>
</div>
)
}
export default ContainerModal;
Seems that this in the lgClose function is not pointing to MainContainer.
If that is the case, one way of solving it is to bind the lgClosefunction to MainContainer in the constructor:
constructor(props, context) {
super(props, context);
this.state = {
lgShow: false
};
this.lgClose = this.lgClose.bind(this); // Add this line
}
Hope this helps :)
In your ContainerModal try this:
import React from 'react';
import { Button, Modal } from 'react-bootstrap';
// child component
const ContainerModal = (props) => {
return (
<div>
<Modal
{...props}
size="lg"
aria-labelledby="example-modal-sizes-title-lg"
>
<Modal.Header closeButton>
<Modal.Title id="example-modal-sizes-title-lg">
Large Modal
</Modal.Title>
</Modal.Header>
<Modal.Body>item</Modal.Body>
</Modal>
</div>
)
}
export default ContainerModal;
And in renderList, do this:
<ContainerModal show={this.state.lgShow} onHide={this.lgClose} />
Here is the reason why your lgShow remains true.
This explanation would be little longer. So please bear with me.
Here is what you have done.
You have added your ContainerModal as a child of div (className="item-list") inside renderList method which has an onClick listener attached to itself.
When you click on the list item it works fine and the modal appears but when you click on the close button on the modal things become fishy.
This is because of the onClick listener of the div (className="item-list") getting called after the
ContainerModal's click listener is called setting the lgShow to true again. This behavior is termed as event bubbling where both parent and child receive event in a bottom-up way (i.e child receiving the first event followed by the parent).
To verify this behavior add a console.log to the onClick listener of the div.
Here is a link to a github question for the explanation on event bubbling
Solution
First and foremost suggestion would be to take out the ContainerModal from the renderList method to the root div. This is because right now for every list item of yours a ContainerModal is being created which is unnecessary. For example if you have 10 users in the list you are creating 10 ContainerModal (To verify this increase the number of users and you could see the shade of the modal getting darker.)
The solution to the above problem is to take the ContainerModal to the root level where it's onClick will not coincide with the onClick of the div (className="item-list") in the renderList and your problem would be solved.
Here is the modified solution.
import React, { Component } from 'react';
import { Link } from 'react-router-dom'
import { connect } from 'react-redux';
import { getAllUsers,getMessages } from './../actions';
import { bindActionCreators} from 'redux';
import { Button, Modal } from 'react-bootstrap';
import ContainerModal from './../components/popup_modal';
// parent component
class MainContainer extends Component {
constructor(props, context) {
super(props, context);
this.state = {
lgShow: false
};
}
componentWillMount(){
this.props.getAllUsers();
}
renderList = ({list}) => {
if(list){
return list.map((item)=>{
return(
<div key={item._id} className="item-list" onClick={() => this.setState({lgShow: true})}>
<div className="title">{item.firstName} {item.lastName}</div>
<div className="body">{item.age}</div>
<div>
{/* <ContainerModal lgShow={this.state.lgShow} lgClose={this.lgClose}/> */}
</div>
</div>
)
})
}
}
lgClose = () => {
this.setState({lgShow:false});
console.log('im in the function');
}
render() {
console.log(this.state);
return (
<div className="App">
<div className="top">
<h3>Messages</h3>
<Link to="/form">Add</Link>
</div>
<div className="messages_container">
{this.renderList(this.props.users)}
</div>
<ContainerModal lgShow={this.state.lgShow} lgClose={this.lgClose}/>
</div>
);
}
}
function mapStateToProps(state) {
return {
messages:state.messages,
users:state.users
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({getAllUsers,getMessages},dispatch);
}
export default connect(mapStateToProps,mapDispatchToProps)(MainContainer);
import React from 'react';
import { Button, Modal } from 'react-bootstrap';
// child component
const ContainerModal = (props) => {
return (
<div>
<Modal
size="lg"
show={props.lgShow}
onHide={ props.lgClose }
aria-labelledby="example-modal-sizes-title-lg"
>
<Modal.Header closeButton>
<Modal.Title id="example-modal-sizes-title-lg">
Large Modal
</Modal.Title>
</Modal.Header>
<Modal.Body>item</Modal.Body>
</Modal>
</div>
)
}
export default ContainerModal;
Look out for the changed position of Container Modal. Rest of the code is fine.
I have also uploaded a working solution to code sandbox. Have a look at it.
Hope this helps.
I want to make when i click button, show modal in different class.
how to make it this?
import React, { Component } from 'react';
import addmodal from './addmodal';
class page extends Component {
...//skip
handleAdd= () =>{
//...how?
}
render(){
return (
<button onClick={this.handleAdd} > Add </button>
)
}
}
import React, { Component } from 'react';
class addmodal extends Component {
// ... skip
render(){
return(
<modal inOpen={this.state.isopen} >
...//skip
</modal>
)
}
}
export default addmodal;
I can't call this addmodal...
how to call modal?
First, your variable naming is terrible. I fixed it for you here. The state that dictates if modal is open must be on the parent component since you have your trigger there. Then, pass it as props to the modal component. Here is the fixed code for you:
import React, { Component } from 'react';
import AddModal from './addmodal';
class Page extends Component {
constructor(){
super();
this.state = { isModalOpen: false };
}
...//skip
handleAdd= () =>{
this.setState({ isModalOpen: true });
}
render(){
return (
<div>
<button onClick={this.handleAdd} > Add </button>
<AddModal isOpen={this.state.isModalOpen} />
</div>
)
}
}
import React, { Component } from 'react';
class AddModal extends Component {
// ... skip
render(){
return(
<modal inOpen={this.props.isOpen} >
...//skip
</modal>
)
}
}
export default AddModal;
what about using trigger. Its very usefull.
<Modal trigger={ <button onClick={() => onCLickPay()} className={someting}> When clicked here modal with show up</button> }`enter code here` >
I've got this simple code from react bootstrap website...
import {Modal} from 'react-bootstrap';
import React from 'react';
export default class ModalExperiment extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false
}
}
render(){
let openModal = () => this.setState({open: true});
let closeModal = () => this.setState({ open: false });
return (
<div>
<button type='button' onClick={openModal} className='launch'>Launch modal</button>
<Modal
show={this.state.open}
onHide={closeModal}
aria-labelledby="ModalHeader" className='modalClass'
>
<Modal.Header closeButton>
<Modal.Title id='ModalHeader'>A Title Goes here</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Some Content here</p>
</Modal.Body>
<Modal.Footer>
// If you don't have anything fancy to do you can use
// the convenient `Dismiss` component, it will
// trigger `onHide` when clicked
<Modal.Dismiss className='dismiss btn btn-default'>Cancel</Modal.Dismiss>
// Or you can create your own dismiss buttons
<button className='btn btn-primary' onClick={closeModal}>
Close
</button>
</Modal.Footer>
</Modal>
</div>
)
}
}
And here is my test... simple I want to click the launch button, verify the Modal opens, and then click on the Cancel button to verify the Modal is no longer opened.
'use strict';
import React from 'react';
import ReactAddons from 'react/addons';
import {Modal} from 'react-bootstrap';
import ModalExperiment from '../ModalExperiment';
import ReactDOM from 'react-dom';
let TestUtils = React.addons.TestUtils;
fdescribe('ModalExperimentFunctional', function() {
let page;
fit("click the ok button should open the modal", () => {
page = TestUtils.renderIntoDocument(<ModalExperiment/>);
let launch = TestUtils.findRenderedDOMComponentWithClass(page, 'launch');
let openButton = React.findDOMNode(launch);
openButton.click(); // work till here
//let modal = TestUtils.findRenderedComponentWithType(page, Modal);// this managed to find Modal type
let modal = TestUtils.findRenderedDOMComponentWithClass(page, 'modalClass'); // this fails to find the modalClass..
});
});
How can I find the Cancel button please? I tried all sort of things, nothing seems to have worked for me.
I finally got it working using jquery selector..
import {Modal} from 'react-bootstrap';
import React from 'react';
export default class ModalExperiment extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false
}
}
openModal() {
this.setState({open: true});
}
closeModal() {
this.setState({open: false });
}
render(){
return (
<div>
<button type='button' onClick={this.openModal.bind(this)} className='openButton'>Launch modal</button>
{this.state.open?
<Modal
show={this.state.open}
onHide={this.closeModal.bind(this)}
aria-labelledby="ModalHeader" className='modalClass'
>
<Modal.Header closeButton>
<Modal.Title id='ModalHeader'>A Title Goes here</Modal.Title>
</Modal.Header>
<Modal.Body>
<p>Some Content here</p>
</Modal.Body>
<Modal.Footer>
<button className='closeButton btn btn-primary' onClick={this.closeModal.bind(this)}>
Close
</button>
</Modal.Footer>
</Modal> : ''}
</div>
)
}
}
And here is the test...
'use strict';
import React from 'react';
import ReactAddons from 'react/addons';
import {Modal} from 'react-bootstrap';
import ModalExperiment from '../ModalExperiment';
import $ from 'jquery';
let TestUtils = React.addons.TestUtils;
describe('ModalExperimentFunctional', function() {
let page;
beforeEach(() => {
document.body.innerHTML = '';
page = TestUtils.renderIntoDocument(<ModalExperiment/>);
});
fit("click the ok button should open the modal", () => {
expect($('.closeButton').length).toBe(0);
let openButton = React.findDOMNode(TestUtils.findRenderedDOMComponentWithClass(page, 'openButton'));
TestUtils.Simulate.click(openButton);
expect($('.closeButton').length).toBe(1);
$('.closeButton').click();
expect($('.closeButton').length).toBe(0);
});
});
In test use:
const component = ReactTestUtils.renderIntoDocument(<Modal />);
ReactTestUtils.Simulate.click(document.body.getElementsByClassName("close")[0]);