I am trying to write a test for a formik form to get submitted after I have filled the form correctly. I am using Material UI auto complete and Text Field for this. I have created a dynamic component for both Textfield and Autocomplete field. Now the issue I am facing is that I am unable to access the value options from the dropdown.
Here is my test file
import React from "react";
import {
render,
fireEvent,
screen,
waitFor,
within,
act,
} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import Step1 from "../Step1";
describe("Step1 Component", () => {
const handleNext = jest.fn();
// Mock handleNext function
// Mock useFormik hook
jest.mock("formik", () => ({
useFormik: jest.fn(() => ({
handleSubmit: jest.fn(),
isValid: true, // Mock the isValid property to return true
values: {}, // You may need to add values if your form relies on them
})),
}));
beforeEach(() => {
render(<Step1 handleNext={handleNext} />);
});
test("form renders without crashing", () => {
const formElement = screen.getByTestId("step1-form");
expect(formElement).toBeInTheDocument();
});
test("form renders validations", () => {
const nextButton = screen.getByTestId("next-button");
fireEvent.click(nextButton);
const validation1 = screen.getByText("First name is required");
const validation2 = screen.getByText("Last name is required");
expect(validation1).toBeInTheDocument();
expect(validation2).toBeInTheDocument();
});
test("rendering and submitting a basic Formik form", async () => {
const user = userEvent.setup();
await user.type(
screen.getByRole("textbox", { name: "First Name" }),
"John"
);
await user.type(screen.getByRole("textbox", { name: "Last Name" }), "Dee");
const selectLabel1 = "Pakistan";
const selectLabel2 = "Master";
const dropdown1 = screen.getByRole("combobox", { name: "Country" });
userEvent.click(dropdown1);
// Locate the corresponding popup (`listbox`) of options.
const optionsPopupEl = await screen.findByRole("option", {
name: selectLabel1,
});
const dropdown2 = screen.getByRole("combobox", { name: "Degree" });
userEvent.click(dropdown2);
// Locate the corresponding popup (`listbox`) of options.
const optionsPopupEl2 = await screen.findByRole("option", {
name: selectLabel2,
});
console.log(optionsPopupEl2, "OPT");
// Click an option in the popup.
userEvent.click(within(optionsPopupEl).findByText("Pakistan"));
fireEvent.change(dropdown1, { target: { value: "Pakistan" } });
// userEvent.click(within(optionsPopupEl2).findByText("Master"));
// userEvent.click(optionsPopupEl);
// userEvent.click(optionsPopupEl2);
await user.click(screen.getByTestId("next-button"));
// Assert that handleNextMock has been called
await waitFor(() => expect(handleNext).toHaveBeenCalled());
});
});
This is the form I am performing the tests on
import React from "react";
import { Box, Grid } from "@mui/material";
// import Grid from "@mui/material/Unstable_Grid2/Grid2";
import { useFormik } from "formik";
import { InputField, AutoCompleteField } from "../../../../components/fields";
import FormHeader from "../FormHeader/FormHeader";
import { useValidationSchema, useInitialValues } from "./utils";
const Step1 = (props) => {
const {
loading,
submitLoading,
stepsArray,
activeStep,
handleBack,
handleNext,
} = props;
const formik = useFormik({
initialValues: useInitialValues(),
validationSchema: useValidationSchema(),
enableReinitialize: true,
validateOnMount: true,
onSubmit: (values) => {
console.log(values, "submit");
},
});
const handleNextClick = () => {
formik.handleSubmit();
if (formik?.isValid) {
handleNext();
}
};
const handleBackClick = () => {
handleBack();
};
const countryOptions = [
{ label: "Pakistan", value: "Pakistan" },
{ label: "India", value: "India" },
{ label: "England", value: "England" },
{ label: "USA", value: "USA" },
];
const degreeOptions = [
{ label: "Bachelor", value: "Bachelor" },
{ label: "Master", value: "Master" },
{ label: "PHD", value: "PHD" },
];
return (
<Box sx={{ background: "white", p: 3 }}>
<FormHeader
loading={loading}
handleBack={handleBackClick}
handleNext={handleNextClick}
activeStep={activeStep}
title={"Step 1"}
steps={stepsArray}
/>
<Box sx={{ p: 2, pt: 3, pointerEvents: submitLoading ? "none" : "auto" }}>
<form data-testid="step1-form">
<Grid container spacing={2}>
<Grid xs={6}>
<InputField
formik={formik}
name="firstName"
type="text"
loading={loading}
required
label="First Name"
/>
</Grid>
<Grid xs={6}>
<InputField
formik={formik}
name="lastName"
type="text"
loading={loading}
required
label="Last Name"
/>
</Grid>
<Grid xs={6}>
<AutoCompleteField
data-testid="country"
name="Country"
formik={formik}
loading={loading}
label="Country"
options={countryOptions}
required
/>
</Grid>
<Grid xs={6}>
<AutoCompleteField
data-testid="degree"
name="Degree"
formik={formik}
loading={loading}
label="Degree"
options={degreeOptions}
required
/>
</Grid>
</Grid>
</form>
</Box>
</Box>
);
};
export default React.memo(Step1);
Here is the Custom Autocomplete I have created
import React from "react";
// import {
// Autocomplete,
// TextField,
// FormControl,
// Skeleton,
// } from "src/shared/material";
import {
Autocomplete,
TextField,
FormControl,
Skeleton,
Box,
Typography,
} from "@mui/material";
import { useState } from "react";
import { useEffect } from "react";
const AutoCompleteField = ({
formik,
name,
label,
options,
required,
loading,
}) => {
const [defaultValue, setDefaultValue] = useState("Select a Country");
useEffect(() => {
if ((name && !defaultValue) || formik?.values[name] === "")
setDefaultValue(formik.values[name]);
}, [formik, name]);
return (
<>
{loading ? (
<Skeleton variant="rounded" width={"100%"} height={55} />
) : (
<FormControl
key={name}
fullWidth
error={Boolean(formik?.touched[name] && formik?.errors[name])}
sx={{ pointerEvents: "auto" }}
>
<Autocomplete
data-testid="dropdown-field"
id={name}
error={Boolean(formik?.touched?.[name] && formik?.errors?.[name])}
helperText={formik?.touched?.[name] && formik?.errors?.[name]}
options={options}
getOptionLabel={(option) => option.label} // Specify how to extract the label
onChange={(e, val) => {
formik?.setFieldValue(name, val ? val.value : ""); // Adjust for the case when value is null
}}
renderOption={(props, option) => (
<Box
display="flex"
component="li"
flexDirection="row"
alignItems="center"
{...props}
>
<Typography variant="body2">{option.label}</Typography>
</Box>
)}
renderInput={(params) => (
<TextField
{...params}
name={name}
label={label}
variant="outlined"
fullWidth
inputProps={{
...params.inputProps,
autoComplete: "new-password", // disable autocomplete and autofill
}}
/>
)}
/>
</FormControl>
)}
</>
);
};
export default React.memo(AutoCompleteField);
I have tried different solutions but nothing has worked so far.