feat: expand intelligent investigation workspace
Generic Container CI/CD / test-build-publish (push) Successful in 1m52s
Frontend CI/CD / build-test-publish-and-deploy (push) Successful in 1m52s

This commit is contained in:
2026-10-30 17:30:19 +08:00
parent ddd03dbff6
commit c0e4231b83
223 changed files with 13155 additions and 5304 deletions
@@ -1,4 +1,4 @@
import { render, screen, fireEvent, waitFor, within } from "@testing-library/react";
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
import { describe, expect, it, vi } from "vitest";
import userEvent from "@testing-library/user-event";
import { AnswerUiView } from "./answer-ui-view";
@@ -33,6 +33,29 @@ function taskBlock(includeData = true) {
return parsed;
}
describe("local interactive answer renderer", () => {
it("shows navigation for long answers while keeping short metric and evidence replies quiet", () => {
const fixture = taskBlock(false);
fixture.spec.elements.root.children = ["metric", "evidence"];
delete fixture.spec.elements.form;
const { rerender } = render(<AnswerUiView block={fixture} />);
expect(screen.getByRole("group", { name: "当前压力" })).toBeVisible();
expect(screen.queryByRole("navigation", { name: "回答内容导航" })).not.toBeInTheDocument();
const longer = structuredClone(fixture);
longer.spec.elements.evidence.props.summary = "待核对的监测资料。".repeat(150);
rerender(<AnswerUiView block={longer} />);
expect(screen.getByRole("navigation", { name: "回答内容导航" })).toBeVisible();
});
it("uses a single form and evidence card's own headings without fixed wrapper instructions", () => {
render(<AnswerUiView block={taskBlock()} onSubmit={vi.fn()} />);
expect(screen.getByLabelText("现场记录(必填)")).toBeVisible();
expect(screen.getByRole("button", { name: /读数依据/ })).toBeVisible();
expect(screen.queryByRole("button", { name: "收起补充资料" })).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: "收起核查依据" })).not.toBeInTheDocument();
expect(screen.queryByText("直接探索已加载数据")).not.toBeInTheDocument();
expect(screen.queryByText("区分记录、推断与用户资料")).not.toBeInTheDocument();
});
it("adapts response and report composition without replacing a reader's form draft", async () => {
const user = userEvent.setup();
const fixture = taskBlock();
@@ -68,38 +91,33 @@ describe("local interactive answer renderer", () => {
expect(screen.getByRole("note")).toHaveTextContent("能力限制");
expect(screen.getByRole("note")).not.toHaveTextContent("注意事项");
});
it("combines metrics, selected rows and evidence into one deliberate quote without sending", async () => {
it("shows data and evidence without row selection, quoting or analysis controls", async () => {
const user = userEvent.setup();
const ask = vi.fn();
const submit = vi.fn();
render(<AnswerUiView block={taskBlock()} onAsk={ask} onSubmit={submit} />);
await user.click(screen.getByRole("button", { name: "将当前压力加入分析" }));
await user.click(screen.getByRole("checkbox", { name: "选择第 1 行" }));
await user.click(screen.getByRole("button", { name: "将所选行加入分析" }));
render(<AnswerUiView block={taskBlock()} onSubmit={submit} />);
expect(screen.getByRole("group", { name: "当前压力" })).toHaveTextContent("0.2130MPa");
expect(screen.getByRole("table")).toHaveTextContent("北区入口");
expect(screen.getAllByRole("columnheader")).toHaveLength(2);
await user.click(screen.getByRole("button", { name: /读数依据.*测试查询/ }));
await user.click(screen.getByRole("button", { name: "将读数依据加入分析" }));
const tray = screen.getByRole("region", { name: "分析内容篮" });
expect(tray).toHaveTextContent("已选 3 项分析内容");
expect(ask).not.toHaveBeenCalled();
await user.type(within(tray).getByRole("textbox"), "比较差异并核查原因");
await user.click(within(tray).getByRole("button", { name: "带入对话" }));
expect(ask).toHaveBeenCalledWith({ title: "3 项内容的联合分析", text: expect.stringContaining("分析目标:比较差异并核查原因") });
const quote = ask.mock.calls[0][0].text;
for (const exact of ["0.2130MPa", "来源:监测快照", "对象:北区入口;压力(MPa):0.2130", "资料编号:DEMO-26", "引用内容块:task"]) expect(quote).toContain(exact);
await waitFor(() => expect(screen.getByText("仅用于演练")).toBeVisible());
expect(screen.getByText("DEMO-26")).toBeVisible();
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /加入分析|针对.*提问|取消选行/ })).not.toBeInTheDocument();
expect(screen.queryByRole("region", { name: "分析内容篮" })).not.toBeInTheDocument();
expect(submit).not.toHaveBeenCalled();
await user.click(within(tray).getByRole("button", { name: "移除当前压力" }));
expect(tray).toHaveTextContent("已选 2 项分析内容");
expect(screen.getByRole("button", { name: "将当前压力加入分析" })).toHaveAttribute("aria-pressed", "false");
});
it("keeps the same form and selected content when more data arrives and support sections collapse", async () => {
it("keeps the same form when more data arrives and support sections collapse", async () => {
const user = userEvent.setup();
const ask = vi.fn();
const { rerender } = render(<AnswerUiView block={taskBlock(false)} onAsk={ask} onSubmit={vi.fn()} />);
const { rerender } = render(<AnswerUiView block={taskBlock(false)} onSubmit={vi.fn()} />);
const editor = screen.getByLabelText("现场记录(必填)");
await user.type(editor, "未发送的现场草稿");
await user.click(screen.getByRole("button", { name: "将当前压力加入分析" }));
rerender(<AnswerUiView block={taskBlock()} onAsk={ask} onSubmit={vi.fn()} />);
const grouped = taskBlock();
grouped.spec.elements.root.children = [...(grouped.spec.elements.root.children ?? []), "extra-form"];
grouped.spec.elements["extra-form"] = { ...grouped.spec.elements.form, props: {
title: "其他资料", description: "可选补充", fields: [{ name: "extra", label: "其他记录", kind: "text", required: false }]
} };
rerender(<AnswerUiView block={grouped} onSubmit={vi.fn()} />);
expect(screen.getByLabelText("现场记录(必填)")).toBe(editor);
expect(editor).toHaveValue("未发送的现场草稿");
await user.click(screen.getByRole("button", { name: "收起补充资料" }));
@@ -108,25 +126,22 @@ describe("local interactive answer renderer", () => {
await user.click(screen.getByRole("button", { name: "展开补充资料" }));
await waitFor(() => expect(editor).toBeVisible());
expect(editor).toHaveValue("未发送的现场草稿");
expect(screen.getByRole("region", { name: "分析内容篮" })).toHaveTextContent("已选 1 项分析内容");
await user.click(screen.getByRole("button", { name: "收起补充资料" }));
const updated = taskBlock();
updated.spec.elements.table.props.rows = [["新增入口", "0.24"]];
rerender(<AnswerUiView block={updated} onAsk={ask} onSubmit={vi.fn()} />);
rerender(<AnswerUiView block={updated} onSubmit={vi.fn()} />);
expect(screen.getByLabelText("现场记录(必填)")).toBe(editor);
await waitFor(() => expect(editor).toBeVisible());
expect(editor).toHaveValue("未发送的现场草稿");
});
it("rejects oversized row quotes without truncating values or removing existing selections", async () => {
const user = userEvent.setup();
it("preserves long original cell values without requiring a selection or quote action", () => {
const fixture = taskBlock();
fixture.spec.elements.table.props.rows = Array.from({ length: 12 }, (_, index) => [String(index), "长".repeat(800)]);
render(<AnswerUiView block={fixture} onAsk={vi.fn()} />);
await user.click(screen.getByRole("button", { name: "将当前压力加入分析" }));
await user.click(screen.getByRole("checkbox", { name: "选择入口记录的全部筛选结果" }));
await user.click(screen.getByRole("button", { name: "将所选行加入分析" }));
expect(screen.getByRole("status")).toHaveTextContent("8000 字");
expect(screen.getByRole("region", { name: "分析内容篮" })).toHaveTextContent("已选 1 项分析内容");
expect(screen.getByRole("checkbox", { name: "选择第 12 行" })).toBeChecked();
render(<AnswerUiView block={fixture} />);
expect(screen.getAllByText("长".repeat(800))).toHaveLength(12);
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /加入分析|针对.*提问/ })).not.toBeInTheDocument();
});
it("expresses supplied context and cautions as readable notes without announcing historical alerts", () => {
@@ -143,16 +158,14 @@ describe("local interactive answer renderer", () => {
expect(screen.queryByRole("note")).not.toBeInTheDocument();
expect(screen.getByText("普通回答")).toBeInTheDocument();
});
it("offers a named metric quote action and retains the exact value, unit and source", async () => {
const user = userEvent.setup();
const ask = vi.fn();
it("retains a metric's exact value, unit and source without action buttons", () => {
render(<AnswerUiView block={block("Metric", {
label: "当前压力", value: "0.3000", unit: "MPa", source: "已加载记录",
})} onAsk={ask} />);
await user.click(screen.getByRole("button", { name: "针对当前压力提问" }));
expect(ask).toHaveBeenCalledWith({
title: "当前压力", text: "当前压力:0.3000MPa\n来源:已加载记录\n引用内容块:test",
});
})} />);
const metric = screen.getByRole("group", { name: "当前压力" });
expect(metric).toHaveTextContent("0.3000MPa");
expect(metric).toHaveTextContent("来源:已加载记录");
expect(screen.queryByRole("button")).not.toBeInTheDocument();
});
it("renders through json-render and lets readers inspect exact chart data with keyboard tabs", async () => {
const user = userEvent.setup();
@@ -199,22 +212,21 @@ describe("local interactive answer renderer", () => {
feature_ids: ["DEMO-P001", "DEMO-P002"]
});
});
it("filters and sorts loaded rows, then quotes the selected original values without sending", async () => {
it("filters and sorts loaded rows without selecting or sending content", async () => {
const user = userEvent.setup();
const ask = vi.fn();
const submit = vi.fn();
render(<AnswerUiView block={block("Table", {
title: "监测记录", columns: ["监测点", "压力"],
rows: [["南站", "0.32"], ["北站", "0.21"], ["南站二号", "0.26"]]
})} onAsk={ask} onSubmit={submit} />);
})} onSubmit={submit} />);
await user.type(screen.getByRole("textbox", { name: "搜索监测记录" }), "南站");
expect(screen.getByText("2 / 3 行")).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "按压力排序" }));
const rows = screen.getAllByRole("row");
expect(rows[1]).toHaveTextContent("南站二号");
await user.click(screen.getByRole("checkbox", { name: "选择第 3 行" }));
await user.click(screen.getByRole("button", { name: "针对这部分提问" }));
expect(ask).toHaveBeenCalledWith({ title: "监测记录", text: expect.stringContaining("监测点:南站二号;压力:0.26") });
expect(rows[1]).toHaveTextContent("0.26");
expect(screen.queryByRole("checkbox")).not.toBeInTheDocument();
expect(screen.queryByRole("button", { name: /加入分析|针对.*提问/ })).not.toBeInTheDocument();
expect(submit).not.toHaveBeenCalled();
});
it("keeps form values after a failed submission", async () => {