import { expect, test, type Page } from "@playwright/test"; import { mockAgentApi } from "./support/mock-agent-api"; import { mockWorkbenchContext } from "./support/mock-workbench-context"; import { expectThemeColor } from "./support/theme-colors"; test.beforeEach(async ({ page }) => { await page.emulateMedia({ reducedMotion: "reduce" }); await mockWorkbenchContext(page); await mockAgentApi(page); await page.goto("/", { waitUntil: "domcontentloaded" }); }); for (const width of [320, 375, 1440]) { test(`layer panel basemap selection has one border and no persistent outer ring at ${width}px`, async ({ page }) => { await page.setViewportSize({ width, height: 1000 }); await page.getByRole("button", { name: /图层:管理地图图层/ }).click(); const panel = page.getByRole("region", { name: "图层", exact: true }); const card = panel.getByRole("button", { name: "无底图", exact: true }); await expect(card).toHaveAttribute("aria-pressed", "true"); await expectThemeColor(card, "border-color", "--action-blue"); await expect(card).toHaveCSS("border-width", "1px"); await expect(card).toHaveCSS("box-shadow", "none"); await card.click(); await expect(card).toHaveCSS("outline-style", "none"); const restingBox = await card.boundingBox(); await page.keyboard.press("Tab"); await card.focus(); await expect(card).toHaveCSS("outline-width", "1px"); await expect(card).toHaveCSS("outline-offset", "-1px"); await expectThemeColor(card, "outline-color", "--action-focus-ring"); expect(await card.boundingBox()).toEqual(restingBox); await page.keyboard.press("Enter"); await expect(card).toHaveAttribute("aria-pressed", "true"); await panel.screenshot({ path: `test-results/form-focus/layer-panel-${width}.png` }); }); } test("standalone basemap controls share the same pointer and keyboard boundary", async ({ page }) => { await renderControls(page); for (const label of ["面板底图", "浮动底图"]) { const control = page.getByRole("region", { name: label, exact: true }); if (label === "浮动底图") await control.getByRole("button", { name: "浮动底图:当前 浅色" }).hover(); const selected = control.getByRole("button", { name: "浅色", exact: true }); await expect(selected).toHaveCSS("border-width", "1px"); await expectThemeColor(selected, "border-color", "--action-blue"); const restingShadow = await selected.evaluate((element) => getComputedStyle(element).boxShadow); expect(restingShadow).not.toMatch(/0px 0px 0px [12]px/); await selected.click(); await expect(selected).toHaveCSS("outline-style", "none"); await page.keyboard.press("Tab"); await selected.focus(); await expect(selected).toHaveCSS("outline-width", "1px"); await expect(selected).toHaveCSS("outline-offset", "-1px"); await expect(selected).toHaveCSS("box-shadow", restingShadow); const next = control.getByRole("button", { name: "影像", exact: true }); await next.focus(); await page.keyboard.press("Enter"); await expect(next).toHaveAttribute("aria-pressed", "true"); await expect(selected).toHaveAttribute("aria-pressed", "false"); await control.screenshot({ path: `test-results/form-focus/${label}.png` }); } }); async function renderControls(page: Page) { await page.evaluate(async () => { const paths = ["/node_modules/.vite/deps/react.js", "/node_modules/.vite/deps/react-dom_client.js", "/src/features/map/core/components/base-layers-control.tsx"]; const [react, dom, controls] = await Promise.all(paths.map((path) => import(path))); const h = react.default.createElement; function Examples() { const [panelId, setPanelId] = react.default.useState("light"); const [floatingId, setFloatingId] = react.default.useState("light"); const options = [{ id: "light", label: "浅色" }, { id: "satellite", label: "影像" }]; return h(react.default.Fragment, null, h(controls.BaseLayersControl, { title: "面板底图", variant: "panel", options, activeLayerId: panelId, onSelectLayer: setPanelId }), h(controls.BaseLayersControl, { title: "浮动底图", options, activeLayerId: floatingId, onSelectLayer: setFloatingId }) ); } const container = document.createElement("div"); container.style.cssText = "position:fixed;z-index:40;top:80px;right:40px;padding:24px;background:var(--surface-reading);display:grid;gap:32px"; document.body.append(container); dom.default.createRoot(container).render(h(Examples)); }); }