React unit test image on load - reactjs

I am using this "async image" component but I have problems in unit tests, for the "load" success section with the image
import { useEffect, useState } from 'react'
const AsyncImage = (props) => {
const [loadedSrc, setLoadedSrc] = useState(null)
useEffect(() => {
setLoadedSrc(null)
if (props.src) {
const handleLoad = () => {
setLoadedSrc(props.src)
}
const image = new Image()
image.addEventListener('load', handleLoad)
image.src = props.src
return () => {
image.removeEventListener('load', handleLoad)
}
}
}, [props.src])
if (loadedSrc === props.src) {
return (
<img {...props} />
)
}
return null
}
export default AsyncImage
Unit test:
global.Image = class {
constructor() {
this.onload = jest.fn();
this.addEventListener = jest.fn()
this.removeEventListener = jest.fn()
setTimeout(() => {
this.onload();
}, 50);
}
};
test("render AsyncImage", async () => {
const logo = LOAD_SUCCESS_SRC
let container
await act(async () => {
container = render(
<Router>
<Provider store={store}>
<AsyncImage
src={logo}
alt="test.png"
className='img-fluid img-catalogCards'
/>
</Provider>
</Router>
)
})
})
At the beginning of the unit test add the image class, it enters the setTimeout but still does not enter the handleLoad function
How do I get to the handleLoad function when loading the image so that it enters the return section of the image?

Related

Test a local variable in jest/enzyme with conditional rendering

I have a component that renders either a sign in comp or an iframe based on a variable that is set to true or not. The variable will be true if the endpoint(using useParams hook) state is "signIn".
I'm having trouble trying to set or test this in Jest. I've tried setting a variable in jest const isSignIn = true and using props but it's still just showing the iframe comp.
The JSX
const myComp = () => {
const { target } = useParams();
const navigate = useNavigate();
const [endpoint, setEndpoint] = useState(null);
const isSignIn = endpoint === "signIn";
useEffect(() => {
if (Object.values(targetEndpoints).includes(`${BaseURL}/${target}`)) {
setEndpoint(`${BaseURL}/${target}`);
} else {
navigate(`${SignInURL}`);
}
}, [target, navigate]);
console.log({ isSignIn });
return (
<section className="container">
{isSignIn ? (
<SignIn />
) : (
<>
<div className="child__container">
<iframe
className="iframe"
src={`${iframe_URL}/${iframeEndpoints[endpoint]}`}
title="my_iframe"
></iframe>
</div>
</>
)}
</section>
);
};
The Test
import React from "react";
import * as ReactRouterDom from "react-router-dom";
import MyComp from ".";
import SignIn from "#Src/routes/SignIn/SignIn";
import { mount } from "enzyme";
describe("<MyComp/>", () => {
const mockSetState = jest.fn();
jest.mock("react", () => ({
useState: (initial) => [initial, mockSetState],
}));
const comp = mount(<MyComp />);
beforeEach(() => {
jest.spyOn(ReactRouterDom, "useParams").mockImplementation(() => {
return { target: "signIn" };
});
});
it("Should render SignIn comp", () => {
//Below just shows the iframe and not the sign in comp
console.log(comp.debug())
});
});

Get AdSense Ads to Reload on Route Change in Next JS

We've implemented Google AdSense on our Next.JS website, but we'd like to have ads reload whenever the route changes.
Here is what I tried:
const AdBlock = props => {
const { userLoaded, currentUserIsSilverPlus } = useAuth()
useEffect(() => {
window.adsbygoogle = window.adsbygoogle || []
window.adsbygoogle.push({})
}, [])
const reloadAds = () => {
const { googletag } = window
if (googletag) {
googletag.cmd.push(function () {
googletag.pubads().refresh()
})
}
}
Router.events.on("routeChangeStart", reloadAds)
if (userLoaded && !currentUserIsSilverPlus) {
return (
<ins
className="adsbygoogle adbanner-customize"
style={{ display: "block" }}
data-ad-client="ca-pub-1702181491157560"
data-ad-slot={props.slot}
data-ad-format={props.format}
data-full-width-responsive={props.responsive}
/>
)
}
return ""
}
export default AdBlock
The ads load, however, they never appear to refresh. What am I missing?
My solution was to observe router event changes, when routeChangeStart ads had to be unmounted, when routeChangeComplete they had to be mounted remounted.
// components/Adsense.tsx
function Ads() {
const adsRef = useRef<HTMLModElement | null>(null);
useEffect(() => {
const executeWindowAds = () => {
try {
// #ts-ignore
(adsbygoogle = window.adsbygoogle || []).push({});
} catch (error: any) {
console.error('error ads======>', error.message);
}
};
const insHasChildren = adsRef.current?.childNodes.length;
if (!insHasChildren) {
executeWindowAds();
}
}, []);
return (
<ins
ref={adsRef}
className="adsbygoogle"
style={{ display: 'block' }}
data-ad-client="ca-pub-xxx"
data-ad-slot="xxx"
data-ad-format="auto"
data-full-width-responsive="true"
></ins>
);
}
export default function Adsense() {
const router = useRouter();
const [shouldMount, setShouldMount] = useState(true);
useEffect(() => {
const onRouteChangeStart = () => setShouldMount(false);
const onRouteChangeComplete = () => setShouldMount(true);
router.events.on('routeChangeStart', onRouteChangeStart);
router.events.on('routeChangeComplete', onRouteChangeComplete);
return () => {
router.events.off('routeChangeStart', onRouteChangeStart);
router.events.off('routeChangeComplete', onRouteChangeComplete);
};
}, [router.events]);
return shouldMount ? <Ads /> : null;
}
On your layout page, for example a sidebar where each page has the same sidebar:
// components/LayoutBlogPost.tsx
import dynamic from 'next/dynamic';
const Adsense = dynamic(() => import('./Adsense'), { ssr: false });
export default function LayoutBlogPost({children}: {children: any}) {
return (
<div>
{children}
<Adsense />
</div>
);
}
try useRouter hook and handling router events inside useEffect:
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter();
useEffect(() => {
router.events.on('routeChangeError', handleChange)
}, [])
return (
<>
</>
)
}

