feat(web): 建立云端控制台前端基线
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
// SPEC: PJ2026-010405 云端控制台.
|
||||
// Implementation reference: draft-2026-07-13-p0-cloud-console.
|
||||
|
||||
import { expect, test } from "../fixtures/test";
|
||||
|
||||
const viewports = [
|
||||
{ name: "desktop", width: 1920, height: 1080 },
|
||||
{ name: "compact", width: 960, height: 600 },
|
||||
{ name: "mobile", width: 390, height: 844 }
|
||||
] as const;
|
||||
|
||||
test("industrial AppShell keeps one bounded scroll owner across target viewports", async ({ page }, testInfo) => {
|
||||
await mockProviderProfiles(page);
|
||||
for (const viewport of viewports) {
|
||||
await page.setViewportSize({ width: viewport.width, height: viewport.height });
|
||||
await page.goto("/admin/provider-profiles");
|
||||
await expect(page.getByRole("heading", { name: "Provider Profiles", exact: true })).toBeVisible();
|
||||
await expect(page.locator(".data-table")).toContainText("codex-api");
|
||||
|
||||
const geometry = await page.evaluate(() => {
|
||||
const shell = document.querySelector<HTMLElement>(".platform-shell");
|
||||
const content = document.querySelector<HTMLElement>(".platform-content");
|
||||
const sidebar = document.querySelector<HTMLElement>(".platform-sidebar");
|
||||
return {
|
||||
documentScrollWidth: Math.round(document.documentElement.scrollWidth),
|
||||
bodyScrollWidth: Math.round(document.body.scrollWidth),
|
||||
shellHeight: Math.round(shell?.getBoundingClientRect().height ?? 0),
|
||||
contentOverflowY: content ? getComputedStyle(content).overflowY : "missing",
|
||||
sidebarDisplay: sidebar ? getComputedStyle(sidebar).display : "missing"
|
||||
};
|
||||
});
|
||||
expect(geometry.documentScrollWidth).toBeLessThanOrEqual(viewport.width);
|
||||
expect(geometry.bodyScrollWidth).toBeLessThanOrEqual(viewport.width);
|
||||
expect(geometry.shellHeight).toBe(viewport.height);
|
||||
expect(geometry.contentOverflowY).toBe("auto");
|
||||
expect(geometry.sidebarDisplay).toBe(viewport.name === "mobile" ? "none" : "flex");
|
||||
|
||||
await page.screenshot({ path: testInfo.outputPath(`console-${viewport.name}.png`) });
|
||||
}
|
||||
});
|
||||
|
||||
test("Dialog and mobile Drawer trap, restore and unlock focus without motion", async ({ page }, testInfo) => {
|
||||
await mockProviderProfiles(page);
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
await page.setViewportSize({ width: 1920, height: 1080 });
|
||||
await page.goto("/admin/provider-profiles");
|
||||
|
||||
const keyButton = page.getByRole("button", { name: "Key", exact: true }).first();
|
||||
await keyButton.click();
|
||||
const dialog = page.getByRole("dialog", { name: "Provider Credential" });
|
||||
await expect(dialog).toBeVisible();
|
||||
await expect(page.locator("body")).toHaveClass(/console-scroll-locked/u);
|
||||
await expect(dialog.locator("#provider-api-key-input")).toBeVisible();
|
||||
await page.screenshot({ path: testInfo.outputPath("console-overlay-desktop.png") });
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(dialog).toHaveCount(0);
|
||||
await expect(page.locator("body")).not.toHaveClass(/console-scroll-locked/u);
|
||||
await expect(keyButton).toBeFocused();
|
||||
|
||||
await page.setViewportSize({ width: 390, height: 844 });
|
||||
const mobileTrigger = page.getByRole("button", { name: "打开主导航" });
|
||||
await mobileTrigger.click();
|
||||
const drawer = page.getByRole("dialog", { name: "HWLAB 导航" });
|
||||
await expect(drawer).toBeVisible();
|
||||
await expect(drawer.getByRole("navigation", { name: "主导航" })).toBeVisible();
|
||||
await expect(page.locator("body")).toHaveClass(/console-scroll-locked/u);
|
||||
await page.keyboard.press("Escape");
|
||||
await expect(drawer).toHaveCount(0);
|
||||
await expect(mobileTrigger).toBeFocused();
|
||||
await expect(page.locator("body")).not.toHaveClass(/console-scroll-locked/u);
|
||||
});
|
||||
|
||||
async function mockProviderProfiles(page: import("@playwright/test").Page): Promise<void> {
|
||||
await page.route("**/v1/admin/provider-profiles", async (route) => {
|
||||
await route.fulfill({
|
||||
status: 200,
|
||||
contentType: "application/json",
|
||||
body: JSON.stringify({
|
||||
items: [
|
||||
{ profile: "codex-api", configured: true, secretRef: { namespace: "hwlab-v03", name: "gpt-pika", keys: ["auth.json"] }, credentialHashSuffix: "a19f", validationStatus: "completed", valuesPrinted: false },
|
||||
{ profile: "deepseek", configured: true, secretRef: { namespace: "hwlab-v03", name: "deepseek", keys: ["api-key"] }, credentialHashSuffix: "b470", validationStatus: "pending", valuesPrinted: false }
|
||||
]
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user