Issue
I have a header component that renders a form component:
import { useWeatherContext } from "../lib/Context/WeatherContext";
import HeaderForm from "./HeaderForm";
const Header = () => {
const { setWeather } = useWeatherContext();
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const target = e.target as typeof e.target & {
cityZip: { value: string };
};
setWeather({ location: target.cityZip.value });
target.cityZip.value = "";
};
return (
<header>
<h2>Weathur</h2>
<HeaderForm handleSubmit={handleSubmit} />
</header>
);
};
export default Header;
import React from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
const HeaderForm = ({
handleSubmit,
}: {
handleSubmit: (e: React.FormEvent<HTMLFormElement>) => void;
}) => {
return (
<form
data-testid="city-form"
autoComplete="off"
id="search-form"
onSubmit={(e) => {
handleSubmit(e);
}}
>
<input
data-testid="city-input"
type="text"
name="cityZip"
placeholder="City or Zip..."
aria-label="City or Zip Code Input"
required
onInvalid={(e) => {
const target = e.target as HTMLInputElement;
target.setCustomValidity("Please enter a valid city or zip code.");
}}
onInput={(e) => {
const target = e.target as HTMLInputElement;
target.setCustomValidity("");
}}
/>
<button
data-testid="city-form-button"
type="submit"
aria-label="Submit Button for Search"
>
<FontAwesomeIcon icon={faMagnifyingGlass} />
</button>
</form>
);
};
export default HeaderForm;
Previously, I had this form within the header, but then extracted it because I could not figure out how to test the handleSubmit() function. So after extracting the form, and passing handleSubmit as a prop, my test looks like this (and passes):
import { screen, fireEvent, waitFor, render } from "@testing-library/react";
import { WeatherContextProvider } from "../src/lib/Context/WeatherContext";
import { ErrorContextProvider } from "../src/lib/Context/ErrorContext";
import { LoadingContextProvider } from "../src/lib/Context/LoadingContext";
import HeaderForm from "../src/components/HeaderForm";
const handleSubmitMock = vi.fn();
const renderElements = () => {
render(
<WeatherContextProvider>
<ErrorContextProvider>
<LoadingContextProvider>
<HeaderForm handleSubmit={handleSubmitMock} />
</LoadingContextProvider>
</ErrorContextProvider>
</WeatherContextProvider>
);
};
describe("Header", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("should trigger handleSubmit when form is submitted", async () => {
renderElements();
const input = screen.getByTestId("city-input");
const button = screen.getByTestId("city-form-button");
fireEvent.change(input, { target: { value: "11102" } });
fireEvent.click(button);
await waitFor(() => {
expect(handleSubmitMock).toHaveBeenCalledTimes(1);
});
});
it("should not allow a blank input to be submitted", async () => {
renderElements();
const input = screen.getByTestId("city-input");
const button = screen.getByTestId("city-form-button");
fireEvent.change(input, { target: { value: "" } });
fireEvent.click(button);
await waitFor(() => {
expect(handleSubmitMock).not.toHaveBeenCalledTimes(1);
});
});
});
I'm confused why I needed to extract my form in the first place. Is there a way to access handleSubmit within Header()? I am using Vitest and React Testing Library for this, along with React, Vite, and Typescript.
I appreciate any help, if more code is necessary, here is the github repo!
Solution
Is there a way to access handleSubmit within Header()?
No, it's an internal function not exposed to you or available to be mocked. In order to be able to test it, you need to be able to pass it as a prop. This is an incredibly powerful concept referred to as dependency injection (in functional programming). Whenever you need to be able to test something that is "internal" to a function, pass it as an argument.
Your intuition was correct, remember this pattern and use it frequently, can't overstate it's importance.
Answered By - Adam Jenkins
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.