feat(charts): manage investigation charts in a scrolling workspace
Generic Container CI/CD / test-build-publish (push) Successful in 2m7s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 2m8s

This commit is contained in:
2026-11-10 17:56:23 +08:00
parent 865491cb23
commit 0ae0a41e61
80 changed files with 4672 additions and 470 deletions
@@ -1,4 +1,4 @@
import { expect, test } from "@playwright/test";
import { expect, test, type Locator, type TestInfo } from "@playwright/test";
import { mockAgentApi } from "./support/mock-agent-api";
import { mockWorkbenchContext } from "./support/mock-workbench-context";
import { responsesFixture } from "./support/responses-fixture";
@@ -7,6 +7,22 @@ const title = "空间候选管段(距离近,非故障定位)";
const ids = ["19025", "19026", "19022", "19027"];
const distances = [283.85, 283.86, 291.45, 292.44];
async function checkHeaderTarget(ui: Locator, testInfo: TestInfo, phase: string) {
const header = ui.getByRole("columnheader", { name: "距离(米)" });
const button = header.getByRole("button");
const cell = (await header.boundingBox())!;
const target = (await button.boundingBox())!;
await testInfo.attach(`${phase}-header-target`, { body: JSON.stringify({ cell, target }), contentType: "application/json" });
expect(Math.abs(target.x - cell.x)).toBeLessThanOrEqual(1);
expect(Math.abs(target.y - cell.y)).toBeLessThanOrEqual(1);
expect(Math.abs(target.width - cell.width)).toBeLessThanOrEqual(1);
expect(Math.abs(target.height - cell.height)).toBeLessThanOrEqual(1);
const background = await button.evaluate((element) => getComputedStyle(element).backgroundColor);
await header.hover({ position: { x: cell.width - 6, y: cell.height / 2 } });
await expect.poll(() => button.evaluate((element) => getComputedStyle(element).backgroundColor)).not.toBe(background);
await expect(ui.getByRole("columnheader").first().getByRole("button")).toHaveCSS("background-color", background);
}
for (const width of [1440, 375]) for (const kind of ["Table", "Chart"] as const) {
test(`${kind} data stays display-only inline and expanded at ${width}px`, async ({ page }, testInfo) => {
await page.setViewportSize({ width, height: 1000 });
@@ -38,16 +54,25 @@ for (const width of [1440, 375]) for (const kind of ["Table", "Chart"] as const)
await expect(ui.getByRole("table")).toContainText("19025");
await expect(ui.getByRole("table")).toContainText("283.85");
await expect(ui.getByRole("columnheader")).toHaveCount(2);
await expect(ui.locator("thead svg")).toHaveCount(0);
await expect(ui.getByRole("checkbox")).toHaveCount(0);
await expect(ui.getByRole("button", { name: /加入分析|针对.*提问|取消选行/ })).toHaveCount(0);
await expect(ui.getByRole("region", { name: "分析内容篮" })).toHaveCount(0);
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
await page.evaluate(() => document.fonts.ready);
await checkHeaderTarget(ui, testInfo, "inline");
await ui.locator("table").screenshot({ path: testInfo.outputPath("inline-display.png") });
const sort = ui.getByRole("button", { name: "按距离(米)排序", exact: true });
await sort.click();
const header = ui.getByRole("columnheader", { name: "距离(米)" });
const column = (await header.boundingBox())!;
await header.click({ position: { x: column.width - 6, y: column.height / 2 } });
await expect(ui.getByRole("columnheader", { name: "距离(米)" })).toHaveAttribute("aria-sort", "ascending");
await expect(sort.locator("svg.lucide-arrow-up")).toHaveCount(1);
await expect(ui.locator("thead svg")).toHaveCount(1);
await sort.click();
await expect(ui.getByRole("columnheader", { name: "距离(米)" })).toHaveAttribute("aria-sort", "descending");
await expect(sort.locator("svg.lucide-arrow-down")).toHaveCount(1);
await expect(ui.locator("thead svg")).toHaveCount(1);
await expect(ui.locator("tbody tr").first()).toContainText("19027");
await ui.getByRole("textbox", { name: `搜索${title}` }).fill("19025");
await expect(ui.locator("tbody tr")).toHaveCount(1);
@@ -56,15 +81,19 @@ for (const width of [1440, 375]) for (const kind of ["Table", "Chart"] as const)
ui = workspace.locator('[data-answer-ui="selection-style"]');
await expect(ui.getByRole("textbox", { name: `搜索${title}` })).toHaveValue("19025");
await expect(ui.getByRole("columnheader", { name: "距离(米)" })).toHaveAttribute("aria-sort", "descending");
await expect(ui.getByRole("button", { name: "按距离(米)排序", exact: true }).locator("svg.lucide-arrow-down")).toHaveCount(1);
await expect(ui.locator("thead svg")).toHaveCount(1);
await expect(ui.getByRole("checkbox")).toHaveCount(0);
await expect(ui.locator('tbody tr[data-state="selected"]')).toHaveCount(0);
await expect(ui.getByRole("button", { name: /加入分析|针对.*提问|取消选行/ })).toHaveCount(0);
await ui.getByRole("textbox", { name: `搜索${title}` }).fill("");
await page.getByRole("button", { name: "关闭通知", exact: true }).evaluateAll((buttons) => buttons.forEach((button) => (button as HTMLButtonElement).click()));
await checkHeaderTarget(ui, testInfo, "expanded");
await ui.locator("[data-interactive-table]").screenshot({ path: testInfo.outputPath("expanded-display.png") });
await ui.getByRole("button", { name: "按距离(米)排序", exact: true }).focus();
await page.keyboard.press("Enter");
await expect(ui.getByRole("columnheader", { name: "距离(米)" })).toHaveAttribute("aria-sort", "none");
await expect(ui.locator("thead svg")).toHaveCount(0);
await ui.getByRole("textbox", { name: `搜索${title}` }).fill("没有匹配的管段");
await expect(ui.getByRole("cell", { name: "当前筛选没有匹配的数据。" })).toHaveAttribute("colspan", "2");
await expect(ui.getByRole("checkbox")).toHaveCount(0);