Facing problem for code coverage in React and JEST - reactjs

I have a parent component(App.js) and I have child component(Counter), I am updating the state by passing a function from parent component to child component due to this I am unable to cover 100% code coverage.
I am trying for 100% code coverage for app.js in app.test.js.
After run test command ex: npm test -- app.test.js
its suggesting to cover the setState() and setCounter() methods.
please help me in this.
Thanks in Advance.
import { connect } from 'react-redux'
import logo from './logo.svg';
import './App.css';
import {Counter} from './counter';
import MouseTracker from './renderProps/Mouse';
function App(props) {
const [state, setState] = useState(false);
const [counter, setCounter] = useState(0);
const updateMyState = () => {
setState(true);
setCounter(counter+1)
}
return (
<div className="App">
<MouseTracker />
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>{props.counter}</p>
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
onClick={props.onClick}
>
Learn React
</a>
{/* Passing my updateMyState function as props */}
<Counter propEvents={updateMyState} />
</header>
</div>
);
}
function mapStateToProps(state) {
return { ...state }
}
export default connect(mapStateToProps)(App);
Counter component code
export const Counter = (props) => {
return (
<button id="abc" onClick={props.propEvents}>hello</button>
// {props?.st?.map(i => {
// return <p key={i.id}>id {i.id}, name: {i.name}</p>
// })}
)
}
*** Test code ***
import React from 'react';
import App from './App';
import { render, screen } from '#testing-library/react'
import {BrowserRouter as Router } from 'react-router-dom'
import { createStore } from 'redux'
import { Provider } from 'react-redux';
import renderer from 'react-test-renderer';
import configureMockStore from "redux-mock-store";
const initialState = {counter: 'Redux User'};
const mockStore = configureMockStore();
const mStore = mockStore({});
function reducer(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
return [
{
counter: state.counter + 1
},
...state
]
default:
return state
}
}
function renderC(
ui,
{
initialState,
store = createStore(reducer, initialState)
} = {}
) {
function Wrapper({ children }) {
return <Provider store={store}>{children}</Provider>
}
return render(ui, { wrapper: Wrapper })
}
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({ adapter: new Adapter() });
import { mount, shallow } from 'enzyme';
const clickFn = jest.fn();
describe('My Test Suite', () => {
const component = mount(<Provider store={mStore}><Router><App onClick={clickFn}/></Router> </Provider>);
it('Test App component', () => {
const changeCounter = jest.fn();
const handleClick = jest.spyOn(React, "useState");
handleClick.mockImplementation(size => [size, changeCounter]);
console.log(component.debug())
const code = component.find('code');
expect(code.text()).toBe('src/App.js');
})
it('Renders the connected app with initialState', () => {
renderC(<App />, { initialState: { counter: '123' } })
expect(screen.getByText('123')).toBeInTheDocument()
})
});```

Related

React testing `useEffect` with `useState` update

On my component render, my useEffects hooks called, a function. the function updates the state status depending on the condition within the useEffects produce.
So in this case how to test the `mobileMenu` and how to set different condition in useEffect to test it?
I hope both my useEffects and useState need to mocked. I am in learning process with react. I could not get any correct answer upon searching, any one help me please?
here is my app.tsx
my ts file:
import { Footer, Header, ProductCart, ProductPhotoGallery, Tabs } from '#mcdayen/components';
import { Cart, Logo, MobileMenu, NaviLinks, QuickSearch, User } from '#mcdayen/micro-components';
import { initialNaviLinksProps, initialPhotoProps, initialTabsProps, NaviLinksProps, sizeProps } from '#mcdayen/prop-types';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchCartDetails, sizeHandler } from './store/cart.slice';
import { AppDispatch, RootState } from './store/store.config';
export function App() {
const dispatch:AppDispatch = useDispatch();
dispatch(fetchCartDetails());
const {product} = useSelector((state:RootState) => state.cartStore)
const [mobileMenu, setMobileMenu] = useState<boolean>(false);
const [linkProps, setLinkProps] = useState<NaviLinksProps | null>(null);
function mobileMenuHandler() {
setMobileMenu((current: boolean) => !current);
}
useEffect(() => {
setLinkProps(initialNaviLinksProps);
const mobileId = document.getElementById('mobileMenu');
if (mobileId?.offsetParent) {
mobileMenuHandler();
}
}, []);
useEffect(() => {
setLinkProps((props) => {
return mobileMenu ? { ...initialNaviLinksProps } : { ...initialNaviLinksProps, classProps: props?.classProps + ' hidden' }
})
}, [mobileMenu]);
function onSizeSelect(selectedSize: sizeProps) {
dispatch(sizeHandler(selectedSize));
}
return (
<section className="box-border m-auto flex flex-col pl-[18px] py-6 min-h-screen flex-wrap px-5 md:container md:w-[1440px] md:pl-[70px] pr-5 ">
<Header>
<Logo />
{linkProps && <NaviLinks passNaviLinks={linkProps} />}
<div className="flex gap-3">
<QuickSearch />
<Cart />
<User />
<MobileMenu menuHandler={mobileMenuHandler} />
</div>
</Header>
<main className='flex flex-col justify-between lg:flex-row'>
<div className='hidden lg:block w-[325px]'>
<div>
<Tabs tabProps={initialTabsProps} />
</div>
</div>
<div className='grow-0 flex-auto' >
{initialPhotoProps.length && <ProductPhotoGallery gallery={initialPhotoProps} />}
</div>
<div className='flex bg-white'>
{product && <ProductCart sizeSelect={onSizeSelect} passCartProps={product} />}
</div>
</main>
<Footer />
</section>
);
}
export default App;
My spec:
import { configureStore } from '#reduxjs/toolkit';
import { render } from '#testing-library/react';
import React from 'react';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';
import App from './app';
import cartReducer from './store/cart.slice';
jest.mock('react', () => ({
...jest.requireActual('react'),
useState: jest.fn(),
}));
export function createTestStore() {
const store = configureStore({
reducer: {
cartStore:cartReducer,
}
})
return store;
}
describe('App', () => {
const setMobileMenu = jest.fn();
const useStateMock = (initState: boolean) => [initState, setMobileMenu];
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
afterEach(() => {
jest.clearAllMocks();
});
const store = createTestStore();
it('should render successfully', () => {
const { baseElement } = render(
<BrowserRouter>
<Provider store={store}>{<App />}</Provider>
</BrowserRouter>
);
expect(baseElement).toBeTruthy();
useStateMock(true);
expect(setMobileMenu).toHaveBeenCalledWith(true);
});
});
I am getting an error at: `
jest.spyOn(React, 'useState').mockImplementation(useStateMock);
`
as : Argument of type '(initState: boolean) => (boolean | jest.Mock<any, any>)[]' is not assignable to parameter of type '() => [unknown, Dispatch<unknown>]'.
and my test failing.
Need help for:
test the useEffect hook on anonymous function ( mocking )
fixing the error highlighted
testing the state on setMobileMenu
Any one please help me with the correct way?
Try to declare useStateMock as:
const useStateMock = (initState: any) => [initState, setMobileMenu];

How to use createSelectorHook()

I am receiving this error on CodeSandbox: Cannot read properties of undefined (reading 'getState')
What is the proper way to use createSelectorHook()? Could someone please create a CodeSandbox to illustrate its usage?
index.js:
import React from "react";
import { createRoot } from "react-dom/client";
import { createStore } from "redux";
import reducer from "./reducer";
import App from "./App";
import myContext from "./context";
import {
Provider,
createStoreHook,
createDispatchHook,
createSelectorHook
} from "react-redux";
export const useStore = createStoreHook(myContext);
export const useDispatch = createDispatchHook(myContext);
export const useSelector = createSelectorHook(myContext);
const store = createStore(reducer, 0);
const root = createRoot(document.getElementById("root"));
root.render(
<Provider store={store} context={myContext}>
<App />
</Provider>
);
App.js:
import React, { useState, useRef } from "react";
import { useSelector, useDispatch } from "./";
import context from "./context";
const Counter = (props) => {
return (
<context.Provider value={props}>
<A />
</context.Provider>
);
};
const A = (props) => {
return <B />;
};
const B = (props) => {
return <C />;
};
const C = (props) => {
const v = useSelector((state) => state);
const dispatch = useDispatch();
return (
<div>
<button onClick={() => dispatch({ type: "DECREMENT" })}>-</button>
value:<span>{v}</span>
<button onClick={() => dispatch({ type: "INCREMENT" })}>+</button>
</div>
);
};
const App = () => {
const stepInput = useRef(1);
const [step, updateStep] = useState(1);
return (
<div>
step:
<input
ref={stepInput}
type="number"
onChange={() => updateStep(stepInput.current.value)}
/>
<Counter step={step} />
</div>
);
};
export default App;
I think react-redux v8.0.0 removed DefaultRootState (inferring the state):
Now that React-Redux itself is written in TS, we've opted to remove the DefaultRootState type entirely. State generics now default to unknown instead.
source: the release notes
So, createStoreHook appears to exist if you'd like to (a) bind to a custom context or, (b) ensure a call to something like useState().getStore() returns a properly typed redux store.
Note, I am not positive, if this is a correct or even suggested use case for (b). I also discovered that this works following the documented example for useAppDispatch:
export const useAppStore = () => useStore<RootState>();

Trying to connect Metamask wallet to my react app through web3.js

I am trying to connect the metamask wallet to my react-app. But in the process I am getting a few errors as mentioned below please help.
getWeb3.js
import Web3 from 'web3';
import {useEffect, useState} from 'react';
// Wallet connect code
const useWeb3 = () => {
const [web3,setweb3] = useState(null);
useEffect(() => {
var instance;
if(window.ethereum) {
try{
instance = new Web3(window.ethereum);
}catch(error){
console.error(error);
};
}else if (window.web3) {
instance = new Web3(window.web3);
}else{
const provider = new Web3.provider.HttpProvider('http://127.0.0.1:8545')
instance = new Web3(provider);
}setweb3(instance);
},[]);
return web3;
};
export {useWeb3};
store.js
import React, {useReducer, useContext, createContext} from 'react';
const StoreContext = createContext();
const initialState = {
message: '',
address: null,
balance: 0
};
const reducer = (state, action) => {
switch(action.type){
case "NEW-ADDRESS":
return {
...state,
address: action.newAddress,
message: action.message
}
default:
throw new Error('Unknown type of action ${action.type');
}
};
export const StoreProvider = ({children}) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StoreContext.Provider value = {{state, dispatch}}>
{children}
</StoreContext.Provider>
);
};
export const useStore = () => useContext(StoreContext);
storeApi.js
import {useStore} from './store'
const useStoreApi = () => {
const {state, dispatch} = useStore();
return {
address: state.address,
balance: state.balance,
message: state.message,
setBalance: (newBalance) => {
dispatch({
type: "SET-BALANCE",
newBalance
});
},
setAddress: newAddress => {
dispatch({
type: "New-Address",
newAddress,
message: "New account added successfully!"
});
}
};
};
export {useStoreApi};
app.js
import { useStoreApi } from './storeApi';
import { useWeb3 } from './getWeb3';
import logo from './logo.svg';
import './App.css';
import {Button} from '#material-ui/core';
function App() {
const {address,balance,message, setBalance,setAddress} = useStoreApi();
const web3 = useWeb3();
const setUserAccount = async (e) => {
console.log("rhis add");
if(window.ethereum) {
await window.ethereum.enable();
web3.eth.getAccounts().then(accounts => {
setAddress(accounts[0]);
console.log("rhis add");
});
}
};
setUserAccount();
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
<code>src/App.js</code>
</p>
{
address ? (
<>
<p> Your add: {address}</p></>
): "s"
}
{/* <Button variant="outlined" color="primary" onClick={() => setUserAccount()}>Conn</Button> */}
{/* <form action="http://localhost:3000/">
<button type="submit" onClick={() => setUserAccount()}>Submit</button>
</form> */}
</header>
</div>
);
}
export default App;
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {StoreProvider} from './store';
ReactDOM.render(
<React.StrictMode>
<StoreProvider>
<App />
</StoreProvider>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals();
Errors:
App.js:16 Uncaught (in promise) TypeError: Cannot read properties of null (reading 'eth')
at setUserAccount (App.js:16)
store.js:20 Uncaught Error: Unknown type of action ${action.type
index.js:1 The above error occurred in the component:
at StoreProvider (http://localhost:3000/static/js/main.chunk.js:847:3)
It looks like you're importing useWeb3 wrong:
const web3 = useWeb3();
But you export it like:
export {useWeb3};
So now you would have to call it like: web3.useWeb3()
Is this code you found at a tutorial somewhere? It's hard to follow...

React-Router-Dom <Link> not render page

I'm building a practice app that uses Unsplash to render users photos. I'm using React and Redux. With react-router-dom, I'm trying to follow the docs but I find it very confusing to set up. Here's what I have so far. When I click on a result out of a returned list of results from a search, I want it to render a user page profile.
index.js (make sure I have react-router-do set up correctly):
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import './index.css';
import App from './App';
// import store from './app/store';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware, compose } from "redux";
import thunk from "redux-thunk";
import reducers from "./app/reducers/rootReducer";
import * as serviceWorker from './serviceWorker';
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(reducers, storeEnhancers(applyMiddleware(thunk)));
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
Top component App
import React from "react";
import { BrowserRouter as Router, Route } from "react-router-dom";
import Images from "./app/components/Images";
import Search from "./app/components/Search";
import UserProfile from "./app/components/UserProfile";
import "./App.css";
function App() {
return (
<>
<Search />
<Images />
<Router>
<Route link="/userProfile">
<UserProfile />
</Route>
</Router>
</>
);
}
export default App;
search (parent component to searchResults where exists):
import React, { useState, useEffect } from "react";
import { connect } from "react-redux";
import { queryAction } from "../actions/queryAction";
import SearchResults from "./SearchResults";
const Search = (props) => {
const [query, setQuery] = useState("");
console.log(props.searches);
const searchPhotos = async (e) => {
e.preventDefault();
console.log("submitting form");
props.queryAction(query);
};
const showUsers = (user, e) => {
e.preventDefault()
console.log(user)
};
return (
<>
<form className="form" onSubmit={searchPhotos}>
<label className="label" htmlFor="query">
{" "}
</label>
<input
type="text"
name="query"
className="input"
placeholder={`Try "dog" or "apple"`}
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button type="submit" className="button">
Search
</button>
</form>
<SearchResults results={props.searches} showUsers={showUsers} />
</>
);
};
const mapStateToProps = (state) => {
return {
searches: state.searches,
};
};
const mapDispatchToProps = (dispatch) => {
return {
queryAction: (entry) => dispatch(queryAction(entry)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(Search);
searchResults:
import React from "react";
import { BrowserRouter as Router, Link } from "react-router-dom";
import { getUserAction } from "../actions/getUserAction";
import { connect } from "react-redux";
const SearchResults = (props) => {
const { results } = props.results.searches;
const handleClick = (result, e) => {
e.preventDefault();
props.getUser(result.username);
};
return (
<>
{results &&
results.map((result, id) => {
return (
<div key={id}>
<Router>
<Link to="/userProfile" onClick={(e) => handleClick(result, e)}>
{result.username}
</Link>
</Router>
</div>
);
})}
</>
);
};
const mapDispatchToProps = (dispatch) => {
return {
getUser: (query) => dispatch(getUserAction(query)),
};
};
export default connect(null, mapDispatchToProps)(SearchResults);
and finally the UserProfile component:
import React from 'react';
import { connect } from 'react-redux';
const UserProfile = props => {
console.log(props)
return (
<div>
</div>
);
}
const mapStateToProps = state => {
return {
user: state.users
}
}
export default connect(mapStateToProps, null)(UserProfile);
app component
import React from "react";
import { Switch, Route } from "react-router-dom";
import Images from "./app/components/Images";
import Search from "./app/components/Search";
import UserProfile from "./app/components/UserProfile";
import "./App.css";
function App() {
return (
<>
<Search />
<Images />
<Switch>
<Route path="/userProfile/:username">
<UserProfile />
</Route>
</Switch>
</>
);
}
export default App;
SearchResults component
import React from "react";
import { Link } from "react-router-dom";
const SearchResults = (props) => {
const { results } = props.results.searches;
const handleClick = (result, e) => {
e.preventDefault();
props.getUser(result.username);
};
return (
<>
{results &&
results.map((result, id) => {
return (
<div key={id}>
<Link to={`/userProfile/${result.username}`}>
{result.username}
</Link>
</div>
);
})}
</>
);
};
export default SearchResults;
UserProfile component
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { getUserAction } from "../actions/getUserAction";
const UserProfile = props => {
useEffect(() => {
props.getUserAction(props.match.params.username)
},[])
console.log(props)
return (
<div>
{props.user
? <div>{user.username}</div>
: <div>Loading...</div>
}
</div>
);
}
const mapStateToProps = state => {
return {
user: state.users
}
}
const mapDispatchToProps = (dispatch) => {
return {
getUser: (query) => dispatch(getUserAction(query)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(UserProfile);
Edit: Add a param to your link and remove the onclick. Update the Route to expect a :username param. You can access the param through props in UserProfile component.
Make sure to perform the action or access state when mounting the UserProfile component so you have some data when it renders.
Edit 2: Added UserProfile component to answer. You want to dispatch your action when the component is mounting. Also, set a ternary to show "Loading..." if state.user isn't done being fetched.

Using apollo client with react-router gives a router-context warning about client is undefined

I'm trying to add apollo to a walmart electrode starter react app and I'm getting the warning about the client property.
It seems to work fine if I just use the apollo provider to wrap a component that is not a react-router. When I add the react router- it shows that warning - however the page still loads with apollo fine.
Warning: Failed context type: The context `client` is marked as required in `Apollo(Home)`, but its value is `undefined`.
in Apollo(Home) (created by Connect(Apollo(Home)))
in Connect(Apollo(Home)) (created by RouterContext)
in RouterContext
in Provider
app.jsx
//
// This is the client side entry point for the React app.
//
import React from "react";
import {render} from "react-dom";
import {routes} from "./routes";
import {Router} from "react-router";
import {createStore} from "redux";
import "./styles/base.css";
import rootReducer from "./reducers";
import ApolloClient, {createNetworkInterface} from "apollo-client";
import { ApolloProvider } from "react-apollo";
//
// Add the client app start up code to a function as window.webappStart.
// The webapp's full HTML will check and call it once the js-content
// DOM is created.
//
const graphUri = "https://api.graph.cool/simple/v1/foo";
window.webappStart = () => {
const initialState = window.__PRELOADED_STATE__;
const store = createStore(rootReducer, initialState);
const client = new ApolloClient({
networkInterface: createNetworkInterface({ uri: graphUri })
});
render(
<ApolloProvider store={store} client={client}>
<Router>{routes}</Router>
</ApolloProvider>,
document.querySelector(".js-content")
);
};
home.jsx
import React, {PropTypes} from "react";
import {connect} from "react-redux";
import {toggleCheck, incNumber, decNumber} from "../actions";
import gql from "graphql-tag";
import { graphql } from "react-apollo";
class Home extends React.Component {
render() {
const props = this.props;
const {checked, value} = props;
const { loading, allExperiences } = this.props.data;
if (loading) {
return <div>Loading</div>;
} else {
return (
<div>
<div>
<h2>Managing States with Redux</h2>
<label>
<input onChange={props.onChangeCheck} type={"checkbox"} checked={checked}/>
Checkbox
</label>
<div>
<button type={"button"} onClick={props.onDecrease}>-</button>
{value}
<button type={"button"} onClick={props.onIncrease}>+</button>
</div>
</div>
<ul>
{allExperiences.map(post =>
<li key={post.id}>
{post.title}
</li>
)}
</ul>
</div>
);
}
}
}
Home.propTypes = {
checked: PropTypes.bool,
data: PropTypes.shape({
loading: PropTypes.bool.isRequired,
allExperiences: PropTypes.array
}).isRequired,
value: PropTypes.number.isRequired
};
const mapStateToProps = (state) => {
return {
checked: state.checkBox.checked, value: state.number.value
};
};
const mapDispatchToProps = (dispatch) => {
return {
onChangeCheck: () => {
dispatch(toggleCheck());
},
onIncrease: () => {
dispatch(incNumber());
},
onDecrease: () => {
dispatch(decNumber());
}
};
};
const getQuery = gql`
query allExperiences {
allExperiences {
id,
title,
subtitle,
description
}
}`;
const HomeWithData = graphql(getQuery)(Home);
export default connect(mapStateToProps, mapDispatchToProps)(HomeWithData);
routes.jsx
import React from "react";
import {Route} from "react-router";
import Home from "./components/home";
export const routes = (
<Route path="/" component={Home}/>
);

Resources