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

This commit is contained in:
yyh 2026-06-15 19:55:33 +08:00 committed by GitHub
parent 1e8329f02c
commit 6c3857a4c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 896 additions and 125 deletions

View File

@ -113,7 +113,7 @@ jobs:
run: vp exec playwright install --with-deps chromium
- name: Run dify-ui tests
run: vp test run --coverage --silent=passed-only
run: vp test run --project unit --coverage --silent=passed-only
- name: Report coverage
if: ${{ env.CODECOV_TOKEN != '' }}
@ -123,3 +123,26 @@ jobs:
flags: dify-ui
env:
CODECOV_TOKEN: ${{ env.CODECOV_TOKEN }}
dify-ui-storybook-test:
name: dify-ui Storybook Tests
runs-on: depot-ubuntu-24.04-4
defaults:
run:
shell: bash
working-directory: ./packages/dify-ui
steps:
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Setup web environment
uses: ./.github/actions/setup-web
- name: Install Chromium for Browser Mode
run: vp exec playwright install --with-deps chromium
- name: Run dify-ui Storybook tests
run: vp run test:storybook

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',

View File

@ -24,6 +24,9 @@ const preview: Preview = {
docs: {
toc: true,
},
a11y: {
test: 'error',
},
},
tags: ['autodocs'],
}

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:",

View File

@ -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 />

View File

@ -141,6 +141,7 @@ export const AsLink: Story = {
args: {
variant: 'ghost-accent',
render: <a href="https://example.com" />,
nativeButton: false,
children: 'Link Button',
},
}

View File

@ -207,6 +207,11 @@ export const States: Story = {
</FieldRoot>
</div>
),
parameters: {
a11y: {
test: 'todo',
},
},
}
function ControlledDemo() {

View File

@ -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.',

View File

@ -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

View File

@ -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.',

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'],
},
},
})

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,
},
},
},
],
},
})

845
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -85,10 +85,12 @@ catalog:
'@remixicon/react': 4.9.0
'@rgrove/parse-xml': 4.2.0
'@sentry/react': 10.57.0
'@storybook/addon-a11y': 10.4.4
'@storybook/addon-docs': 10.4.4
'@storybook/addon-links': 10.4.4
'@storybook/addon-onboarding': 10.4.4
'@storybook/addon-themes': 10.4.4
'@storybook/addon-vitest': 10.4.4
'@storybook/nextjs-vite': 10.4.4
'@storybook/react': 10.4.4
'@storybook/react-vite': 10.4.4