/** OpenVue/Deni 迁移的实际导航、移动表格与弹窗行为,不断言包名或源码形状。 */ import assert from "node:assert/strict"; import { test } from "node:test"; import { eventually, login, scenario } from "./helpers/console-browser.mjs"; const navigation = (page) => page.getByRole("navigation", { name: "会员中心导航", exact: true }); async function assertNavigationCannotTakeFocus(page) { const links = page.locator('nav[aria-label="会员中心导航"] a[href]'); await links.first().evaluate((element) => element.focus()); assert.equal( await links .first() .evaluate((element) => element.closest("nav").contains(document.activeElement), ), false, "The closed navigation cannot receive keyboard focus", ); } test( "mobile navigation traps focus, closes on Escape and returns to its opener", { timeout: 30000 }, (t) => scenario(t, {}, async (page) => { await page.setViewportSize({ width: 390, height: 844 }); await page.emulateMedia({ reducedMotion: "reduce" }); await login(page, "/security/ssh-keys"); await page.getByText("audit-01", { exact: true }).waitFor(); const opener = page.getByRole("button", { name: "打开菜单", exact: true, }); await assertNavigationCannotTakeFocus(page); await opener.click(); const drawer = page.getByRole("dialog", { name: "主导航", exact: true }); const close = drawer.getByRole("button", { name: "关闭菜单", exact: true, }); await close.waitFor(); await eventually( () => close.evaluate((element) => element === document.activeElement), Boolean, "Opening navigation focuses its close action", ); await page.keyboard.press("Shift+Tab"); assert.equal( await drawer.evaluate((element) => element.contains(document.activeElement), ), true, ); await page.keyboard.press("Tab"); assert.equal( await drawer.evaluate((element) => element.contains(document.activeElement), ), true, ); await page.keyboard.press("Escape"); await drawer.waitFor({ state: "hidden" }); await eventually( () => opener.evaluate((element) => element === document.activeElement), Boolean, "Closing navigation restores the opener", ); await opener.click(); // 当前后台菜单把 SSH 放在独立入口;目标安全组位于尚未展开的安全分组。 await navigation(page) .getByRole("button", { name: "安全", exact: true }) .click(); await navigation(page) .getByRole("link", { name: "安全组", exact: true }) .click(); await page.waitForURL((url) => url.pathname === "/security/groups"); await page .getByRole("heading", { name: "安全组", exact: true }) .waitFor(); await assertNavigationCannotTakeFocus(page); assert.equal( await page .locator("body") .evaluate( (element) => getComputedStyle(element).overflow === "hidden", ), false, ); return { focusContained: true, escapeRestoresOpener: true, routeClosesDrawer: true, scrollRestored: true, }; }), ); test( "navigation changes between overlay and static layout at the shared breakpoint", { timeout: 30000 }, (t) => scenario(t, {}, async (page) => { await page.emulateMedia({ reducedMotion: "reduce" }); await page.setViewportSize({ width: 1025, height: 900 }); await login(page, "/security/ssh-keys"); await navigation(page).waitFor(); assert.equal( await page .getByRole("button", { name: "打开菜单", exact: true }) .isVisible(), false, ); await page.setViewportSize({ width: 1024, height: 900 }); const opener = page.getByRole("button", { name: "打开菜单", exact: true, }); await opener.waitFor(); await assertNavigationCannotTakeFocus(page); await opener.click(); await navigation(page).waitFor(); await page.setViewportSize({ width: 1280, height: 900 }); await navigation(page).waitFor(); await eventually( () => page .locator("body") .evaluate( (element) => getComputedStyle(element).overflow !== "hidden", ), Boolean, "Resizing to desktop releases overlay scroll lock", ); assert.equal( await page.getByRole("dialog", { name: "主导航", exact: true }).count(), 0, ); const link = navigation(page).getByRole("link", { name: "SSH 密钥", exact: true, }); await link.focus(); assert.equal( await link.evaluate((element) => element === document.activeElement), true, "Desktop navigation is focusable after the overlay closes", ); return { desktopAt1025: true, overlayAt1024: true, desktopResizeReleasesOverlay: true, }; }), ); test( "mobile table retains field labels and an editable dialog without page overflow", { timeout: 30000 }, (t) => scenario(t, {}, async (page) => { await page.setViewportSize({ width: 375, height: 812 }); await page.emulateMedia({ reducedMotion: "reduce" }); await login(page, "/security/ssh-keys"); const name = page.getByText("audit-01", { exact: true }); await name.waitFor(); const label = await name.evaluate((element) => { const cell = element.closest("td"); return { value: cell?.getAttribute("data-label"), displayed: cell ? getComputedStyle(cell, "::before").content : "none", }; }); assert.equal(label.value, "名称"); assert.equal(label.displayed.includes("名称"), true); await page .getByRole("button", { name: "编辑", exact: true }) .first() .click(); const dialog = page.getByRole("dialog"); const field = dialog.getByRole("textbox").first(); await field.fill("mobile-key"); const box = await dialog.boundingBox(); assert.ok( box.x >= -1 && box.x + box.width <= 376, "The editor fits the phone viewport", ); await dialog.getByRole("button", { name: "取消", exact: true }).click(); await dialog.waitFor({ state: "hidden" }); const fits = await page.evaluate( () => document.documentElement.scrollWidth <= window.innerWidth + 1, ); assert.equal(fits, true); return { tableFieldLabels: true, editableDialogFits: true, pageHasNoHorizontalOverflow: true, }; }), );