diff --git a/packages/dify-ui/src/alert-dialog/__tests__/index.spec.tsx b/packages/dify-ui/src/alert-dialog/__tests__/index.spec.tsx
index 71ae1085ad4..df94da72e6d 100644
--- a/packages/dify-ui/src/alert-dialog/__tests__/index.spec.tsx
+++ b/packages/dify-ui/src/alert-dialog/__tests__/index.spec.tsx
@@ -1,46 +1,7 @@
import { render } from 'vitest-browser-react'
-import {
- AlertDialog,
- AlertDialogActions,
- AlertDialogCancelButton,
- AlertDialogConfirmButton,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogTitle,
- AlertDialogTrigger,
-} from '../index'
+import { AlertDialog, AlertDialogActions, AlertDialogContent, AlertDialogTitle } from '../index'
describe('AlertDialog wrapper', () => {
- describe('Rendering', () => {
- it('should render alert dialog content when dialog is open', async () => {
- const screen = await render(
-
-
- Confirm Delete
- This action cannot be undone.
-
- ,
- )
-
- await expect.element(screen.getByRole('alertdialog')).toHaveTextContent('Confirm Delete')
- await expect
- .element(screen.getByRole('alertdialog'))
- .toHaveTextContent('This action cannot be undone.')
- })
-
- it('should not render content when dialog is closed', async () => {
- const screen = await render(
-
-
- Hidden Title
-
- ,
- )
-
- expect(screen.container.querySelector('[role="alertdialog"]')).not.toBeInTheDocument()
- })
- })
-
describe('Props', () => {
it('should apply custom className to popup', async () => {
const screen = await render(
@@ -67,77 +28,18 @@ describe('AlertDialog wrapper', () => {
})
})
- describe('User Interactions', () => {
- it('should open and close dialog when trigger and cancel button are clicked', async () => {
- const screen = await render(
-
- Open Dialog
-
- Action Required
- Please confirm the action.
-
- Cancel
-
-
- ,
- )
-
- expect(screen.container.querySelector('[role="alertdialog"]')).not.toBeInTheDocument()
-
- await screen.getByRole('button', { name: 'Open Dialog' }).click()
- await expect.element(screen.getByRole('alertdialog')).toHaveTextContent('Action Required')
-
- await screen.getByRole('button', { name: 'Cancel' }).click()
- await vi.waitFor(() => {
- expect(screen.container.querySelector('[role="alertdialog"]')).not.toBeInTheDocument()
- })
- })
- })
-
describe('Composition Helpers', () => {
- it('should render actions wrapper and confirm button', async () => {
+ it('should forward className to the actions wrapper', async () => {
const screen = await render(
Action Required
-
- Confirm
-
+
,
)
await expect.element(screen.getByTestId('actions')).toHaveClass('custom-actions')
- await expect.element(screen.getByRole('button', { name: 'Confirm' })).toBeInTheDocument()
- })
-
- it('should keep dialog open after confirm click and close via cancel helper', async () => {
- const onConfirm = vi.fn()
-
- const screen = await render(
-
- Open Dialog
-
- Action Required
-
- Cancel
- Confirm
-
-
- ,
- )
-
- await screen.getByRole('button', { name: 'Open Dialog' }).click()
- await expect.element(screen.getByRole('alertdialog')).toBeInTheDocument()
-
- await screen.getByRole('button', { name: 'Confirm' }).click()
- expect(onConfirm).toHaveBeenCalledTimes(1)
- await expect.element(screen.getByRole('alertdialog')).toBeInTheDocument()
-
- await screen.getByRole('button', { name: 'Cancel' }).click()
- await vi.waitFor(() => {
- expect(screen.container.querySelector('[role="alertdialog"]')).not.toBeInTheDocument()
- })
})
})
})
diff --git a/packages/dify-ui/src/alert-dialog/index.stories.tsx b/packages/dify-ui/src/alert-dialog/index.stories.tsx
index 03975f880af..bc2cf398148 100644
--- a/packages/dify-ui/src/alert-dialog/index.stories.tsx
+++ b/packages/dify-ui/src/alert-dialog/index.stories.tsx
@@ -67,6 +67,9 @@ export const Default: Story = {
await expect(dialog).toBeVisible()
})
+ await userEvent.click(body.getByRole('button', { name: 'Delete' }))
+ await expect(dialog).toBeVisible()
+
await userEvent.click(body.getByRole('button', { name: 'Cancel' }))
await waitFor(async () => {
await expect(
diff --git a/packages/dify-ui/src/avatar/__tests__/index.spec.tsx b/packages/dify-ui/src/avatar/__tests__/index.spec.tsx
index b92cc39c9a3..8231d196e5d 100644
--- a/packages/dify-ui/src/avatar/__tests__/index.spec.tsx
+++ b/packages/dify-ui/src/avatar/__tests__/index.spec.tsx
@@ -1,5 +1,5 @@
import { render } from 'vitest-browser-react'
-import { Avatar, AvatarFallback, AvatarImage, AvatarRoot } from '..'
+import { Avatar } from '..'
function stubImageLoader() {
const originalImage = window.Image
@@ -27,26 +27,12 @@ function stubImageLoader() {
describe('Avatar', () => {
describe('Rendering', () => {
- it('should keep the fallback visible when avatar URL is provided before image load', async () => {
- const screen = await render(
- ,
- )
-
- await expect.element(screen.getByText('J')).toBeInTheDocument()
- })
-
it('should render fallback with uppercase initial when avatar is null', async () => {
const screen = await render()
expect(screen.container.querySelector('img')).not.toBeInTheDocument()
await expect.element(screen.getByText('A')).toBeInTheDocument()
})
-
- it('should render the fallback when avatar is provided', async () => {
- const screen = await render()
-
- await expect.element(screen.getByText('J')).toBeInTheDocument()
- })
})
describe('className prop', () => {
@@ -58,58 +44,13 @@ describe('Avatar', () => {
})
})
- describe('Primitives', () => {
- it('should support composed avatar usage through exported primitives', async () => {
- const screen = await render(
-
-
-
- J
-
- ,
- )
-
- await expect.element(screen.getByTestId('avatar-root')).toBeInTheDocument()
- await expect.element(screen.getByText('J')).toBeInTheDocument()
- await expect.element(screen.getByText('J')).toHaveStyle({ backgroundColor: 'rgb(1, 2, 3)' })
- })
- })
-
- describe('Edge Cases', () => {
- it('should handle empty string name gracefully', async () => {
- const screen = await render()
-
- expect(screen.container.firstElementChild).toBeInTheDocument()
- expect(screen.container.textContent).toBe('')
- })
-
- it.each([
- { name: '中文名', expected: '中', label: 'Chinese characters' },
- { name: '123User', expected: '1', label: 'number' },
- ])(
- 'should display first character when name starts with $label',
- async ({ name, expected }) => {
- const screen = await render()
-
- await expect.element(screen.getByText(expected)).toBeInTheDocument()
- },
- )
-
- it('should handle empty string avatar as falsy value', async () => {
- const screen = await render()
-
- expect(screen.container.querySelector('img')).not.toBeInTheDocument()
- await expect.element(screen.getByText('T')).toBeInTheDocument()
- })
- })
-
describe('onLoadingStatusChange', () => {
- it('should forward image loading status changes', async () => {
+ it('should show fallback until the image loads and forward status changes', async () => {
const { images, restore } = stubImageLoader()
const onStatusChange = vi.fn()
try {
- await render(
+ const screen = await render(
{
/>,
)
+ await expect.element(screen.getByText('J')).toBeVisible()
await vi.waitFor(() => {
expect(onStatusChange).toHaveBeenCalledWith('loading')
})
@@ -126,6 +68,7 @@ describe('Avatar', () => {
await vi.waitFor(() => {
expect(onStatusChange).toHaveBeenCalledWith('loaded')
})
+ await expect.element(screen.getByRole('img', { name: 'John' })).toBeVisible()
} finally {
restore()
}
diff --git a/packages/dify-ui/src/button/__tests__/index.spec.tsx b/packages/dify-ui/src/button/__tests__/index.spec.tsx
index b4803f08782..c932e94ecfa 100644
--- a/packages/dify-ui/src/button/__tests__/index.spec.tsx
+++ b/packages/dify-ui/src/button/__tests__/index.spec.tsx
@@ -7,16 +7,6 @@ const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElem
describe('Button', () => {
describe('rendering', () => {
- it('renders children text', async () => {
- const screen = await render()
- await expect.element(screen.getByRole('button')).toHaveTextContent('Click me')
- })
-
- it('renders as a native button element by default', async () => {
- const screen = await render()
- expect(screen.getByRole('button').element().tagName).toBe('BUTTON')
- })
-
it('defaults to type="button"', async () => {
const screen = await render()
await expect.element(screen.getByRole('button')).toHaveAttribute('type', 'button')
@@ -39,51 +29,8 @@ describe('Button', () => {
})
})
- describe('loading', () => {
- it('shows spinner when loading', async () => {
- const screen = await render()
- expect(
- screen.getByRole('button').element().querySelector('[aria-hidden="true"]'),
- ).toBeInTheDocument()
- })
-
- it('hides spinner when not loading', async () => {
- const screen = await render()
- expect(
- screen.getByRole('button').element().querySelector('[aria-hidden="true"]'),
- ).not.toBeInTheDocument()
- })
-
- it('keeps loading buttons focusable by default', async () => {
- const screen = await render()
- const button = screen.getByRole('button').element()
-
- expect(button).not.toHaveAttribute('disabled')
- expect((button as HTMLButtonElement).disabled).toBe(false)
- expect(button).toHaveAttribute('aria-disabled', 'true')
-
- asHTMLElement(button).focus()
- expect(button).toHaveFocus()
- })
-
- it('does not set aria-busy when loading', async () => {
- const screen = await render()
- await expect.element(screen.getByRole('button')).not.toHaveAttribute('aria-busy')
- })
-
- it('does not set aria-busy when not loading', async () => {
- const screen = await render()
- await expect.element(screen.getByRole('button')).not.toHaveAttribute('aria-busy')
- })
- })
-
- describe('disabled', () => {
- it('disables button when disabled prop is set', async () => {
- const screen = await render()
- await expect.element(screen.getByRole('button')).toBeDisabled()
- })
-
- it('does not keep normal disabled buttons focusable by default', async () => {
+ describe('disabled states', () => {
+ it('uses native disabled semantics for regular disabled buttons', async () => {
const screen = await render()
const button = screen.getByRole('button').element()
@@ -102,13 +49,6 @@ describe('Button', () => {
})
describe('events', () => {
- it('fires onClick when clicked', async () => {
- const onClick = vi.fn()
- const screen = await render()
- await screen.getByRole('button').click()
- expect(onClick).toHaveBeenCalledTimes(1)
- })
-
it('does not implicitly submit a form through a loading submit button', async () => {
const onSubmit = vi.fn((event: React.FormEvent) => event.preventDefault())
const screen = await render(
diff --git a/packages/dify-ui/src/collapsible/__tests__/index.spec.tsx b/packages/dify-ui/src/collapsible/__tests__/index.spec.tsx
index 6ee69322065..3f8522632f3 100644
--- a/packages/dify-ui/src/collapsible/__tests__/index.spec.tsx
+++ b/packages/dify-ui/src/collapsible/__tests__/index.spec.tsx
@@ -2,38 +2,6 @@ import { render } from 'vitest-browser-react'
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from '../index'
describe('Collapsible wrappers', () => {
- it('renders the Base UI anatomy with an accessible trigger', async () => {
- const screen = await render(
-
- Recovery keys
- Panel content
- ,
- )
-
- await expect.element(screen.getByTestId('collapsible-root')).toBeInTheDocument()
- await expect
- .element(screen.getByRole('button', { name: 'Recovery keys' }))
- .toHaveAttribute('data-panel-open', '')
- await expect.element(screen.getByText('Panel content')).toBeInTheDocument()
- })
-
- it('toggles open state through the trigger without caller-owned state', async () => {
- const screen = await render(
-
- Toggle section
- Hidden content
- ,
- )
- const trigger = screen.getByRole('button', { name: 'Toggle section' })
-
- await expect.element(trigger).not.toHaveAttribute('data-panel-open')
-
- await trigger.click()
-
- await expect.element(trigger).toHaveAttribute('data-panel-open', '')
- await expect.element(screen.getByText('Hidden content')).toBeInTheDocument()
- })
-
it('forwards className to every compound part', async () => {
const screen = await render(
@@ -49,17 +17,18 @@ describe('Collapsible wrappers', () => {
expect(screen.container.querySelector('.custom-root')).toBeInTheDocument()
})
- it('passes Base UI panel props through to the panel', async () => {
+ it('keeps closed panel content mounted when requested', async () => {
const screen = await render(
-
- Styled trigger
- Styled panel
+
+ Recovery options
+ Recovery codes
,
)
await expect
- .element(screen.getByRole('button', { name: 'Styled trigger' }))
- .toHaveAttribute('data-panel-open', '')
- await expect.element(screen.getByText('Styled panel')).toBeInTheDocument()
+ .element(screen.getByRole('button', { name: 'Recovery options' }))
+ .toHaveAttribute('aria-expanded', 'false')
+ await expect.element(screen.getByText('Recovery codes')).toBeInTheDocument()
+ await expect.element(screen.getByText('Recovery codes')).not.toBeVisible()
})
})
diff --git a/packages/dify-ui/src/collapsible/index.stories.tsx b/packages/dify-ui/src/collapsible/index.stories.tsx
index d8943fde28f..e90303c31cd 100644
--- a/packages/dify-ui/src/collapsible/index.stories.tsx
+++ b/packages/dify-ui/src/collapsible/index.stories.tsx
@@ -1,5 +1,6 @@
import type { Meta, StoryObj } from '@storybook/react-vite'
import * as React from 'react'
+import { expect, waitFor } from 'storybook/test'
import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from '.'
import { cn } from '../cn'
@@ -76,6 +77,22 @@ export const DefaultClosed: Story = {
),
+ play: async ({ canvas, userEvent }) => {
+ const trigger = canvas.getByRole('button', { name: 'Recovery keys' })
+
+ await expect(trigger).toHaveAttribute('aria-expanded', 'false')
+ await expect(canvas.queryByText('alien-bean-pasta')).not.toBeInTheDocument()
+
+ await userEvent.click(trigger)
+
+ await expect(trigger).toHaveAttribute('aria-expanded', 'true')
+ await expect(canvas.getByText('alien-bean-pasta')).toBeVisible()
+
+ await userEvent.click(trigger)
+ await waitFor(async () => {
+ await expect(canvas.queryByText('alien-bean-pasta')).not.toBeInTheDocument()
+ })
+ },
}
export const DefaultOpen: Story = {
diff --git a/packages/dify-ui/src/file-tree/__tests__/index.spec.tsx b/packages/dify-ui/src/file-tree/__tests__/index.spec.tsx
index 2b0c9673ca6..85a4eddc5ef 100644
--- a/packages/dify-ui/src/file-tree/__tests__/index.spec.tsx
+++ b/packages/dify-ui/src/file-tree/__tests__/index.spec.tsx
@@ -67,25 +67,6 @@ describe('FileTree', () => {
await expect.element(selectedFile).toHaveAttribute('data-selected')
})
- it('collapses and expands folders with click and native button keyboard behavior', async () => {
- const screen = await render()
- const src = screen.getByRole('button', { name: 'src' })
-
- await src.click()
-
- await expect
- .element(screen.getByRole('button', { name: 'src' }))
- .toHaveAttribute('aria-expanded', 'false')
- expect(screen.container.textContent).not.toContain('components')
-
- await src.click()
-
- await expect
- .element(screen.getByRole('button', { name: 'src' }))
- .toHaveAttribute('aria-expanded', 'true')
- await expect.element(screen.getByRole('button', { name: 'components' })).toBeInTheDocument()
- })
-
it('activates file preview buttons without navigation semantics', async () => {
const onPreview = vi.fn()
const screen = await render()
diff --git a/packages/dify-ui/src/input/__tests__/index.spec.tsx b/packages/dify-ui/src/input/__tests__/index.spec.tsx
index 0a21e8555dc..0bcdf350bde 100644
--- a/packages/dify-ui/src/input/__tests__/index.spec.tsx
+++ b/packages/dify-ui/src/input/__tests__/index.spec.tsx
@@ -4,19 +4,6 @@ import { Form } from '../../form'
import { Input } from '../index'
describe('Input', () => {
- it('should render a labelled Base UI input with its value', async () => {
- const screen = await render(
- ,
- )
-
- const input = screen.getByRole('textbox', { name: 'Workspace name' })
-
- await expect.element(input).toHaveValue('Dify')
- })
-
it('should use Field invalid state', async () => {
const screen = await render(
diff --git a/packages/dify-ui/src/select/__tests__/index.spec.tsx b/packages/dify-ui/src/select/__tests__/index.spec.tsx
index a29e73e4a9f..2802b145061 100644
--- a/packages/dify-ui/src/select/__tests__/index.spec.tsx
+++ b/packages/dify-ui/src/select/__tests__/index.spec.tsx
@@ -13,7 +13,6 @@ import {
SelectValue,
} from '../index'
-const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
const renderWithSafeViewport = (ui: React.ReactNode) =>
render({ui}
)
@@ -258,58 +257,6 @@ describe('Select wrappers', () => {
})
describe('SelectItem', () => {
- it('should render options when children are provided', async () => {
- const screen = await renderOpenSelect()
-
- await expect.element(screen.getByRole('option', { name: 'Seattle' })).toBeInTheDocument()
- await expect.element(screen.getByRole('option', { name: 'New York' })).toBeInTheDocument()
- })
-
- it('should navigate items with arrow keys', async () => {
- const screen = await render(
- ,
- )
-
- const trigger = asHTMLElement(screen.getByRole('combobox', { name: 'city select' }).element())
-
- trigger.focus()
- trigger.dispatchEvent(
- new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true, cancelable: true }),
- )
- await expect
- .element(screen.getByRole('option', { name: 'Seattle' }))
- .toHaveAttribute('data-highlighted')
-
- const highlightedItem = asHTMLElement(
- screen.getByRole('option', { name: 'Seattle' }).element(),
- )
- highlightedItem.dispatchEvent(
- new KeyboardEvent('keydown', { key: 'ArrowDown', bubbles: true, cancelable: true }),
- )
-
- await expect
- .element(screen.getByRole('option', { name: 'New York' }))
- .toHaveAttribute('data-highlighted')
- })
-
it('should expose disabled item semantics', async () => {
const screen = await render(