Im tryng to test a class component that needs some props to render the html, not sure why is not working. Im starting tests with jest and react now, so im not really experienced. What im missing?
Test component
const mockStore = configureMockStore();
const store = mockStore({});
describe("Pokemon detail", () => {
const mockPokemon = {
sprites: {
back_default:
"https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/132.png"
},
name: "ditto",
abilities: [
{
ability: {
name: "imposter",
url: 'https://pokeapi.co/api/v2/ability/150/"'
}
},
{
ability: {
name: "imposter",
url: "https://pokeapi.co/api/v2/ability/150/"
}
}
],
types: [
{
type: {
name: "normal"
}
}
]
}
const wrapper = mount(
<Provider store={store}>
<PokemonDetail pokemon={mockPokemon} />
</Provider>
);
expect(wrapper).toMatchSnapshot();
});
error message is this
TypeError: Cannot read property 'sprites' of undefined
render() {
<h1>oi</h1>
if (this.props.pokemon.sprites) {
^
const habilidades = this.props.pokemon.abilities.map(element => {
return <li key={element.ability.url}>{element.ability.name}</li>;
});
tested component
class PokemonDetail extends React.Component {
render() {
if (this.props.pokemon.sprites) {
const habilidades = this.props.pokemon.abilities.map(element => {
return <li key={element.ability.url}>{element.ability.name}</li>;
});
const tipos = this.props.pokemon.types.map(element => {
return <li key={element.type.url}>{element.type.name}</li>;
});
return (
<div className="ui card">
<div className="image">
<img src={this.props.pokemon.sprites.back_default} />
</div>
<div className="content">
<a className="header">{this.props.pokemon.name}</a>
</div>
<button
onClick={() => this.props.favoritePokemon(this.props.pokemon.name)}
className="ui button primary"
>
Add
</button>
</div>
);
}
return <div />;
}
}
const mapStateToProps = state => {
return { pokemon: state.pokemon };
};
export default connect(mapStateToProps,{favoritePokemon})(PokemonDetail);
Not sure why the mockPokemon with fake data is not taking place on the props object....seems right to me
Since PokemonDetail is a Redux connected component, and pokemon prop is handled by Redux, the prop in <PokemonDetail pokemon={mockPokemon} /> will be overridden by Redux.
Instead, the object should be passed as a part of Redux store:
const store = mockStore({ pokemon: mockPokemon });
const wrapper = mount(
<Provider store={store}>
<PokemonDetail />
</Provider>
);
Related
I am new to Jest. Not sure if I should check for the modal or if method is rendered. How would I do so?
I have the following class component:
export class HelloWorld extends Component<Props> {
renderModal(name: string, email: string) {
return (
<Modal>
<div>
<p>Hello World</p>
</div>
</ Modal>
)
}
render() {
const { auth: { isUserVerified, name, email }}
<div>
{isUserVerified || this.renderModal(name, email)}
</div>
}
}
And in Jest
describe('renderModal', () => {
test.('it renders renderModal if isUserVerified is false', () => {
const props = {
...propsFixture
isUserVerified: false;
}
const component = shallow(<HellowWorld {...props} />);
expect(component.contains(<Modal />)).toBe(true);
})
})
I have a pure React-Redux application and it is working as expected.
The App.js
import React, { useEffect } from "react";
import { useDispatch } from "react-redux";
import { Router, Route, Switch, Redirect } from "react-router-dom";
import history from "../history";
import LandingPage from "./home/LandingPage";
import { displayModules } from "../actions";
import Cart from "./home/Cart";
const App = () => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(displayModules());
}, [dispatch]);
return (
<Router history={history}>
<Switch>
<Route path="/" exact component={LandingPage}></Route>
<Route path="/cart" exact component={Cart}></Route>
<Route render={() => <Redirect to="/" />} />
</Switch>
</Router>
);
};
export default App;
The LandingPage has a nested component called Tile.
import React from "react";
import { useSelector, useDispatch } from "react-redux";
import Tile from "../common/Tile";
import { addItemToCart, displayCartContents } from "../../actions";
import "./LandingPage.css";
const LandingPage = () => {
const modules = useSelector(state => state.data.modules);
const cart = useSelector(state => state.data.cart);
const dispatch = useDispatch();
const addToCart = item => {
dispatch(addItemToCart(item));
};
return (
<div className="app">
<div className="header">
<div className="text">Insurance modules</div>
<i
className="shopping cart icon"
onClick={() => {
dispatch(displayCartContents());
}}
>
<span className="badge">{cart.length}</span>
</i>
</div>
<div className="body">
{modules.map(module => (
<Tile key={module.id} module={module} addToCart={addToCart}></Tile>
))}
</div>
</div>
);
};
export default LandingPage;
Tile.js has a button which I want to test.
import React, { useState } from "react";
import "./Tile.css";
const Tile = props => {
const { module, addToCart } = props;
const [coverage, setCoverage] = useState(parseInt(module.coverageMax - module.coverageMin) / 2);
const [price, setPrice] = useState((coverage * module.risk) / 100);
return (
<div className="tile">
<div className="tile-description">
<div>
<i className={`${module.icon} icon`}></i>
</div>
<div className="tile-name">{module.name}</div>
<div className="tile-risk">Risk(%): {module.risk}</div>
</div>
<div className="tile-footer">
<div className="tile-range">
<div className="field-label">
Select Coverage: <span className="coverage-display">{coverage}</span>
</div>
<div className="slidecontainer">
<span className="slider-step">{module.coverageMin}</span>
<input
type="range"
min={module.coverageMin}
max={module.coverageMax}
value={coverage}
className="slider"
onChange={e => {
setCoverage(e.target.value);
setPrice((e.target.value * module.risk) / 100);
}}
></input>
<span className="slider-step">{module.coverageMax}</span>
</div>
</div>
<div>
PRICE at this Coverage:<span className="tile-price">{price}</span>
</div>
<button
className="tile-button"
onClick={() => {
addToCart({
id: module.id,
name: module.name,
coverage: coverage,
price: price,
timeStamp: Math.ceil(new Date().getTime() * Math.random() * Math.random())
});
}}
>
Add module to cart
</button>
</div>
</div>
);
};
export default Tile;
App.test.js works fine and I am able to find the nested Landing Page div by className prop.
import React from "react";
import configureStore from "redux-mock-store";
import { Provider } from "react-redux";
import renderer from "react-test-renderer";
import App from "../components/App";
import history from "../history";
import { displayModules } from "../actions";
import { DISPLAY_MODULES } from "../actions/types";
const mockStore = configureStore([]);
describe("App Component test", () => {
let store = {};
let wrappedComponent = {};
const expectedActions = {
type: DISPLAY_MODULES,
payload: [
{
id: 0,
icon: "bicycle",
name: "Bike",
coverageMin: 0,
coverageMax: 3000,
risk: 30
},
{
id: 1,
icon: "gem",
name: "Jewelry",
coverageMin: 500,
coverageMax: 10000,
risk: 5
},
{
id: 2,
icon: "microchip",
name: "Electronics",
coverageMin: 500,
coverageMax: 6000,
risk: 35
},
{
id: 3,
icon: "football ball",
name: "Sports Equipment",
coverageMin: 0,
coverageMax: 20000,
risk: 30
}
]
};
beforeEach(() => {
store = mockStore({
data: {
modules: [],
cart: [],
total: 0
}
});
store.dispatch = jest.fn(displayModules);
wrappedComponent = renderer.create(
<Provider store={store}>
<App />
</Provider>
);
});
it("should render with given state from Redux store", () => {
expect(wrappedComponent.toJSON()).toMatchSnapshot();
});
it("should have an app from Landing Page", () => {
expect(wrappedComponent.root.findByProps({ className: "app" })).toBeDefined();
});
it("should show landing page for default route", () => {
*debugger;
expect(wrappedComponent.root.findByProps({ className: "shopping cart icon" })).toBeDefined();*
});
it("should show cart page for /cart route", () => {
history.push("/cart");
expect(wrappedComponent.root.findByProps({ className: "backward icon" })).toBeDefined();
});
it("should redirect to landing page for unmatched 404 routes", () => {
history.push("/someRandomRoute");
expect(wrappedComponent.root.findByProps({ className: "shopping cart icon" })).toBeDefined();
});
it("should dispatch displayModules action on app mount", async () => {
const actualAction = await store.dispatch();
expect(actualAction).toEqual(expectedActions);
});
});
But If you see the test debugger
The children of div with className: body has no children.
That is why it is not able to find the Tile component.
Can you suggest why the children are null for the body?
I have seen this before, even i tried with Enzyme i faced this issue.
Since it is a Redux wrapped component the , i cant directly create the Landing page or Tile component for testing.
How to test the nested items?
You are providing an empty array to modules inside redux state:
store = mockStore({
data: {
modules: [], // your modules is empty so no tiles will render
cart: [],
total: 0
}
});
Another issue is that you mock store.dispatch so it no longer changes the redux store even if some action is dispatched:
store.dispatch = jest.fn(displayModules);
If you want to test that an action was dispatched you can use:
const actions = store.getActions()
Which will give you all actions which were dispatched.
If you want to test how your app renders based on your store data you can either:
Setup the store in the test:
const existingModules = [ ... ]; // list of modules
store = mockStore({
data: {
modules: existingModules,
cart: [],
total: 0
}
});
You can mock useSelector in your test:
const existingModules = [ ... ]; // list of modules
const spy = jest.spyOn(redux, 'useSelector')
spy.mockReturnValue(existingModules)
I have implemented an app which uses react-router to handle the routes in my web-app. I want to trigger the function logintoggle which is on the Header.js component from a function from the Hompage.js component. The App.js has all the routes in one file.
Can anyone explain to me how this can be achieved with small code snippet?
App.js
render() {
const { location } = this.props;
return (
<IntlProvider
locale="a"
messages="s"
>
<Fragment>
<div>
<Headers />
<Switch>
<Route exact path="/women" component={HomePage} />
</Switch>
</div>
</Fragment>
</IntlProvider>
);
}
}
export default App;
Header
class Header extends React.Component {
constructor(props) {
super(props);
}
logintoggle(tab) {
if (this.state.activeTab !== tab) {
this.setState({
activeTab: tab
});
}
}
}
Homepage.js
class CheckOut extends Component {
constructor(props) {
super(props);
}
}
When you need to have a shared state among the components React.Context API is what you need. It allows you to create a separate context provider, which will provide the state and the methods to manipulate this state to all the components you need. In the example below I have a LoginContextProvider with activeTab state variable. I provide activeTab and setActiveTab to all the components inside LoginContextProvider's children. Header changes activeTab to 1, Homepage changes to 2 and LoginContextDebug represents the actual activeTab value.
const LoginContext = React.createContext(null);
const LoginContextProvider = ({ children }) => {
const [activeTab, setActiveTab] = React.useState(0);
return (
<LoginContext.Provider value={{ setActiveTab, activeTab }}>
{children}
</LoginContext.Provider>
);
};
const Header = () => {
// Use setActiveTab here
const { setActiveTab } = React.useContext(LoginContext);
return (
<div>
<h1>I am header</h1>
<button onClick={() => setActiveTab(1)}>Set activeTab to 1</button>
</div>
);
};
const Homepage = () => {
// Use setActiveTab here
const { setActiveTab } = React.useContext(LoginContext);
return (
<div>
<h1>I am homepage</h1>
<button onClick={() => setActiveTab(2)}>Set activeTab to 2</button>
</div>
);
};
const LoginContextDebug = () => {
const { activeTab } = React.useContext(LoginContext);
return (
<pre style={{ padding: 10, background: "lightgray" }}>
activeTab={activeTab}
</pre>
);
};
const App = () => (
<LoginContextProvider value={null}>
<Header />
<Homepage />
<LoginContextDebug />
</LoginContextProvider>
);
ReactDOM.render(<App />, document.getElementById('root'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I'm trying to pass extra props to this.props.children, I saw this answer how to pass props to children with React.cloneElement?
and from some reason although I'm not getting any error I can't see the prop
So I have this state
this.state = {
open: true
}
and I want to pass it down to this.props.children, and this is what I've done so far:
{
React.Children.map(this.props.children, child =>
React.cloneElement(child, {sidebarState: this.state.open}))
}
and when I'm console.logging this.props on the children I can't see my new props.
--- EDIT ---
In the children it looks like this:
render() {
console.log(this.props)
// other code
}
BTW I'm using react 16.0
Here is an example.
Before (without passing props down to the children):
<div className="layout">
{children}
</div>
After (passing extra props1 and props2 props to every child):
<div className="layout">
{
React.Children.map(children, (child) => {
return React.cloneElement(child, {
props1: 1,
props2: 2,
});
})
}
</div>
props1 and props2 get merged with the existing props for every child.
Regarding TypeScript types, you have to use React.ReactElement instead of React.ReactNode or the TS compiler will complain at React.Children.map (or ts-ignore it):
type Props = {
children: React.ReactElement;
};
See https://medium.com/better-programming/passing-data-to-props-children-in-react-5399baea0356 for more explanation, that helped me a lot to understand how to do it! (#mediumPaywall)
There are two ways to pass props to children:
Children as function
Instead of being a React element, children can be a function.
Call the children function:
const List = ({ children, sidebarState }) => (
<ul>
{
children(sidebarState)
}
</ul>
);
Passing the children a function:
<List sidebarState={sidebarState}>
{
(sidebarState) => (
<Item sidebarState={sidebarState} />
)
}
</List>
Working example:
const { Component } = React;
const Item = ({ sidebarState }) => (
<li>{sidebarState ? 'open' : 'close'}</li>
);
const List = ({ children, sidebarState }) => (
<ul>
{
children(sidebarState)
}
</ul>
);
class App extends Component {
constructor(props) {
super(props);
this.state = {
sidebarState: true
}
}
toggleOpen = () => this.setState((prevState) => ({
sidebarState: !prevState.sidebarState
}));
render() {
const { sidebarState } = this.state;
return (
<div>
<button onClick={this.toggleOpen}>Toggle</button>
<List sidebarState={sidebarState}>
{
(sidebarState) => (
<Item sidebarState={sidebarState} />
)
}
</List>
</div>
);
}
}
ReactDOM.render(
<App />,
demo
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="demo"></div>
React.cloneElement
Working example:
const { Component } = React;
const Item1 = ({ sidebarState }) => (
<li>{sidebarState ? 'open' : 'close'}</li>
);
const Item2 = ({ sidebarState }) => (
<li>{sidebarState ? 'open' : 'close'}</li>
);
const List = ({ children, sidebarState }) => (
<ul>
{
React.Children.map(children, (child) => React.cloneElement(child, { sidebarState }))
}
</ul>
);
class App extends Component {
constructor(props) {
super(props);
this.state = {
sidebarState: true
}
}
toggleOpen = () => this.setState((prevState) => ({
sidebarState: !prevState.sidebarState
}));
render() {
const { sidebarState } = this.state;
return (
<div>
<button onClick={this.toggleOpen}>Toggle</button>
<List sidebarState={sidebarState}>
<Item1 />
<Item2 />
</List>
</div>
);
}
}
ReactDOM.render(
<App />,
demo
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="demo"></div>
I have a simple app. It has two components:
Items - it just renders items from the store
AddItem - it just add an item to the store
The question is: why when I submit the form of AddItem component this component is mounted again? If I don't invoke addItem function there is no rerender of AddItem. Any thoughts?
Live example on WebpackBin
Here is the app
import React, { Component } from 'react';
import { BrowserRouter, Match, Link, Redirect, browserHistory } from 'react-router';
import { Provider, connect } from 'react-redux';
import { createStore, bindActionCreators } from 'redux';
import { render } from 'react-dom';
// ACTIONS
const add = (item) => {
return {
type: 'ADD',
payload: item
}
};
// REDUCER
const initialState = {
items: [
'hello',
'world'
]
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'ADD': {
return { items: [...state.items, action.payload] };
}
default : {
return state;
}
}
}
// STORE
const store = createStore(reducer);
// ADD ITEM
class AddItem extends Component {
constructor(props) {
super(props);
this.state = {
foo: 'bar'
};
}
componentDidMount() {
console.log('mount AddItem');
}
onSubmit(event) {
event.preventDefault();
this.props.addItem(this.itemTitle.value);
this.form.reset();
this.setState({
foo: 'asd'
});
}
render() {
return (
<div>
<form onSubmit={::this.onSubmit} ref={node => this.form = node}>
<div>
<label htmlFor="item-title">Item</label>
</div>
<div>
<input id="item-title" type="text" defaultValue="baz" ref={node => this.itemTitle = node}/>
</div>
<input type="submit" value="Add" />
<div>
{this.state.foo}
</div>
</form>
<button>Click</button>
</div>
);
}
}
// ITEMS
class Items extends Component {
render() {
return (
<ul>
{
this.props.items.map(item => {
return <li key={item}>{item}</li>;
})
}
</ul>
);
}
}
// LAYOUT
class Layout extends Component {
render() {
const MatchWithProps = ({component:Component, ...rest}) => {
<Match {...rest} render={() => (
<Component {...this.props} />
)}/>
};
return (
<BrowserRouter>
<div>
Reload
<br />
<Link activeOnlyWhenExact activeClassName="active" to="/">Items</Link>
<Link activeClassName="active" to="/add">Add Item</Link>
<hr />
<Match exactly pattern="/" render={() => <Items {...this.props} /> } />
<Match exactly pattern="/add" component={() => <AddItem addItem={this.props.itemActions.add} />} />
</div>
</BrowserRouter>
);
}
}
const mapStateToProps = (state) => {
return {
items: state.items
};
}
const itemActions = { add };
const mapDispatchToProps = (dispatch) => {
return {
itemActions: bindActionCreators(itemActions, dispatch)
};
}
const ConnectedLayout = connect(mapStateToProps, mapDispatchToProps)(Layout);
// PROVIDER
const provider = (
<Provider store={store}>
<ConnectedLayout />
</Provider>
);
render(provider, document.querySelector('#react-container'));