chore: add Storybook stories for MyForm component
This commit is contained in:
+3
-3
@@ -1,11 +1,11 @@
|
|||||||
import "./App.css";
|
import "./App.css";
|
||||||
|
|
||||||
|
import { MyForm } from "./stories/myForm";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<section>
|
<MyForm></MyForm>
|
||||||
<text>TanStackForm</text>
|
|
||||||
</section>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<typeof MyForm> = {
|
||||||
|
title: "Components/MyForm",
|
||||||
|
component: MyForm,
|
||||||
|
parameters: {
|
||||||
|
layout: "centered",
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
onSubmit: fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof MyForm>;
|
||||||
|
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -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 (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
maxWidth: "400px",
|
||||||
|
margin: "20px auto",
|
||||||
|
fontFamily: "sans-serif",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<h2>Registration Form (TanStack Form)</h2>
|
||||||
|
|
||||||
|
<form
|
||||||
|
onSubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
form.handleSubmit();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<form.Field
|
||||||
|
name="username"
|
||||||
|
validators={{
|
||||||
|
onChange: ({ value }) => {
|
||||||
|
if (!value) return "Full name is required";
|
||||||
|
if (!/^[a-zA-Za-z\s]+$/.test(value)) {
|
||||||
|
return "Please enter letters only";
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(field) => (
|
||||||
|
<div style={{ marginBottom: "15px" }}>
|
||||||
|
<label
|
||||||
|
htmlFor={field.name}
|
||||||
|
style={{ display: "block", marginBottom: "5px" }}
|
||||||
|
>
|
||||||
|
Full Name (Letters only):
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
value={field.state.value}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: "8px",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{field.state.meta.errors.length > 0 && (
|
||||||
|
<span style={{ color: "red", fontSize: "12px" }}>
|
||||||
|
{field.state.meta.errors.join(", ")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
|
||||||
|
<form.Field
|
||||||
|
name="age"
|
||||||
|
validators={{
|
||||||
|
onChange: ({ value }) => {
|
||||||
|
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) => (
|
||||||
|
<div style={{ marginBottom: "15px" }}>
|
||||||
|
<label
|
||||||
|
htmlFor={field.name}
|
||||||
|
style={{ display: "block", marginBottom: "5px" }}
|
||||||
|
>
|
||||||
|
Age (Numbers only):
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
type="number"
|
||||||
|
value={field.state.value}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: "8px",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{field.state.meta.errors.length > 0 && (
|
||||||
|
<span style={{ color: "red", fontSize: "12px" }}>
|
||||||
|
{field.state.meta.errors.join(", ")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
|
||||||
|
<form.Field
|
||||||
|
name="password"
|
||||||
|
validators={{
|
||||||
|
onChange: ({ value }) => {
|
||||||
|
if (!value) return "Password is required";
|
||||||
|
if (value.length < 8) {
|
||||||
|
return "Password must be at least 8 characters long";
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(field) => (
|
||||||
|
<div style={{ marginBottom: "15px" }}>
|
||||||
|
<label
|
||||||
|
htmlFor={field.name}
|
||||||
|
style={{ display: "block", marginBottom: "5px" }}
|
||||||
|
>
|
||||||
|
Password:
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
id={field.name}
|
||||||
|
name={field.name}
|
||||||
|
type="password"
|
||||||
|
value={field.state.value}
|
||||||
|
onBlur={field.handleBlur}
|
||||||
|
onChange={(e) => field.handleChange(e.target.value)}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: "8px",
|
||||||
|
boxSizing: "border-box",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{field.state.meta.errors.length > 0 && (
|
||||||
|
<span style={{ color: "red", fontSize: "12px" }}>
|
||||||
|
{field.state.meta.errors.join(", ")}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</form.Field>
|
||||||
|
|
||||||
|
<form.Subscribe
|
||||||
|
selector={(state) => [state.canSubmit, state.isSubmitting]}
|
||||||
|
>
|
||||||
|
{([canSubmit, isSubmitting]) => (
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
disabled={!canSubmit}
|
||||||
|
style={{
|
||||||
|
width: "100%",
|
||||||
|
padding: "10px",
|
||||||
|
backgroundColor: canSubmit ? "#007bff" : "#ccc",
|
||||||
|
color: "white",
|
||||||
|
border: "none",
|
||||||
|
borderRadius: "4px",
|
||||||
|
cursor: canSubmit ? "pointer" : "not-allowed",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isSubmitting ? "Submitting..." : "Sign Up"}
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</form.Subscribe>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user