Skip to content

Commit

Permalink
feat: Add tests for Form based on aiven-core.
Browse files Browse the repository at this point in the history
  • Loading branch information
programmiri committed Oct 27, 2022
1 parent 7587359 commit 381896b
Show file tree
Hide file tree
Showing 2 changed files with 197 additions and 0 deletions.
154 changes: 154 additions & 0 deletions coral/src/app/components/Form.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
// ❗️ This implementation mirrors the Aiven core and is kept up to date with it.
// The Aiven core solution will be its own open source package soon.
// we're mirroring the implementation as it is, using only the components we actively use in coral.
// ❗️The Aiven core code base is the source of truth for this component.
import { Button } from "@aivenio/design-system";
import type { RenderResult } from "@testing-library/react";
import { cleanup, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import React from "react";
import type { DeepPartial, FieldValues } from "react-hook-form";
import {
Form,
SubmitErrorHandler,
SubmitHandler,
TextInput,
useForm,
} from "src/app/components/Form";
import { z } from "zod";

type WrapperProps<T extends FieldValues> = {
schema: any;
defaultValues?: DeepPartial<T>;
onSubmit: SubmitHandler<T>;
onError: SubmitErrorHandler<T>;
};

const Wrapper = <T extends FieldValues>({
schema,
defaultValues,
onSubmit,
onError,
children,
}: React.PropsWithChildren<WrapperProps<T>>): React.ReactElement => {
const form = useForm<T>({ schema, defaultValues });
return (
<Form onSubmit={onSubmit} onError={onError} {...form}>
{children}
</Form>
);
};

describe("Form", () => {
const onSubmit = jest.fn();
const onError = jest.fn();
let results: RenderResult;
let user: ReturnType<typeof userEvent.setup>;

beforeEach(() => {
user = userEvent.setup();
});

afterEach(() => {
cleanup();
onSubmit.mockClear();
onError.mockClear();
});

const renderForm = <T extends FieldValues>(
children: React.ReactNode,
{ schema, defaultValues }: { schema: any; defaultValues?: DeepPartial<T> }
) => {
return render(
<Wrapper<T>
schema={schema}
defaultValues={defaultValues}
onSubmit={onSubmit}
onError={onError}
>
{children}
<Button type="submit" title="Submit" />
</Wrapper>
);
};

const typeText = async (value: string) => {
const el = screen.getByRole("textbox");
await user.clear(el);
await user.type(el, value);
};

const submit = async () => {
await user.click(screen.getByRole("button", { name: "Submit" }));
};

const assertSubmitted = (data: any) => {
expect(onSubmit).toHaveBeenCalledWith(data, expect.anything());
};

describe("<Form>", () => {
const schema = z.object({ name: z.string().min(3, "error") });
type Schema = z.infer<typeof schema>;

beforeEach(() => {
results = renderForm(
<TextInput<Schema> name="name" labelText="TextInput" />,
{ schema }
);
});

it("should call onSubmit() after submit if there are no validation errors", async () => {
await user.type(screen.getByLabelText("TextInput"), "abc");
await submit();
await waitFor(() =>
expect(onSubmit).toHaveBeenCalledWith(
{ name: "abc" },
expect.anything()
)
);
});

it("should call onError() after submit if there are validation errors", async () => {
await user.type(screen.getByLabelText("TextInput"), "a");
await submit();
await waitFor(() => expect(onError).toHaveBeenCalled());
expect(onError.mock.calls[0][0]).toMatchObject({
name: { message: "error" },
});
});
});

describe("<TextInput>", () => {
const schema = z.object({ name: z.string().min(3, "error") });
type Schema = z.infer<typeof schema>;

beforeEach(() => {
results = renderForm(
<TextInput<Schema> name="name" labelText="TextInput" />,
{ schema }
);
});

it("should render <TextInput>", () => {
expect(results.container).toMatchSnapshot();
});

it("should render label", () => {
expect(screen.queryByLabelText("TextInput")).toBeVisible();
});

it("should sync value to form state", async () => {
await typeText("value{tab}");
await submit();
assertSubmitted({ name: "value" });
});

it("should render errors after blur event and hide them after valid input", async () => {
await typeText("a{tab}");
await waitFor(() => expect(screen.queryByText("error")).toBeVisible());

await typeText("abc{tab}");
await waitFor(() => expect(screen.queryByText("error")).toBeNull());
});
});
});
43 changes: 43 additions & 0 deletions coral/src/app/components/__snapshots__/Form.test.tsx.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Form <TextInput> should render <TextInput> 1`] = `
<div>
<form>
<div
style="display: flex; flex-direction: column;"
>
<label
class="w-full"
for="input-13"
id="input-13-label"
>
<span
class="inline-block mb-2 font-medium typography-body-small text-grey-60"
>
TextInput
</span>
<span
class="relative block"
>
<input
class="block w-full rounded-sm disabled:cursor-not-allowed disabled:border-grey-20 disabled:bg-grey-5 typography-body-small text-grey-70 disabled:text-grey-40 placeholder:text-grey-40 focus:outline-none px-3 py-3 border border-grey-20 hover:border-grey-50 focus:border-info-70"
id="input-13"
name="name"
type="text"
/>
</span>
</label>
<p
class="block mt-1 mb-3 typography-caption-default"
>
 
</p>
</div>
<button
class="text-white bg-primary-80 active:bg-primary-90 active:ring-0 focus-visible:ring-2 focus-visible:ring-primary-100 focus-visible:ring-inset hover:bg-primary-70 disabled:bg-primary-5 inline-block border-0 rounded-sm transition whitespace-nowrap focus:outline-none relative typography-body-default py-3 px-4"
title="Submit"
type="submit"
/>
</form>
</div>
`;

0 comments on commit 381896b

Please sign in to comment.