import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { describe, expect, it, vi } from 'vitest' import Switch from '../index' import { SwitchSkeleton } from '../skeleton' const getThumb = (switchElement: HTMLElement) => switchElement.querySelector('span') describe('Switch', () => { it('should render in unchecked state when value is false', () => { render() const switchElement = screen.getByRole('switch') expect(switchElement).toBeInTheDocument() expect(switchElement).toHaveAttribute('aria-checked', 'false') expect(switchElement).not.toHaveAttribute('data-checked') }) it('should render in checked state when value is true', () => { render() const switchElement = screen.getByRole('switch') expect(switchElement).toHaveAttribute('aria-checked', 'true') expect(switchElement).toHaveAttribute('data-checked', '') }) it('should call onChange with next value when clicked', async () => { const onChange = vi.fn() const user = userEvent.setup() render() const switchElement = screen.getByRole('switch') await user.click(switchElement) expect(onChange).toHaveBeenCalledWith(true) expect(onChange).toHaveBeenCalledTimes(1) expect(switchElement).toHaveAttribute('aria-checked', 'false') }) it('should work in controlled mode with value prop', async () => { const onChange = vi.fn() const user = userEvent.setup() const { rerender } = render() const switchElement = screen.getByRole('switch') expect(switchElement).toHaveAttribute('aria-checked', 'false') await user.click(switchElement) expect(onChange).toHaveBeenCalledWith(true) expect(switchElement).toHaveAttribute('aria-checked', 'false') rerender() expect(switchElement).toHaveAttribute('aria-checked', 'true') }) it('should not call onChange when disabled', async () => { const onChange = vi.fn() const user = userEvent.setup() render() const switchElement = screen.getByRole('switch') expect(switchElement).toHaveClass('data-[disabled]:cursor-not-allowed') expect(switchElement).toHaveAttribute('data-disabled', '') await user.click(switchElement) expect(onChange).not.toHaveBeenCalled() }) it('should apply correct size classes', () => { const { rerender } = render() const switchElement = screen.getByRole('switch') expect(switchElement).toHaveClass('h-2.5', 'w-3.5', 'rounded-[2px]') rerender() expect(switchElement).toHaveClass('h-3', 'w-5') rerender() expect(switchElement).toHaveClass('h-4', 'w-7') rerender() expect(switchElement).toHaveClass('h-5', 'w-9') }) it('should apply custom className', () => { render() expect(screen.getByRole('switch')).toHaveClass('custom-test-class') }) it('should expose checked state styling hooks on the root and thumb', () => { const { rerender } = render() const switchElement = screen.getByRole('switch') const thumb = getThumb(switchElement) expect(switchElement).toHaveClass('bg-components-toggle-bg-unchecked', 'data-[checked]:bg-components-toggle-bg') expect(thumb).toHaveClass('data-[checked]:translate-x-[14px]') expect(thumb).not.toHaveAttribute('data-checked') rerender() expect(switchElement).toHaveAttribute('data-checked', '') expect(thumb).toHaveAttribute('data-checked', '') }) it('should expose disabled state styling hooks instead of relying on opacity', () => { const { rerender } = render() const switchElement = screen.getByRole('switch') expect(switchElement).toHaveClass( 'data-[disabled]:bg-components-toggle-bg-unchecked-disabled', 'data-[disabled]:data-[checked]:bg-components-toggle-bg-disabled', ) expect(switchElement).toHaveAttribute('data-disabled', '') rerender() expect(switchElement).toHaveAttribute('data-disabled', '') expect(switchElement).toHaveAttribute('data-checked', '') }) it('should have focus-visible ring styles', () => { render() const switchElement = screen.getByRole('switch') expect(switchElement).toHaveClass('focus-visible:ring-2') }) it('should respect prefers-reduced-motion', () => { render() const switchElement = screen.getByRole('switch') expect(switchElement).toHaveClass('motion-reduce:transition-none') }) describe('loading state', () => { it('should render as disabled when loading', async () => { const onChange = vi.fn() const user = userEvent.setup() render() const switchElement = screen.getByRole('switch') expect(switchElement).toHaveClass('data-[disabled]:cursor-not-allowed') expect(switchElement).toHaveAttribute('aria-busy', 'true') expect(switchElement).toHaveAttribute('data-disabled', '') await user.click(switchElement) expect(onChange).not.toHaveBeenCalled() }) it('should show spinner icon for md and lg sizes', () => { const { rerender, container } = render() expect(container.querySelector('span[aria-hidden="true"] i')).toBeInTheDocument() rerender() expect(container.querySelector('span[aria-hidden="true"] i')).toBeInTheDocument() }) it('should not show spinner for xs and sm sizes', () => { const { rerender, container } = render() expect(container.querySelector('span[aria-hidden="true"] i')).not.toBeInTheDocument() rerender() expect(container.querySelector('span[aria-hidden="true"] i')).not.toBeInTheDocument() }) it('should apply disabled data-state hooks when loading', () => { const { rerender } = render() const switchElement = screen.getByRole('switch') expect(switchElement).toHaveAttribute('data-disabled', '') rerender() expect(switchElement).toHaveAttribute('data-disabled', '') expect(switchElement).toHaveAttribute('data-checked', '') }) }) }) describe('SwitchSkeleton', () => { it('should render a plain div without switch role', () => { render() expect(screen.queryByRole('switch')).not.toBeInTheDocument() expect(screen.getByTestId('skeleton-switch')).toBeInTheDocument() }) it('should apply skeleton styles', () => { render() const el = screen.getByTestId('skeleton-switch') expect(el).toHaveClass('bg-text-quaternary', 'opacity-20') }) it('should apply correct skeleton size classes', () => { const { rerender } = render() const el = screen.getByTestId('s') expect(el).toHaveClass('h-2.5', 'w-3.5', 'rounded-[2px]') rerender() expect(el).toHaveClass('h-5', 'w-9', 'rounded-[6px]') }) it('should apply custom className to skeleton', () => { render() expect(screen.getByTestId('s')).toHaveClass('custom-class') }) })