Simulate click - enzyme - reactjs

I have a problem with Enzyme tool to test React app.
In my component I have login form and I want to test if after click on button the p tag will fill by text.
In fact after clicking submit there is sent request to the api (which doesn't exist now), the error about unreachable endpoint is returned.
Tried to test it in a lot of ways, but noticed one funny thing. Using:
it('returns error if endpoint not reachable', () => {
const wrapper = mount(<LoginForm dispatch={dispatch} store={store} />);
wrapper.find('button').simulate('click');
console.log(wrapper.debug());
});
returns HTML code in console. But there p tag is also not filled. So my question is how to use simulate function here?
First time I thought it is caused by some timeout. But using setTimeout gives the same result.
Thank you for your help!

Enzyme gives the ability to submit a click event. If it's not working i'm curious if you have the right element selected. An example from the docs...
https://github.com/airbnb/enzyme/blob/master/docs/api/ShallowWrapper/simulate.md#example
class Foo extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
const { count } = this.state;
return (
<div>
<div className={`clicks-${count}`}>
{count} clicks
</div>
<a href="url" onClick={() => { this.setState({ count: count + 1 }); }}>
Increment
</a>
</div>
);
}
}
const wrapper = shallow(<Foo />);
expect(wrapper.find('.clicks-0').length).to.equal(1);
wrapper.find('a').simulate('click');
expect(wrapper.find('.clicks-1').length).to.equal(1);
So in your particular case you mentioned an API call is made after clicking the submit button. You'll need to isolate and mock this behavior using something like Sinon.
http://sinonjs.org/

Related

How to prevent Default with typescript with reactjs

Here is my component. whenever i call the event it works fine. But page getting refreshed each time. how to prevent this. I understood that i need to add event.preventDefult but adding that shows error. please show me the correct way to do this:
export class Header extends React.Component {
state:NaviState = {
show:false
}
toggle = ():void =>{
console.log("h"); //page getting refreshing each click
this.state = {show:!this.state.show}
}
render() {
return (
<header>
<h1><a className="hidden tantum-logo" href="#">Welocome to Tantum website</a></h1>
<div className="screen"></div>
<nav className="small">
<a className="nav-icon hidden" onClick={this.toggle} href="#">tantum-Menu</a>
</nav>
</header>
)
}
}
You can solve this problem in 2 different ways:
Remove the href attribute - since you want to add a onClick listener you can simply not provide the href value and this should prevent a page reload;
If you want to keep the href (not sure why you would want this) you can do intercept the event in your function:
toggle = (ev) : any => { // event is implicitly passed to the function
e.preventDefault();
e.stopImmediatePropogation(); // might not be needed, would need testing.
this.state = {show:!this.state.show}
}
#thanks ayushya, it works for me:
toggle = (e: { preventDefault: () => void; }):any =>{
e.preventDefault();
this.state = {show:!this.state.show};
}

React: how to load different api on different button click

