Skip to content

Commit

Permalink
test(FormBuilder): check whether disabled is being respected
Browse files Browse the repository at this point in the history
  • Loading branch information
ribeirojose committed Apr 28, 2024
1 parent d980c85 commit d4720c7
Show file tree
Hide file tree
Showing 13 changed files with 133 additions and 58 deletions.
2 changes: 2 additions & 0 deletions src/components/FormBuilder/fields/CheckboxField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "#/components/ui/Form";

import { BaseField, withConditional } from "../fields";
import isFieldDisabled from "../isFieldDisabled";

export interface CheckboxFieldProps extends BaseField {
type: "checkbox";
Expand All @@ -31,6 +32,7 @@ export const CheckboxField = withConditional<CheckboxFieldProps>(
<Checkbox
checked={formField.value}
onCheckedChange={formField.onChange}
disabled={isFieldDisabled(form, field)}
/>
</FormControl>
<div className="space-y-1 leading-none">
Expand Down
2 changes: 2 additions & 0 deletions src/components/FormBuilder/fields/TextAreaField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
import { Textarea } from "#/components/ui/Textarea";

import { BaseField, withConditional } from "../fields";
import isFieldDisabled from "../isFieldDisabled";

export interface TextAreaFieldProps extends BaseField {
length?: {
Expand Down Expand Up @@ -47,6 +48,7 @@ export const TextAreaField = withConditional<TextAreaFieldProps>(
<FormDescription>{field.description}</FormDescription>
<FormControl>
<Textarea
disabled={isFieldDisabled(form, field)}
placeholder={field.placeholder}
className="resize-none"
{...formField}
Expand Down
80 changes: 37 additions & 43 deletions src/components/FormBuilder/fields/selects/SelectField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import * as Select from "#/components/ui/Select";

import { BaseField, withConditional } from "../../fields";
import isFieldDisabled from "../../isFieldDisabled";

export interface SelectFieldProps extends BaseField {
options?: Array<{
Expand All @@ -20,48 +21,41 @@ export interface SelectFieldProps extends BaseField {
}

export const SelectField = withConditional<SelectFieldProps>(
({ form, field }) => {
const disabled =
typeof field.disabled === "function"
? field.disabled(form.getValues())
: field.disabled;
({ form, field }) => (
<FormField
control={form.control}
name={field.name}
rules={field.required ? { required: true } : undefined}
render={({ field: formField }) => (
<FormItem className="w-full">
<FormLabel tooltip={field.tooltip}>{field.label}</FormLabel>
<FormDescription>{field.description}</FormDescription>
<Select.SelectRoot
onValueChange={formField.onChange}
defaultValue={String(formField.value)}
name={field.name}
disabled={isFieldDisabled(form, field)}
>
<FormControl>
<Select.SelectTrigger className="h-10 w-full rounded-md border dark:border-2 shadow-none">
<Select.SelectValue placeholder={field.placeholder} />
</Select.SelectTrigger>
</FormControl>
<Select.SelectContent className="z-[10000]">
{field.options?.map((option) => (
<Select.SelectItem
key={String(option.value)}
value={String(option.value)}
>
{option.label}
</Select.SelectItem>
))}
</Select.SelectContent>
</Select.SelectRoot>

return (
<FormField
control={form.control}
name={field.name}
rules={field.required ? { required: true } : undefined}
render={({ field: formField }) => (
<FormItem className="w-full">
<FormLabel tooltip={field.tooltip}>{field.label}</FormLabel>
<FormDescription>{field.description}</FormDescription>
<Select.SelectRoot
onValueChange={formField.onChange}
defaultValue={String(formField.value)}
name={field.name}
disabled={disabled}
>
<FormControl>
<Select.SelectTrigger className="h-10 w-full rounded-md border dark:border-2 shadow-none">
<Select.SelectValue placeholder={field.placeholder} />
</Select.SelectTrigger>
</FormControl>
<Select.SelectContent className="z-[10000]">
{field.options?.map((option) => (
<Select.SelectItem
key={String(option.value)}
value={String(option.value)}
>
{option.label}
</Select.SelectItem>
))}
</Select.SelectContent>
</Select.SelectRoot>

<FormMessage />
</FormItem>
)}
/>
);
}
<FormMessage />
</FormItem>
)}
/>
)
);
12 changes: 12 additions & 0 deletions src/components/FormBuilder/isFieldDisabled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { CommonFieldProps, FormFieldProps } from "./fields";

type FieldProps = CommonFieldProps<FormFieldProps>;

export default function isFieldDisabled(
form: FieldProps["form"],
field: FieldProps["field"]
) {
return typeof field.disabled === "function"
? field.disabled(form.getValues())
: field.disabled;
}
12 changes: 12 additions & 0 deletions tests/components/FormBuilder/CheckboxField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,18 @@ describe("CheckboxField", () => {
expect(checkboxElement).toBeInTheDocument();
});

it("renders the checkbox label correctly", () => {
renderFormField(CheckboxField, field);

Check failure on line 22 in tests/components/FormBuilder/CheckboxField.test.tsx

View workflow job for this annotation

GitHub Actions / Run Type Check & Linters

Expected 3 arguments, but got 2.
const labelElement = screen.getByText("Test Checkbox");
expect(labelElement).toBeInTheDocument();
});

it("disables the checkbox when the disabled prop is set to true", () => {
renderFormField(CheckboxField, { ...field, disabled: true });

Check failure on line 28 in tests/components/FormBuilder/CheckboxField.test.tsx

View workflow job for this annotation

GitHub Actions / Run Type Check & Linters

Expected 3 arguments, but got 2.
const checkboxElement = screen.getByRole("checkbox");
expect(checkboxElement).toBeDisabled();
});

it("updates the form state correctly when interacted with", () => {
const { form } = renderFormField(CheckboxField, field);

Check failure on line 34 in tests/components/FormBuilder/CheckboxField.test.tsx

View workflow job for this annotation

GitHub Actions / Run Type Check & Linters

Expected 3 arguments, but got 2.

Expand Down
7 changes: 7 additions & 0 deletions tests/components/FormBuilder/DatePickerInput.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ describe("DatePickerInput", () => {
expect(datePickerElement).toBeInTheDocument();
});

it("displays the selected date in the correct format", () => {
vi.setSystemTime(mockDate);
renderFormField(DatePickerInput, { ...field, defaultValue: mockDate });

Check failure on line 23 in tests/components/FormBuilder/DatePickerInput.test.tsx

View workflow job for this annotation

GitHub Actions / Run Type Check & Linters

Expected 3 arguments, but got 2.
const buttonElement = screen.getByRole("button");
expect(buttonElement).toHaveTextContent("Jan 1, 2022");
});

it("updates the form state correctly when a date is selected", () => {
vi.setSystemTime(mockDate);
const { form } = renderFormField(DatePickerInput, field);

Check failure on line 30 in tests/components/FormBuilder/DatePickerInput.test.tsx

View workflow job for this annotation

GitHub Actions / Run Type Check & Linters

Expected 3 arguments, but got 2.
Expand Down
14 changes: 14 additions & 0 deletions tests/components/FormBuilder/FormField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ describe("FormField", () => {
);
};

// it("renders the correct error message when validation fails", async () => {
// render(
// renderFormField({
// name: "test",
// rules: { required: "Field is required" },
// render: ({ field }) => <input {...field} />,
// })
// );
// const inputElement = screen.getByRole("textbox");
// fireEvent.blur(inputElement);
// const errorMessage = await screen.findByRole("alert");
// expect(errorMessage).toHaveTextContent("Field is required");
// });

it("renders the appropriate field component based on the render prop", () => {
render(
renderFormField({
Expand Down
11 changes: 11 additions & 0 deletions tests/components/FormBuilder/HiddenField.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, it, expect } from "vitest";
import { screen } from "@testing-library/react";
import { HiddenField } from "#/components";
import { renderFormField } from "../../../tests/helpers/renderFormField";

Expand All @@ -12,4 +13,14 @@ describe("HiddenField", () => {
const hiddenInput = form.getValues("test");
expect(hiddenInput).toBe("hiddenValue");
});

it("does not render any visible elements", () => {
renderFormField(HiddenField, {
type: "hidden",
name: "test",
value: "hiddenValue",
});
const hiddenInput = screen.queryByRole("textbox");
expect(hiddenInput).not.toBeInTheDocument();
});
});
28 changes: 19 additions & 9 deletions tests/components/FormBuilder/InputField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,40 @@ import { renderFormField } from "tests/helpers/renderFormField";
import { InputField } from "#/components";

describe("InputField", () => {
const field = { type: "input", name: "test", value: "" };
it("renders an input element", () => {
const field = { type: "input", name: "test", value: "" };
renderFormField(InputField, field);
const inputElement = screen.getByRole("textbox");
expect(inputElement).toBeInTheDocument();
});

// it("applies the appropriate validation rules", () => {
// const field = { type: "input", name: "test", required: true };
// render(<TestForm field={field} />);
it("applies the disabled prop when set to true", () => {
renderFormField(InputField, { ...field, disabled: true });
const inputElement = screen.getByRole("textbox");
expect(inputElement).toBeDisabled();
});

// it("shows an error message when the input value is invalid", async () => {
// renderFormField(InputField, {
// ...field,
// required: true,
// length: { minimum: 5 },
// });
// const inputElement = screen.getByRole("textbox");
// fireEvent.change(inputElement, { target: { value: "" } });
// expect(inputElement).toBeInvalid();
// fireEvent.change(inputElement, { target: { value: "test" } });
// fireEvent.blur(inputElement);
// const errorMessage = await screen.findByRole("alert");
// expect(errorMessage).toBeInTheDocument();
// });

it("updates the form state correctly when interacted with", () => {
const field = {
const { form } = renderFormField(InputField, {
type: "input",
name: "test",
value: "",
defaultValue: "",
mode: "text",
} as const;
const { form } = renderFormField(InputField, field);
});
const inputElement = screen.getByRole("textbox");
fireEvent.change(inputElement, { target: { value: "Test value" } });
expect(form.getValues("test")).toBe("Test value");
Expand Down
6 changes: 6 additions & 0 deletions tests/components/FormBuilder/SwitchField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ describe("SwitchField", () => {
fireEvent.click(switchElement);
expect(form.getValues("test")).toBe(true);
});

it("disables the switch when the disabled prop is set to true", () => {
renderFormField(SwitchField, { ...field, disabled: true });
const switchElement = screen.getByRole("switch");
expect(switchElement).toBeDisabled();
});
});
5 changes: 5 additions & 0 deletions tests/components/FormBuilder/TextAreaField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ describe("TextAreaField", () => {
// fireEvent.change(textareaElement, { target: { value: "" } });
// expect(textareaElement).toBeInvalid();
// });
it("applies the disabled prop when set to true", () => {
renderFormField(TextAreaField, { ...field, disabled: true });
const textareaElement = screen.getByRole("textbox");
expect(textareaElement).toBeDisabled();
});

it("updates the form state correctly when interacted with", () => {
const { form } = renderFormField(TextAreaField, field);
Expand Down
11 changes: 6 additions & 5 deletions tests/helpers/renderFormField.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React from "react";
import * as React from "react";
import { render, renderHook } from "@testing-library/react";
import { useForm, FormProvider } from "react-hook-form";
import { useForm, FormProvider, UseFormProps } from "react-hook-form";
import { CommonFieldProps, FormFieldProps } from "#/components";

export const renderFormField = (
FieldComponent: React.ComponentType<any>,
field: any,
formProps: any = {}
FieldComponent: React.ComponentType<CommonFieldProps<FormFieldProps>>,
field: FormFieldProps,
formProps: UseFormProps
) => {
const { result } = renderHook(() => useForm(formProps));
const form = result.current;
Expand Down
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"strict": true,
"lib": ["dom", "dom.iterable", "esnext"],
"types": ["vitest/globals"],

"plugins": [
{ "transform": "typescript-transform-paths" },
{ "transform": "typescript-transform-paths", "afterDeclarations": true }
Expand Down

0 comments on commit d4720c7

Please sign in to comment.