test(dify-ui): add strict Storybook a11y checks (#37459)

This commit is contained in:
yyh
2026-06-15 11:55:33 +00:00
committed by GitHub
parent 1e8329f02c
commit 6c3857a4c8
14 changed files with 896 additions and 125 deletions
+2
View File
@@ -8,6 +8,8 @@ const config: StorybookConfig = {
'@storybook/addon-links',
'@storybook/addon-docs',
'@storybook/addon-themes',
'@storybook/addon-a11y',
'@storybook/addon-vitest',
'@chromatic-com/storybook',
],
framework: '@storybook/react-vite',
+3
View File
@@ -24,6 +24,9 @@ const preview: Preview = {
docs: {
toc: true,
},
a11y: {
test: 'error',
},
},
tags: ['autodocs'],
}
+5 -2
View File
@@ -157,8 +157,9 @@
"scripts": {
"storybook": "storybook dev -p 6006",
"storybook:build": "storybook build",
"test": "vp test",
"test:watch": "vp test --watch",
"test": "vp test --project unit",
"test:storybook": "vp test --project storybook --run",
"test:watch": "vp test --project unit --watch",
"type-check": "tsgo"
},
"peerDependencies": {
@@ -178,9 +179,11 @@
"@dify/tsconfig": "workspace:*",
"@egoist/tailwindcss-icons": "catalog:",
"@iconify-json/ri": "catalog:",
"@storybook/addon-a11y": "catalog:",
"@storybook/addon-docs": "catalog:",
"@storybook/addon-links": "catalog:",
"@storybook/addon-themes": "catalog:",
"@storybook/addon-vitest": "catalog:",
"@storybook/react-vite": "catalog:",
"@tailwindcss/vite": "catalog:",
"@tanstack/react-hotkeys": "catalog:",
@@ -297,8 +297,9 @@ const CommandPaletteList = () => {
<AutocompleteList className="max-h-72 rounded-lg border border-divider-subtle bg-components-panel-bg p-1 shadow-xs">
{groups.map((group, groupIndex) => (
<AutocompleteGroup key={group.label} items={group.items}>
{groupIndex > 0 && <AutocompleteSeparator />}
<AutocompleteGroupLabel>{group.label}</AutocompleteGroupLabel>
<AutocompleteGroupLabel className={groupIndex > 0 ? 'mt-1 border-t border-divider-subtle pt-2' : undefined}>
{group.label}
</AutocompleteGroupLabel>
<AutocompleteCollection>
{(item: Suggestion) => (
<AutocompleteItem key={item.value} value={item} className="grid grid-cols-[1fr_auto]">
@@ -691,7 +692,7 @@ export const CommandPalette: Story = {
>
<AutocompleteInputGroup className="mb-2">
<span className="i-ri-search-line ml-2 size-4 shrink-0 text-text-tertiary" aria-hidden="true" />
<AutocompleteInput placeholder="Run a command…" aria-label="Run a command" />
<AutocompleteInput placeholder="Run a command…" aria-label="Run a command" aria-expanded="true" />
<AutocompleteClear />
</AutocompleteInputGroup>
<CommandPaletteList />
@@ -141,6 +141,7 @@ export const AsLink: Story = {
args: {
variant: 'ghost-accent',
render: <a href="https://example.com" />,
nativeButton: false,
children: 'Link Button',
},
}
@@ -207,6 +207,11 @@ export const States: Story = {
</FieldRoot>
</div>
),
parameters: {
a11y: {
test: 'todo',
},
},
}
function ControlledDemo() {
@@ -9,10 +9,12 @@ function PaginationExample({
initialPage = 2,
initialPageSize = 25,
totalPages = 200,
label = 'Pagination',
}: {
initialPage?: number
initialPageSize?: number
totalPages?: number
label?: string
}) {
const [page, setPage] = React.useState(initialPage)
const [pageSize, setPageSize] = React.useState(initialPageSize)
@@ -21,6 +23,7 @@ function PaginationExample({
<Pagination
page={page}
totalPages={totalPages}
aria-label={label}
onPageChange={setPage}
pageSize={{
value: pageSize,
@@ -42,10 +45,10 @@ function PaginationDemo(props: React.ComponentProps<typeof PaginationExample>) {
function DesignSpecDemo() {
return (
<div className="flex w-236 max-w-full flex-col gap-6 bg-components-panel-bg px-16 py-10">
<PaginationExample />
<PaginationExample initialPage={2} initialPageSize={25} />
<PaginationExample initialPage={2} initialPageSize={25} />
<PaginationExample initialPage={2} initialPageSize={25} />
<PaginationExample label="Default pagination" />
<PaginationExample label="Hover pagination" initialPage={2} initialPageSize={25} />
<PaginationExample label="Focused pagination" initialPage={2} initialPageSize={25} />
<PaginationExample label="Page size pagination" initialPage={2} initialPageSize={25} />
</div>
)
}
@@ -74,11 +77,19 @@ type Story = StoryObj<typeof meta>
export const Playground: Story = {
render: () => <PaginationDemo />,
parameters: {
a11y: {
test: 'todo',
},
},
}
export const DesignSpec: Story = {
render: () => <DesignSpecDemo />,
parameters: {
a11y: {
test: 'todo',
},
docs: {
description: {
story: 'Pagination rows with default, hover-like, focused, page-size, and skeleton examples.',
@@ -127,9 +127,9 @@ export const Infotip: Story = {
closeDelay={200}
aria-label="Set which resource to use first when running models."
render={(
<span className="inline-flex h-4 w-4 shrink-0 items-center justify-center">
<button type="button" className="inline-flex h-4 w-4 shrink-0 items-center justify-center rounded-sm outline-hidden focus-visible:ring-2 focus-visible:ring-state-accent-solid">
<span aria-hidden className="i-ri-question-line h-3.5 w-3.5 text-text-quaternary hover:text-text-tertiary" />
</span>
</button>
)}
/>
<PopoverContent
@@ -113,6 +113,9 @@ export const DesignSpec: Story = {
</div>
),
parameters: {
a11y: {
test: 'todo',
},
docs: {
description: {
story: 'Figma node 2473:9851: segmented control examples with text+icon and icon-only rows, with and without outer padding.',
@@ -168,6 +171,9 @@ export const DataAttributeStates: Story = {
</div>
),
parameters: {
a11y: {
test: 'todo',
},
docs: {
description: {
story: '`SegmentedControlItem` gets `data-pressed` and `data-disabled` from Base UI Toggle. Accent, neutral, and multiple-selection examples are composed through props and className.',
-24
View File
@@ -1,8 +1,5 @@
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite-plus'
import { playwright } from 'vite-plus/test/browser-playwright'
const isCI = !!process.env.CI
export default defineConfig({
plugins: [react()],
@@ -16,25 +13,4 @@ export default defineConfig({
'@base-ui/react/use-render',
],
},
test: {
globals: true,
setupFiles: ['./vitest.setup.ts'],
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: 'chromium' }],
headless: true,
},
coverage: {
provider: 'v8',
include: ['src/**/*.{ts,tsx}'],
exclude: [
'src/**/*.stories.{ts,tsx}',
'src/**/__tests__/**',
'src/themes/**',
'src/styles/**',
],
reporter: isCI ? ['json', 'json-summary'] : ['text', 'json', 'json-summary'],
},
},
})
+71
View File
@@ -0,0 +1,71 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vite-plus'
import { playwright } from 'vite-plus/test/browser-playwright'
const dirname = path.dirname(fileURLToPath(import.meta.url))
const configDir = path.join(dirname, '.storybook')
const isCI = !!process.env.CI
export default defineConfig({
plugins: [react()],
resolve: {
tsconfigPaths: true,
},
optimizeDeps: {
include: [
'@base-ui/react/form',
'@base-ui/react/merge-props',
'@base-ui/react/use-render',
],
},
test: {
coverage: {
provider: 'v8',
include: ['src/**/*.{ts,tsx}'],
exclude: [
'src/**/*.stories.{ts,tsx}',
'src/**/__tests__/**',
'src/themes/**',
'src/styles/**',
],
reporter: isCI ? ['json', 'json-summary'] : ['text', 'json', 'json-summary'],
},
projects: [
{
extends: true,
test: {
name: 'unit',
globals: true,
setupFiles: ['./vitest.setup.ts'],
include: ['src/**/__tests__/**/*.spec.{ts,tsx}'],
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: 'chromium' }],
headless: true,
},
},
},
{
extends: true,
plugins: [
storybookTest({
configDir,
}),
],
test: {
name: 'storybook',
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: 'chromium' }],
headless: true,
},
},
},
],
},
})