diff --git a/src/App.tsx b/src/App.tsx index ffa2512..ceb56e5 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,11 @@ import "./App.css"; +import { MyForm } from "./stories/myForm"; + function App() { return ( <> -
- TanStackForm -
+ ); } diff --git a/src/stories/myForm.stories.tsx b/src/stories/myForm.stories.tsx new file mode 100644 index 0000000..87d1a07 --- /dev/null +++ b/src/stories/myForm.stories.tsx @@ -0,0 +1,119 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { within, userEvent, expect, fn } from "storybook/test"; +import { MyForm } from "./myForm"; + +const meta: Meta = { + title: "Components/MyForm", + component: MyForm, + parameters: { + layout: "centered", + }, + args: { + onSubmit: fn(), + }, +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; + +export const ValidationErrors: Story = { + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + + const nameInput = canvas.getByLabelText(/Full Name/i); + await userEvent.type(nameInput, "12345"); + + const ageInput = canvas.getByLabelText(/Age/i); + await userEvent.type(ageInput, "15"); + + const passwordInput = canvas.getByLabelText(/Password/i); + await userEvent.type(passwordInput, "123"); + + await userEvent.tab(); + + await expect( + canvas.getByText(/Please enter letters only/i), + ).toBeInTheDocument(); + await expect( + canvas.getByText(/Age must be at least 18/i), + ).toBeInTheDocument(); + await expect( + canvas.getByText(/Password must be at least 8 characters long/i), + ).toBeInTheDocument(); + }, +}; + +export const SuccessfulSubmit: Story = { + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + + const nameInput = canvas.getByLabelText(/Full Name/i); + await userEvent.type(nameInput, "Somayeh Ghanavati"); + + const ageInput = canvas.getByLabelText(/Age/i); + await userEvent.type(ageInput, "37"); + + const passwordInput = canvas.getByLabelText(/Password/i); + await userEvent.type(passwordInput, "password123"); + + const submitButton = canvas.getByRole("button", { name: /Sign Up/i }); + await expect(submitButton).toBeEnabled(); + + await userEvent.click(submitButton); + }, +}; + +export const FillAndFixForm: Story = { + play: async ({ canvasElement, args }) => { + const canvas = within(canvasElement); + + const nameInput = canvas.getByLabelText(/Full Name/i); + const ageInput = canvas.getByLabelText(/Age/i); + const passwordInput = canvas.getByLabelText(/Password/i); + const submitButton = canvas.getByRole("button", { name: /Sign Up/i }); + + await userEvent.type(nameInput, "1234"); + await userEvent.type(ageInput, "10"); + await userEvent.type(passwordInput, "12"); + await userEvent.tab(); + + await expect( + canvas.queryByText(/Please enter letters only/i), + ).toBeInTheDocument(); + await expect( + canvas.queryByText(/Age must be at least 18/), + ).toBeInTheDocument(); + + await expect( + canvas.queryByText(/Password must be at least 8 characters long/), + ).toBeInTheDocument(); + + await expect(submitButton).toBeDisabled(); + + await userEvent.clear(nameInput); + await userEvent.type(nameInput, "Somayeh Ghanavati"); + + await userEvent.clear(ageInput); + await userEvent.type(ageInput, "36"); + + await userEvent.clear(passwordInput); + await userEvent.type(passwordInput, "123456789"); + + await expect( + canvas.queryByText(/Please enter letters only/i), + ).not.toBeInTheDocument(); + await expect( + canvas.queryByText(/Age must be at least 18/), + ).not.toBeInTheDocument(); + + await expect( + canvas.queryByText(/Password must be at least 8 characters long/), + ).not.toBeInTheDocument(); + + await expect(submitButton).toBeEnabled(); + + await userEvent.click(submitButton); + }, +}; diff --git a/src/stories/myForm.tsx b/src/stories/myForm.tsx new file mode 100644 index 0000000..2c0e529 --- /dev/null +++ b/src/stories/myForm.tsx @@ -0,0 +1,186 @@ +import React from "react"; +import { useForm } from "@tanstack/react-form"; + +export function MyForm() { + const form = useForm({ + defaultValues: { + username: "", + age: "", + password: "", + }, + onSubmit: async ({ value }) => { + console.log("Form submitted successfully:", value); + alert(JSON.stringify(value, null, 2)); + }, + }); + + return ( +
+

Registration Form (TanStack Form)

+ +
{ + e.preventDefault(); + e.stopPropagation(); + form.handleSubmit(); + }} + > + { + if (!value) return "Full name is required"; + if (!/^[a-zA-Za-z\s]+$/.test(value)) { + return "Please enter letters only"; + } + return undefined; + }, + }} + > + {(field) => ( +
+ + field.handleChange(e.target.value)} + style={{ + width: "100%", + padding: "8px", + boxSizing: "border-box", + }} + /> + {field.state.meta.errors.length > 0 && ( + + {field.state.meta.errors.join(", ")} + + )} +
+ )} +
+ + { + if (!value) return "Age is required"; + if (isNaN(Number(value))) { + return "Please enter numbers only"; + } + if (Number(value) < 18) { + return "Age must be at least 18"; + } + return undefined; + }, + }} + > + {(field) => ( +
+ + field.handleChange(e.target.value)} + style={{ + width: "100%", + padding: "8px", + boxSizing: "border-box", + }} + /> + {field.state.meta.errors.length > 0 && ( + + {field.state.meta.errors.join(", ")} + + )} +
+ )} +
+ + { + if (!value) return "Password is required"; + if (value.length < 8) { + return "Password must be at least 8 characters long"; + } + return undefined; + }, + }} + > + {(field) => ( +
+ + field.handleChange(e.target.value)} + style={{ + width: "100%", + padding: "8px", + boxSizing: "border-box", + }} + /> + {field.state.meta.errors.length > 0 && ( + + {field.state.meta.errors.join(", ")} + + )} +
+ )} +
+ + [state.canSubmit, state.isSubmitting]} + > + {([canSubmit, isSubmitting]) => ( + + )} + +
+
+ ); +}