packages/app/component-tests/focus-ring.spec.ts
Rama dev · réplica de lectura del repositorio original.
import { expect, story } from "../../stories/playwright/story"
story("returns focus to a menu trigger without a ring after a pointer open", async ({ mount, page }) => {
await mount("ui-menu--basic")
const trigger = page.getByRole("button", { name: "Open menu" })
await trigger.click()
await expect(page.getByRole("menuitem", { name: "Save" })).toBeVisible()
// The menu takes focus a task after it opens; an Escape before that rings the trigger, which still holds focus.
await expect(page.getByRole("menu")).toBeFocused()
await page.keyboard.press("Escape")
await expect(trigger).toBeFocused()
await expect(trigger).toHaveCSS("outline-style", "none")
})
story("keeps the ring on a menu trigger after a keyboard open", async ({ mount, page }) => {
await mount("ui-menu--basic")
const trigger = page.getByRole("button", { name: "Open menu" })
await page.keyboard.press("Shift")
await trigger.focus()
await page.keyboard.press("Enter")
await expect(page.getByRole("menuitem", { name: "Save" })).toBeVisible()
await page.keyboard.press("Escape")
await expect(trigger).toBeFocused()
await expect(trigger).toHaveCSS("outline-style", "solid")
})
story("keeps the ring on a menu trigger opened from code after a pointer open", async ({ mount, page }) => {
await mount("ui-menu--programmatic-and-non-modal")
const trigger = page.getByRole("button", { name: "Controlled" })
await trigger.click()
await expect(page.getByRole("menuitem", { name: "Rename" })).toBeVisible()
await page.keyboard.press("Escape")
await expect(page.getByRole("menuitem", { name: "Rename" })).toHaveCount(0)
await page.getByRole("button", { name: "Open from code" }).click()
await expect(page.getByRole("menuitem", { name: "Rename" })).toBeVisible()
await page.keyboard.press("Escape")
await expect(trigger).toBeFocused()
await expect(trigger).toHaveCSS("outline-style", "solid")
})
story("leaves focus where an outside click put it when a non-modal menu closes", async ({ mount, page }) => {
await mount("ui-menu--programmatic-and-non-modal")
const trigger = page.getByRole("button", { name: "Non-modal" })
await trigger.click()
await expect(page.getByRole("menuitem", { name: "Pin" })).toBeVisible()
// Kobalte starts listening for outside presses a task after the menu opens, queued behind the one that focuses it.
await expect(page.getByRole("menu")).toBeFocused()
await page.mouse.click(4, 300)
await expect(page.getByRole("menuitem", { name: "Pin" })).toHaveCount(0)
// The close focus restore has run once the story counts it; the click on empty space left focus on the body.
await expect(page.getByLabel("Non-modal focus restores")).toHaveText("1")
expect(await page.evaluate(() => document.activeElement === document.body)).toBe(true)
await expect(trigger).not.toBeFocused()
})
story("returns focus to a dialog's trigger without a ring after a pointer open", async ({ mount, page }) => {
await mount("ui-dialog--opened-from-button")
const trigger = page.getByRole("button", { name: "Choose model" })
await trigger.click()
await expect(page.getByText("Alpha and Beta are available.")).toBeVisible()
await expect
.poll(() => page.evaluate(() => !!document.activeElement?.closest('[data-component="dialog-v2"]')))
.toBe(true)
await page.keyboard.press("Escape")
await expect(trigger).toBeFocused()
await expect(trigger).toHaveCSS("outline-style", "none")
})
story("keeps the ring on a dialog's trigger after a keyboard open", async ({ mount, page }) => {
await mount("ui-dialog--opened-from-button")
const trigger = page.getByRole("button", { name: "Choose model" })
await page.keyboard.press("Shift")
await trigger.focus()
await page.keyboard.press("Enter")
await expect(page.getByText("Alpha and Beta are available.")).toBeVisible()
await expect
.poll(() => page.evaluate(() => !!document.activeElement?.closest('[data-component="dialog-v2"]')))
.toBe(true)
await page.keyboard.press("Escape")
await expect(trigger).toBeFocused()
await expect(trigger).toHaveCSS("outline-style", "solid")
})
story("keeps queue rings inside controls under the list fade", async ({ mount, page }) => {
await mount("session-queue-panel--scrolling")
const list = page.locator('[data-slot="session-queue-list"]')
const remove = page.locator('[data-action="session-queue-remove"]').first()
await expect(list).toHaveCSS("scroll-padding-top", "8px")
await expect(list).toHaveCSS("scroll-padding-bottom", "12px")
await page.keyboard.press("Shift")
await remove.focus()
await expect(remove).toHaveCSS("outline-style", "solid")
await expect(remove).toHaveCSS("outline-offset", "-2px")
})
story("leaves no ring on the review toggle after a pointer click", async ({ mount, page }) => {
await mount("app-session-header-actions--review-toggle")
const toggle = page.getByRole("button", { name: "Toggle review" })
await toggle.click()
// A later key press makes Chromium ring whatever still holds focus, so the click must not leave it there.
await page.keyboard.press("Shift")
expect(await toggle.evaluate((element) => element.matches(":focus-visible"))).toBe(false)
await expect(toggle).toHaveCSS("outline-style", "none")
})