fix(ui): refine acrylic controls and timeline interactions
Restore full acrylic materials to stacked task cards and floating controls. Extend the map behind the navigation header while preserving document and control insets. Align timeline and scale readouts, refresh dense-marker hover content, and highlight model and approval menu options. Keep narrow timeline headers clear of their legend and actions. Update coupled geometry checks and retain city zoom-12 and menu regression coverage. Validation: scoped git diff --check and read-only UI geometry inspection. Test suites were not rerun at the user's request.
This commit is contained in:
@@ -94,7 +94,8 @@ test("desktop Agent docks beside the workspace with a solid reading surface", as
|
||||
const headerBox = await agentHeader.boundingBox();
|
||||
const workspaceBox = await workspace.boundingBox();
|
||||
expect(headerBox!.height).toBe(topBarBox!.height);
|
||||
expect(headerBox!.y).toBe(workspaceBox!.y);
|
||||
expect(headerBox!.y).toBe(topBarBox!.y + topBarBox!.height);
|
||||
expect(workspaceBox!.y).toBe(topBarBox!.y);
|
||||
await expect(topBar).toHaveCSS("box-shadow", "none");
|
||||
await expect(agentHeader).toHaveCSS("box-shadow", "none");
|
||||
await expect(agentHeader.locator(":scope > div").first()).toHaveCSS("background-color", "rgba(0, 0, 0, 0)");
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import { expect, test, type Locator } from "@playwright/test";
|
||||
import { mockAgentApi } from "./support/mock-agent-api";
|
||||
import { mockWorkbenchContext } from "./support/mock-workbench-context";
|
||||
import { expectThemeColor } from "./support/theme-colors";
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.emulateMedia({ reducedMotion: "reduce" });
|
||||
await mockWorkbenchContext(page);
|
||||
await mockAgentApi(page);
|
||||
await page.route("**/api/v1/agent/chat/models", (route) => route.fulfill({
|
||||
headers: { "Access-Control-Allow-Origin": "*" },
|
||||
json: {
|
||||
default_model: "test/fast",
|
||||
models: [
|
||||
{ id: "test/fast", label: "快速", description: "快速回答和日常任务处理", icon: "bolt" },
|
||||
{ id: "test/expert", label: "专家", description: "深入分析和复杂任务处理", icon: "sparkle" }
|
||||
]
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
for (const width of [1440, 375]) {
|
||||
test(`model options show hover and keyboard highlight without changing selection at ${width}px`, async ({ page }) => {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: width < 1024 ? "打开 Agent 面板" : /展开 Agent 助手面板/ }).click();
|
||||
// Radix hides the trigger from the accessibility tree while its modal menu is open.
|
||||
const trigger = page.locator('button[aria-label="选择 Agent 模型"]');
|
||||
await expect(trigger).toHaveAccessibleDescription("当前模型:快速");
|
||||
await trigger.click();
|
||||
await waitForMenuAnimations(page.getByRole("menu"));
|
||||
const fast = page.getByRole("menuitem", { name: /快速回答和日常任务处理/ });
|
||||
const expert = page.getByRole("menuitem", { name: /深入分析和复杂任务处理/ });
|
||||
const box = await expert.boundingBox();
|
||||
for (const item of [fast, expert]) {
|
||||
await item.hover();
|
||||
await expect(item).toHaveAttribute("data-highlighted", "");
|
||||
await expectThemeColor(item, "background-color", "--action-selection-soft-hover");
|
||||
await expectThemeColor(item, "border-color", "--action-border-hover");
|
||||
await expect(trigger).toHaveAttribute("aria-description", "当前模型:快速");
|
||||
}
|
||||
expect(await expert.boundingBox()).toEqual(box);
|
||||
await page.getByRole("menu").screenshot({ path: `test-results/agent-select-menu/model-${width}.png` });
|
||||
await page.keyboard.press("ArrowUp");
|
||||
await expect(fast).toBeFocused();
|
||||
await expectThemeColor(fast, "background-color", "--action-selection-soft-hover");
|
||||
await page.keyboard.press("ArrowDown");
|
||||
await expect(expert).toBeFocused();
|
||||
await page.keyboard.press("Enter");
|
||||
await expect(page.getByRole("menu")).toHaveCount(0);
|
||||
await expect(trigger).toHaveAccessibleDescription("当前模型:专家");
|
||||
await expect(trigger).toBeFocused();
|
||||
});
|
||||
|
||||
test(`approval options highlight both selected and unselected modes at ${width}px`, async ({ page }) => {
|
||||
await page.setViewportSize({ width, height: 900 });
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
await page.getByRole("button", { name: width < 1024 ? "打开 Agent 面板" : /展开 Agent 助手面板/ }).click();
|
||||
const trigger = page.locator('button[aria-label="权限批准模式"]');
|
||||
await trigger.click();
|
||||
await waitForMenuAnimations(page.getByRole("menu"));
|
||||
const request = page.getByRole("menuitem", { name: /工具权限逐次确认/ });
|
||||
const always = page.getByRole("menuitem", { name: /自动允许本轮权限请求/ });
|
||||
for (const item of [request, always]) {
|
||||
await item.hover();
|
||||
await expectThemeColor(item, "background-color", "--action-selection-soft-hover");
|
||||
await expectThemeColor(item, "border-color", "--action-border-hover");
|
||||
await expect(trigger).toContainText("请求批准");
|
||||
}
|
||||
await page.getByRole("menu").screenshot({ path: `test-results/agent-select-menu/approval-${width}.png` });
|
||||
await always.click();
|
||||
await expect(page.getByRole("menu")).toHaveCount(0);
|
||||
await expect(trigger).toBeFocused();
|
||||
await expect(trigger).toContainText("始终允许");
|
||||
await trigger.click();
|
||||
await waitForMenuAnimations(page.getByRole("menu"));
|
||||
await always.hover();
|
||||
await expectThemeColor(always, "background-color", "--action-selection-soft-hover");
|
||||
await page.keyboard.press("ArrowUp");
|
||||
await expect(request).toBeFocused();
|
||||
await expectThemeColor(request, "background-color", "--action-selection-soft-hover");
|
||||
await page.keyboard.press("Enter");
|
||||
await expect(trigger).toContainText("请求批准");
|
||||
await expect(trigger).toBeFocused();
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForMenuAnimations(menu: Locator) {
|
||||
await expect(menu).toBeVisible();
|
||||
await menu.evaluate((element) => Promise.all(
|
||||
element.getAnimations({ subtree: true })
|
||||
.filter((animation) => animation.playState === "running")
|
||||
.map((animation) => animation.finished.catch(() => undefined))
|
||||
));
|
||||
}
|
||||
@@ -65,6 +65,50 @@ async function waitForMapIdle(page: Page) {
|
||||
);
|
||||
}
|
||||
|
||||
test("city buildings render only from zoom 12 in 3D", async ({ page }) => {
|
||||
test.skip(
|
||||
process.env.TJWATER_CITY_SCENE_TEST !== "1",
|
||||
"Requires the generated local city-model scene"
|
||||
);
|
||||
test.setTimeout(60_000);
|
||||
await mockWorkbenchContext(page, undefined, undefined, {}, [], {
|
||||
tiandituToken: "city-scene-tianditu-token"
|
||||
});
|
||||
await page.route("https://t0.tianditu.gov.cn/**", (route) =>
|
||||
route.fulfill({ contentType: "image/png", body: EMPTY_RASTER_TILE })
|
||||
);
|
||||
await page.goto("/", { waitUntil: "domcontentloaded" });
|
||||
const toggle = page
|
||||
.getByRole("group", { name: "地图缩放", exact: true })
|
||||
.locator("button[aria-pressed]");
|
||||
await expect(toggle).toBeEnabled({ timeout: 15_000 });
|
||||
await toggle.click();
|
||||
await page.evaluate(() =>
|
||||
window.__waterNetworkMap!.jumpTo({ center: [121.464, 30.916], zoom: 12, pitch: 55, bearing: 0 })
|
||||
);
|
||||
const renderedBuildings = () => page.evaluate(() => {
|
||||
const map = window.__waterNetworkMap!;
|
||||
return map.getLayer("fengxian-city-buildings")
|
||||
? map.queryRenderedFeatures({ layers: ["fengxian-city-buildings"] }).length
|
||||
: 0;
|
||||
});
|
||||
|
||||
await expect.poll(renderedBuildings, { timeout: 30_000 }).toBeGreaterThan(0);
|
||||
await page.screenshot({ path: "test-results/city-model/zoom-12.png" });
|
||||
for (const zoom of [11.99, 11]) {
|
||||
await page.evaluate((value) => window.__waterNetworkMap!.jumpTo({ zoom: value }), zoom);
|
||||
await expect.poll(renderedBuildings).toBe(0);
|
||||
}
|
||||
await page.screenshot({ path: "test-results/city-model/zoom-11.png" });
|
||||
for (const zoom of [12, 15, 16]) {
|
||||
await page.evaluate((value) => window.__waterNetworkMap!.jumpTo({ zoom: value }), zoom);
|
||||
await expect.poll(renderedBuildings, { timeout: 15_000 }).toBeGreaterThan(0);
|
||||
}
|
||||
await toggle.click();
|
||||
await expect.poll(() => page.evaluate(() => window.__waterNetworkMap!.getPitch())).toBe(0);
|
||||
await expect.poll(renderedBuildings).toBe(0);
|
||||
});
|
||||
|
||||
// Uses generated local building data, while unrelated backend services are isolated.
|
||||
test("3D adds opaque buildings without enabling duplicate GIS layers", async ({ page }) => {
|
||||
test.skip(
|
||||
|
||||
@@ -301,7 +301,8 @@ async function expectDesktopMainFrameGeometry(page: Page) {
|
||||
expect(agentBox!.x).toBe(0);
|
||||
expect(agentBox!.y).toBe(56);
|
||||
expect(workspaceBox!.x).toBe(agentBox!.x + agentBox!.width);
|
||||
expect(workspaceBox!.y).toBe(agentBox!.y);
|
||||
expect(workspaceBox!.y).toBe(0);
|
||||
expect(workspaceBox!.height).toBe(agentBox!.height + agentBox!.y);
|
||||
expect(mapBox).toEqual(workspaceBox);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user