Why Enzyme test unworking in React? - reactjs

I need to test Button component
it's Button :
import React from "react";
import './Button.css'
const Button = props => {
return(
<button className={"Button"}
onClick={props.onClick}
disabled={props.disabled}
>
{props.children}
</button>
)
}
export default Button
It's my Button.test.js:
import React from 'react';
import {shallow} from 'enzyme';
import Button from "./Button";
it('has a title class', () => {
const wrapper = shallow(<Button/>);
expect(wrapper.hasClass('Button')).to.equal(true);
I'm add enzyme to react. In the console I has an error:
enter image description here
tell me how to solve the problem, i'm new in React.

You need to call hasClass on the button element instead of the wrapper:
expect(wrapper.find('button').hasClass('Button')).to.equal(true);

Related

How to properly export a component from a React custom hook and a function to control it?

What I want to do is to create a reusable and convenient way of showing an alert or a confirmation modal.
Using library modals usually require you to import a Modal component and create a state variable and pass it as a prop to the imported component to control its visibility.
What I want to do is to create a custom hook that exports a modal component with all the customization (maybe a wrapper around a Modal component from a library) and a function to toggle the visibility.
Something like below.
This is the hook code:
import React, {useState} from 'react'
import 'antd/dist/antd.css'
import {Modal as AntdModal} from 'antd'
const useModal = () => {
const [on, setOn] = useState(false)
const toggleModal = () => setOn(!on)
const Modal = ({onOK, ...rest}) => (
<AntdModal
{...rest}
visible={on}
onOk={() => {
onOK && onOK()
toggleModal()
}}
onCancel={toggleModal}
/>
)
return {
on,
toggleModal,
Modal,
}
}
export default useModal
And this is how I use it:
import React, {useState} from 'react'
import ReactDOM from 'react-dom'
import useModal from './useModal'
import {Button} from 'antd'
const App = () => {
const {toggleModal, Modal} = useModal()
return (
<div>
<Button type="primary" onClick={toggleModal}>
Open Modal
</Button>
<Modal title="Simple" onOK={() => alert('Something is not OK :(')}>
<p>Modal content...</p>
</Modal>
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
Here is a sandbox to see it in action and test it out. There are two buttons, one which shows a Modal which is normally imported from the library (here antd) and one that is from a custom hook useModal.
The one form the hook works except it seems something is wrong with it. The appearing transition is working but when you close the modal it suddenly disappears with no transition. It seems the component is immediately destroyed before transitioning out. What am I doing wrong?
If I understand it correct, you want to render a Component and also need a function which can control it (toggle it's visibility).
Though it is not possible the way you are trying to achieve with the react hooks, because on state change you are actually updating your Modal too and that is causing an unmount of the Dialogue from DOM.
You can use below solution to achieve the same result. The Solution uses a component with forwardRef and useImperativeHandle and will achieve a decoupled function which you can use to toggle your dialogue using button click:
NOTE: You need to upgrade to react and react-dom from v-16.7.0-alpha (as in your sandbox code) to latest (16.14.0) [I have not tried other intermediate versions]
Modal Component:
import React, {useState, forwardRef, useImperativeHandle} from 'react'
import 'antd/dist/antd.css'
import {Modal as AntdModal} from 'antd'
const Modal = forwardRef(({onOK, ...rest}, ref) => {
useImperativeHandle(ref, () => ({
toggleModal: toggleModal
}));
const [on, setOn] = useState(false)
const toggleModal = () => setOn(!on)
return (
<AntdModal
{...rest}
visible={on}
onOk={() => {
onOK && onOK()
toggleModal()
}}
onCancel={toggleModal}
/>
)
});
export default Modal;
And this is how to use it:
import React, {useState, useRef} from 'react'
import ReactDOM from 'react-dom'
import Modal from './ModalWrapper'
import {Button, Modal as AntdModal} from 'antd'
const App = () => {
const [on, setOn] = useState(false)
const toggle = () => setOn(!on)
const modalRef = useRef()
return (
<div>
<Button type="warning" onClick={() => setOn(true)}>
Normal Import
</Button>
<br />
<br />
<Button type="primary" onClick={() => modalRef.current.toggleModal()}>
From Modal Component
</Button>
<AntdModal visible={on} onOk={toggle} onCancel={toggle}>
<p>I was imported directly...</p>
<p>I was imported directly...</p>
<p>I was imported directly...</p>
</AntdModal>
<Modal
title="Simple"
ref={modalRef}
onOK={() => alert('Things are now OK :)')}
>
<p>I was imported from Modal Component...</p>
<p>I was imported from Modal Component...</p>
<p>I was imported from Modal Component...</p>
</Modal>
</div>
)
}
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
I hope it will help your use case.
Thanks.

Bootstrap modal didn't close while clicking outside of modal in React

I used a react-bootstrap modal to show notification in React. It works fine but it didn't close when I click outside of the modal.
Modal code
import React from "react";
import ReactDom from 'react-dom';
import Modal from "react-bootstrap/Modal";
import ModalBody from "react-bootstrap/ModalBody";
import ModalHeader from "react-bootstrap/ModalHeader";
import ModalFooter from "react-bootstrap/ModalFooter";
import ModalTitle from "react-bootstrap/ModalTitle";
import 'bootstrap/dist/css/bootstrap.css';
class ForgetPassword extends React.Component{
constructor(props)
{
super(props);
this.state={
modalIsOpen:true
}
}
render()
{
return (
<Modal show={this.state.modalIsOpen}>
<ModalHeader>
<ModalTitle>Hi</ModalTitle>
</ModalHeader>
<ModalBody>asdfasdf</ModalBody>
<ModalFooter>This is the footer</ModalFooter>
</Modal>
);
}
}
export default ForgetPassword;
You don't have toggle set on the Modal component. You need to add the toggle prop to <Modal>, giving it a function, which when triggered, will toggle the value of the IsOpen in prop.
import React from "react";
import Modal from "react-bootstrap/Modal";
import ModalBody from "react-bootstrap/ModalBody";
import ModalHeader from "react-bootstrap/ModalHeader";
import ModalFooter from "react-bootstrap/ModalFooter";
import ModalTitle from "react-bootstrap/ModalTitle";
import 'bootstrap/dist/css/bootstrap.css';
class ForgetPassword extends React.Component{
state = {
modalIsOpen: true
}
toggleModal = () => {
this.setState(prevState => ({
modalIsOpen: !prevState.modalIsOpen
}));
};
render() {
return (
<Modal show={this.state.modalIsOpen} onHide={this.toggleModal}>
<ModalHeader>
<ModalTitle>Hi</ModalTitle>
</ModalHeader>
<ModalBody>asdfasdf</ModalBody>
<ModalFooter>This is the footer</ModalFooter>
</Modal>
);
}
}
export default ForgetPassword;
Source
The bootstrap react docs make reference to the onHide prop that should be on the modal - A callback fired when the header closeButton or non-static backdrop is clicked. Required if either are specified.
You will want to use this to set the show modal state as false, probably through a function.

ShallowWrapper is empty when running test

I'm new to testing so I'm trying to add Enzyme to one of my projects. My problem is that when using find(), the ShallowWrapper is empty. Also I'm using Material UI, so I don't know if this is part of the problem.
The component I'm testing
import React from "react";
import { withStyles } from "#material-ui/core/styles";
import AppBar from "#material-ui/core/AppBar";
import Toolbar from "#material-ui/core/Toolbar";
import Typography from "#material-ui/core/Typography";
const styles = theme => ({
root: {
flexGrow: 1
},
background: {
backgroundColor: "#2E2E38"
},
title: {
color: "#FFE600",
flexGrow: 1
}
});
const NavBar = ({ classes }) => {
return (
<div className={classes.root} data-test="nav-bar">
<AppBar className={classes.background}>
<Toolbar>
<Typography variant="h5" className={classes.title}>
App
</Typography>
</Toolbar>
</AppBar>
</div>
);
};
export default withStyles(styles)(NavBar);
The test
import React from "react";
import { shallow } from "enzyme";
import NavBar from "./NavBar";
describe("NavBar component", () => {
it("Should render without errors.", () => {
let component = shallow(<NavBar />);
let navbar = component.find("data-test", "nav-bar");
console.log("Log is", component);
expect(navbar).toBe(1);
});
});
Try changing your selector in find(selector) to the following to target the element with data-test="nav-bar". You may need to use dive() to be able to access the inner components of the style component:
import React from "react";
import { shallow } from "enzyme";
import NavBar from "./NavBar";
describe("NavBar component", () => {
it("Should render without errors.", () => {
const component = shallow(<NavBar />);
// Use dive() to access inner components
const navbar = component.dive().find('[data-test="nav-bar"]');
// Test that we found a single element by targeting length property
expect(navbar.length).toBe(1);
});
});
You can also use an object syntax if you prefer:
const navbar = component.find({'data-test': 'nav-bar'});
Alternatively to using dive(), you could instead mount() the component instead of shallow(), but it depends on your use case:
import React from "react";
import { mount } from "enzyme";
import NavBar from "./NavBar";
describe("NavBar component", () => {
it("Should render without errors.", () => {
const component = mount(<NavBar />);
// Use dive() to access inner components
const navbar = component.find('[data-test="nav-bar"]');
// Test that we found a single element by targeting length property
expect(navbar.length).toBe(1);
});
});
Hopefully that helps!
I ran into this issue for a different reason where I could not find a SingleDatePicker element. The example in 2. A React Component Constructor from the documentation fixed it for me.
https://enzymejs.github.io/enzyme/docs/api/selector.html#1-a-valid-css-selector
using
wrapper.find(SingleDatePicker).prop('onDateChange')(now);
instead of
wrapper.find('SingleDatePicker').prop('onDateChange')(now);
did the trick for me.

React exporting withRouter and withStyles error

I am using react along with redux and material-ui to make a component. I am attempting to write an export statement export default connect()(withRouter(FirstPage))(withStyles(styles)(FirstPage))
However, this doesn't seem to work I get an error that says
TypeError: Cannot set property 'props' of undefined
this.props = props;
This error is referencing one of my node_modules.
Here is my full code:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {withRouter} from 'react-router-dom'
import { withStyles } from '#material-ui/core/styles';
import Card from '#material-ui/core/Card';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import Button from '#material-ui/core/Button';
const styles = theme =>({
root: {
maxWidth: 345,
},
})
class FirstPage extends Component {
state = {
feeling: ''
}
//This function will dispatch the users response to index.js
//The dispatch type here is 'SET_FEELING'
submitData=(event) => {
event.preventDefault();
this.props.dispatch({type: 'SET_FEELING', payload: this.state})
this.changeLocation();
}
//This function will update the local state with the users response
handleChange= (event) => {
this.setState({
feeling: event.target.value
})
}
//This function will change the current url when a button is clicked
changeLocation= ()=> {
this.props.history.push('/secondPage')
}
render(){
const { classes } = this.props;
return(
<div>
<Card >
<CardContent className={classes.root}>
<form>
<input onChange={this.handleChange} placeholder='How are you feeling' value={this.state.feeling} />
</form>
</CardContent>
<CardActions>
<Button onClick={this.submitData}>Submit</Button>
</CardActions>
</Card>
</div>
)
}
}
//this export connects the component to the reduxStore as well as allowing us to use the history props
export default connect()(withRouter(FirstPage))(withStyles(styles)(FirstPage))
I believe the following code should work:
export default withRouter(connect()(withStyles(styles)(FirstPage)))
Instead of
export default connect()(withRouter(FirstPage))(withStyles(styles)(FirstPage))
First of all, connect() returns a function that only accepts an argument. Second, connect() should be wrapped inside withRouter(). This problem is stated in the github docs of React Router.
without using react-redux :
export default (withStyles(styles), withRouter)(FirstPage);

Jest and Enzyme with React: Mock method with Translate

I am doing some unit tests for a project I'm working on, and I have the following Modal on React. Note that I usually pass t for translation, which I am attempting to do in the test but it's working:
import React, { Component } from 'react'
import { Modal, Button } from 'react-bootstrap'
import {translate} from 'react-polyglot'
class MessageModal extends Component {
render () {
const {messageModal: {message, isOpen}, closeMessageModal, t} = this.props
return (
<Modal show={isOpen}>
<Modal.Body>
{message ? t(`MESSAGE_MODAL.${message}`) : ''}
</Modal.Body>
<Modal.Footer>
<Button onClick={closeMessageModal}>Close</Button>
</Modal.Footer>
</Modal>
)
}
}
export default translate()(MessageModal)
and the following test:
import React from 'react'
import MessageModal from './MessageModal'
import { shallow } from 'enzyme'
import { shallowToJson } from 'enzyme-to-json'
// import {translate} from 'react-polyglot'
describe('Testing MessageModal', () => {
const tree = shallow(
<MessageModal t={jest.fn()} messageModal={{message: 'message', isOpen: true}}/>
)
it('Renders correctly', () => {
expect(shallowToJson(tree.dive().setProps({t: jest.fn()}))).toMatchSnapshot()
})
})
And I'm getting two main errors:
TypeError: t is not a function
and
Warning: Failed context type: The context `t` is marked as required in `_translate`, but its value is `undefined`.
I have tried everything to get this solved but I wasn't able to make it work. If you could help me that'd be great.
What you could do is also exporting the not decorated MessageModal component.
import React, { Component } from 'react'
import { Modal, Button } from 'react-bootstrap'
import {translate} from 'react-polyglot'
export class MessageModal extends Component {
render () {
const {messageModal: {message, isOpen}, closeMessageModal, t} = this.props
return (
<Modal show={isOpen}>
<Modal.Body>
{message ? t(`MESSAGE_MODAL.${message}`) : ''}
</Modal.Body>
<Modal.Footer>
<Button onClick={closeMessageModal}>Close</Button>
</Modal.Footer>
</Modal>
)
}
}
export default translate()(MessageModal)
This way you can import the not decorated component in your test so you don't have to deal with the higher order component part.
import { MessageModal } from './MessageModal'

Resources