import { expect, userEvent, within } from "storybook/test"; import type { Meta, StoryObj } from "@storybook/react-vite"; import type { ReactNode } from "react"; import { InputPassword } from "../inputs/InputPassword"; import { InputText } from "../inputs/InputText"; import { Text } from "../primitives/Text"; import { View } from "../primitives/View"; import { Story } from "../story-components/Story"; import { Form } from "./Form"; import { FormField } from "./FormField"; interface SignUpValues { username: string; password: string; } function validatePassword(value: string): string | undefined { return value.length < 8 ? "Password must be at least 8 characters." : undefined; } function FormFieldDemo(): ReactNode { return ( defaultValues={{ username: "", password: "" }} render={({ control }) => ( ( )} /> fieldError ? ( {fieldError.message} Use a passphrase for something memorable. ) : undefined } render={({ field, labelId, describedBy, invalid, required }) => ( )} /> )} onSubmit={() => {}} /> ); } type ThisStory = StoryObj; export default { title: "alouette/Forms/FormField", component: FormField, parameters: { componentSubtitle: "Wires a react-hook-form Controller to FormItem's label/error/layout. Must be used inside
, whose render params supply the control that types name and field.value. The renderError prop accepts a resolver returning ReactNode, so error content isn't limited to plain strings.", }, } satisfies Meta; export const FormFieldStory: ThisStory = { render: () => ( ), play: async ({ canvasElement }) => { const canvas = within(canvasElement); const usernameInput = canvas.getByLabelText("Username"); const passwordInput = canvas.getByLabelText("Password"); await expect(usernameInput).toHaveAttribute("aria-required", "true"); await expect(usernameInput).toHaveAttribute("aria-invalid", "false"); await expect(usernameInput).not.toHaveAttribute("aria-describedby"); await expect(passwordInput).toHaveAttribute("aria-required", "false"); await userEvent.click(usernameInput); await userEvent.tab(); const requiredMessage = canvas.getByText("Username is required."); await expect(requiredMessage).toBeVisible(); // The label is not a tab stop: Tab goes straight to the next input. await expect(passwordInput).toHaveFocus(); // The error reaches the input: aria-invalid, and the message as its // description. await expect(usernameInput).toHaveAttribute("aria-invalid", "true"); await expect(usernameInput).toHaveAttribute( "aria-describedby", requiredMessage.id, ); // Pressing the label focuses the input, like a native