I am trying to test a functional component in that functional component when I do the resize of the window I am calculating the height using useLayoutEffect.
The feature is working, but I was not able to find a proper doc for testing with hooks.
So what I have tried is
app.test.js
import React from "react";
import { shallow } from "enzyme";
import App from "..";
describe("App Page", () => {
it("should render App Page", () => {
const wrapper = shallow(<App />);
expect(wrapper).toMatchSnapshot();
});
it("should adjust the height on window resize", () => {
const wrapper = shallow(<App />);
global.innerHeight = 600;
global.dispatchEvent(new Event("resize"));
console.log(wrapper.debug()); // how can i test the useLayoutEffect
});
});
app.js
import React, { useLayoutEffect, useState, useEffect, useRef } from "react";
import { Layout } from "antd";
const { Header } = Layout;
function useWindowSize() {
const isClient = typeof window === "object";
function getSize() {
return {
width: isClient ? window.innerWidth : undefined,
height: isClient ? window.innerHeight : undefined,
};
}
const [windowSize, setWindowSize] = useState(getSize);
useEffect(() => {
if (!isClient) {
return false;
}
function handleResize() {
setWindowSize(getSize());
}
window.addEventListener("resize", handleResize);
return () => window.removeEventListener("resize", handleResize);
}, []); // Empty array ensures that effect is only run on mount and unmount
return windowSize;
}
const App = () => {
const headerRef = useRef(null);
const size = useWindowSize();
const [barHeight, setBarHeight] = useState(56);
useLayoutEffect(() => {
setBarHeight(headerRef.current.offsetHeight);
}, [size]);
return (
<Layout className="layout">
<HeaderContainer ref={headerRef}>
<Header>.....</Header>
</HeaderContainer>
</Layout>
);
};
export default App;
Should I use https://www.npmjs.com/package/#testing-library/react-hooks for these or is there any way to test it using Enzyme itself.
Related
hello I'm writing test for a component with ref. I'd like to mock the ref element and change some properties but have no idea how to. Any suggestions?
import { test, expect } from "#playwright/experimental-ct-react";
import { useState } from "react";
import { useRef } from "react";
import Main from "./index";
test.use({ viewport: { width: 500, height: 500 } });
test("should work", async ({ mount }) => {
const refMain = useRef();
const refFooter = useRef();
const refMenuButton = useRef();
const [mainPostion, setMainPostion] = useState(startMainPostion);
const refContainerForMainFooter = useRef({
refMain,
refFooter,
refMenuButton,
});
const component = await mount(
<Main setMainPostion={setMainPostion} theme='light' ref={ref} />
);
// await expect(component).toContainText('DAMAN');
});
I am writing tests for components of my projects and one of components is changing context value. How can I check if click actually changes context value? Code I have right now gives error "Invalid hook call. Hooks can only be called inside of the body of a function component." What is the way to actually use hooks in tests?
import { useState, useContext } from "react";
import { IntlProvider } from "react-intl";
import { BrowserRouter } from "react-router-dom";
import { render, screen, fireEvent } from "#testing-library/react";
import { messages } from "../../App/App.const";
import Navbar from "./Navbar";
import { DarkModeContext } from "../../contexts/DarkModeContext";
function NavbarMock() {
const [search, setSearch] = useState("");
const [language, setLanguage] = useState("en");
return (
<IntlProvider
messages={messages[language as keyof typeof messages]}
locale={language}
defaultLocale="en"
>
<BrowserRouter>
<Navbar
setLanguage={setLanguage}
language={language}
setSearch={setSearch}
search={search}
/>
</BrowserRouter>
</IntlProvider>
);
}
describe("testing navbar component", () => {
test("renders logo correctly", async () => {
render(<NavbarMock />);
const logo = screen.getByText(/Todoly/i);
expect(logo).toBeInTheDocument();
});
test("renders mode icon correctly", async () => {
render(<NavbarMock />);
const svgEl = screen.getByTitle("mode icon");
expect(svgEl).toBeInTheDocument();
});
test("mode changes", async () => {
render(<NavbarMock />);
const svgEl = screen.getByTitle("mode icon");
const { isDarkMode } = useContext(DarkModeContext);
fireEvent.click(svgEl);
expect(isDarkMode).toBe(true);
});
test("renders language icon correctly", async () => {
render(<NavbarMock />);
const flagEl = screen.getByAltText("en");
expect(flagEl).toBeInTheDocument();
});
});
Ok, the error isn't caused by any of the provider, instead it's caused by the way you write useContext.
A hook can't be used unless it's referenced inside a Component, this is the RULE by React hooks. Because states defined by hooks are meaningless outside.
const AnotherNavbarMock = () => {
const { isDarkMode } = useContext(DarkModeContext);
return <NavbarMock />
}
The above code is the working example. Basically a test isn't a component, you have to define a component and then use it via <AnotherNavbarMock />, otherwise all the hook code would fail.
We have a functionality, when the content is not visible on the screen then we scroll content till the end of content. here is the hooks i have written. quite new to testing Lib it would be great if someone have already written unit test for something like this.
import { useEffect, useRef } from 'react';
export default function useOnScreen() {
const ref = useRef<HTMLElement | null>();
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
const scrollToView = (timer: number) => {
const scrollRef = ref?.current;
if (ref) {
timeoutRef.current && clearTimeout(timeoutRef.current);
timeoutRef.current = setTimeout(() => {
scrollRef?.current.scrollIntoView({
behavior: 'smooth',
block: 'end',
inline: 'end',
});
}, timer);
}
};
return [ref, scrollToView];
}
Need help here to write test case
import useOnScreen from '../useOnScreen';
import {renderHook, act} from '#testing-library/react-hooks';
describe('useOnScreen', () => {
it('useOnScreen', () => {
const [ref, scrollToView] = renderHook(() => useOnScreen());
});
});
I have the following component, where I create ref on nav to close the menu on click outside of nav:
import { useState, useEffect, useRef, } from 'react';
const Header = () => {
const [menuOpen, setMenuOpen] = useState(false);
const navRef = useRef(null);
const hideMenu = () => setMenuOpen(false);
const handleClick = event => {
if (menuOpen && !navRef.current.contains(event.target)) {
hideMenu();
}
};
useEffect(() => {
document.addEventListener('click', handleClick);
return () => {
document.removeEventListener('click', handleClick);
};
});
return (
<header className="header">
<nav className="header-nav" ref={navRef}>
...
</nav>
</header>
);
};
export default Header;
And this is the unit test:
import React from 'react';
import '#testing-library/jest-dom';
import { cleanup, fireEvent } from '#testing-library/react';
import renderer from 'react-test-renderer';
import Header from './Header';
const { act } = renderer;
afterEach(cleanup);
describe('Header', () => {
test('should open and close mobile menu', () => {
const headerComponent = renderer.create(<Header />);
const headerRoot = headerComponent.root;
const navContainer = headerRoot.findByType('nav');
act(() => {
// open menu
navContainer.children[0].props.onClick(new MouseEvent('click'));
});
act(() => {
// click on document
fireEvent(document, new MouseEvent('click'));
});
headerTree = headerComponent.toJSON();
expect(headerTree).toMatchSnapshot();
});
});
The test run results in the following error:
TypeError: Cannot read property 'contains' of null
26 |
27 | const handleClick = (event) => {
> 28 | if (menuOpen && !navRef.current.contains(event.target)) {
| ^
29 | hideMenu();
30 | }
31 | };
I have tried to mock ref.currrent but it's still null:
jest.spyOn(React, 'useRef').mockReturnValue({
current: navContainer,
});
Please advice how I can organize the test to be able to test this
P.S. I have found this answer but it doesn't suit me as I don't wanna pass ref as a prop: https://stackoverflow.com/a/59207195/3132457
I nav component then will toggle state in a sidebar as well as open and close a menu and then trying to get this pass in code coverage. When I log inside my test my state keeps showing up as undefined. Not sure how to tackle this one here.
Component.js:
const Navigation = (props) => {
const {
classes,
...navProps
} = props;
const [anchorEl, setanchorEl] = useState(null);
const [sidebarOpen, setsidebarOpen] = useState(false);
const toggleSidebar = () => {
setsidebarOpen(!sidebarOpen);
};
const toggleMenuClose = () => {
setanchorEl(null);
};
const toggleMenuOpen = (event) => {
setanchorEl(event.currentTarget);
};
return (
<Fragment>
<Button
onClick={toggleMenuOpen}
/>
<SideMenu
toggleSidebar={toggleSidebar}
>
<Menu
onClose={toggleMenuClose}
>
</SideMenu>
</Fragment>
);
};
export default Navigation;
Test.js:
import { renderHook, act } from '#testing-library/react-hooks';
// Components
import Navigation from './navigation';
test('sidebar should be closed by default', () => {
const newProps = {
valid: true,
classes: {}
};
const { result } = renderHook(() => Navigation({ ...newProps }));
expect(result.current.sidebarOpen).toBeFalsy();
});
Author of react-hooks-testing-library here.
react-hooks-testing-library is not for testing components and interrogating the internal hook state to assert their values, but rather for testing custom react hooks and interacting withe the result of your hook to ensure it behaves how you expect. For example, if you wanted to extract a useMenuToggle hook that looked something like:
export function useMenuToggle() {
const [anchorEl, setanchorEl] = useState(null);
const [sidebarOpen, setsidebarOpen] = useState(false);
const toggleSidebar = () => {
setsidebarOpen(!sidebarOpen);
};
const toggleMenuClose = () => {
setanchorEl(null);
};
const toggleMenuOpen = (event) => {
setanchorEl(event.currentTarget);
};
return {
sidebarOpen,
toggleSidebar,
toggleMenuClose,
toggleMenuOpen
}
}
Then you could test it with renderHook:
import { renderHook, act } from '#testing-library/react-hooks';
// Hooks
import { useMenuToggle } from './navigation';
test('sidebar should be closed by default', () => {
const newProps = {
valid: true,
classes: {}
};
const { result } = renderHook(() => useMenuToggle());
expect(result.current.sidebarOpen).toBeFalsy();
act(() => {
result.current.toggleSidebar()
})
expect(result.current.sidebarOpen).toBeTruthy();
});
Generally though, when a hook is only used by a single component and/or in a single context, we recommend you simply test the component and allow the hook to be tested through it.
For testing your Navigation component, you should take a look at react-testing-library instead.
import React from 'react';
import { render } from '#testing-library/react';
// Components
import Navigation from './navigation';
test('sidebar should be closed by default', () => {
const newProps = {
valid: true,
classes: {}
};
const { getByText } = render(<Navigation {...newProps} />);
// the rest of the test
});