Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/react/src/__tests__/form/ColorPicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ describe('ColorPicker', () => {
expect(screen.getByText('Brand Color')).toBeInTheDocument();
});

it('does not wrap a single color input in a fieldset/legend', () => {
const { container } = render(<ColorPicker label="Brand Color" />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
});

it('renders color input', () => {
render(<ColorPicker label="Color" />);
const input = screen.getByLabelText('Color');
Expand Down
12 changes: 12 additions & 0 deletions packages/react/src/__tests__/form/DatePicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ describe('DatePicker', () => {
expect(screen.getByText('Start Date')).toBeInTheDocument();
});

it('does not wrap a single input in a fieldset/legend', () => {
const { container } = render(<DatePicker label="Start Date" />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
});

it('associates the visible label with the input via htmlFor/id', () => {
render(<DatePicker id="start" label="Start Date" />);
const input = screen.getByLabelText('Start Date');
expect(input).toHaveAttribute('id', 'start');
});

it('renders date input by default', () => {
const { container } = render(<DatePicker label="Date" />);
const input = container.querySelector('input[type="date"]');
Expand Down
12 changes: 12 additions & 0 deletions packages/react/src/__tests__/form/Editor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ describe('Editor', () => {
expect(screen.getByText('Code')).toBeInTheDocument();
});

it('does not wrap a single textarea in a fieldset/legend', () => {
const { container } = render(<Editor label="Code" />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
});

it('associates the visible label with the textarea via htmlFor/id', () => {
render(<Editor id="snippet" label="Code" />);
const textarea = screen.getByLabelText('Code');
expect(textarea).toHaveAttribute('id', 'snippet');
});

it('renders textarea', () => {
render(<Editor label="Code" />);
expect(screen.getByRole('textbox')).toBeInTheDocument();
Expand Down
12 changes: 12 additions & 0 deletions packages/react/src/__tests__/form/File.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ describe('File', () => {
expect(screen.getByText('Upload')).toBeInTheDocument();
});

it('does not wrap a single file input in a fieldset/legend', () => {
const { container } = render(<File label="Upload" />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
});

it('does not wrap a drag-drop file input in a fieldset/legend', () => {
const { container } = render(<File label="Upload" withDragDrop />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
});

it('renders file input', () => {
const { container } = render(<File label="Upload" />);
const input = container.querySelector('input[type="file"]');
Expand Down
71 changes: 68 additions & 3 deletions packages/react/src/__tests__/form/Input.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,34 @@ describe('Input', () => {
expect(screen.getByText('Name')).toBeInTheDocument();
});

it('does not wrap a single input in a fieldset/legend', () => {
const { container } = render(<Input label="Email" type="email" />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
});

it('associates the visible label with the input via htmlFor/id', () => {
render(<Input label="Email" type="email" />);
const input = screen.getByLabelText('Email');
expect(input).toBeInstanceOf(HTMLInputElement);
expect(input).toHaveAttribute('id');
expect(input.id).toBeTruthy();
});

it('renders the visible label with the daisyUI v5 fieldset-legend class', () => {
render(<Input id="email" label="Email" type="email" />);
const labelEl = screen.getByText('Email').closest('label');
expect(labelEl).toBeInTheDocument();
expect(labelEl).toHaveClass('fieldset-legend');
expect(labelEl).toHaveAttribute('for', 'email');
});

it('uses an explicit caller-provided id', () => {
render(<Input id="user-email" label="Email" />);
const input = screen.getByLabelText('Email');
expect(input).toHaveAttribute('id', 'user-email');
});

it('renders hint text', () => {
render(<Input label="Email" hint="We will never share your email" />);
expect(screen.getByText('We will never share your email')).toBeInTheDocument();
Expand All @@ -30,6 +58,18 @@ describe('Input', () => {
expect(screen.getByRole('textbox')).toHaveAttribute('aria-invalid', 'true');
});

it('wires aria-describedby to the hint when present', () => {
render(<Input id="phone" label="Phone" hint="Numbers only" />);
const input = screen.getByRole('textbox');
expect(input).toHaveAttribute('aria-describedby', 'phone-hint');
});

it('wires aria-describedby to the error when present', () => {
render(<Input id="phone" label="Phone" error="Required" />);
const input = screen.getByRole('textbox');
expect(input).toHaveAttribute('aria-describedby', 'phone-error');
});

it('shows required indicator', () => {
render(<Input label="Name" required />);
expect(screen.getByText('*')).toBeInTheDocument();
Expand All @@ -54,9 +94,34 @@ describe('Input', () => {
expect(onClear).toHaveBeenCalledOnce();
});

it('renders inline label', () => {
render(<Input label="Floating" inline />);
expect(screen.getByText('Floating')).toHaveClass('label');
it('keeps the clear button keyboard-focusable', () => {
render(<Input label="Search" clearable onClear={() => {}} />);
expect(screen.getByLabelText('Clear input')).not.toHaveAttribute('tabindex', '-1');
});

it('renders inline label as a floating label associated with the input', () => {
const { container } = render(<Input id="city" label="City" inline />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
const wrapper = container.querySelector('label.floating-label');
expect(wrapper).toBeInTheDocument();
expect(wrapper).toHaveAttribute('for', 'city');
const input = screen.getByLabelText('City');
expect(input).toHaveAttribute('id', 'city');
expect(wrapper?.contains(input)).toBe(true);
expect(input).toHaveAttribute('placeholder', ' ');
});

it('does not inject a single-space placeholder in non-inline mode', () => {
render(<Input id="city" label="City" />);
const input = screen.getByLabelText('City');
expect(input).not.toHaveAttribute('placeholder');
});

it('preserves a caller-provided placeholder in inline mode', () => {
render(<Input id="city" label="City" inline placeholder="Type a city" />);
const input = screen.getByLabelText('City');
expect(input).toHaveAttribute('placeholder', 'Type a city');
});

it('forwards ref', () => {
Expand Down
22 changes: 22 additions & 0 deletions packages/react/src/__tests__/form/Password.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ describe('Password', () => {
expect(screen.getByText('Password')).toBeInTheDocument();
});

it('does not wrap a single input in a fieldset/legend', () => {
const { container } = render(<Password label="Password" />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
});

it('associates the visible label with the input via htmlFor/id', () => {
render(<Password id="user-pw" label="Password" />);
const input = screen.getByLabelText('Password');
expect(input).toHaveAttribute('id', 'user-pw');
});

it('renders as password type by default', () => {
const { container } = render(<Password label="Password" />);
const input = container.querySelector('input');
Expand All @@ -33,6 +45,16 @@ describe('Password', () => {
expect(screen.queryByLabelText('Show password')).not.toBeInTheDocument();
});

it('keeps the visibility toggle keyboard-focusable', () => {
render(<Password label="Password" />);
expect(screen.getByLabelText('Show password')).not.toHaveAttribute('tabindex', '-1');
});

it('keeps the clear button keyboard-focusable', () => {
render(<Password label="Password" clearable onClear={() => {}} />);
expect(screen.getByLabelText('Clear password')).not.toHaveAttribute('tabindex', '-1');
});

it('renders error', () => {
render(<Password label="Password" error="Too short" />);
expect(screen.getByText('Too short')).toBeInTheDocument();
Expand Down
7 changes: 7 additions & 0 deletions packages/react/src/__tests__/form/Pin.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ describe('Pin', () => {
expect(inputs).toHaveLength(4);
});

it('does not wrap inputs in a fieldset/legend', () => {
const { container } = render(<Pin length={4} />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
expect(screen.getByRole('group')).toHaveAttribute('aria-label', 'PIN input');
});

it('renders 6 inputs', () => {
render(<Pin length={6} />);
const inputs = screen.getAllByRole('textbox');
Expand Down
12 changes: 12 additions & 0 deletions packages/react/src/__tests__/form/Range.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,18 @@ describe('Range', () => {
expect(screen.getByText('Volume')).toBeInTheDocument();
});

it('does not wrap a single slider in a fieldset/legend', () => {
const { container } = render(<Range label="Volume" />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
});

it('associates the visible label with the slider via htmlFor/id', () => {
render(<Range id="vol" label="Volume" />);
const slider = screen.getByLabelText('Volume');
expect(slider).toHaveAttribute('id', 'vol');
});

it('renders as range input', () => {
render(<Range label="Volume" />);
expect(screen.getByRole('slider')).toBeInTheDocument();
Expand Down
13 changes: 13 additions & 0 deletions packages/react/src/__tests__/form/RichTextEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ describe('RichTextEditor', () => {
expect(screen.getByText('Content')).toBeInTheDocument();
});

it('does not wrap the editor in a fieldset/legend', () => {
const { container } = render(<RichTextEditor label="Content" />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
});

it('associates the visible label with the contenteditable region via aria-labelledby', () => {
render(<RichTextEditor id="content-editor" label="Content" />);
const editor = screen.getByLabelText('Content');
expect(editor).toHaveAttribute('id', 'content-editor');
expect(editor).toHaveAttribute('aria-labelledby', 'content-editor-label');
});

it('renders contentEditable area', () => {
render(<RichTextEditor label="Content" />);
expect(screen.getByRole('textbox')).toBeInTheDocument();
Expand Down
13 changes: 13 additions & 0 deletions packages/react/src/__tests__/form/Select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ describe('Select', () => {
expect(screen.getByText('Country')).toBeInTheDocument();
});

it('does not wrap a single select in a fieldset/legend', () => {
const { container } = render(<Select label="Country" options={options} />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
});

it('associates the visible label with the select via htmlFor/id', () => {
render(<Select id="country" label="Country" options={options} />);
const select = screen.getByLabelText('Country');
expect(select).toBeInstanceOf(HTMLSelectElement);
expect(select).toHaveAttribute('id', 'country');
});

it('renders options', () => {
render(<Select label="Country" options={options} />);
expect(screen.getByText('Option 1')).toBeInTheDocument();
Expand Down
13 changes: 13 additions & 0 deletions packages/react/src/__tests__/form/Textarea.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ describe('Textarea', () => {
expect(screen.getByText('Description')).toBeInTheDocument();
});

it('does not wrap a single textarea in a fieldset/legend', () => {
const { container } = render(<Textarea label="Description" />);
expect(container.querySelector('fieldset')).not.toBeInTheDocument();
expect(container.querySelector('legend')).not.toBeInTheDocument();
});

it('associates the visible label with the textarea via htmlFor/id', () => {
render(<Textarea id="bio" label="Bio" />);
const textarea = screen.getByLabelText('Bio');
expect(textarea).toBeInstanceOf(HTMLTextAreaElement);
expect(textarea).toHaveAttribute('id', 'bio');
});

it('renders hint', () => {
render(<Textarea label="Bio" hint="Max 500 characters" />);
expect(screen.getByText('Max 500 characters')).toBeInTheDocument();
Expand Down
13 changes: 6 additions & 7 deletions packages/react/src/components/form/ColorPicker/ColorPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,16 +129,15 @@ export const ColorPicker = forwardRef<HTMLInputElement, ColorPickerProps>(
}, [onRandomColor]);

return (
<fieldset className="fieldset">
<div className="fieldset w-full">
{label && (
<legend className="fieldset-legend">
<label htmlFor={id} className="fieldset-legend">
{label}
{required && <span className="text-error ml-1">*</span>}
</legend>
</label>
)}
<label
<span
className={cn('input w-full', error && 'input-error', className)}
htmlFor={id}
style={{ paddingInlineStart: 0, overflow: 'hidden' }}
>
<input
Expand Down Expand Up @@ -221,7 +220,7 @@ export const ColorPicker = forwardRef<HTMLInputElement, ColorPickerProps>(
{iconRight}
</span>
)}
</label>
</span>
{hint && !error && (
<p id={hintId} className="fieldset-label">
{hint}
Expand All @@ -232,7 +231,7 @@ export const ColorPicker = forwardRef<HTMLInputElement, ColorPickerProps>(
{error}
</p>
)}
</fieldset>
</div>
);
},
);
Expand Down
Loading
Loading