Enzyme- How to set state of inner component? - reactjs

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();"
});

Related

Get HTMLInputElement from ref to PrimeReact's InputText

I am trying to use a ref to get to the underlying input element of an InputText component. I used this.textFieldRef = React.createRef() to set up the ref, and then the attribute ref={this.textFieldRef} to hook it up. Yet in the componentDidMount I cannot use this.textFieldRef.current.select() because select() is not a function available for that object. So somehow, InputText is not returning the underlying HTMLInputElement.
Does anyone know how I can get from a ref to something that allows me to select() the text in the InputText element?
Here is my code, which is actually in TypeScript...
import * as React from 'react';
import { InputText } from 'primereact/inputtext';
export class ValueCard extends React.Component<{}, {}> {
textFieldRef: React.RefObject<any> = React.createRef();
componentDidMount = () => {
if (this.textFieldRef.current instanceof InputText) {
this.textFieldRef.current.select();
}
}
render() {
return = (
<InputText
value="test"
ref={this.textFieldRef}
/>
);
}
}
Looking at the source for PrimeReact's InputText component (source), they are attaching a reference to the inner input element to this.element.
This allows you to just add .element to your reference:
this.textFieldRef.current.element.select();
I tested it out in this sandbox, and it seems to work as expect:
https://codesandbox.io/s/203k7vx26j
Maybe you could try to use react-dom library:
ReactDOM.findDOMNode(this.textFieldRef.current).querySelector('input');

React Unit test function vs ArrowFunction

