diff --git a/tests/review-style-layout.e2e.test.mjs b/tests/review-style-layout.e2e.test.mjs index 2618d95..8a05483 100644 --- a/tests/review-style-layout.e2e.test.mjs +++ b/tests/review-style-layout.e2e.test.mjs @@ -15,6 +15,58 @@ import { const maintenanceMessage = "系统正在升级维护,部分业务暂时不可用,请稍后再试。如有紧急问题请联系客服,我们正在尽快恢复服务。"; +// 真实 KKY 实例在“网络与安全”中同时提供安全组和白名单入口,不绕过组件权限/加载条件。 +const networkResponses = { + "/host/5": { + host: { + id: 5, + product_id: 123, + module: "kky_ecs", + name: "本地弹性云", + status: "Active", + billing_cycle: "monthly", + upgrade_allowed: true, + }, + }, + "/kky_ecs/5": { + cpu: 2, + memory: 4, + bandwidth: 10, + protect: 0, + bill_method: 1, + ip: "192.0.2.5", + ips: ["192.0.2.5"], + intranet_ip: "10.0.0.5", + username: "root", + port: 22, + image: { name: "Ubuntu 24.04" }, + security_group_id: 1, + security_group_name: "本地安全组", + }, + "/kky_ecs/5/status": { status: "on", rescue: false }, + "/kky_ecs/disk/5": { list: [] }, + "/kky_ecs/monitor/5": { + sample_info: [], + cpu: [], + memory: [], + bandwidth: [], + disk: [], + }, + "/kky_ecs/5/traffic": { + billing: { mode: "bandwidth", monthly_quota: 0, unit_price: "0.00" }, + usage_status: "available", + quota_status: "not_applicable", + }, + "/domain_whitelist/form": { + fields: [ + { name: "domains", title: "域名", type: "textarea", required: true }, + ], + ips: ["192.0.2.5"], + need_review: true, + }, + "/domain_whitelist/record": { list: [], count: 0 }, +}; + async function layoutBoundary( page, { maintenance = true, content = "help", message = maintenanceMessage } = {}, @@ -28,6 +80,8 @@ async function layoutBoundary( ); const reply = (data) => route.fulfill({ json: { status: 200, msg: "", data } }); + if (content === "network" && Object.hasOwn(networkResponses, path)) + return reply(networkResponses[path]); if (path === "/common") { const response = await route.fetch(); const body = await response.json(); @@ -400,3 +454,28 @@ for (const [path, name] of [ }), ); } + +test( + "instance network navigation has one keyboard stop per action", + { timeout: 30000 }, + (t) => + scenario(t, {}, async (page) => { + await layoutBoundary(page, { maintenance: false, content: "network" }); + await login(page, "/servers/server?id=5"); + await page.getByRole("tab", { name: "网络与安全", exact: true }).click(); + for (const name of ["前往安全组管理", "前往域名白名单"]) { + const link = page.getByRole("link", { name, exact: true }); + await link.waitFor(); + await link.focus(); + await page.keyboard.press("Tab"); + assert.equal( + await link.evaluate((element) => + element.contains(document.activeElement), + ), + false, + "Tab must leave each network navigation action after its first stop", + ); + } + return { navigationActions: 2, singleKeyboardStop: true }; + }), +);