I need to load different API urls on different button click:
Here is the API Component:
const companiesUrl = "http://localhost:3005/local-companies-xxx";
class Companies extends Component {
state = {
companyDetails: [],
currentPage: 0
};
get items() {
const {currentPage, companiesDetails} = this.state;
return companiesDetails.slice(currentPage * 3, currentPage * 3 + 3)
}
changeCurrentPage = (i) => {
this.setState({
currentPage : i
})
};
componentDidMount() {
fetch(companiesUrl)
.then(data => data.json())
.then(info => this.setState({companiesDetails: info}))
.catch(error => console.error("error connecting to the api"));
}
[more code... removed because not really necessary to show right now]
Here is the button Component:
class HomeButton extends Component {
render() {
return (
<div className="button" onClick={this.props.passClick}>
<p>{this.props.buttonText}</p>
</div>
)
}
}
export default HomeButton;
And here is the Parent:
class WhomWeHelp extends Component {
state = {
count : 0
};
increment() {
this.setState({
count : this.state.count + 1
})
}
render() {
return (
<section>
<div>
<HomeButton buttonText="xxx" passClick={this.increment} />
<HomeButton buttonText="yyy" passClick={this.increment} />
<HomeButton buttonText="zzzz" passClick={this.increment} />
</div>
<div>
<Companies />
</div>
</section>
)
}
}
export default WhomWeHelp;
As you can see I tried to add an onClick={this.props.passClick} to the button Component and pass the click to the Parent with passClick={this.increment} so that it runs the method
increment() {
this.setState({
count : 0
})
}
Then I wanted to transfer this state to the API Component and load a different API Url per different button.
Button xxx should load the api with an xxx url, button yyy should load the yyy companies, and zzz the zzz companies.
However right now when I click the 3 buttons I get WhomWeHelp.jsx:13 Uncaught TypeError: Cannot read property 'setState' of undefined at increment...
Why I am getting such error? If instead of this.setstate, I put a console log inside increment() {} it works fine.
Also is there a better way to achieve what I am doing avoiding to pass the click from child to parent, and then to pass the state from parent to API child?
Thanks
If when you put a console.log inside increment, it works fine, then I would say your this is the problem and your this is probably undefined. I imagine it is a scope issue.
Try changing your increment function to an arrow function expression like this:
increment = () => {
this.setState((state) => ({
count: state.count + 1
}));
}
For the issue of sending different API requests for different button clicks, you can create another arrow function expression as the binding event and send a flag to determine which API should be hit for each button. For example:
<button onClick={() => this.increment("xxx")}>xxx</button>
Check this reproducible example out where every button does something different to update the state:

React: document.getElementById (or onClick) does not work

I want a random number to be generated and put into the "roomid" input field once the "generate" button is clicked, but the value shown is still 0 after I clicked the button. It guess it is not the problem of onClick because there was indeed a "?" added to the URL after I clicked the button. What is wrong with the code? Thank you so much for your help]1
React way of doing something like this would be
class StartStream extends Component {
state = { randomId: 0 }
generate = () => {
this.setState({ randomId: Math.random() * 1000 });
}
render() {
return <div>
<input type="text" value={this.state.randomId} />
<button onClick={this.generate}>Generate</button>
</div>;
}
}
Here is a working demo https://jsfiddle.net/tfc3javx/1/

Enzyme- How to set state of inner component?

I need to access the state of the inner component, to make it active for click event, my problem is Enzyme does not allow this when using mount, this can only be achieved by shallow rendering of enzyme as mentioned over here, also as mentioned I have tried to use dive to fetch the Form component and again from Form to get Button component which I need to reach, the problem is that my test case keeps on failing as Form component length is zero.
enzyme: 3.1.0
enzyme-adapter-react-15: 1.0.1"
I am pretty new to Enzyme, Any help will be appreciated, Thanks
contactus.test.js :
test('It should simulate submit action ', ()=>{
let contactUs = shallow(<ContactUs />);
sinon.spy(ContactUs.prototype, 'submitMessage');// Verify this method call
let form = contactUs.find(Form)
expect(form.length).toBe(1);//Failing over here
let button = form.dive().find(Button);
expect(button.length).toBe(1);
button.setState({disabled : false});//Need to achieve this
expect(button).toBeDefined();
expect(button.length).toBe(1);
expect(button.props().label).toBe('SEND MESSAGE');
button.find('a').get(0).simulate('click');
expect(ContactUs.prototype.submitMessage).toHaveProperty('callCount',
1);
});
contactus.js :
import React, {Component,PropTypes} from 'react';
import Form from './form';
import {sendSubscriptionMessage} from '../../network';
import Button from '../Fields/Button';
export default class ContactUs extends Component {
constructor(props) {
super(props);
this.state = {
contactData: {}
}
}
onChangeHandler(event) {
let value = event.target.value;
this.state.contactData[event.target.name] = value;
}
submitMessage(event) {
event.preventDefault();
sendSubscriptionMessage(this.state.contactData);
}
render() {
return (<div className = "row pattern-black contact logo-container" id = "contact">
<div className = "container" >
<h2 className = "sectionTitle f-damion c-white mTop100" >
Get in Touch!
<Form onChangeHandler = {
this.onChangeHandler.bind(this)
} >
<Button onClick = {
this.submitMessage.bind(this)
}
className = "gradientButton pink inverse mTop50"
label = "SEND MESSAGE" / >
</Form> </div>
</div>
);
}
}
First of all I think you should not test the Button and the Form functionalities here. In this file you should test only the ContactForm component.
For the first fail, this should work:
find('Form') (the Form should have quotes)
Same for the button:
find('Button');
In this way you don't even have to import the Form and the Button components in your test file at all;
Then, you don't have to set any state for that button. You test the button functionality in the Button.test.js file.
All you have to do here is to call its method like this:
button.nodes[0].props.onClick();
Overall, this is how your test should look like ( note that I didn't test it, I've been using Jest for testing my components, but the logic should be the same ):
test('It should simulate submit action ', ()=>{
const wrapper = shallow(<ContactUs />);
const spy = sinon.spy(ContactUs.prototype, 'submitMessage'); // Save the spy into a new variable
const form = wrapper.find('Form') // I don't know if is the same, but in jest is enough to pass the component name as a string, so you don't have to import it in your test file anymore.
expect(form).to.have.length(1);
const button = wrapper.find('Button'); // from what I see in your code, the Button is part of the ContactUs component, not of the Form.
expect(button).to.have.length(1);
/* These lines should not be part of this test file. Create a test file only for the Button component.
button.setState({disabled : false});
expect(button).toBeDefined();
expect(button.length).toBe(1);
expect(button.props().label).toBe('SEND MESSAGE');
*/
button.nodes[0].props.onClick(); // Here you call its method directly, cause we don't care about its internal functionality; we want to check if the "submitMessage" method has been called.
assert(spy.called); // Here I'm not very sure... I'm a Jest fan and i would have done it like this "expect(spy).toHaveBeenCalled();"
});