I have simple page with two divs. Background color of the second div depends on state of the active property. If active is true then it should use .active class from CSS file, otherwise use .two style.
I wrote unit test for this scenario to check if the style of the second div has been changed after state was changed.
I realized one thing, that when i execute style() function to get correct style name, unit test is not working and my style on second div is not updated. But when i execute style as an arrow function everything works. Do any of you know, why this happens? whats the problem with normal call of function? why render() is not called?
Arrow function console output (expected)
console.log src/containers/Example/Example.test.js:18
false
console.log src/containers/Example/Example.test.js:19
two
console.log src/containers/Example/Example.test.js:21
true
console.log src/containers/Example/Example.test.js:22
active
Normal function (wrong output)
console.log src/containers/Example/Example.test.js:18
false
console.log src/containers/Example/Example.test.js:19
two
console.log src/containers/Example/Example.test.js:21
true
console.log src/containers/Example/Example.test.js:22
two
Component with Arrow function
When you replace () => this.style() by this.style() unit test will fail.
import React, {Component} from 'react';
import styles from './Example.module.css';
class Example extends Component {
state = {
active: false
};
active = () => {
this.setState({active: !this.state.active});
};
style = () => {
return this.state.active ? styles.active : styles.two;
};
render() {
return (
<div>
<div onClick={() => this.active()} className={styles.one}/>
<div className={() => this.style()}/>
</div>
);
}
}
export default Example;
Unit test for Component
import React from 'react';
import Adapter from 'enzyme-adapter-react-16';
import {configure, mount} from 'enzyme';
import styles from './Example.module.css';
import Example from './Example';
configure({adapter: new Adapter()});
let component;
beforeEach(() => {
component = mount(<Example/>);
});
it('description', () => {
let two = component.find('div').at(2);
console.log(component.state().active);
console.log(two.props()["className"]());
component.setState({active: true});
console.log(component.state().active);
console.log(two.props()["className"]());
});
For second case this.style() you need to slightly modify console output
replace this console.log(two.props()["className"]); by this console.log(two.props()"className");
replace this console.log(two.props()["className"]); by this console.log(two.props()"className");
The problem isn't specific to unit testing but to the usage of functions in JavaScript. It would be applicable to production application as well.
onClick prop is expected to be a function. () => this.style() expression is a function. this.style() is the result of calling style method, a string.
Since style method is already bound to component instance (it's an arrow), it doesn't need to be wrapped with another arrow. It should be:
<div className={this.style}/>

react-dropzone onDrop firing twice

So I'm trying to add a pretty simple file upload to my React + Redux App and I found that Dropzone to be the most convinient way to do it. Here's my setup.
I have a FileInput component
import React from 'react'
import Dropzone from 'react-dropzone'
class FileInput extends React.Component {
constructor(props) {
super(props);
this.onChange = this.onChange.bind(this);
}
onChange(files) {
// For Redux form
if (this.props.input) {
const {input: {onChange}} = this.props;
onChange(files[0])
}
else if(this.props.onChange){
this.props.onChange(files[0])
}
else{
console.warn('redux-form-dropzone => Forgot to pass onChange props ?');
}
}
render() {
return (
<Dropzone onDrop={ this.onChange } {...this.props} >
Drag&Drop the image <br/> or click to select one
</Dropzone>
)
}
}
export default FileInput
And I use it on tha page like this:
<FileInput
onChange={(file) => console.log('dropped', file)}
className='add-avatar-dropzone'
activeClassName='dropzone-active'
/>
(console.log used for debugging purposes ofcource)
But when I try to drop a file, I get 2 log outputs. The first being the file I dropped, the second - some kind of a Proxy, probably provided by react itself...
I wonder how to get rid of that proxy and why is it doing that in the first place?
Thing I tried
Couple obvious problem-points I tried to eliminate, but did not seem to make any change.
Renaming the onChange function to something else like handleDrop and rewriting it as handleDrop = (files) => {
Removing the constructor (seems weird to assign something to itself)
It ended up being a simple matter and it's really silly for me not to think of it.
The onChange prop in the code above got passed on to Dropzone and then to the input field, wich was the source of the confusion.
I modified the code to work like this:
render() {
const { onChange, ...rest } = this.props
return (
<Dropzone onDrop={ this.onChange } {...rest} >
Drag&Drop the image <br/> or click to select one
</Dropzone>
)
}
That works just fine

Checkbox is not `checked` after simulate `change` with enzyme

I tried to use enzyme to simulate change event on a checkbox, and use chai-enzyme to assert if it's been checked.
This is my Hello react component:
import React from 'react';
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
checked: false
}
}
render() {
const {checked} = this.state;
return <div>
<input type="checkbox" defaultChecked={checked} onChange={this._toggle.bind(this)}/>
{
checked ? "checked" : "not checked"
}
</div>
}
_toggle() {
const {onToggle} = this.props;
this.setState({checked: !this.state.checked});
onToggle();
}
}
export default Hello;
And my test:
import React from "react";
import Hello from "../src/hello.jsx";
import chai from "chai";
import {mount} from "enzyme";
import chaiEnzyme from "chai-enzyme";
import jsdomGlobal from "jsdom-global";
import spies from 'chai-spies';
function myAwesomeDebug(wrapper) {
let html = wrapper.html();
console.log(html);
return html
}
jsdomGlobal();
chai.should();
chai.use(spies);
chai.use(chaiEnzyme(myAwesomeDebug));
describe('<Hello />', () => {
it('checks the checkbox', () => {
const onToggle = chai.spy();
const wrapper = mount(<Hello onToggle={onToggle}/>);
var checkbox = wrapper.find('input');
checkbox.should.not.be.checked();
checkbox.simulate('change', {target: {checked: true}});
onToggle.should.have.been.called.once();
console.log(checkbox.get(0).checked);
checkbox.should.be.checked();
});
});
When I run this test, the checkbox.get(0).checked is false, and the assertion checkbox.should.be.checked() reports error:
AssertionError: expected the node in <Hello /> to be checked <input type="checkbox" checked="checked">
You can see the message is quite strange since there is already checked="checked" in the output.
I'm not sure where is wrong, since it involves too many things.
You can also see a demo project here: https://github.com/js-demos/react-enzyme-simulate-checkbox-events-demo, notice these lines
I think some of the details of my explanation might be a bit wrong, but my understanding is:
When you do
var checkbox = wrapper.find('input');
It saves a reference to that Enzyme node in checkbox, but there are times that when the Enzyme tree gets updated, but checkbox does not. I don't know if this is because the reference in the tree changes and therefore the checkbox is now a reference to a node in an old version of the tree.
Making checkbox a function seems to make it work for me, because now the value of checkbox() is always taken from the most up to date tree.
var checkbox = () => wrapper.find('input');
checkbox().should.not.be.checked();
checkbox().simulate('change', {target: {checked: true}});
///...
It is not bug, but "it works as designed".
Enzyme underlying uses the react test utils to interact with react, especially with the simulate api.
Simulate doesn't actually update the dom, it merely triggers react event handlers attached to the component, possibly with the additional parameters you pass in.
According to the answer I got here (https://github.com/facebook/react/issues/4950 ) this is because updating the dom would require React to reimplement a lot of the browsers functionality, probably still resulting in unforeseen behaviours, so they decided to simply rely on the browser to do the update.
The only way to actually test this is to manually update the dom yourself and then call the simulate api.
Below solution best worked for me:
it('should check checkbox handleClick event on Child component under Parent', () => {
const handleClick = jest.fn();
const wrapper = mount(
<Parent onChange={handleClick} {...dependencies}/>,); // dependencies, if any
checked = false;
wrapper.setProps({ checked: false });
const viewChildren = wrapper.find(Children);
const checkbox = viewChildren.find('input[type="checkbox"]').first(); // If you've multiple checkbox nodes and want to select first
checkbox.simulate('change', { target: { checked: true } });
expect(handleClick).toHaveBeenCalled();
});
Hope this helps.
This is what worked for me:
wrapper.find(CCToggle)
.find('input[type="checkbox"]')
.simulate('change', { target: { checked: true } })
CCToggle is my component.

ReactJS - Add custom event listener to component

In plain old HTML I have the DIV
<div class="movie" id="my_movie">
and the following javascript code
var myMovie = document.getElementById('my_movie');
myMovie.addEventListener('nv-enter', function (event) {
console.log('change scope');
});
Now I have a React Component, inside this component, in the render method, I am returning my div. How can I add an event listener for my custom event? (I am using this library for TV apps - navigation )
import React, { Component } from 'react';
class MovieItem extends Component {
render() {
if(this.props.index === 0) {
return (
<div aria-nv-el aria-nv-el-current className="menu_item nv-default">
<div className="indicator selected"></div>
<div className="category">
<span className="title">{this.props.movieItem.caption.toUpperCase()}</span>
</div>
</div>
);
}
else {
return (
<div aria-nv-el className="menu_item nv-default">
<div className="indicator selected"></div>
<div className="category">
<span className="title">{this.props.movieItem.caption.toUpperCase()}</span>
</div>
</div>
);
}
}
}
export default MovieItem;
Update #1:
I applied all the ideas provided in the answers. I set the navigation library to debug mode and I am able to navigate on my menu items only based on the keyboard (as you can see in the screenshot I was able to navigate to Movies 4) but when I focus an item in the menu or press enter, I dont see anything in the console.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class MenuItem extends Component {
constructor(props) {
super(props);
// Pre-bind your event handler, or define it as a fat arrow in ES7/TS
this.handleNVFocus = this.handleNVFocus.bind(this);
this.handleNVEnter = this.handleNVEnter.bind(this);
this.handleNVRight = this.handleNVRight.bind(this);
}
handleNVFocus = event => {
console.log('Focused: ' + this.props.menuItem.caption.toUpperCase());
}
handleNVEnter = event => {
console.log('Enter: ' + this.props.menuItem.caption.toUpperCase());
}
handleNVRight = event => {
console.log('Right: ' + this.props.menuItem.caption.toUpperCase());
}
componentDidMount() {
ReactDOM.findDOMNode(this).addEventListener('nv-focus', this.handleNVFocus);
ReactDOM.findDOMNode(this).addEventListener('nv-enter', this.handleNVEnter);
ReactDOM.findDOMNode(this).addEventListener('nv-right', this.handleNVEnter);
//this.refs.nv.addEventListener('nv-focus', this.handleNVFocus);
//this.refs.nv.addEventListener('nv-enter', this.handleNVEnter);
//this.refs.nv.addEventListener('nv-right', this.handleNVEnter);
}
componentWillUnmount() {
ReactDOM.findDOMNode(this).removeEventListener('nv-focus', this.handleNVFocus);
ReactDOM.findDOMNode(this).removeEventListener('nv-enter', this.handleNVEnter);
ReactDOM.findDOMNode(this).removeEventListener('nv-right', this.handleNVRight);
//this.refs.nv.removeEventListener('nv-focus', this.handleNVFocus);
//this.refs.nv.removeEventListener('nv-enter', this.handleNVEnter);
//this.refs.nv.removeEventListener('nv-right', this.handleNVEnter);
}
render() {
var attrs = this.props.index === 0 ? {"aria-nv-el-current": true} : {};
return (
<div ref="nv" aria-nv-el {...attrs} className="menu_item nv-default">
<div className="indicator selected"></div>
<div className="category">
<span className="title">{this.props.menuItem.caption.toUpperCase()}</span>
</div>
</div>
)
}
}
export default MenuItem;
I left some lines commented because in both cases I am not able to get the console lines to be logged.
Update #2: This navigation library does not work well with React with its original Html Tags, so I had to set the options and rename the tags to use aria-* so it would not impact React.
navigation.setOption('prefix','aria-nv-el');
navigation.setOption('attrScope','aria-nv-scope');
navigation.setOption('attrScopeFOV','aria-nv-scope-fov');
navigation.setOption('attrScopeCurrent','aria-nv-scope-current');
navigation.setOption('attrElement','aria-nv-el');
navigation.setOption('attrElementFOV','aria-nv-el-fov');
navigation.setOption('attrElementCurrent','aria-nv-el-current');
If you need to handle DOM events not already provided by React you have to add DOM listeners after the component is mounted:
Update: Between React 13, 14, and 15 changes were made to the API that affect my answer. Below is the latest way using React 15 and ES7. See answer history for older versions.
class MovieItem extends React.Component {
componentDidMount() {
// When the component is mounted, add your DOM listener to the "nv" elem.
// (The "nv" elem is assigned in the render function.)
this.nv.addEventListener("nv-enter", this.handleNvEnter);
}
componentWillUnmount() {
// Make sure to remove the DOM listener when the component is unmounted.
this.nv.removeEventListener("nv-enter", this.handleNvEnter);
}
// Use a class arrow function (ES7) for the handler. In ES6 you could bind()
// a handler in the constructor.
handleNvEnter = (event) => {
console.log("Nv Enter:", event);
}
render() {
// Here we render a single <div> and toggle the "aria-nv-el-current" attribute
// using the attribute spread operator. This way only a single <div>
// is ever mounted and we don't have to worry about adding/removing
// a DOM listener every time the current index changes. The attrs
// are "spread" onto the <div> in the render function: {...attrs}
const attrs = this.props.index === 0 ? {"aria-nv-el-current": true} : {};
// Finally, render the div using a "ref" callback which assigns the mounted
// elem to a class property "nv" used to add the DOM listener to.
return (
<div ref={elem => this.nv = elem} aria-nv-el {...attrs} className="menu_item nv-default">
...
</div>
);
}
}
Example on Codepen.io
You could use componentDidMount and componentWillUnmount methods:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class MovieItem extends Component
{
_handleNVEvent = event => {
...
};
componentDidMount() {
ReactDOM.findDOMNode(this).addEventListener('nv-event', this._handleNVEvent);
}
componentWillUnmount() {
ReactDOM.findDOMNode(this).removeEventListener('nv-event', this._handleNVEvent);
}
[...]
}
export default MovieItem;
First off, custom events don't play well with React components natively. So you cant just say <div onMyCustomEvent={something}> in the render function, and have to think around the problem.
Secondly, after taking a peek at the documentation for the library you're using, the event is actually fired on document.body, so even if it did work, your event handler would never trigger.
Instead, inside componentDidMount somewhere in your application, you can listen to nv-enter by adding
document.body.addEventListener('nv-enter', function (event) {
// logic
});
Then, inside the callback function, hit a function that changes the state of the component, or whatever you want to do.
I recommend using React.createRef() and ref=this.elementRef to get the DOM element reference instead of ReactDOM.findDOMNode(this). This way you can get the reference to the DOM element as an instance variable.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class MenuItem extends Component {
constructor(props) {
super(props);
this.elementRef = React.createRef();
}
handleNVFocus = event => {
console.log('Focused: ' + this.props.menuItem.caption.toUpperCase());
}
componentDidMount() {
this.elementRef.addEventListener('nv-focus', this.handleNVFocus);
}
componentWillUnmount() {
this.elementRef.removeEventListener('nv-focus', this.handleNVFocus);
}
render() {
return (
<element ref={this.elementRef} />
)
}
}
export default MenuItem;
Here is a dannyjolie more detailed answer without need of component reference but using document.body reference.
First somewhere in your app, there is a component method that will create a new custom event and send it.
For example, your customer switch lang.
In this case, you can attach to the document body a new event :
setLang(newLang) {
// lang business logic here
// then throw a new custom event attached to the body :
document.body.dispatchEvent(new CustomEvent("my-set-lang", {detail: { newLang }}));
}
Once that done, you have another component that will need to listen to the lang switch event. For example, your customer is on a given product, and you will refresh the product having new lang as argument.
First add/remove event listener for your target component :
componentDidMount() {
document.body.addEventListener('my-set-lang', this.handleLangChange.bind(this));
}
componentWillUnmount() {
document.body.removeEventListener('my-set-lang', this.handleLangChange.bind(this));
}
then define your component my-set-langw handler
handleLangChange(event) {
console.log("lang has changed to", event.detail.newLang);
// your business logic here .. this.setState({...});
}

Resources