aboutsummaryrefslogtreecommitdiffstats
path: root/src/utils/hooks/use-form/use-form.test.ts
diff options
context:
space:
mode:
Diffstat (limited to 'src/utils/hooks/use-form/use-form.test.ts')
-rw-r--r--src/utils/hooks/use-form/use-form.test.ts225
1 files changed, 225 insertions, 0 deletions
diff --git a/src/utils/hooks/use-form/use-form.test.ts b/src/utils/hooks/use-form/use-form.test.ts
new file mode 100644
index 0000000..e2c30b5
--- /dev/null
+++ b/src/utils/hooks/use-form/use-form.test.ts
@@ -0,0 +1,225 @@
+import { describe, expect, it } from '@jest/globals';
+import { act, renderHook } from '@testing-library/react';
+import type { ChangeEvent, FormEvent } from 'react';
+import { useForm } from './use-form';
+
+const generateSubmitEvent = () =>
+ new Event('submit', {
+ bubbles: true,
+ cancelable: true,
+ }) as unknown as FormEvent<HTMLFormElement>;
+
+/**
+ * Generate a new change event.
+ *
+ * @param {string} name - The field name.
+ * @param {unknown} value - The new value of the field.
+ * @returns {ChangeEvent<HTMLInputElement>} The event.
+ */
+const generateChangeEvent = (name: string, value: unknown, type = 'text') => {
+ const ev = new Event('change');
+ Object.defineProperty(ev, 'target', {
+ value: {
+ checked: type === 'checkbox' || type === 'radio' ? value : undefined,
+ name,
+ type,
+ value: type === 'checkbox' || type === 'radio' ? undefined : value,
+ },
+ writable: false,
+ });
+
+ return ev as unknown as ChangeEvent<HTMLInputElement>;
+};
+
+describe('useForm', () => {
+ it('can initialize the data', () => {
+ const initialValues = {
+ foo: 'impedit',
+ bar: 42,
+ };
+ const { result } = renderHook(() => useForm({ initialValues }));
+
+ expect(result.current.values.bar).toBe(initialValues.bar);
+ expect(result.current.values.foo).toBe(initialValues.foo);
+ });
+
+ it('can use a handler to validate the submit process', async () => {
+ const data = {
+ name: 'John',
+ };
+ const submitHandler = jest.fn((_data) => undefined);
+ const { result } = renderHook(() =>
+ useForm({
+ initialValues: data,
+ submitHandler,
+ })
+ );
+
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+ expect.assertions(3);
+
+ await act(async () => {
+ await result.current.submit(generateSubmitEvent());
+ });
+
+ expect(result.current.validationErrors).toBeNull();
+ expect(submitHandler).toHaveBeenCalled();
+ expect(result.current.submitStatus).toBe('SUCCEEDED');
+ });
+
+ it('can submit the data and reset to initial values', async () => {
+ const data = {
+ initial: {
+ name: 'John',
+ },
+ new: {
+ name: 'Phoebe',
+ },
+ };
+ const { result } = renderHook(() =>
+ useForm({
+ initialValues: data.initial,
+ })
+ );
+
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+ expect.assertions(3);
+
+ act(() => {
+ result.current.update(generateChangeEvent('name', data.new.name));
+ });
+
+ expect(result.current.values.name).toBe(data.new.name);
+
+ await act(async () => {
+ await result.current.submit(generateSubmitEvent());
+ });
+
+ expect(result.current.submitStatus).toBe('SUCCEEDED');
+ expect(result.current.values.name).toBe(data.initial.name);
+ });
+
+ it('can submit after validating the data', async () => {
+ const data = {
+ initial: {
+ name: 'John',
+ },
+ new: {
+ name: 'Phoebe',
+ },
+ errors: {
+ name: 'Expect name to have at least 2 letters.',
+ },
+ };
+ const submitHandler = jest.fn((_data) => undefined);
+ const { result } = renderHook(() =>
+ useForm({
+ initialValues: data.initial,
+ submitHandler,
+ validations: {
+ name: {
+ error: data.errors.name,
+ validator: (value) => value.length > 1,
+ },
+ },
+ })
+ );
+
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+ expect.assertions(4);
+
+ act(() => {
+ result.current.update(generateChangeEvent('name', data.new.name));
+ });
+
+ expect(result.current.values.name).toBe(data.new.name);
+
+ await act(async () => {
+ await result.current.submit(generateSubmitEvent());
+ });
+
+ expect(result.current.validationErrors).toBeNull();
+ expect(submitHandler).toHaveBeenCalled();
+ expect(result.current.submitStatus).toBe('SUCCEEDED');
+ });
+
+ it('can abort submit if data validation fails', async () => {
+ const minAge = 18;
+ const data = {
+ initial: {
+ name: 'H',
+ age: 17,
+ },
+ errors: {
+ age: `Expect age to be at least ${minAge}.`,
+ name: 'Expect name to have at least 2 letters.',
+ },
+ };
+ const submitHandler = jest.fn((_data) => undefined);
+ const { result } = renderHook(() =>
+ useForm({
+ initialValues: data.initial,
+ submitHandler,
+ validations: {
+ age: {
+ error: data.errors.age,
+ validator: (value) => value >= minAge,
+ },
+ name: {
+ error: data.errors.name,
+ validator: (value) => value.length > 1,
+ },
+ },
+ })
+ );
+
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+ expect.assertions(4);
+
+ await act(async () => {
+ await result.current.submit(generateSubmitEvent());
+ });
+
+ expect(result.current.validationErrors?.age).toBe(data.errors.age);
+ expect(result.current.validationErrors?.name).toBe(data.errors.name);
+ expect(submitHandler).not.toHaveBeenCalled();
+ expect(result.current.submitStatus).toBe('FAILED');
+ });
+
+ it('can partially validate the data before submit', async () => {
+ const data = {
+ initial: {
+ name: 'H',
+ age: 17,
+ },
+ errors: {
+ name: 'Expect name to have at least 2 letters.',
+ },
+ };
+ const submitHandler = jest.fn((_data) => undefined);
+ const { result } = renderHook(() =>
+ useForm({
+ initialValues: data.initial,
+ submitHandler,
+ validations: {
+ name: {
+ error: data.errors.name,
+ validator: (value) => value.length > 1,
+ },
+ },
+ })
+ );
+
+ // eslint-disable-next-line @typescript-eslint/no-magic-numbers
+ expect.assertions(4);
+
+ await act(async () => {
+ await result.current.submit(generateSubmitEvent());
+ });
+
+ expect(result.current.validationErrors?.age).toBeUndefined();
+ expect(result.current.validationErrors?.name).toBe(data.errors.name);
+ expect(submitHandler).not.toHaveBeenCalled();
+ expect(result.current.submitStatus).toBe('FAILED');
+ });
+});