NextJS & ts-jest, can't figure out how to test onClick - reactjs

I have been looking for several hours now but can't figure this out.
How would I go about testing the onClick?
This is my card component:
import { useRouter } from "next/router";
import React from "react";
interface Props {
url: string;
name: string;
}
const LargeCard = (props: Props) => {
const router = useRouter();
return (
<button
onClick={() => {
router.push(props.url);
}}
className="w-full h-96 grid place-items-center hover:border border-dark-3 bg-dark-5 hover:cursor-pointer"
>
<p className="text-2xl uppercase text-dark-1 font-thin">{props.name}</p>
</button>
);
};
export default LargeCard;
And this is the test file I've got up until now.
import { expect, it } from "#jest/globals";
import { render } from "#testing-library/react";
import LargeCard from "./largeCard";
describe("LargeCard", () => {
it("renders the button", () => {
const { queryByText } = render(
<LargeCard name={"test"} url={"/test"} />
);
expect(queryByText("test")).toBeTruthy();
});
});

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];

Add List element to favorites

So I've created this array of objects and I would like to add some of them to favorites page. I thought I'd create onClick boolean change to each element and once value true object would be added favorites page. First of all, I am not sure if that's the right way to do it and secondly, now I am struggling with the fact that value of boolean in each element in the list is changing with that click instead of desired one. Probably I bit more than I could chew hence I am asking for some guidance.
MeetupList file
import { useState } from 'react'
import classes from './MeetupList.module.css'
import { IState as Props } from '../../pages/AllMeetups'
import Card from '../ui/Card'
interface IProps {
meetups: Props['meetupsy']
}
const MeetupList: React.FC<IProps> = ({meetups}) => {
const [toggle, setToggle] = useState(false)
const toggler = () => {
toggle ? setToggle(false): setToggle(true)
}
const renderList = ():JSX.Element[] => {
return meetups.map((meetup) => {
return(
<Card key={meetup.id}>
<li className={classes.list}>
<div className={classes.image}>
<img src={meetup.image} alt={meetup.title} />
</div>
<div className={classes.listheader}>
<h3>{meetup.title}</h3>
<address>{meetup.address}</address>
<p>{meetup.description}</p>
</div>
<div className={classes.actions}>
<button onClick={toggler}>{toggle ? <span>To Favorite</span>:<span>Not Favorite</span>}</button>
</div>
</li>
</Card>
)
})
}
return (
<ul className={classes.render}>
{renderList()}
</ul>
)
}
export default MeetupList;
All Meetups Page
import React, {useState, useEffect} from 'react'
import MeetupList from '../components/meetups/MeetupList'
import NewMeetupForm from '../components/meetups/NewMeetupForm'
import Popup from '../components/ui/Popup'
//import classes from './AllMeetups.module.css'
import './AllMeetups.css'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faCalendarPlus } from '#fortawesome/free-regular-svg-icons'
export interface IState {
meetupsy: {
id: number
image: string
title: string
address: string
description: string
}[]
}
const AllMeetups = () => {
const [meetups, setMeetups] = useState<IState['meetupsy']>([
{
id: 123,
image: 'https://i.imgur.com/i4WpOUM.jpg',
title: 'Car Meetup',
address: 'Baker Street 14',
description: 'EUDM Meetup'
},
{
id: 1233,
image: 'https://cdn.pixabay.com/photo/2017/12/09/08/18/pizza-3007395__480.jpg',
title: 'Cooking Show',
address: 'Downtown 14',
description: 'Pizza Cooking Show'
}
])
useEffect(() => {
const data = window.localStorage.getItem('MEETUP_LIST');
if (data !== null) setMeetups(JSON.parse(data))
},[])
useEffect(() => {
window.localStorage.setItem('MEETUP_LIST', JSON.stringify(meetups))
}, [meetups])
const [buttonPopup, setButtonPopup] = useState(false);
return (
<section>
<h1 className='oke'>All Current Meetups</h1>
<MeetupList meetups={meetups} />
<button className='open' onClick={() => setButtonPopup(true)}><FontAwesomeIcon icon={faCalendarPlus} /></button>
<Popup trigger={buttonPopup} setTrigger={setButtonPopup}>
<NewMeetupForm meetups={meetups} setMeetups={setMeetups} />
</Popup>
</section>
)
}
export default AllMeetups

How to perform search operation from Navbar when data is recieved from global Context API