Cannot read properties of undefined (reading 'find') in Jest and Enzyme React 17

I am working through a react app using v17. I have a component that adds an expense. The functionality in the component works as expected in the GUI but when I try to test it using jest/enzyme it throws an error of TypeError: Cannot read properties of undefined (reading 'find'). In the GUI it finds the expense I am trying to edit without issue. Am I not testing it correctly when trying to match a snapshot?
Edit Expense Component
import React from "react";
import { useParams } from "react-router-dom";
import { connect } from "react-redux";
import { ExpenseForm } from "./ExpenseForm";
import { editExpense, removeExpense } from "../actions/expenses";
//React router
import { useNavigate } from "react-router-dom";
export const EditExpensePage = (props) => {
const navigate = useNavigate();
const { expenseID } = useParams();
const foundExpense = props.expenses.find(
(expense) => expense.id === expenseID
);
return (
<div>
<h1>Edit Expense</h1>
<ExpenseForm
expense={foundExpense}
onSubmit={(expense) => {
// //Edit expense action expects 2 params (id, updates)
props.editExpense(expenseID, expense);
// //Redirect to dashboard
navigate("/");
}}
/>
<button
onClick={(e) => {
e.preventDefault();
props.removeExpense(expenseID);
navigate("/");
}}
>
Remove Expense
</button>
</div>
);
};
const mapStateToProps = (state, props) => ({
expenses: state.expenses
});
const mapDispatchToProps = (dispatch) => ({
editExpense: (id, expense) => dispatch(editExpense(id, expense)),
removeExpense: (id) => dispatch(removeExpense(id))
});
export default connect(mapStateToProps, mapDispatchToProps)(EditExpensePage);
Current Test
import React from "react";
import { shallow } from "enzyme";
import { EditExpensePage } from "../../components/EditExpensePage";
import { testExpenses } from "../fixtures/expenses";
let history, editExpense, removeExpense, wrapper;
//Mock for use navigate
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate
}));
const setup = (props) => {
const component = shallow(
<EditExpensePage
{...props}
expense={editExpense}
history={history}
removeExpense={removeExpense}
/>
);
return {
component: component
};
};
describe("EditForm component", () => {
beforeEach(() => {
setup();
});
test("should render EditExpensePage", () => {
expect(wrapper).toMatchSnapshot();
});
});
Updated editExpense value in test
const setup = (props) => {
//editExpense = testExpenses[1]; //Same error
editExpense = jest.fn(); //Same error
let removeExpense = jest.fn();
let history = jest.fn();
const component = shallow(
<EditExpensePage
{...props}
expense={editExpense}
history={history}
removeExpense={removeExpense}
/>
);
return {
component: component
};
};
Updated Test File
import React from "react";
import { shallow } from "enzyme";
import { EditExpensePage } from "../../components/EditExpensePage";
import { testExpenses } from "../fixtures/expenses";
let editExpense, expenseID, removeExpense, wrapper;
//Mock for use navigate
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate
}));
const setup = (props) => {
expenseID = 1;
editExpense = [testExpenses.find((expense) => expense.id === expenseID)];
console.log(editExpense);
//Output from this console log
// [
// {
// id: 1,
// description: 'Wifi payment',
// note: 'Paid wifi',
// amount: 10400,
// createdAt: 13046400000
// }
// ]
const component = shallow(
<EditExpensePage
{...props}
expense={editExpense}
removeExpense={removeExpense}
/>
);
return {
component: component
};
};
describe("EditForm component", () => {
beforeEach(() => {
setup();
});
test("should render EditExpensePage", () => {
expect(wrapper).toMatchSnapshot();
});
});
Updated your code
import React from "react";
import { shallow } from "enzyme";
import { EditExpensePage } from "../../components/EditExpensePage";
import { testExpenses } from "../fixtures/expenses";
let history, editExpense, removeExpense, wrapper;
//Mock for use navigate
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate
}));
const setup = (props) => {
editExpense = testExpenses; //it should be an array like this [{ id: 1 }]
const component = shallow(
<EditExpensePage
{...props}
expenses={editExpense}
history={history}
removeExpense={removeExpense}
/>
);
return {
component: component
};
};
describe("EditForm component", () => {
beforeEach(() => {
setup();
});
test("should render EditExpensePage", () => {
expect(wrapper).toMatchSnapshot();
});
});
Your EditExpensePage is calling props.expenses, but in your test cases, you never set it up.
You only introduce it here
let history, editExpense, removeExpense, wrapper;
but you haven't set the value for editExpense which means it's undefined.
That's why undefined.find throws an error.
I'd suggest you set a mocked value for editExpense.

