Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(test): react hook form tests & stories #2931

Merged
merged 14 commits into from
May 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
105 changes: 104 additions & 1 deletion packages/components/autocomplete/__tests__/autocomplete.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from "react";
import {act, render} from "@testing-library/react";
import {render, renderHook, act} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {useForm} from "react-hook-form";

import {Autocomplete, AutocompleteItem, AutocompleteSection} from "../src";
import {Modal, ModalContent, ModalBody, ModalHeader, ModalFooter} from "../../modal/src";
Expand Down Expand Up @@ -220,3 +221,105 @@ describe("Autocomplete", () => {
expect(autocomplete).toHaveAttribute("aria-expanded", "false");
});
});

describe("Autocomplete with React Hook Form", () => {
let autocomplete1: HTMLInputElement;
let autocomplete2: HTMLInputElement;
let autocomplete3: HTMLInputElement;
let submitButton: HTMLButtonElement;
let wrapper: any;
let onSubmit: () => void;

beforeEach(() => {
const {result} = renderHook(() =>
useForm({
defaultValues: {
withDefaultValue: "cat",
withoutDefaultValue: "",
requiredField: "",
},
}),
);

const {
handleSubmit,
register,
formState: {errors},
} = result.current;

onSubmit = jest.fn();

wrapper = render(
<form className="flex w-full max-w-xs flex-col gap-2" onSubmit={handleSubmit(onSubmit)}>
<Autocomplete
data-testid="autocomplete-1"
{...register("withDefaultValue")}
aria-label="Favorite Animal"
items={itemsData}
label="Favorite Animal"
>
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
</Autocomplete>
<Autocomplete
data-testid="autocomplete-2"
{...register("withoutDefaultValue")}
aria-label="Favorite Animal"
items={itemsData}
label="Favorite Animal"
>
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
</Autocomplete>
<Autocomplete
data-testid="autocomplete-3"
{...register("requiredField", {required: true})}
aria-label="Favorite Animal"
items={itemsData}
label="Favorite Animal"
>
{(item) => <AutocompleteItem key={item.value}>{item.label}</AutocompleteItem>}
</Autocomplete>
{errors.requiredField && <span className="text-danger">This field is required</span>}
<button data-testid="submit-button" type="submit">
Submit
</button>
</form>,
);

autocomplete1 = wrapper.getByTestId("autocomplete-1");
autocomplete2 = wrapper.getByTestId("autocomplete-2");
autocomplete3 = wrapper.getByTestId("autocomplete-3");
submitButton = wrapper.getByTestId("submit-button");
});

it("should work with defaultValues", () => {
expect(autocomplete1).toHaveValue("Cat");
expect(autocomplete2).toHaveValue("");
expect(autocomplete3).toHaveValue("");
});

it("should not submit form when required field is empty", async () => {
const user = userEvent.setup();

await user.click(submitButton);

expect(onSubmit).toHaveBeenCalledTimes(0);
});

it("should submit form when required field is not empty", async () => {
const user = userEvent.setup();

await user.click(autocomplete3);

expect(autocomplete3).toHaveAttribute("aria-expanded", "true");

let listboxItems = wrapper.getAllByRole("option");

await user.click(listboxItems[1]);

expect(autocomplete3).toHaveValue("Dog");

await user.click(submitButton);

expect(onSubmit).toHaveBeenCalledTimes(1);
});
});
3 changes: 2 additions & 1 deletion packages/components/autocomplete/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@
"framer-motion": "^11.0.28",
"clean-package": "2.2.0",
"react": "^18.0.0",
"react-dom": "^18.0.0"
"react-dom": "^18.0.0",
"react-hook-form": "^7.51.3"
},
"clean-package": "../../../clean-package.config.json"
}
110 changes: 79 additions & 31 deletions packages/components/autocomplete/stories/autocomplete.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type {ValidationResult} from "@react-types/shared";

import React, {Key} from "react";
import {Meta} from "@storybook/react";
import {useForm} from "react-hook-form";
import {autocomplete, input, button} from "@nextui-org/theme";
import {
Pokemon,
Expand Down Expand Up @@ -686,6 +687,45 @@ const CustomStylesWithCustomItemsTemplate = ({color, ...args}: AutocompleteProps
);
};

const WithReactHookFormTemplate = (args: AutocompleteProps) => {
const {
register,
formState: {errors},
handleSubmit,
} = useForm({
defaultValues: {
withDefaultValue: "cat",
withoutDefaultValue: "",
requiredField: "",
},
});

const onSubmit = (data: any) => {
// eslint-disable-next-line no-console
console.log(data);
alert("Submitted value: " + JSON.stringify(data));
};

return (
<form className="flex w-full max-w-xs flex-col gap-2" onSubmit={handleSubmit(onSubmit)}>
<Autocomplete {...args} {...register("withDefaultValue")}>
{items}
</Autocomplete>
<Autocomplete {...args} {...register("withoutDefaultValue")}>
{items}
</Autocomplete>
<Autocomplete {...args} {...register("requiredField", {required: true})}>
{items}
</Autocomplete>

{errors.requiredField && <span className="text-danger">This field is required</span>}
<button className={button({class: "w-fit"})} type="submit">
Submit
</button>
</form>
);
};

