How to update a child component's props in a test case? - reactjs

I have a function that should get invoked when a prop (newItems) changes:
componentDidUpdate(prevProps) {
const { title } = this.state;
const { newItems } = this.props;
const { newItems: prevNewItems } = prevProps;
if (prevNewItems !== newItems) {
this.updateTitle(title, newItems); // testing if this method gets called
}
}
The problem on the test below is that componentDidUpdate lifecycle hook doesn't register a new prop after I call setProps, thus the spy receives "0 number of calls"
it('calls updateTitle when newItems changes', () => {
const wrapper = mount(
<Provider store={store}>
<MyComponent {...props} newItems={0}/>
</Provider>,
);
const MyComponentWrapper = wrapper.find('MyComponent');
const spy = jest.spyOn(MyComponentWrapper.instance(), 'updateTitle');
wrapper.setProps({
children: <MyComponent {...props} newItems={1} />
});
wrapper.update();
expect(spy).toHaveBeenCalled(); // Received number of calls: 0 (should be 1)
});
How can I update props on a child component (MyComponent) that is wrapped in a Provider?

You can update the props of the child component (MyComponent) by passing new props to the wrapper.setProps() method. Instead of passing children property, pass the updated newItems property:
wrapper.setProps({
newItems: 1
});
You can then call wrapper.update() to re-render the component with the updated props.
After this, the componentDidUpdate lifecycle method should be triggered, and the updateTitle method should be called with the updated newItems prop.

Related

Why is JEST testing giving "Could not find "store" error when call setState?