Expect method to have been called is not fulfilled on "onInput" for a contentEditable element

I have a component that uses contentEditable as an input method. The part from the component that is of interest is:
<div className="enter-edit-mode" onClick={view.enterEditMode}>
<div className="user-input" ref="content" contentEditable onInput={view.textChanged}></div>
</div>
The component works fine - it gets into the textChanged method on user input. The method looks like this:
textChanged: function (e) {
var view = this,
textValue = e.target.innerHTML;
view.setState({
enteringText: textValue.length,
temporaryValue: textValue
});
}
The problem I'm facing appears when I try to test the input behavior. The setup is done with enzyme, chai, sinon. I'm rendering the component using a simple renderComponent function, using enzyme's mount method.
beforeEach(function () {
view = renderComponent(card);
viewReact = view.get(0);
});
it('should enter text changed method on input', function () {
let spy = sinon.spy(viewReact, 'textChanged');
view.find('.user-input').simulate('input');
expect(spy).to.have.been.called;
spy.restore();
});
It outputs expected textChanged to have been called at least once, but it was never called. The weird part is, however, if I put a console.log inside the component's method, it gets there.
What I've tried to make it work
use sinon.stub instead of spy, as I though that maybe something in my method doesn't work properly
call it with view.find('.user-input').simulate('input', {target: {value: "lorem"}) or .simulate('input', {key: 'a'})
If instead of simulating the input I do a viewReact.textChanged(), it obviously works.
I'm guessing that it's the input method on contentEditable that's causing this. Any suggestions? How can I properly enter text in the onInput method? (even if it gets in the textChanged method, the text is empty)
I could reproduce your issue trying to test the following component (which looks similar to yours):
const MyComponent = React.createClass({
textChanged(e) { console.log('text changed') },
render() {
return (
<div className="enter-edit-mode">
<div className="user-input" ref="content" contentEditable onInput={ this.textChanged }></div>
</div>
);
}
});
I also managed to get the test working, in a somewhat convoluted way:
it('should enter text changed method on input', () => {
let view = mount(<MyComponent/>);
let spy = sinon.spy(view.instance(), 'textChanged');
view = view.mount();
view.find('.user-input').simulate('input');
expect(spy).to.be.called;
spy.restore();
});
The view.mount() to re-mount the component seems to do the trick here.
I'm wasn't familiar with Enzyme at all (although I like it :), but it looks like it's adding various layers around components, in which case it's easy for Sinon spies to get "lost".
One possible caveat: I did all of this testing in Node using jsdom, and not in a browser.
I made a little test, I don't have sinon but I found a use case:
class Home extends Component {
state = {
status: 'default'
}
textChanged = () => {
this.setState({
status: 'changed'
})
}
render () {
return (
<div className="enter-edit-mode" >
<div className="user-input" ref="content" contentEditable onInput={this.textChanged}>Hello</div>
<span>{this.state.status}</span>
</div>
)
}
}
And the test
describe('component', () => {
it('should say changed', () => {
const component = shallow(<Home />);
expect(component.find('span').text()).toEqual('default');
component.find('.user-input').simulate('input', {key: 'a'})
expect(component.find('span').text()).toEqual('changed');
});
});
It passed all is expected

Resources