export const Default = {
render: Template,
args: {
Expand Down Expand Up @@ -733,15 +773,6 @@ export const DisabledOptions = {
},
};

export const WithDescription = {
render: MirrorTemplate,

args: {
...defaultProps,
description: "Select your favorite animal",
},
};

export const LabelPlacement = {
render: LabelPlacementTemplate,

Expand Down Expand Up @@ -782,6 +813,27 @@ export const EndContent = {
},
};

export const IsInvalid = {
render: Template,

args: {
...defaultProps,
isInvalid: true,
variant: "bordered",
defaultSelectedKey: "dog",
errorMessage: "Please select a valid animal",
},
};

export const WithDescription = {
render: MirrorTemplate,

args: {
...defaultProps,
description: "Select your favorite animal",
},
};

export const WithoutScrollShadow = {
render: Template,

Expand Down Expand Up @@ -847,67 +899,63 @@ export const WithValidation = {
},
};

export const IsInvalid = {
render: Template,
export const WithSections = {
render: WithSectionsTemplate,

args: {
...defaultProps,
isInvalid: true,
variant: "bordered",
defaultSelectedKey: "dog",
errorMessage: "Please select a valid animal",
},
};

export const Controlled = {
render: ControlledTemplate,
export const WithCustomSectionsStyles = {
render: WithCustomSectionsStylesTemplate,

args: {
...defaultProps,
},
};

export const CustomSelectorIcon = {
render: Template,
export const WithAriaLabel = {
render: WithAriaLabelTemplate,

args: {
...defaultProps,
disableSelectorIconRotation: true,
selectorIcon: <SelectorIcon />,
label: "Select an animal 馃惞",
"aria-label": "Select an animal",
},
};

export const CustomItems = {
render: CustomItemsTemplate,
export const WithReactHookForm = {
render: WithReactHookFormTemplate,

args: {
...defaultProps,
},
};

export const WithSections = {
render: WithSectionsTemplate,
export const Controlled = {
render: ControlledTemplate,

args: {
...defaultProps,
},
};

export const WithCustomSectionsStyles = {
render: WithCustomSectionsStylesTemplate,
export const CustomSelectorIcon = {
render: Template,

args: {
...defaultProps,
disableSelectorIconRotation: true,
selectorIcon: <SelectorIcon />,
},
};

export const WithAriaLabel = {
render: WithAriaLabelTemplate,
export const CustomItems = {
render: CustomItemsTemplate,

args: {
...defaultProps,
label: "Select an animal 馃惞",
"aria-label": "Select an animal",
},
};

Expand Down
74 changes: 73 additions & 1 deletion packages/components/checkbox/__tests__/checkbox.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import * as React from "react";
import {render, act} from "@testing-library/react";
import {render, renderHook, act} from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import {useForm} from "react-hook-form";

import {Checkbox, CheckboxProps} from "../src";

Expand Down Expand Up @@ -128,3 +129,74 @@ describe("Checkbox", () => {
expect(onChange).toBeCalled();
});
});

describe("Checkbox with React Hook Form", () => {
let checkbox1: HTMLInputElement;
let checkbox2: HTMLInputElement;
let checkbox3: HTMLInputElement;
let submitButton: HTMLButtonElement;
let onSubmit: () => void;

beforeEach(() => {
const {result} = renderHook(() =>
useForm({
defaultValues: {
withDefaultValue: true,
withoutDefaultValue: false,
requiredField: false,
},
}),
);

const {
handleSubmit,
register,
formState: {errors},
} = result.current;

onSubmit = jest.fn();

render(
<form className="flex flex-col gap-4" onSubmit={handleSubmit(onSubmit)}>
<Checkbox {...register("withDefaultValue")} />
<Checkbox {...register("withoutDefaultValue")} />
<Checkbox {...register("requiredField", {required: true})} />
{errors.requiredField && <span className="text-danger">This field is required</span>}
<button type="submit">Submit</button>
</form>,
);

checkbox1 = document.querySelector("input[name=withDefaultValue]")!;
checkbox2 = document.querySelector("input[name=withoutDefaultValue]")!;
checkbox3 = document.querySelector("input[name=requiredField]")!;
submitButton = document.querySelector("button")!;
});

it("should work with defaultValues", () => {
expect(checkbox1.checked).toBe(true);
expect(checkbox2.checked).toBe(false);
expect(checkbox3.checked).toBe(false);
});

it("should not submit form when required field is empty", async () => {
const user = userEvent.setup();

await user.click(submitButton);

expect(onSubmit).toHaveBeenCalledTimes(0);
});

it("should submit form when required field is not empty", async () => {
act(() => {
checkbox3.click();
});

expect(checkbox3.checked).toBe(true);

const user = userEvent.setup();

await user.click(submitButton);

expect(onSubmit).toHaveBeenCalledTimes(1);
});
});