import React from 'react'; import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent, waitFor, act } from '@testing-library/react'; import { useForm, useFormContext } from 'react-hook-form'; import { PixelForm } from '../../forms/PixelForm'; type LoginFields = { email: string; password: string }; function LoginForm({ onSubmit }: { onSubmit: (data: LoginFields) => void }) { const form = useForm({ defaultValues: { email: '', password: '' } }); return ( ( Email We never spam. {fieldState.error?.message} )} /> ); } describe('PixelForm', () => { it('PixelForm.Root provides form context', () => { const form = (null as unknown) as ReturnType; let captured: unknown = null; function Probe() { captured = useFormContext(); return probe; } function Wrapper() { const f = useForm({ defaultValues: { x: '' } }); return ( {}}> ); } render(); expect(screen.getByTestId('probe')).toBeInTheDocument(); expect(captured).not.toBeNull(); // shape check — react-hook-form context exposes `register` expect(typeof (captured as { register: unknown }).register).toBe('function'); void form; }); it('PixelForm.Field renders via render prop with field + fieldState', () => { const renderSpy = vi.fn(); function W() { const form = useForm({ defaultValues: { name: 'joangel' } }); return ( {}}> { renderSpy(args); return ; }} /> ); } render(); expect(screen.getByTestId('name')).toHaveValue('joangel'); expect(renderSpy).toHaveBeenCalled(); const arg = renderSpy.mock.calls[0][0]; expect(arg.field).toBeDefined(); expect(arg.fieldState).toBeDefined(); expect(typeof arg.field.onChange).toBe('function'); }); it('PixelForm.Item generates linked IDs (Label htmlFor matches Control id) + aria-describedby', () => { render( {}} />); const input = screen.getByTestId('email-input'); const label = screen.getByText('Email'); expect(input.getAttribute('id')).toBeTruthy(); expect(label.getAttribute('for')).toBe(input.getAttribute('id')); const describedBy = input.getAttribute('aria-describedby'); expect(describedBy).toBeTruthy(); // description + message ids both referenced const desc = screen.getByText('We never spam.'); expect(describedBy).toContain(desc.id); }); it('PixelForm.Message renders error text when field has error and sets aria-invalid', async () => { function W() { const form = useForm<{ email: string }>({ defaultValues: { email: '' } }); return ( {}} > ( Email {fieldState.error?.message} )} /> ); } render(); fireEvent.click(screen.getByText('Go')); await waitFor(() => { expect(screen.getByText('Email is required')).toBeInTheDocument(); }); const input = screen.getByTestId('email'); expect(input.getAttribute('aria-invalid')).toBe('true'); }); it('Submitting calls onSubmit with form values', async () => { const onSubmit = vi.fn(); render(); const input = screen.getByTestId('email-input') as HTMLInputElement; fireEvent.change(input, { target: { value: 'me@joangel.ai' } }); fireEvent.click(screen.getByText('Submit')); await waitFor(() => { expect(onSubmit).toHaveBeenCalledTimes(1); }); expect(onSubmit.mock.calls[0][0]).toEqual({ email: 'me@joangel.ai', password: '' }); }); });