test(dify-ui): disable base ui animations globally (#35467)

This commit is contained in:
yyh
2026-04-24 16:12:23 +08:00
committed by GitHub
parent 2d09c4788d
commit 791fc5819d
4 changed files with 39 additions and 35 deletions

View File

@@ -90,6 +90,22 @@ See `[web/docs/overlay-migration.md](../../web/docs/overlay-migration.md)` for t
- `pnpm -C packages/dify-ui storybook` — Storybook on the default port. Each primitive has `index.stories.tsx`.
- `pnpm -C packages/dify-ui type-check``tsgo --noEmit` for this package only.
### Disabling Animations In Tests
Base UI can wait for `element.getAnimations()` to finish before it unmounts overlays, panels, and transition-driven components. Browser-based test runners can make that timing unstable, especially when tests assert final DOM state rather than animation behavior.
Set the Base UI test flag in a Vitest setup file to skip those waits:
```ts
(
globalThis as typeof globalThis & {
BASE_UI_ANIMATIONS_DISABLED: boolean
}
).BASE_UI_ANIMATIONS_DISABLED = true
```
`packages/dify-ui/vitest.setup.ts` already applies this for primitive tests.
See `[AGENTS.md](./AGENTS.md)` for:
- Component authoring rules (one-component-per-folder, `cva` + `cn`, relative imports inside the package, subpath imports from consumers).

View File

@@ -3,19 +3,20 @@ import { toast, ToastHost } from '../index'
const asHTMLElement = (element: HTMLElement | SVGElement) => element as HTMLElement
declare global {
// eslint-disable-next-line vars-on-top
var BASE_UI_ANIMATIONS_DISABLED: boolean | undefined
const dispatchToastMouseOver = (element: HTMLElement | SVGElement) => {
element.dispatchEvent(new MouseEvent('mouseover', {
bubbles: true,
}))
}
const dispatchToastMouseOut = (element: HTMLElement | SVGElement) => {
element.dispatchEvent(new MouseEvent('mouseout', {
bubbles: true,
relatedTarget: document.body,
}))
}
describe('@langgenius/dify-ui/toast', () => {
beforeAll(() => {
// Base UI waits for `requestAnimationFrame` + `getAnimations().finished`
// before unmounting a toast. Fake timers can't reliably drive that path,
// so short-circuit it to keep auto-dismiss assertions deterministic in CI.
globalThis.BASE_UI_ANIMATIONS_DISABLED = true
})
beforeEach(() => {
vi.clearAllMocks()
vi.useFakeTimers()
@@ -28,10 +29,6 @@ describe('@langgenius/dify-ui/toast', () => {
vi.useRealTimers()
})
afterAll(() => {
globalThis.BASE_UI_ANIMATIONS_DISABLED = undefined
})
it('should render a success toast when called through the typed shortcut', async () => {
const screen = await render(<ToastHost />)
@@ -62,13 +59,13 @@ describe('@langgenius/dify-ui/toast', () => {
expect(document.body.querySelectorAll('[role="dialog"]')).toHaveLength(3)
expect(document.body.querySelectorAll('button[aria-label="Close notification"][aria-hidden="true"]')).toHaveLength(3)
screen.getByRole('region', { name: 'Notifications' }).element().dispatchEvent(new MouseEvent('mouseover', {
bubbles: true,
}))
const viewport = screen.getByRole('region', { name: 'Notifications' }).element()
dispatchToastMouseOver(viewport)
await vi.waitFor(() => {
expect(document.body.querySelector('button[aria-label="Close notification"][aria-hidden="true"]')).not.toBeInTheDocument()
})
dispatchToastMouseOut(viewport)
})
it('should render a neutral toast when called directly', async () => {
@@ -115,11 +112,11 @@ describe('@langgenius/dify-ui/toast', () => {
onClose,
})
screen.getByRole('region', { name: 'Notifications' }).element().dispatchEvent(new MouseEvent('mouseover', {
bubbles: true,
}))
const viewport = screen.getByRole('region', { name: 'Notifications' }).element()
dispatchToastMouseOver(viewport)
await expect.element(screen.getByRole('button', { name: 'Close notification' })).toBeInTheDocument()
dispatchToastMouseOut(viewport)
asHTMLElement(screen.getByRole('button', { name: 'Close notification' }).element()).click()
await vi.waitFor(() => {
@@ -128,21 +125,6 @@ describe('@langgenius/dify-ui/toast', () => {
expect(onClose).toHaveBeenCalledTimes(1)
})
it('should auto dismiss toasts with the Base UI default timeout', async () => {
const screen = await render(<ToastHost />)
toast('Default timeout')
await expect.element(screen.getByText('Default timeout')).toBeInTheDocument()
await vi.advanceTimersByTimeAsync(4999)
expect(document.body).toHaveTextContent('Default timeout')
await vi.advanceTimersByTimeAsync(1)
await vi.waitFor(() => {
expect(document.body).not.toHaveTextContent('Default timeout')
})
})
it('should respect the host timeout configuration', async () => {
const screen = await render(<ToastHost timeout={3000} />)

View File

@@ -11,6 +11,7 @@ export default defineConfig({
},
test: {
globals: true,
setupFiles: ['./vitest.setup.ts'],
browser: {
enabled: true,
provider: playwright(),

View File

@@ -0,0 +1,5 @@
(
globalThis as typeof globalThis & {
BASE_UI_ANIMATIONS_DISABLED: boolean
}
).BASE_UI_ANIMATIONS_DISABLED = true