import { describe, expect, it, vi } from 'vitest'; import '@testing-library/jest-dom/vitest'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { FormProvider, useForm } from 'react-hook-form'; import { NativeSelect } from '@/components/form/NativeSelect'; import { FormField } from '@/components/form/rhf'; const options = [ { value: 7, label: 'Number seven' }, { value: '7', label: 'Text seven' }, { value: 0, label: 'Zero' }, { value: '', label: 'None' }, { value: 9, label: 'Unavailable', disabled: true }, ]; function Harness({ onSubmit, disabled = false, }: { onSubmit: (values: { choice: string | number }) => void; disabled?: boolean; }) { const methods = useForm({ defaultValues: { choice: 7 as string | number } }); return (
disabled={disabled} options={options} />
); } describe('NativeSelect form values', () => { it('preserves numeric, string and empty values through RHF instead of coercing to DOM strings', async () => { const onSubmit = vi.fn(); render(); const user = userEvent.setup(); const select = screen.getByRole('combobox', { name: 'Choice' }); expect(select).toHaveDisplayValue('Number seven'); for (const [label, value] of [ ['Text seven', '7'], ['Zero', 0], ['None', ''], ] as const) { await user.selectOptions(select, screen.getByRole('option', { name: label })); await user.click(screen.getByRole('button', { name: 'Save' })); await waitFor(() => expect(onSubmit).toHaveBeenLastCalledWith({ choice: value }, expect.anything()), ); } await user.selectOptions(select, screen.getByRole('option', { name: 'Unavailable' })); expect(select).toHaveDisplayValue('None'); }); it('keeps disabled values intact and skips the control during tab navigation', async () => { const onSubmit = vi.fn(); render(); const user = userEvent.setup(); await user.tab(); expect(screen.getByRole('button', { name: 'Save' })).toHaveFocus(); await user.keyboard('{Enter}'); await waitFor(() => expect(onSubmit).toHaveBeenCalledWith({ choice: 7 }, expect.anything())); }); it('preserves an unlisted edit value and follows reordered dynamic options without changing it', async () => { const onChange = vi.fn(); const view = render( , ); expect(screen.getByRole('combobox', { name: 'Choice' })).toHaveDisplayValue('saved-custom'); view.rerender( , ); expect(screen.getByRole('combobox', { name: 'Choice' })).toHaveDisplayValue('Saved choice'); expect(onChange).not.toHaveBeenCalled(); await userEvent .setup() .selectOptions(screen.getByRole('combobox'), screen.getByRole('option', { name: 'New' })); expect(onChange).toHaveBeenCalledWith('new'); }); });