I'm writing an application in React and I'm unit testing this with Jest and Enzyme.
I have a very simple component that represents an input field, which contains the following code:
// 'Container' component definition.
class Container extends React.Component<containerProps, containerState> {
static defaultProps = { };
state = {
hasValue: false
};
constructor(props: containerProps) {
super(props);
// Bind all the event handlers for the component.
(this: any).onChange = this.onChange.bind(this);
}
onChange(event: MouseEvent) : void {
this.setState(
{
hasValue: (event.target: window.HTMLInputElement).value !== ''
}
);
// Prevent a default browser event from happening.
event.preventDefault();
}
createComponentProps(): componentProps {
return {
cssClass: this.createComponentCssClass()
};
}
// Create the CSS class to pass to the component.
createComponentCssClass(): string {
let className = '';
if (this.state.hasValue) { className = className !== '' ? className + 'value' : 'value'; }
if (this.props.errorMessage && this.props.errorMessage !== '') {
className = className !== '' ? className + ' error' : 'error';
}
// Return the CSS class to apply to the component.
return className;
}
// Renders the 'Container' component.
render(): React$Element<any> {
return (
<Component {...this.createComponentProps()} />
);
}
}
So, it's a fairly simple component.
Now, when the contents of the input field are changed, a state change occurs which forces a different CSS class to be applied to the component.
I can confirm that this is working since it's working in the browser as intended.
Now, I'm writing a unit test to verify that the className value is passed to the component using the following code:
it('Passes down the \'cssClass\' property to the \'Input Field\' Component.', () => {
// Act.
const wrapper = mount(
<InputFieldContainer primaryThemeColor="TEAL" accentThemeColor="PINK" useAccentColor={true} />
);
wrapper.find('input').simulate('change', { target: { value: 'value' }});
wrapper.update();
// Assert.
expect(wrapper.find(InputFieldComponent).props().cssClass).toEqual('value');
});
Thus, I'm rendering the component, I simulate a change event, and I check the CSS class property of the component, however, it's an empty string. It seems it didn't update regarding the state change (but only in unit tests).
Reading the state in Jest, using console.log(wrapper.state()) gives me a JSON object saying hasValue: true, so the state is updated, but even after calling wrapper.update(), the CSS class does not seem to be passed.
What am I missing here?
Kind regards
This seems to be an issue with Enzyme (https://github.com/airbnb/enzyme/issues/1153).
After updating the wrapper and your component should be in sync.
Related
Hi am using reactquill in my child component and i want to update my parent state when users type. currently i am doing it using onBlur() but that is not what the users want.
this is my child component.
public componentWillReceiveProps(newProps): void {
//console.log(newProps, "new props");
this.setState({
text: newProps.value
});
}
public setProps() {
//console.log("set props", this.state.text);
if(this.state.text === "<p><br></p>"){
this.props.onChange("");
} else {
this.props.onChange(this.state.text);
}
}
public handleChange(value) {
this.setState({ text: value });
//console.log("update props of parent", value);
//this.props.onChange(value);
}
public render() {
return (
<div>
<div className="text-editor" onBlur= {this.setProps}>
<ReactQuill value={this.state.text}
onChange={this.handleChange}
//onKeyPress={this.handleKeyDown}
//onKeyDown={this.handleKeyDown}
onBlur= {this.setProps}
modules={this.modules}
formats={this.formats}/>
</div>
</div>
);
}
and this i from my Parent Component calling the child;
public renderEditableAnswer = (cellInfo) => {
return (
<div>
<QnAAnswerInput
value={cellInfo.original.Answer}
onChange={data => this.updateQnAAnswer(data, cellInfo)}
/>
</div>
);
}
public updateQnAAnswer = (data, cellInfo) => {
let qnaItems = [...this.state.qnaItems];
let index;
if(cellInfo.original.Id != null){
index = _.findIndex(qnaItems,d => d.Id == cellInfo.original.Id);
} else {
index = _.findIndex(qnaItems,d => d.identifier == cellInfo.original.identifier);
}
if(this.getText(data) !== this.getText(cellInfo.original.Answer)){
let item = {
...qnaItems[index],
Answer: data,
};
qnaItems[index] = item;
this.setState({ qnaItems });
this.updateActionHistory(item,index);
}
}
this component is inside a ReactTable cell, hence the cellInfo. Note that i do have one functionality in the parent component that would add a new row to the table which needs to have an empty values for the child component. i noticed that without the WillReceiveProps method, my "Add New Empty Row" is not working.
In my current code, if i comment out the this.props.onChange(this.state.text); inside the handleChange method, typing inside the editor fires the componentWillReceiveProps (iterating through all my reacttable values, which is a lot) which renders a delay in typing a text. and this is not good.
is there anyway for me to update my parent state with onChange without having typing delays?
Use only componentDidMount() and componentDidUpdate() the other life cycle methods are bad practice.
You have a typing delay because of componentWillReceiveProps, never use it. I do not understand your code, there are no names and you have unnecessary code.
Instead of onBlur= {this.setProps} in div ,
call it in componentDidUpdate
componentDidUpdate = ( prevProps , prevState) =>{
if(prevState.editorHtml !== this.state.editorHtml )
this.setProps()
}
Do you have any better solution?
First of all, I'm really new into React, so forgive my lack of knowledge about the subject.
As far as I know, when you setState a new value, it renders again the view (or parts of it that needs re-render).
I've got something like this, and I would like to know if it's a good practice or not, how could I solve this kind of issues to improve, etc.
class MyComponent extends Component {
constructor(props) {
super(props)
this.state = {
key: value
}
this.functionRender = this.functionRender.bind(this)
this.changeValue = this.changeValue.bind(this)
}
functionRender = () => {
if(someParams !== null) {
return <AnotherComponent param={this.state.key} />
}
else {
return "<span>Loading</span>"
}
}
changeValue = (newValue) => {
this.setState({
key: newValue
})
}
render() {
return (<div>... {this.functionRender()} ... <span onClick={() => this.changeValue(otherValue)}>Click me</span></div>)
}
}
Another component
class AnotherComponent extends Component {
constructor (props) {
super(props)
}
render () {
return (
if (this.props.param === someOptions) {
return <div>Options 1</div>
} else {
return <div>Options 2</div>
}
)
}
}
The intention of the code is that when I click on the span it will change the key of the state, and then the component <AnotherComponent /> should change because of its parameter.
I assured that when I make the setState, on the callback I throw a console log with the new value, and it's setted correctly, but the AnotherComponent doesn't updates, because depending on the param given it shows one thing or another.
Maybe I need to use some lifecycle of the MyComponent?
Edit
I found that the param that AnotherComponent is receiving it does not changes, it's always the same one.
I would suggest that you'll first test it in the parent using a simple console.log on your changeValue function:
changeValue = (newValue) => {
console.log('newValue before', newValue);
this.setState({
key: newValue
}, ()=> console.log('newValue after', this.state.key))
}
setState can accept a callback that will be invoked after the state actually changed (remember that setState is async).
Since we can't see the entire component it's hard to understand what actually goes on there.
I suspect that the newValue parameter is always the same but i can't be sure.
It seems like you're missing the props in AnotherComponent's constructor. it should be:
constructor (props) {
super(props) // here
}
Try replacing the if statement with:
{this.props.param === someOptions? <div>Options 1</div>: <div>Options 2</div>}
also add this function to see if the new props actually get to the component:
componentWillReceiveProps(newProps){
console.log(newProps);
}
and check for the type of param and someOptions since you're (rightfully) using the === comparison.
First, fat arrow ( => ) autobind methods so you do not need to bind it in the constructor, second re-renders occur if you change the key of the component.
Ref: https://reactjs.org/docs/lists-and-keys.html#keys
I need to get data from DB depending on a search string value. Therefore I'm using an input field. The search string is stored as a state value.
The data for the component comes from a container (using npm meteor/react-meteor-data).
Now my problem is, how do I get the search string into the container to set the parameter for the publication?
container/example.js
export default createContainer((prop) => {
Meteor.subscribe('images', searchString) // How to get searchString?
return { files: Images.find({}).fetch() }
}, Example)
component/example.jsx
class Example extends Component {
constructor(props) {
super(props)
this.state = {
searchString: ''
}
}
searchImage(event) {
const searchString = event.target.value
this.setState({ searchString })
}
render() {
return (<Input onChange={ this.searchImage.bind(this) }/>)
}
}
export default Example
publication
Meteor.publish('images', function(search) {
return Images.find({ title: search }).cursor
})
Maybe you can create two different components: a parent and a child, and you can wrap child component with createContainer HOC like the following
childComponent.js
const Example = (props) => {
return <Input onChange={props.searchImage}/>
}
export default createContainer(({searchString}) => {
Meteor.subscribe('images', searchString)
return { files: Images.find({}).fetch() }
}, Example)
parentComponent.js
class ExampleWrapper extends Component {
constructor(props) {
super(props)
this.state = {
searchString: ''
}
}
searchImage = (event) => {
const searchString = event.target.value
this.setState({ searchString })
} // instead of binding this, you can also use arrow function that
// takes care of binding
render() {
return (<Example searchImage={this.searchImage} searchString={this.state.searchString} {...this.props} />)
}
}
export default ExampleWrapper
The idea is, since createContainer is a higher order component, it doesn't have access to the props of any component wrapped by it.
What we need to do is, passing the value of searchString from a parent component.
The way to do is the following:
ExampleWrapper has a state called searchString and Example component has a prop called searchString. We can set the value of searchString prop to state.searchString.
Since the default export corresponds to createContainer({..some logic…}, Example}), createContainer can make use of prop called searchString.
In order to change the value of state.searchString we also passed searchImage function as a prop to Example component. Whenever there is a change event, onChange triggers searchImage function that updates the value of state.searchString. And eventually, the minute the value of state.searchString changes searchString prop’s value changes thus your subscription result also changes
onChange={ (e)=> {this.setState({ searchString: $(e.target).val() }) } }
This is how we assign values to our internal state properties :)
EDIT: I appear to have misunderstood the question...
I have a stateful Key component that represents a Key in a Keyboard like so:
import React from 'react';
class Key extends React.Component {
constructor(props) {
super(props);
this.state = {
id: props.id,
customClass: props.customClass,
value: props.value,
onAction: props.onAction,
active: false
};
this.handleAction = this.handleAction.bind(this);
this.resetActiveState = this.resetActiveState.bind(this);
}
handleAction(e) {
e.preventDefault();
this.setState ({
active: true
});
this.state.onAction(this.state.value);
//remove key pressed effect after 150 ms by resetting active state
_.delay(() => this.resetActiveState() , 150);
}
resetActiveState() {
this.setState ({
active: false
});
}
render() {
//conditionalProps is empty when active is false.
let conditionalProps = {};
let className = `default-btn ${this.state.customClass}`;
let displayValue = this.state.value;
//enable css attribute selector
if (this.state.active){
conditionalProps['data-active'] = '';
}
return (
<button id={this.state.id} key={this.state.id} className={className}
data-value={this.state.value} {...conditionalProps} onTouchStart={this.handleAction}>
{displayValue}
</button>
);
}
}
Key.defaultProps = {
customClass: '',
onAction: (val) => {}
};
export default Key;
onTouchStart is used to detect a touch event.
onTouchStart handler changes active state to true.
Component re-renders with the appropriate css to give key clicked
effect.
After 150ms, active state is set to false using resetActiveState().
Component re-renders without the key clicked effect css.
conditionalProps attribute is used to conditionally add css styles (using css attribute selector) to achieve 'key pressed' look in the rendered component.
This works as expected but I was wondering if it would be possible to refactor the component so I can extract the logic to maybe a parent component which I can then extend using the Key component.
This would be a perfect use case for a Higher Order Component.
This will allow you to abstract much of the functionality and pass it down to stateless components as props.
The React official docs do a great job of explaining how to use HOCs.
I am working on a wrapper component for smoothly loading images in React. I use enzyme with mocha, chai and sinon to unit test my component. In the test here, I am trying to test that the:
component's state is updated when the image has loaded
the onLoad instance method on the component was called
const wrapper = shallow( );
const onLoad = wrapper.find('img').props().onLoad;
const onLoadSpy = sinon.spy(onLoad); wrapper.update();
const status = wrapper.state().status;
expect(onLoadSpy).to.have.been.called;
expect(status).to.equal('LOADED');
I find that neither the update to the state is reflected by enzyme or the call count of the onLoad spy is updated. This is the corresponding code for the test:
export default class Image extends Component {
constructor(props) {
super(props);
if (props.src != null && typeof props.src === 'string') {
this.state = {
status: LOADING,
};
} else {
this.state = {
status: PENDING,
};
}
this.onLoad = this.onLoad.bind(this);
}
onLoad() {
this.setState({
status: LOADED,
});
}
render() {
//lots of code above the part we care about
const defaultImageStyle = style({
opacity: 0,
transisition: 'opacity 150ms ease',
});
const loadedImageStyle = style({
opacity: 1,
});
let imageStyle = defaultImageStyle;
if (this.state.status === LOADED) {
imageStyle = merge(defaultImageStyle, loadedImageStyle);
} else {
imageStyle = defaultImageStyle;
}
let image;
if (alt != null) {
image = (<img
className={imageStyle}
src={src}
width={width}
height={height}
alt={alt}
onLoad={this.onLoad}
/>);
} else {
image = (<img
className={imageStyle}
src={src}
width={width}
height={height}
role="presentation"
onLoad={this.onLoad}
/>);
}
let statusIndicator = null;
if (this.state.status === LOADING) {
statusIndicator = (<div className={loadingStyle}></div>);
}
return (<div className={wrapperStyle}>
{statusIndicator}
{image}
</div>);
}}
To take a look at the full code for better context:
the source here
the test here
One can test this without relying on sinon. By expecting that the onLoad and onFire event listeners are invoked,the tests check if the img fires the load and error events.
Instead,simulate img's events using enzyme and check that the appropriate state transition occurs:
it('has a state of LOADED if a good src prop is supplied', () => {
const wrapper = shallow(<Image
src="anyString.jpg"
width={300}
height={300}
/>);
const img = wrapper.find('img');
img.simulate('load');
const status = wrapper.state().status;
expect(status).to.equal('LOADED');
});
This also eliminates the need to mount the component. The updated tests can be found here.
The main issue I see with this approach is that the state is an internal thing, and not something that should be known outside the component. Now you are leaking state information ("status" in this case) out into the tests.
Doing this means you are not doing "black-box testing", which is the most valuable type of tests. You are leaking the implementation details of the component. In other words "Encapsulation" should be highly considered.
There are perhaps better ways to test this. You could for instance export a presentational component as well, which takes the parts of the state you need to test as props. Or look for an element that would be rendered when status is "LOADED" with enzyme find method.