// FE-MOB-SETUI-001 to FE-MOB-SETUI-019 import { describe, it, expect, vi } from 'vitest'; import { Globe } from 'lucide-react'; import { render, screen, fireEvent } from '../../../helpers/render'; import { MSetCard, MSetEyebrow, MSetSelectRow, MSetSegments, MSetOnOff, MSetRow, MSetInput, MSetTextarea, MSetButton, MSetHint, } from '../../../../src/mobile/screens/settings/MSettingsUi'; describe('MSetCard', () => { it('FE-MOB-SETUI-001: renders the title row with a 16px icon and the children below', () => { const { container } = render(

card body

, ); expect(screen.getByText('Language & region')).toBeInTheDocument(); expect(screen.getByText('card body')).toBeInTheDocument(); const icon = container.querySelector('svg'); expect(icon).toHaveAttribute('width', '16'); }); it('FE-MOB-SETUI-002: renders the trailing badge slot and keeps the extra class on the section', () => { const { container } = render( 3} className="mt-3">

body

, ); expect(screen.getByText('3')).toBeInTheDocument(); expect(container.querySelector('section')).toHaveClass('mt-3'); }); }); describe('MSetEyebrow', () => { it('FE-MOB-SETUI-003: renders its label and merges the caller class', () => { const { container } = render(Currency); expect(screen.getByText('Currency')).toBeInTheDocument(); expect(container.firstElementChild).toHaveClass('mb-1'); }); }); describe('MSetSelectRow', () => { it('FE-MOB-SETUI-004: shows label plus trailing value and reports the tap', () => { const onClick = vi.fn(); render(English} onClick={onClick} />); const row = screen.getByRole('button', { name: /Language/ }); expect(row).toHaveTextContent('English'); fireEvent.click(row); expect(onClick).toHaveBeenCalledTimes(1); }); it('FE-MOB-SETUI-005: is a plain type=button so it never submits a surrounding form', () => { render(); const row = screen.getByRole('button', { name: 'Timezone' }); expect(row).toHaveAttribute('type', 'button'); fireEvent.click(row); expect(row).toBeInTheDocument(); }); }); describe('MSetSegments', () => { const options = [ { value: 'celsius', label: '°C' }, { value: 'fahrenheit', label: '°F' }, ]; it('FE-MOB-SETUI-006: marks only the current value as pressed', () => { render(); expect(screen.getByRole('button', { name: '°F' })).toHaveAttribute('aria-pressed', 'true'); expect(screen.getByRole('button', { name: '°C' })).toHaveAttribute('aria-pressed', 'false'); }); it('FE-MOB-SETUI-007: reports the tapped option value', () => { const onChange = vi.fn(); render(); fireEvent.click(screen.getByRole('button', { name: '°C' })); expect(onChange).toHaveBeenCalledWith('celsius'); }); }); describe('MSetOnOff', () => { it('FE-MOB-SETUI-008: exposes both segments under the group label with the current state pressed', () => { render(); const group = screen.getByRole('group', { name: 'Show traffic' }); expect(group).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'On' })).toHaveAttribute('aria-pressed', 'true'); expect(screen.getByRole('button', { name: 'Off' })).toHaveAttribute('aria-pressed', 'false'); }); it('FE-MOB-SETUI-009: reports true from the On segment and false from the Off segment', () => { const onChange = vi.fn(); render(); fireEvent.click(screen.getByRole('button', { name: 'On' })); expect(onChange).toHaveBeenLastCalledWith(true); fireEvent.click(screen.getByRole('button', { name: 'Off' })); expect(onChange).toHaveBeenLastCalledWith(false); }); }); describe('MSetRow', () => { it('FE-MOB-SETUI-010: renders label, sub line and the trailing control', () => { render(km} />); expect(screen.getByText('Distance')).toBeInTheDocument(); expect(screen.getByText('Kilometres or miles')).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'km' })).toBeInTheDocument(); }); it('FE-MOB-SETUI-011: only rows after the first carry the divider', () => { const { container: first } = render(); expect(first.firstElementChild).not.toHaveClass('border-t'); const { container: second } = render(); expect(second.firstElementChild).toHaveClass('border-t'); // Without a sub prop the row stays a single line of text. expect(second.textContent).toBe('Second'); }); }); describe('MSetInput / MSetTextarea', () => { it('FE-MOB-SETUI-012: forwards type, placeholder and change events', () => { const onChange = vi.fn(); render(); const input = screen.getByPlaceholderText('Email') as HTMLInputElement; expect(input.type).toBe('email'); expect(input.value).toBe('a@b.c'); fireEvent.change(input, { target: { value: 'x@y.z' } }); expect(onChange).toHaveBeenCalledTimes(1); }); it('FE-MOB-SETUI-013: mono switches the face and the mono flag never leaks into the DOM', () => { render(); const input = screen.getByLabelText('Secret'); expect(input).toHaveClass('font-mono', 'mt-2'); expect(input).not.toHaveAttribute('mono'); }); it('FE-MOB-SETUI-014: the textarea forwards rows, value and change events', () => { const onChange = vi.fn(); render(); const area = screen.getByLabelText('Notes') as HTMLTextAreaElement; expect(area.rows).toBe(4); expect(area.value).toBe('notes'); expect(area).toHaveClass('mt-2'); fireEvent.change(area, { target: { value: 'more' } }); expect(onChange).toHaveBeenCalledTimes(1); }); }); describe('MSetButton', () => { it('FE-MOB-SETUI-015: the primary variant is the accent-filled pill', () => { render(Save); const button = screen.getByRole('button', { name: 'Save' }); expect(button).toHaveClass('bg-m-act', 'text-m-actfg'); expect(button).toHaveAttribute('type', 'button'); }); it('FE-MOB-SETUI-016: the danger variant uses the danger ink, ghost stays neutral', () => { render( <> Delete Cancel , ); expect(screen.getByRole('button', { name: 'Delete' })).toHaveClass('text-[color:var(--m-st-danger)]'); expect(screen.getByRole('button', { name: 'Cancel' })).toHaveClass('text-m-ink'); }); it('FE-MOB-SETUI-017: a click reaches onClick', () => { const onClick = vi.fn(); render(Update); fireEvent.click(screen.getByRole('button', { name: 'Update' })); expect(onClick).toHaveBeenCalledTimes(1); }); it('FE-MOB-SETUI-018: a disabled button swallows the click', () => { const onClick = vi.fn(); render(Update); const button = screen.getByRole('button', { name: 'Update' }); expect(button).toBeDisabled(); fireEvent.click(button); expect(onClick).not.toHaveBeenCalled(); }); }); describe('MSetHint', () => { it('FE-MOB-SETUI-019: renders the hint text with the caller class', () => { const { container } = render(Stored encrypted.); expect(screen.getByText('Stored encrypted.')).toBeInTheDocument(); expect(container.querySelector('p')).toHaveClass('mb-2'); }); });