I'm trying to test this:
(I need to confirm that when selectedDevice is called with DESKTOP prop, it calls openModal and that method sets the state of modalOpen to true)
openModal = () => {
this.setState({ modalOpen: true });
};
selectedDevice = () => {
const { device } = this.props;
const isMobile = device === MOBILE;
if (isMobile) {
this.closeWindow();
} else {
this.openModal();
}
};
and I'm testing like this (with JEST)
test('should openModal be called', () => {
const wrapper = mount(<Component
{...sampleProps}
deviceType={DESKTOP}
/>);
const selectedDevice = jest.spyOn(wrapper.instance(), 'selectedDevice');
selectedDevice();
expect(wrapper.state().modalActivated).toEqual(true);
});
Apparently, it seems to be reaching the openModal method in my component. However, I'm getting this error:
Invariant Violation: Could not find "store" in either the context or props of "Connect(Styled(Component))". Either wrap the root component in a <Provider>, or explicitly pass "store" as a prop to "Connect(Styled(Component))".
36 |
37 | openModal = () => {
> 38 | this.setState({ modalOpen: true });
| ^
I saw another solutions to that error message, but nothing seems to work for this specific error.
I already tried wrapping the component inside a Provider component with no luck.
You're getting the error because you're using a component which is wrapped inside of the connect HOC from react-redux and the connect HOC needs the redux Provider component with a state.
Your code should be something like this:
test("should openModal be called", () => {
const wrapper = mount(
<Provider store={CreateANewStoreForTest}>
<Component {...sampleProps} deviceType={DESKTOP} />
</Provider>
);
const selectedDevice = jest.spyOn(wrapper.instance(), "selectedDevice");
selectedDevice();
expect(wrapper.state().modalActivated).toEqual(true);
});
and for data on how to create a store read the DOC here

React.memo issue with Redux

I have two components.
function Parent(props){
const handleClick = () => {
console.log(props.stateA);
};
return <div><Child text={stateB} handleClick={handleClick} /></div>
}
const mapStateToProps = (state) => {
return {
stateA: state.stateA // stateA will be changed somewhere else
stateB: state.stateB
}
};
export default connect(mapStateToProps)(Parent);
function Child(props) {
return <div onClick={props.handleClick}>{props.text}</div>
}
export default React.memo(Child,(prev, next) => {
return prev.text === next.text
});
My problem is when stateA is changed somewhere, clicking on Child will log the previous stateA. I can't access the latest stateA.
You can see, I don't want to Child re-render when stateA changes,it should re-render only when stateB changed. But I want to access the latest stateA in Parent when clicking on Child.
Is there any method to solve this problem?
If the Parent component is a functional component then you can use like this
const [valueA, setValueA] = useState('')
useEffect(() => {
setValueA(props.stateA)
},[props.stateA])
console.log(valueA) // latest Value of stateA
return <div><Child text={stateB} handleClick={handleClick} /></div>
I hope it'll work for you.
You should be able to access props.stateA no problem
const handleClick = () => {
console.log(props.stateA);
};
because you accessing parent's props in handleClick. So if props.stateA is stale then the logical conclusion is the parent doesn't receive the latest props. Can we see how you update props/state?
The problem you are experiencing has nothing to do with Redux.
The Parent component passes 2 props to the child: the text which is changed when needed and handleClick which is changed each render of the Parent component - a new function is created each time.
But the React.memo is checking only the text prop, so the child receives a stale handleClick quite often.
The correct solution is to wrap the handleClick with useCallback and check all props in React.memo (react does this by default).
function Parent(props){
const handleClick = useCallback(() => {
console.log(props.stateA);
}, []);
return <div><Child text={stateB} handleClick={handleClick} /></div>
}
const mapStateToProps = (state) => {
return {
stateA: state.stateA // stateA will be changed somewhere else
stateB: state.stateB
}
};
export default connect(mapStateToProps)(Parent);
function Child(props) {
return <div onClick={props.handleClick}>{props.text}</div>
}
export default React.memo(Child);
You can keep a ref to stateA so it is what is logged when you call handleClick. useRef ensures that the last value is used.
function Parent(props){
const stateARef = useRef(props.stateA);
useEffect(() => {
stateARef.current = props.stateA;
}, [props.stateA])
const handleClick = () => {
console.log(stateARef.current);
};
return <div><Child text={stateB} handleClick={handleClick} /></div>
}

Using enzyme, How to find a child component in a component react if they are result of function return

I'm using jest/enzyme and want to check existence child elements of React component
if i have function as component child
const children = () => (
<>
<div>...</div>
<div>...</div>
</>
)
return <Component>{children}</Component>;
why i can't do like this
test('Should render div', () => {
wrapper = shallow(<MyComponent />);
const component = wrapper.find(Component);
expect(component.exists()).toBe(true); //return true
const children = wrapper.find('div')
expect(children.exists()).toBe(false); //return false
});
Your children function is basically a render prop and shallow doesn't render it. You can however trigger the rendering by calling it as a function like
shallow(
shallow(<MyComponent />)
.find(Component)
.prop('children')()
)
So your test will look like
test('Should render div', () => {
wrapper = shallow(<MyComponent />);
const component = wrapper.find(Component);
expect(component.exists()).toBe(true); //return true
const renderProp = shallow(component.prop('children')());
const children = renderProp.find('div');
expect(children.exists()).toBe(true);
});

How to set a property on a child component with React-Enzyme

Given the following simple components:
function ValueInput(props) {
const [val, setVal] = useState(props.value);
function onChange (val) {
setVal(val);
props.onValueChanged(val);
}
return <input value={val} onChange={onChange}/>;
}
function MyComponent(props) {
const [val, setVal] = useState(props.value);
function onValueChanged (val) {
setVal(val);
}
return (
<div>
<div>{val}</div>
<ValueInput value={val} onValueChanged={onValueChanged}/>
</div>
);
}
I'm mounting them in order to test them with Enzyme and Jest:
const component = mount(<MyComponent value={42}/>);
const inputEl = component.find('input');
How do change the value of the inner component in order to that any change to ValueInput is reflected on MyComponent? I'm trying with the following code, but it doesn't work:
console.log(component.debug());
valueInputEl.setProps({value: 24});
// component.update();
console.log(component.debug());
And I get the following error:
ReactWrapper::setProps() can only be called on the root
You could shallow mount your MyComponent and then test it by triggering the onValueChanged prop of your ValueInput child component and test the changes in your state by checking the value prop of the child component.
test('That the parent element is changed when the child component is changed', () => {
const component = shallow(<MyComponent value={42} />);
component.find(ValueInput).prop('onValueChanged')(24);
expect(component.find(ValueInput).prop('value')).toBe(24);
console.log(component.debug());
});
And test for the behaviour of the ValueInput's onChange methods in its own component tests so that it acts more like a unit test.

How enzyme testing the render

How write a test for checking render element or not? For example, component like this, which render depends on existing list of cards:
class ButtonMore extends Component {
render() {
if (!this.props.listCards.length) {
return null;
}
return (
<button onClick={this.props.onClick} className="buttons button-more">
More
</button>
);
}
}
How would check render depends to props?
function setup() {
const props = {
listCards: [1, 2]
};
const wrapper = shallow(
<Provider store={store}>
<ButtonMore {...props} />
</Provider>
);
return {
props,
wrapper
};
}
describe("ButtonMore component", () => {
const { wrapper } = setup();
it("should render button if cards length more then 0", () => {
expect(wrapper.prop("listCards").length).toBe(2); // that's ok
expect(wrapper.find("button").length).toBe(1); // received 0, not 1
});
});
Unfortunately, i don't finded solution in the enzyme documentation.
First of all: "!this.props.listCards.length" checks only that listCards has the property length. You have to ensure that listCards is an array and that the length is more than 0.
Now ensure that your wrapper contains the right data: you can do it with console.log(wrapper.debug());
At the end.. If you don't need the store you can even not use the Provider. Render just ButtonMore and pass onClick and listCards as props.

Resources