DOM doesn't update when I make event changes in jest

Basically, the component's operation is to change the screen and click on the menu button, which has the same event mentioned in the test: somaTabsChange
However, when I run the test, it fails to change the DOM with the new elements.
Basically, this is log i get:
TestingLibraryElementError: Unable to find a label with the text of:
Criar menu
Test
it('should render the menu when save a modal', async () => {
getPageManagerInfo();
nock(env.SERVICE_URL)
.persist()
.post('/modules/cards/menu', {
name: 'menus',
icon: 'menus',
})
.reply(200, {
moduleMenu: {
pages: [],
_id: '61d33fd3b922dd0011ddc024',
icon: 'menus',
name: 'menus',
__v: 0,
},
});
const history = createMemoryHistory();
history.push('/manager/module/cards');
const renderResult = render(
<Router history={history}>
<Route path="/manager/module/:id" exact>
<PageManager />
</Route>
</Router>,
);
await changeTabs(renderResult, 'menus', 'menus');
await callModalForm({
render: renderResult,
buttonLabel: 'Criar menu',
buttonConfirmLabel: 'Salvar',
events: async () => {
await changeInput(renderResult, 'Nome', 'menus');
await changeInput(renderResult, 'Ícone', 'menus');
},
});
await callButton(renderResult, 'Fechar modal');
});
Component
import React, { useEffect, useState } from 'react';
import { useParams, useHistory } from 'react-router-dom';
import { SomaTab } from '#soma/react';
import { getModule } from 'services/ModuleService';
import Module from 'models/ModuleV2';
import LoaderFull from 'components/LoaderFull';
import ListPages from './container/ListPages';
import ListMenus from './container/ListMenus';
import { StyledTabs } from './styles';
const PageManager: React.FC = () => {
const history = useHistory();
const { id } = useParams<{ id: string }>();
const [loading, setLoading] = useState(true);
const [module, setModule] = useState<Module>();
const [tab, setTab] = React.useState<string>('pages');
const getData = async (): Promise<void> => {
const result = await getModule(id);
if (result.data) {
setModule(result.data);
} else {
history.push('/404');
}
setLoading(false);
};
useEffect(() => {
getData();
}, []);
if (loading) {
return <LoaderFull />;
}
return (
<>
<StyledTabs value={tab} onSomaTabsChange={e => setTab(e.detail)}>
<SomaTab value="pages">Páginas</SomaTab>
<SomaTab aria-label="menus" value="menus">
menus
</SomaTab>
</StyledTabs>
{tab === 'pages' ? (
<ListPages module={module!} onUpdate={getData} />
) : (
<ListMenus module={module!} onUpdate={getData} />
)}
</>
);
};
export default PageManager;

jest.doMock not calling mock function

This is my component to be tested.
// WelcomePgaeContainer.jsx
import React, { useEffect, useState } from "react";
import WelcomePage from "./WelcomePage";
import api from "../../../api";
import apiUrl from "../../../apiUrls";
import { Loader } from "../../../shared/components";
const WelComePageContainer = () => {
const [welcomeData, updateWelcomeData] = useState({});
useEffect(() => {
api.get(apiUrl.WELCOME_PAGE.GET_WELCOME_DATA).then(({ data }) => {
updateWelcomeData(data);
});
}, []);
return (
<div>
{Object.keys(welcomeData).length ? (
<WelcomePage />
) : (
<div data-testid="welcome-page-loader" className={styles.loaderContainer}>
<Loader size={5} />
</div>
)}
</div>
);
};
export default WelComePageContainer;
// api.js
import axios from "axios";
const api = axios.create();
api.defaults.headers.post["Content-Type"] = "application/json";
api.interceptors.request.use(
config => {
config.withCredentials = false;
return config;
},
error => Promise.reject(error)
);
api.interceptors.response.use(
response => response,
error => {
return Promise.reject(error.response);
}
);
export default API;
// Welcome.test.js
it("test welcome page is loaded and async data call is made once", async () => {
jest.doMock("../../src/api", () => {
const mock = new MockAdapter(axios);
mock.onGet(apiUrl.WELCOME_PAGE.GET_WELCOME_DATA).reply(200, WELCOME_DATA);
});
const history = createMemoryHistory();
const { findByTestId } = render(
<Router history={history}>
<WelComePageContainer />
</Router>
);
const welcomeViewElement = await findByTestId("welcome-data-container");
expect(welcomeViewElement).toBeTruthy();
});
I am trying to mock import api from "../../../api"; of WelcomePgaeContainer.jsx in my test file. However, when calling from Welcome.test.js the actual is getting invoked rather than the mocked one.

Resources