I am writing a test case using jest, but I am not able to get how to test click simulation if it is not button.
If it is button we write find('button), but what if we click on div and there are nested div
class Section extends React.Component {
constructor(props) {
super(props);
this.state = {
open: props.open,
className: 'accordion-content accordion-close',
headingClassName: 'accordion-heading'
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({
open: !this.state.open
});
}
render() {
const { title, children } = this.props;
const { open } = this.state;
const sectionStateClassname = open
? styles.accordionSectionContentOpened
: styles.accordionSectionContentClosed;
return (
<div className={styles.accordionSection}>
<div
className={styles.accordionSectionHeading}
onClick={this.handleClick}
id="123"
>
{title}
</div>
<div
className={`${
styles.accordionSectionContent
} ${sectionStateClassname}`}
>
{children}
</div>
</div>
);
}
}
here is my jest test case
test('Section', () => {
const handleClick = jest.fn();
const wrapper = mount(<Section onClick={ handleClick} title="show more"/>)
wrapper.text('show more').simulate('click')
expect(handleClick).toBeCalled()
});
You can find element by class:
wrapper.find('.' + styles.accordionSectionHeading).first().simulate('click')
Also, your component seems to not call prop handleClick. Instead, instance method is called, so something like this:
wrapper.instance().handleClick = jest.fn();
expect(wrapper.instance().handleClick).toBeCalled();
seems to be more correct.
Or, better, you can just check if state is changed
expect(wrapper.state('open')).toBeTruthy();
Hope it helps.
Related
Currently, I am working on a simple system where after a button is pushed another webpage opens up. However, I am aiming to display a component after the button is clicked. Hereby, I initialized a state with a boolean set to false. Furthermore, after the button is clicked the state should be set to true which should enable the display of the component ApartmentBooking01. When running the code a new webpage shows up due to the history.push object. However, it does not display my wanted component ApartmentBooking01. Can anyone maybe explain to me what I am doing wrong?
class ApartmentInformation01 extends React.Component {
constructor(props){
super(props);
this.state = {
showBookingInformation: false,
apartmentInformation: [{
apartmentNumber: [],
availableBeds: [],
pricePerNight: []
}]
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
this.props.history.push("api/apartments/apartmentbooking01");
this.setState({
showBookingInformation: !this.state.showBookingInformation
});
}
getApartmentBookingComponent(){
if(this.state.showBookingInformation){
return <ApartmentBooking01/>
}else {
return null;
}
}
componentDidMount() {
axios.get(`http://localhost:8080/api/apartment/`)
.then(res => {
const apartmentInformation = res.data;
this.setState({ apartmentInformation });
})
}
render() {
const { apartmentInformation } = this.state;
return (
<div className='apartmentInformation-container'>
<ul>
{
apartmentInformation.filter(apartmentInfo => apartmentInfo.apartmentNumber == 1)
.map(filteredApartment => (
<div className='apartmentInformation-items'>
<h2 className='apartmentssection-01-price'>Price per Night</h2>
<p className='apartmentInformation-items-pricePerNight'>€{filteredApartment.pricePerNight},- </p>
<h2 className='apartmentssection-01-beds'>Available Beds</h2>
<p className='apartmentInformation-items-availableBeds'>{filteredApartment.availableBeds} Beds</p>
</div>
))
}
</ul>
<button variant="btn-success" onClick={this.handleClick}>More information</button>
{this.getApartmentBookingComponent}
</div>
)
}
}
export default withRouter (ApartmentInformation01)
Try to add event.preventDefault() at handleClick function. This should be in the first line.
also you should add keys for you map method:
<div className='apartmentInformation-items' key={appId}>
I am passing a function to child component and on click in child, it goes to parent function to execute. I want to write a test case but it is unable to find the function.
class Parent extends Component {
closeModal = () =>{
this.setState({
showModal: false
});
}
render(){
return (
<>
{showModal
&& (
<ChildModal
data={modalValue}
cancelHandler={this.cancelHandler}
submitHandler={this.submitHandler}
closeModal = {this.closeModal}
/>
)}
</>
}
}
class ChildModal extends Component{
render(){
const { cancelHandler, submitHandler, closeModal } = this.props;
return(
<div id={modalId}>
<button type="button" id="close" onClick={() =>
closeModal()}>
Cancel</button>
</div>
)
}
}
Test Function:
const closeModal = jest.fn();
const wrapper = shallow(<ChildModal {...props} {...state} />);
const button = wrapper.find('#close');
button.simulate('click');
wrapper.update();
expect(closeModal).toHaveBeenCalled();
You need to use jest.fn. It will return new, unused mock function.
const mockedFunc = jest.fn();
Now, while creating ChildModal, you need to pass mockedFunc in closeModal.
Once you simulate click event on Modal, you need to check below condition:
expect(mockedFunc).toHaveBeenCalled();
If expect return true, parent function was called.
I am struggeling on finding out why my button dont play a sound when I click on it. The console.log() test works fine, but the -part dont. I also tried some npm-packets to solve the problem, but it seems like my code has a general problem. Whats wrong with it? Can someone help me?
The main.js :
import Button from './button';
class Drumpad extends Component {
constructor(props) {
super(props);
this.state = {
Q:
{
id: 'Q',
name: 'Q',
src: 'https://s3.amazonaws.com/freecodecamp/drums/Heater-1.mp3'
},
}
}
render() {
return (
<div style={test}>
<div id='row1'>
<Button cfg={this.state.Q}/>
</div>
</div>
)
}
}
And the button.js:
class Button extends Component {
constructor(props) {
super(props);
this.state = {
}
}
handleClick = () => {
console.log(this.props.cfg.src);
return (
<audio ref='audioClick' src={this.props.cfg.src} type='audio/mp3' autoPlay>
);
};
render() {
return (
<div>
<button style={buttonStyle} onClick={this.handleClick}>
<h1>{this.props.cfg.name}</h1>
</button>
</div>
)
}
}
The handleClick method in button.js returns an <audio> element, which is redundant, since you would like to play the sound onClick.
Instead I used a Audio constructor to create an instance of the audio clip, using the url provided as props, which I set to state.
Then I use a callback to invoke the play() method on it.
handleClick = () => {
const audio = new Audio(this.props.cfg.src);
this.setState({ audio }, () => {
this.state.audio.play();
});
};
So your button.js becomes something like this:
import React, { Component } from "react";
const buttonStyle = {};
export default class Button extends Component {
constructor(props) {
super(props);
this.state = {
audio: false
};
}
handleClick = () => {
console.log(this.props.cfg.src);
const audio = new Audio(this.props.cfg.src);
this.setState({ audio }, () => {
this.state.audio.play();
});
};
render() {
return (
<div>
<button style={buttonStyle} onClick={this.handleClick}>
<h1>{this.props.cfg.name}</h1>
</button>
</div>
);
}
}
Your main.js remains as is.
Here is a working codesandbox.
I know that there are plenty of answers on this, for example this one. I did add the .bind(this) in the component constructor. I also tried the fat arrow method (fakeApiCall = ()=>{ ... }) but when I click Change Me, this error still displays:
import React, { Component } from 'react';
import axios from 'axios';
class App extends Component {
constructor(props){
super(props);
this.state = {
count : 1000
};
this.fakeApiCall = this.fakeApiCall.bind(this);
}
fakeApiCall (){
axios.get('https://jsonplaceholder.typicode.com/users')
.then(function(response){
// the response comes back here successfully
const newCount = response.data.length;
// fail at this step
this.setState({ count : Math.floor(newCount) });
});
}
render() {
return (
<div className="App">
<span style={{ fontSize : 66 }}>{this.state.count}</span>
<input type='button' onClick={this.fakeApiCall} value='Change me' />
</div>
);
}
}
export default App;
Your fakeApiCall function is bound to your context, but the function callback in axios is not.
To solve this, you can use an arrow function, as they automatically bind with your class. You can also do it for fakeApiCall and remove it's binding :
class App extends Component {
constructor(props) {
super(props);
this.state = {
count: 1000
};
}
fakeApiCall = () => {
axios.get('https://jsonplaceholder.typicode.com/users')
.then(response => { //This is an arrow function
const newCount = response.data.length;
this.setState({ count: Math.floor(newCount) });
});
}
render() {
return (
<div className="App">
<span style={{ fontSize: 66 }}>{this.state.count}</span>
<input type='button' onClick={this.fakeApiCall} value='Change me' />
</div>
);
}
}
How to fix this error when I have the binding this way: previously binding in constructor solved but this is a bit complex for me:
{this.onClick.bind(this, 'someString')}>
and
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
Option 1:
Use arrow functions (with babel-plugins)
PS:- Experimental feature
class MyComponent extends Component {
handleClick = (args) => () => {
// access args here;
// handle the click event
}
render() {
return (
<div onClick={this.handleClick(args)}>
.....
</div>
)
}
}
Option 2: Not recommended
Define arrow functions in render
class MyComponent extends Component {
render() {
const handleClick = () => {
// handle the click event
}
return (
<div onClick={handleClick}>
.....
</div>
)
}
}
Option 3:
Use binding in constructor
class MyComponent extends Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
// handle click
}
render() {
return (
<div onClick={this.handleClick}>
.....
</div>
)
}
}
I recommend you to use binding in the class constructor. This will avoid inline repetition (and confusion) and will execute the "bind" only once (when component is initiated).
Here's an example how you can achieve cleaner JSX in your use-case:
class YourComponent extends React.Component {
constructor(props) {
super(props);
// Bind functions
this.handleClick = this.handleClick.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleClick() {
this.onClick('someString');
}
onClick(param) {
// That's your 'onClick' function
// param = 'someString'
}
handleSubmit() {
// Same here.
this.handleFormSubmit();
}
handleFormSubmit() {
// That's your 'handleFormSubmit' function
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<p>...</p>
<button onClick={this.handleClick} type="button">Cancel</button>
<button type="submit">Go!</button>
</form>
);
}
}
Even though all the previous answers can achieve the desire result, but I think the snippet below worth mentioning.
class myComponent extends PureComponent {
handleOnclickWithArgs = arg => {...};
handleOnclickWithoutArgs = () => {...};
render() {
const submitArg = () => this.handleOnclickWithArgs(arg);
const btnProps = { onClick: submitArg }; // or onClick={submitArg} will do
return (
<Fragment>
<button {...btnProps}>button with argument</button>
<button onClick={this.handleOnclickWithoutArgs}>
button without argument
</button>
</Fragment>
);
}
}