React + Jest check component contains another component - reactjs

I have DetailPanel component which contains Detail componet with title prop.
import React from 'react';
import Detail from './Detail';
import InfoTable from './InfoTable';
import EdgeIcon from './EdgeIcon';
import styles from './DetailsPanel.css';
export default class DetailsPanel extends React.PureComponent {
renderTitle() {
const { selectedEdge } = this.props;
if (selectedEdge) {
const sourceNode = 'sourceNode'
const targetNode = 'targetNode'
return (
<span>
{sourceNode}
<EdgeIcon className={styles.arrowIcon} />
{targetNode}
</span>
);
}
return 'default title';
}
render() {
return (
<Detail title={this.renderTitle()} />
);
}
}
I tying to check that if selectedEdge is true Detail title contains EdgeIcon component
test('Detail should contain EdgeIcon if there is selected edge', () => {
const selectedEdge = { source: 1, target: 2 };
const detailsPanel = shallow(
<DetailsPanel
{...props}
selectedEdge={selectedEdge}
/>);
expect(detailsPanel.contains('EdgeIcon')).toBeTruthy();
});
But test fails, because detailsPanel returns false

You can try to use mount instead of shallow if you want to check deep children. For example:
test('Detail should contain EdgeIcon if there is selected edge', () => {
const selectedEdge = { source: 1, target: 2 };
const detailsPanel = mount(
<DetailsPanel
{...props}
selectedEdge={selectedEdge}
/>);
expect(detailsPanel.contains('EdgeIcon')).toBeTruthy();
});

First, when testing a component, you shouldn't test its children. Each children should have its own test.
But if you really need to look into your component's children use mount instead of shallow.
Reference: http://airbnb.io/enzyme/docs/api/ReactWrapper/mount.html

Related

React - Test separate parent component (without redux)

I wanna test parent component, but I want to do this without redux. I have problem because I've got error:
Invariant Violation: Could not find "store" in either the context or props of "Connect(MarkerList)". Either wrap the root component in a , or explicitly pass "store" as a prop to "Connect(MarkerList)".
My parent component:
export class Panel extends React.Component {
constructor(props) {
}
componentDidUpdate(prevProps) {
...
}
handleCheckBox = event => {
...
};
switchPanelStatus = bool => {
...
};
render() {
...
}
}
const mapDispatchToProps = {
isPanelSelect
};
export const PanelComponent = connect(
null,
mapDispatchToProps
)(Panel);
My child component:
export class MarkerList extends Component {
constructor(props) {
...
};
}
componentDidMount() {
...
}
componentDidUpdate() {
...
}
onSelect = (marker, id) => {
...
}
render() {
...
}
}
const mapStateToProps = state => ({
...
});
const mapDispatchToProps = {
...
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MarkerList);
Panel test file:
import '#testing-library/jest-dom'
import "#testing-library/react"
import React from 'react'
import {render, fireEvent, screen} from '#testing-library/react'
import {Panel} from '../Panel';
test("test1", async () => {
const isPanelSelect = jest.fn();
const location = {
pathname: "/createMarker"
}
const {getByText} = render( <Panel isPanelSelect={isPanelSelect} location={location} />)
})
I've tried set store as props to Panel component or wrap It via Provider in my test file but It doesn't help me.
react-redux doesn't work without the store. You can either provide it by the context or props (usually in tests). You can provide a mock version in the test. The main problem is that both components require Redux. You have to manually forward the context to the children if it's provided as prop. The alternative solution is to mount your component within a Redux aware tree:
import { Provider } from "react-redux";
test("test1", async () => {
const { getByText } = render(
<Provider store={createFakeStore()}>
<Panel isPanelSelect={isPanelSelect} location={location} />
</Provider>
);
});

Define a functional component inside storybook preview

I have a custom modal component as functional component and in typescript. This modal component exposes api's through context providers and to access them, I'm using useContext hook.
const { openModal, closeModal } = useContext(ModalContext);
Example code on how I use this api's:
const TestComponent = () => {
const { openModal, closeModal } = useContext(ModalContext);
const modalProps = {}; //define some props
const open = () => {
openModal({...modalProps});
}
return (
<div>
<Button onClick={open}>Open Modal</Button>
</div>
)
}
And I wrap the component inside my ModalManager
<ModalManager>
<TestComponent />
</ModalManager>
This example works absolutely fine in my Modal.stories.tsx
Problem:
But this doesn't work inside my Modal.mdx. It says I cannot access react hooks outside functional component. So, I need to define a TestComponent like component to access my modal api's from context. How to define it and where to define it so that below code for preview works?
import {
Props, Preview, Meta
} from '#storybook/addon-docs/blocks';
<Meta title='Modal' />
<Preview
isExpanded
mdxSource={`
/* source of the component like in stories.tsx */
`}
>
<ModalManager><TestComponent /></ModalManager>
</Preview>
I'm not sure if this is a hack or the only way. I created the TestComponent in different tsx file and then imported it in mdx. It worked.
You may have a utility HOC to render it inside a MDX file as below
HOCComp.tsx in some Utils folder
import React, { FunctionComponent, PropsWithChildren } from 'react';
export interface HOCCompProps {
render(): React.ReactElement;
}
const HOCComp: FunctionComponent<HOCCompProps> = (props: PropsWithChildren<HOCCompProps>) => {
const { render } = props;
return render();
};
export default HOCComp;
Inside MDX File
import HOCComp from './HOC';
<HOCComp render={()=> {
function HOCImpl(){
const [count,setCount] = React.useState(180);
React.useEffect(() => {
const intId = setInterval(() => {
const newCount = count+1;
setCount(newCount);
},1000)
return () => {
clearInterval(intId);
}
})
return <Text>{count}</Text>
}
return <HOCImpl />
}}
/>

