Close the popup menus on an outside click, via the popover API
CI / full-gate (push) Successful in 2m40s
CI / full-gate (push) Successful in 2m40s
This commit is contained in:
@@ -88,12 +88,12 @@ test.describe.serial("authenticated admin journey", () => {
|
||||
const row = page.locator("tr", { hasText: `lang-${suffix}@plainpages.local` });
|
||||
const editHref = await row.locator('a[href^="/admin/users/"]').first().getAttribute("href");
|
||||
await page.goto(`${editHref}`);
|
||||
await expect(page.locator('summary[aria-label="Språk"]')).toHaveCount(1);
|
||||
await expect(page.locator('button[aria-label="Språk"]')).toHaveCount(1);
|
||||
await page.getByRole("button", { name: "Skapa återställningskod" }).click(); // POST-only route
|
||||
await expect(page.getByText("Återställningskod skapad")).toBeVisible();
|
||||
|
||||
// The picker is here too, and following it lands on a real page in the other language.
|
||||
await page.locator('summary[aria-label="Språk"]').click();
|
||||
await page.locator('button[aria-label="Språk"]').click();
|
||||
await page.getByRole("link", { name: /English/i }).click();
|
||||
expect(page.url()).toContain("locale=en-US");
|
||||
await expect(page.locator("html")).toHaveAttribute("lang", "en-US");
|
||||
@@ -120,6 +120,11 @@ test.describe.serial("authenticated admin journey", () => {
|
||||
const row = page.locator("tr", { hasText: email });
|
||||
await expect(row).toBeVisible();
|
||||
|
||||
// Row actions sit behind the kebab popover: opening it reveals them, in the top layer, so the
|
||||
// scrolling table around the row cannot clip the panel.
|
||||
await row.locator("button.kebab").click();
|
||||
await expect(row.locator('a[href^="/admin/users/"]').first()).toBeVisible();
|
||||
|
||||
// Delete through the confirm interstitial (the row's Edit link carries the id).
|
||||
const editHref = await row.locator('a[href^="/admin/users/"]').first().getAttribute("href");
|
||||
await page.goto(`${editHref}/delete`);
|
||||
@@ -186,9 +191,9 @@ test.describe.serial("authenticated admin journey", () => {
|
||||
|
||||
test("logout: signing out ends the session and returns to the login page", async () => {
|
||||
await page.goto("/dashboard");
|
||||
await page.locator("summary.profile").click(); // open the profile dropdown
|
||||
await page.locator("button.profile").click(); // open the profile dropdown
|
||||
// Sign out is the only item in it — the menu offers nothing that goes nowhere.
|
||||
await expect(page.locator("details.menu:has(summary.profile) .menu-item")).toHaveText(["Sign out"]);
|
||||
await expect(page.locator("#profile-menu .menu-item")).toHaveText(["Sign out"]);
|
||||
await page.locator('form[action="/logout"] button[type="submit"]').click();
|
||||
await page.waitForURL(/\/login(\?|$)/);
|
||||
// The session is gone: /dashboard is gated, so it bounces back to the login page (no admin nav).
|
||||
|
||||
@@ -31,7 +31,7 @@ test("the switcher changes language, and the choice survives clicking through th
|
||||
|
||||
// The picker sits in the sidebar footer beside the theme switch; each entry is a plain link to
|
||||
// this same page in that language (zero-JS).
|
||||
await page.locator('summary[aria-label="Language"]').click();
|
||||
await page.locator('button[aria-label="Language"]').click();
|
||||
await page.getByRole("link", { name: /svenska/i }).click();
|
||||
|
||||
await expect(page.locator("html")).toHaveAttribute("lang", "sv-SE");
|
||||
@@ -58,7 +58,7 @@ test("the switcher changes language, and the choice survives clicking through th
|
||||
await expect(page.locator("html")).toHaveAttribute("lang", "sv-SE");
|
||||
|
||||
// …and back to English the same way.
|
||||
await page.locator('summary[aria-label="Språk"]').click();
|
||||
await page.locator('button[aria-label="Språk"]').click();
|
||||
await page.getByRole("link", { name: /English/i }).click();
|
||||
await expect(page.locator("html")).toHaveAttribute("lang", "en-US");
|
||||
await expect(page.getByRole("heading", { name: "Shifts" })).toBeVisible();
|
||||
|
||||
@@ -70,6 +70,35 @@ test("theme switch flips the palette with no JavaScript", async ({ page }) => {
|
||||
expect(dark).not.toBe(light);
|
||||
});
|
||||
|
||||
// The menus are <button popovertarget> + [popover], so the browser dismisses them: the visitor no
|
||||
// longer has to click the trigger again to get rid of one. Driven through the language picker; the
|
||||
// profile menu is the same block. Anchoring is asserted too — without `position-anchor` the panel
|
||||
// silently detaches and lands in the middle of the viewport.
|
||||
test("a popover menu sits on its trigger and closes on an outside click or Esc — no JavaScript", async ({ page }) => {
|
||||
await page.goto("/dashboard");
|
||||
const trigger = page.locator('button[aria-label="Language"]');
|
||||
const panel = page.locator('button[aria-label="Language"] + .menu-pop');
|
||||
|
||||
await expect(panel).toBeHidden();
|
||||
await trigger.click();
|
||||
await expect(panel).toBeVisible();
|
||||
|
||||
// Anchored to the button that opened it: directly above (.up), right edges flush.
|
||||
const t = (await trigger.boundingBox())!;
|
||||
const p = (await panel.boundingBox())!;
|
||||
expect(Math.abs(p.x + p.width - (t.x + t.width))).toBeLessThan(2);
|
||||
expect(t.y - (p.y + p.height)).toBeGreaterThanOrEqual(0);
|
||||
expect(t.y - (p.y + p.height)).toBeLessThan(12);
|
||||
|
||||
await page.getByRole("heading", { name: "Starter dashboard" }).click(); // anywhere else on the page
|
||||
await expect(panel).toBeHidden();
|
||||
|
||||
await trigger.click();
|
||||
await expect(panel).toBeVisible();
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(panel).toBeHidden();
|
||||
});
|
||||
|
||||
test("mobile layout hides the sidebar off-canvas behind the hamburger", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
await page.goto("/dashboard");
|
||||
|
||||
Reference in New Issue
Block a user