import { render, screen, fireEvent } from '@testing-library/react'; import ToggleSwitch from '../toggle-switch'; const IconStub = () => icon; describe('ToggleSwitch component', () => { const defaultProps = { header: 'AI annotations', icon: , checked: false, onChange: jest.fn(), ariaLabel: 'AI toggle', }; beforeEach(() => { jest.clearAllMocks(); }); it('should render in the OFF state and match snapshot', () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); const button = screen.getByRole('switch', { name: 'AI toggle' }); expect(button).toBeInTheDocument(); expect(button).toHaveClass('toggle'); expect(button).not.toHaveClass('toggle--on'); expect(button).not.toHaveClass('toggle--loading'); expect(button).not.toHaveClass('toggle--disabled'); expect(screen.getByText('AI annotations')).toBeInTheDocument(); expect(screen.getByTestId('toggle-icon')).toBeInTheDocument(); }); it('should not render the icon wrapper when icon prop is omitted', () => { const { icon, ...propsWithoutIcon } = defaultProps; const { container } = render(); expect(container.querySelector('.toggle__icon')).not.toBeInTheDocument(); expect(screen.queryByTestId('toggle-icon')).not.toBeInTheDocument(); }); it('should render ON state styling when checked and not loading', () => { render(); const button = screen.getByRole('switch', { name: 'AI toggle' }); expect(button).toHaveClass('toggle--on'); expect(button).not.toHaveClass('toggle--loading'); }); it('should render loading state styling when isLoading is true', () => { render(); const button = screen.getByRole('switch', { name: 'AI toggle' }); expect(button).toHaveClass('toggle--loading'); expect(button).not.toHaveClass('toggle--on'); }); it('should call onChange with toggled value when clicked and not disabled', () => { const onChange = jest.fn(); render( ); const button = screen.getByRole('switch', { name: 'AI toggle' }); fireEvent.click(button); expect(onChange).toHaveBeenCalledTimes(1); expect(onChange).toHaveBeenCalledWith(true); }); it('should not call onChange when disabled and clicked', () => { const onChange = jest.fn(); render( ); const button = screen.getByRole('switch', { name: 'AI toggle' }); expect(button).toBeDisabled(); expect(button).toHaveClass('toggle--disabled'); fireEvent.click(button); expect(onChange).not.toHaveBeenCalled(); }); it('should set aria-checked according to checked prop', () => { const { rerender } = render( ); const button = screen.getByRole('switch', { name: 'AI toggle' }); expect(button).toHaveAttribute('aria-checked', 'false'); rerender(); expect(button).toHaveAttribute('aria-checked', 'true'); }); it('should apply custom className to the root button', () => { render(); const button = screen.getByRole('switch', { name: 'AI toggle' }); expect(button).toHaveClass('toggle'); expect(button).toHaveClass('extra-toggle-class'); }); it('should spread additional props onto the root button', () => { render( ); const button = screen.getByTestId('toggle-root'); expect(button).toHaveAttribute('aria-label', 'Custom label'); }); it('should render the decorative switch track and slider', () => { const { container } = render(); const switchTrack = container.querySelector('.toggle__switch'); expect(switchTrack).toBeInTheDocument(); const slider = switchTrack?.querySelector('.slider'); expect(slider).toBeInTheDocument(); }); it('should add the compact modifier class when `compact` is set', () => { render(); const button = screen.getByRole('switch', { name: 'AI toggle' }); expect(button).toHaveClass('toggle--compact'); }); it('should not add the compact modifier class by default', () => { render(); const button = screen.getByRole('switch', { name: 'AI toggle' }); expect(button).not.toHaveClass('toggle--compact'); }); it('should add the full-width modifier class when `fullWidth` is set', () => { render(); const button = screen.getByRole('switch', { name: 'AI toggle' }); expect(button).toHaveClass('toggle--full-width'); }); it('should default to hugging content (no full-width class)', () => { render(); const button = screen.getByRole('switch', { name: 'AI toggle' }); expect(button).not.toHaveClass('toggle--full-width'); }); it('should render a hidden stable-width decoy when `stableWidthLabel` is set', () => { const { container } = render( ); const decoy = container.querySelector('.toggle__header__stable'); expect(decoy).toBeInTheDocument(); expect(decoy).toHaveTextContent('AI Annotations'); expect(decoy).toHaveAttribute('aria-hidden', 'true'); }); it('should not render the stable-width decoy by default', () => { const { container } = render(); expect( container.querySelector('.toggle__header__stable') ).not.toBeInTheDocument(); }); it('should mark the decoy as aria-hidden', () => { render( ); const decoy = screen.getByText('AI Annotations'); expect(decoy).toHaveAttribute('aria-hidden', 'true'); }); it('should still render the decoy when combined with fullWidth', () => { // The decoy is a no-op for width when fullWidth is on (the parent's // width dominates), but the DOM element should still be present so // toggling fullWidth at runtime doesn't tear down/recreate the node. const { container } = render( ); const button = screen.getByRole('switch', { name: 'AI toggle' }); expect(button).toHaveClass('toggle--full-width'); const decoy = container.querySelector('.toggle__header__stable'); expect(decoy).toBeInTheDocument(); expect(decoy).toHaveTextContent('AI Annotations'); expect(decoy).toHaveAttribute('aria-hidden', 'true'); }); it('should match snapshot for compact + stableWidthLabel', () => { const { asFragment } = render( ); expect(asFragment()).toMatchSnapshot(); }); });