Files
dify/packages/dify-ui/src/switch/__tests__/index.spec.tsx
Stephen Zhou 3c7d6739b5
Some checks failed
autofix.ci / autofix (push) Has been cancelled
Build and Push API & Web / build (api, {{defaultContext}}:api, Dockerfile, DIFY_API_IMAGE_NAME, linux/amd64, ubuntu-latest, build-api-amd64) (push) Has been cancelled
Build and Push API & Web / build (api, {{defaultContext}}:api, Dockerfile, DIFY_API_IMAGE_NAME, linux/arm64, ubuntu-24.04-arm, build-api-arm64) (push) Has been cancelled
Build and Push API & Web / build (web, {{defaultContext}}, web/Dockerfile, DIFY_WEB_IMAGE_NAME, linux/amd64, ubuntu-latest, build-web-amd64) (push) Has been cancelled
Build and Push API & Web / build (web, {{defaultContext}}, web/Dockerfile, DIFY_WEB_IMAGE_NAME, linux/arm64, ubuntu-24.04-arm, build-web-arm64) (push) Has been cancelled
Build and Push API & Web / create-manifest (api, DIFY_API_IMAGE_NAME, merge-api-images) (push) Has been cancelled
Build and Push API & Web / create-manifest (web, DIFY_WEB_IMAGE_NAME, merge-web-images) (push) Has been cancelled
Main CI Pipeline / Skip Duplicate Checks (push) Has been cancelled
Main CI Pipeline / Check Changed Files (push) Has been cancelled
Main CI Pipeline / Run API Tests (push) Has been cancelled
Main CI Pipeline / Skip API Tests (push) Has been cancelled
Main CI Pipeline / API Tests (push) Has been cancelled
Main CI Pipeline / Run Web Tests (push) Has been cancelled
Main CI Pipeline / Skip Web Tests (push) Has been cancelled
Main CI Pipeline / Web Tests (push) Has been cancelled
Main CI Pipeline / Run Web Full-Stack E2E (push) Has been cancelled
Main CI Pipeline / Skip Web Full-Stack E2E (push) Has been cancelled
Main CI Pipeline / Web Full-Stack E2E (push) Has been cancelled
Main CI Pipeline / Style Check (push) Has been cancelled
Main CI Pipeline / Run VDB Tests (push) Has been cancelled
Main CI Pipeline / Skip VDB Tests (push) Has been cancelled
Main CI Pipeline / VDB Tests (push) Has been cancelled
Main CI Pipeline / Run DB Migration Test (push) Has been cancelled
Main CI Pipeline / Skip DB Migration Test (push) Has been cancelled
Main CI Pipeline / DB Migration Test (push) Has been cancelled
Trigger i18n Sync on Push / trigger (push) Has been cancelled
Mark stale issues and pull requests / stale (push) Has been cancelled
test: browser mode for dify ui (#35365)
2026-04-17 12:32:12 +00:00

190 lines
8.7 KiB
TypeScript

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