I am executing search operation from Navbar component for the data that is present in separate Context API, and the results for the search operation will be presented in another component call Blog, which is using same Context API, but the problem here is search operation is not executing in real time, like when I clear the search bar then It's difficult to set search term in use state hook which is present in context API. So in this case how to solve the problem.
Below is my code from context API
import { BlogContext } from "./BlogContext";
import React from "react";
import { useState } from "react";
export const BlogState = (props) => {
const host = "http://localhost:5000";
const blogInitial = [];
const [blog, setBlog] = useState(blogInitial);
let fetchAllNotes = async () => {
//API call
const response = await fetch(`${host}/api/notes/blog/`, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const json = await response.json();
setBlog(json);
};
const searchFilter = (searchWord) => {
const searchTerm =
blog.filter((note) =>
note.description.toLowerCase().includes(searchWord)
) || ((note) => note.title.toLowerCase().includes(searchWord));
setBlog(searchTerm);
};
return (
<BlogContext.Provider value={{ blog, fetchAllNotes, fil, searchFilter }}>
{props.children}
</BlogContext.Provider>
);
};
Code from Navbar component
import React, { useContext, useState } from "react";
import { Link, useNavigate, useLocation } from "react-router-dom";
import { ThemeContext } from "../context/notes/ThemeContext";
import { BlogContext } from "../context/notes/BlogContext";
export const Navbar = () => {
const { searchFilter, blog } = useContext(BlogContext);
const [searchTerm, setSearchTerm] = useState(blog);
const onChange = (e) => {
if (e.target.value === "") {
window.location.reload(true);
} else {
const search = e.target.value.toLowerCase();
setSearchTerm(search);
searchFilter(searchTerm);
}
};
return (
<div>
<nav
<form className="d-flex mx-2">
<input
onChange={onChange}
className="form-control me-2"
type="search"
placeholder="Search"
aria-label="Search"
/>
<button className="btn btn-success mx-2" type="submit">Clear</button>
</form>
</nav>
</div>
);
};
Code from Blog component
import React, { useContext, useEffect } from "react";
import { ThemeContext } from "../context/notes/ThemeContext";
import { BlogContext } from "../context/notes/BlogContext";
import BlogItem from "./BlogItem";
import { FlexNavbar } from "./FlexNavbar";
const Blog = () => {
const { theme } = useContext(ThemeContext);
const { blog } = useContext(BlogContext);
return (
<>
<div
className={`container bg-${theme} text-${
theme === "dark" ? "light" : "dark"
}`}
>
<FlexNavbar className="" />
<div className="row">
{blog.map((notes) => {
return <BlogItem key={notes._id} note={notes} />;
})}
</div>
</div>
</>
);
};
export default Blog;

Type '{ children: Element; }' has no properties in common with type 'IntrinsicAttributes' React -typescript Context

I'm currently coding a React -typescript App for practising FluentUI (a.k.a Fabric). Issue appears
with my App.tsx component.
import React, { useContext, useState } from 'react';
import logo from './logo.svg';
import './App.css';
import Search from './components/Search';
//import CategoriasProvider from './components/Context/CategoriasContext';
import Title from './components/Title';
import { ListGhostingExample } from '../src/components/DrinkList';
import { PrimaryButton } from 'office-ui-fabric-react';
import { CategoriasContext, ICategoriasContextInterface } from './components/Context/CategoriasContext';
import CategoriasProvider from './components/Context/CategoriasContext';
import axios from 'axios';
import './components/DrinkList.css'
import './components/Search.css'
interface IApp{
items:ICategoriasContextInterface[],
renderList:boolean
}
const App =()=> {
const contextValues=useContext(CategoriasContext);
return(
<CategoriasProvider>
<div className="App">
<div className="search">
<Search name={contextValues?.name} image={contextValues?.image} thumbnail={contextValues?.thumbnail} />
</div>
</div>
</CategoriasProvider>
);
}
export default App;
CategoriasProvider comes from a Context (CategoriasContext.tsx ). CategoriasProvider has the mentioned error Inside of CategoriasProvider there's a Search.tsx Component.Search's works as a "wrapper". Code is:
import React, { useEffect, useState } from 'react';
import { SearchBox,ISearchBoxStyles } from 'office-ui-fabric-react/lib/SearchBox';
import { PrimaryButton, IContextualMenuProps, Stack, IStackTokens, StackItem, initializeIcons } from 'office-ui-fabric-react';
import { ComboBox, DefaultPalette, Dropdown, DropdownMenuItemType, IComboBoxOption, IDropdownOption, IDropdownStyles, IStackItemStyles, SelectableOptionMenuItemType, Toggle } from '#fluentui/react';
import { getGlassesOriginal } from './Utils/Utils';
import axios from 'axios';
import '../Search.css';
import { CategoriasContext, ICategoriasContextInterface } from './Context/CategoriasContext';
initializeIcons();
const Search = (props:ICategoriasContextInterface) => {
//State
const [textContent, setTextContent] = useState("");
const [textBoxDisabled,disableTextBox]=useState(false);
const [comboBoxDisabled,disableComboBox]=useState(true);
const CategoriasContextInSearch=React.useContext(CategoriasContext);
const setTextContentInstate = (e: any) =>{
console.log("Contenido de e" + e.target.value);
setTextContent(e.target.value);
}
const showMessageInConsole = ():void => {
console.log(textContent);
setTextContent("");
}
// Example formatting
const stackTokens: IStackTokens = { childrenGap: 20 };
const searchBoxStyles: Partial<ISearchBoxStyles> = { root: { width: 200 } };
const dropdownStyles: Partial<IDropdownStyles> = {
dropdown: { width: 200 },
};
const options: IDropdownOption[] = [
{ key: 'glasses', text: 'Glasses', itemType: DropdownMenuItemType.Header },
];
function getGlasses () {
let outputArray:string[] = [];
console.log("getGlasses");
axios
.get("https://www.thecocktaildb.com/api/json/v1/1/list.php?g=list")
.then((response)=>{
let responseDataJson=response.data.drinks;
for (let element in responseDataJson) {
options.push({key:responseDataJson[element].strGlass,text:responseDataJson[element].strGlass});
}
}
)
return outputArray;
}
function selectSearch(){
if(textBoxDisabled){
disableTextBox(false);
disableComboBox(true);
} else {
disableTextBox(true);
disableComboBox(false);
};
}
useEffect(() => {
//TODO: No se debería llamar siempre a esta función. Solamente cuando se activa el sistmea de búsqueda (y además, cachearlo)
getGlasses()
});
return(
<div className="wrapper">
<div className="one"> <Toggle onClick={selectSearch}/></div>
<div className="two">
{
<SearchBox
name="searchBox"
className="searchBox"
styles={searchBoxStyles}
placeholder="Cheers!"
onChange={setTextContentInstate}
value={textContent}
disabled={textBoxDisabled}
/>
}
</div>
<div className="three">
<Dropdown
placeholder="Select a glass"
options={options}
styles={dropdownStyles}
disabled={comboBoxDisabled}
/>
</div>
<div className="four">
<div className="primaryButton">
<PrimaryButton text="Search" onClick={showMessageInConsole}/>
</div>
</div>
</div>
);
}
export default Search;
Hope you can help me!!! Thanks in advance!
The code which is causing the error in your title is in your comment. It's this line:
export const CategoriasProvider = () => {
You are defining CategoriasProvider as a component which takes no props. It can only accept IntrinsicAttributes which is basically just the key property.
But when you use CategoriasProvider in App you are calling it with JSX element children. You get an error because you have not said that the CategoriasProvider component can accept a children prop.
Any of the following types will solve your problem:
export const CategoriasProvider: React.FC = ({children}) => {
export const CategoriasProvider = ({children}: {children: React.ReactNode}) => {
export const CategoriasProvider = ({children}: React.PropsWithChildren<{}>) => {
Regardless, you'll want to pass the children down as children of the inner Provider component.
return (
<CategoriasContext.Provider value={hola}>
{children}
</CategoriasContext.Provider>
);
Your App component is not going to work as expected because the useContext hook which accesses the CategoriasContext is located outside of the CategoriasProvider. It will just get the default value for the context -- not the value from the provider.
You need to rearrange your components such that the hook call occurs in a component that is rendered inside of the CategoriasProvider.
Try this:
const Search = () => {
const contextValues = useContext(CategoriasContext);
return (
<div className="search">
<Search
name={contextValues?.name}
image={contextValues?.image}
thumbnail={contextValues?.thumbnail}
/>
</div>
);
};
const App = () => {
return (
<CategoriasProvider>
<div className="App">
<Search />
</div>
</CategoriasProvider>
);
};
export default App;

Facing problem for code coverage in React and JEST

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()
})
});```

Resources