How to test HOC with enzyme, chai

I have a HOC function that receives a React component and returns that react component with two new method properties (handleBack & moveitOnTop) like so:
import React, { Component } from "react";
import classNames from "classnames";
export default WrappedComponent => {
return class extends Component {
constructor(props) {
super(props);
this.moveitOnTop = this.moveitOnTop.bind(this);
this.handleBack = this.handleBack.bind(this);
this.state = {
isSearchActive: false
};
}
moveitOnTop(flag) {
this.setState({ isSearchActive: flag });
window.scrollTo(0, -100);
}
handleBack() {
this.setState({ isSearchActive: false });
if (document.body.classList.contains("lock-position")) {
document.body.classList.remove("lock-position");
}
}
render() {
const props = {
...this.props,
isSearchActive: this.state.isSearchActive,
moveitOnTop: this.moveitOnTop,
goBack: this.handleBack
};
const classes = classNames({ "c-ftsOnTop": this.state.isSearchActive });
return (
<div className={classes}>
<WrappedComponent {...props} />
</div>
);
}
};
};
The component:
//import fireAnalytics
import { fireAnalytics } from "#modules/js-utils/lib";
class MyComponent extender Component{
constructor(){
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick(e) {
// calling analytics function by passing vals
fireAnalytics({
event: "GAEvent",
category: "",
action: `Clicked on the ${e.target.id} input`,
label: "Click"
});
// I CALL THE HOC PROPERTY
this.props.moveitOnTop(true);
// I CALL THE HOC PROPERTY
this.props.handleBack();
}
render(){
return(
<div className="emailBlock">
<input type="text" onClick={handleClick} />
<Button className="submit">Submit</Button>
</div>
)
}
}
// export HOC component
export default hoc(MyComponent);
// export just MyComponent
export {MyComponent};
I want to test the HOC:
I need to check that class .c-ftsOnTop exists
I need to check onClick function that calls this.props.handleBack & `this.props.moveitOnTop'
I need to check if className emailBlock exists.
The test that I tried, but fails:
import { mount, shallow } from 'enzyme';
import sinon from 'sinon';
import React from 'react';
import { expect } from 'chai';
import hoc from '....';
import {MyComponent} from '...';
import MyComponent from '....';
it('renders component', () => {
const props = {}
const HocComponent = hoc(MyComponent);
const wrapper = mount(
<HocComponent {...props} />
);
console.log('wrapper:', wrapper);
expect(wrapper.find('.c-ftsOnTop')).to.have.lengthOf(1);
expect(wrapper.hasClass('c-fts-input-container')).toEqual(true);
})
Error
AssertionError: expected {} to have a length of 1 but got 0
console.log: wrapper: ReactWrapper {}
Can anybody help me on how to render the HOC?
Here is a working test:
import { mount } from 'enzyme';
import React from 'react';
import WrappedMyComponent from './MyComponent';
it('renders component', () => {
const props = {}
const moveitOnTopSpy = jest.spyOn(WrappedMyComponent.prototype, 'moveitOnTop');
const handleBackSpy = jest.spyOn(WrappedMyComponent.prototype, 'handleBack');
const wrapper = mount(
<WrappedMyComponent {...props} />
);
// 1. I need to check that class .c-ftsOnTop exists
wrapper.setState({ isSearchActive: true }); // <= set isSearchActive to true so .c-ftsOnTop is added
expect(wrapper.find('.c-ftsOnTop')).toHaveLength(1); // Success!
// 2. I need to check onClick function that calls this.props.handleBack & `this.props.moveitOnTop'
window.scrollTo = jest.fn(); // mock window.scrollTo
wrapper.find('input').props().onClick();
expect(moveitOnTopSpy).toHaveBeenCalled(); // Success!
expect(window.scrollTo).toHaveBeenCalledWith(0, -100); // Success!
expect(handleBackSpy).toHaveBeenCalled(); // Success!
// 3. I need to check if className emailBlock exists
expect(wrapper.find('.emailBlock')).toHaveLength(1); // Success!
})
Details
.c-ftsOnTop is only added when isSearchActive is true so just set the state of the component so the class is added.
If you create your spies on the prototype methods for moveitOnTop and handleBack, then when the hoc creates its instance methods by binding them to this in the constructor, the instance methods will be bound to your spies.
window.scrollTo logs an error to the console by default in jsdom so you can mock it to avoid that error message and to verify that it was called with the expected arguments.
Note that the above test requires the following typos to be fixed in MyComponent:
extender should be extends
constructor should take a props argument
onClick should be bound to this.handleClick instead of just handleClick
handleClick should call this.props.goBack() instead of this.props.handleBack()
(I'm guessing MyComponent was just thrown together as an example of the actual component)

Instance returns NULL for connected component on mount in Jest

I am relatively new to react and apologies for any terms that dont fit the jargon.
I am trying to test a prototype method of a connected component which consists of a ref variable, as below:
app.js
export class Dashboard extends React.Component { // Exporting here as well
constructor(props) {
this.uploadFile = React.createRef();
this.uploadJSON = this.uploadJSON.bind(this);
}
uploadJSON () {
//Function that I am trying to test
//Conditions based on this.uploadFile
}
render() {
return (
<div className="dashboard wrapper m-padding">
<div className="dashboard-header clearfix">
<input
type="file"
ref={this.uploadFile}
webkitdirectory="true"
mozdirectory="true"
hidden
onChange={this.uploadJSON}
onClick={this.someOtherFn}
/>
</div>
<SensorStatList />
<GraphList />
</div>
);
}
const mapStateToProps = state => ({
//state
});
const mapDispatchToProps = dispatch => ({
//actions
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Dashboard);
}
Here, SensorStatList and GraphList are functional components, also connected using redux.
After some research I have my test file to this level:
app.test.js
import { Dashboard } from '../Dashboard';
import { Provider } from 'react-redux';
import configureStore from '../../../store/store';
const store = configureStore();
export const CustomProvider = ({ children }) => {
return (
<Provider store={store}>
{children}
</Provider>
);
};
describe("Dashboard", () => {
let uploadJSONSpy = null;
function mountSetup () {
const wrapper = mount(
<CustomProvider>
<Dashboard />
</CustomProvider>
);
return {
wrapper
};
}
it("should read the file", () => {
const { wrapper } = mountSetup();
let DashboardWrapper = wrapper;
let instance = DashboardWrapper.instance();
console.log(instance.ref('uploadFile')) // TypeError: Cannot read property 'ref' of null
})
Can someone help me understand why this error
console.log(instance.ref('uploadFile'))
// TypeError: Cannot read property 'ref' of null
pops up? Also, if this approach is fine? If not, what are the better options?
wrapper is CustomProvider which has no instance, and ref is supposed to work with deprecated string refs.
In case a ref should be accessed on Dashboard, it can be:
wrapper.find(Dashboard).first().instance().uploadFile.current
In case input wrapper should be accessed, it can be:
wrapper.find('input').first()

How to mock a wrapper component with Jest?

Assume the component is working, I have just removed useless code. In my component to test, I have this:
import {Container} from "flux/utils";
import Dialog from "material-ui/Dialog";
import React from "react";
...
class Component extends React.Component {
state;
static calculateState() {
return {
...
};
}
static getStores() {
return [...];
}
...
render() {
var actions = [...];
return <Dialog
actions={actions}
open={this.state.open}
title="foo"
...
>
<form ...>
{/* My inputs and their values are according to the state. */}
</form>
</Dialog>;
}
}
const MyComponent = Container.create(Component);
export default MyComponent;
In the UI, the inputs and their values are working. However, when I use Jest, it is not.
jest.mock('react-dom');
jest.mock('material-ui/Dialog', () => 'Dialog');
import Dispatcher from "../../dispatcher/Dispatcher";
import MyComponent from "../MyComponent";
import React from "react";
import Renderer from "react-test-renderer";
describe('MyComponent', () => {
it('creates', () => {
const component = Renderer.create(<MyComponent/>);
let tree = component.toJSON();
expect(tree).toMatchSnapshot();
Dispatcher.dispatch({
action: 'myComponent/open'
});
tree = component.toJSON();
expect(tree).toMatchSnapshot();
tree.children[0].children[1].children[0].props.onChange(undefined, undefined, 'john');
tree = component.toJSON();
expect(tree).toMatchSnapshot();
});
});
The first snapshot is good. The second snapshot is good too, the open property is changing. However, when I trigger the onChange of an input, the component state is updating, all good... but when I do the last snapshot, the children are still the same, event if the input values are different (and the state is well updated, confirmed).
I know this is due to the Dialog. If I remove it from the component class, the component works as expected... So how should I declare the mock?
Here is my last try:
jest.mock('material-ui/Dialog', () => {
var React = require("react");
return class extends React.Component {
shouldComponentUpdate() {
return true;
}
render() {
var {children, ...rest} = this.props;
return children;
}
}
});
The children are still the same :(

Resources