0

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!

0

1 Answer 1

0

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.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.