From 45455fb2a123ad4dc7df2dc6b22823b6d82f7278 Mon Sep 17 00:00:00 2001 From: Sakana <15715093608@163.com> Date: Thu, 3 Sep 2026 21:54:21 +0800 Subject: [PATCH] fix(a11y): keep summary above scrolled violations --- plugins/a11y/app/components/SummaryBar.tsx | 2 +- tests/e2e/a11y-messages-hub-static.spec.ts | 26 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/plugins/a11y/app/components/SummaryBar.tsx b/plugins/a11y/app/components/SummaryBar.tsx index 81827c423..b8e9a2c2f 100644 --- a/plugins/a11y/app/components/SummaryBar.tsx +++ b/plugins/a11y/app/components/SummaryBar.tsx @@ -38,7 +38,7 @@ const ACTION = 'inline-flex items-center gap-1.5 text-xs color-muted bg-secondar export function SummaryBar(props: SummaryBarProps) { const plural = (n: number, one: string) => `${n} ${n === 1 ? one : `${one}s`}` return ( -
+
diff --git a/tests/e2e/a11y-messages-hub-static.spec.ts b/tests/e2e/a11y-messages-hub-static.spec.ts index 9d17ede0c..546f1de2c 100644 --- a/tests/e2e/a11y-messages-hub-static.spec.ts +++ b/tests/e2e/a11y-messages-hub-static.spec.ts @@ -34,6 +34,32 @@ test.describe('a11y-messages playground (static hub build)', () => { await expect(page.locator('[data-df-a11y-overlay]')).toContainText('image-alt') }) + test('keeps the a11y summary controls above scrolled violations', async ({ page }) => { + await page.setViewportSize({ width: 1_000, height: 360 }) + const panel = page.frameLocator('iframe[title="A11y Inspector"]') + + await expect(panel.getByRole('checkbox', { name: /image-alt/ })).toBeVisible() + const scrollArea = panel.locator('#a11y-scroll') + const summary = scrollArea.locator(':scope > div').first() + await scrollArea.evaluate(element => element.scrollTo({ top: element.scrollHeight })) + + expect(await scrollArea.evaluate(element => element.scrollTop)).toBeGreaterThan(0) + expect(await summary.locator('button').evaluateAll(buttons => buttons.flatMap((button) => { + const bounds = button.getBoundingClientRect() + const hitTarget = document.elementFromPoint( + bounds.left + bounds.width / 2, + bounds.top + bounds.height / 2, + ) + return hitTarget != null && button.contains(hitTarget) + ? [] + : [button.getAttribute('aria-label') ?? button.title ?? button.textContent?.trim()] + }))).toEqual([]) + + const critical = panel.getByRole('button', { name: /Critical issues/ }) + await critical.click() + await expect(critical).toHaveAttribute('aria-pressed', 'true') + }) + test('messages panel renders the baked feed; its activate action switches docks over the BroadcastChannel', async ({ page }) => { await page.click('button[data-dock-id="devframes_plugin_messages"]') const panel = page.frameLocator('iframe[title="Messages"]')