feat: polish intelligent answer interactions
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { AnswerUiView } from "./answer-ui-view";
|
||||
import { parseAnswerUiBlock } from "./contract";
|
||||
vi.mock("echarts-for-react", () => ({ default: () => <div /> }));
|
||||
function block(type: string, props: Record<string, unknown>) {
|
||||
const result = parseAnswerUiBlock({ version: "answer-ui@1", block_id: "actions", fallback_text: "资料", spec: { root: "action", elements: { action: { type, props, children: [] } } } });
|
||||
if (!result) throw new Error("invalid fixture");
|
||||
return result;
|
||||
}
|
||||
const location = block("MapLocation", { label: "定位要素", source: "查询结果", layer: "pipes", feature_ids: ["P1", "P2"] });
|
||||
const form = block("FollowUpForm", { title: "现场资料", description: "用户补充", fields: [{ name: "record", label: "记录", kind: "text", required: true }] });
|
||||
describe("answer action states", () => {
|
||||
it.each([
|
||||
[{ located_ids: ["P1", "P2"], missing_ids: [] }, "success", "已定位 2 个"],
|
||||
[{ located_ids: ["P1"], missing_ids: ["P2"] }, "partial", "另有 1 个未找到"],
|
||||
[{ located_ids: [], missing_ids: ["P1", "P2"] }, "error", "未找到可定位"],
|
||||
[undefined, "unconfirmed", "未返回定位结果"]
|
||||
])("reports verified location outcomes %j", async (result, state, text) => {
|
||||
render(<AnswerUiView block={location} onLocate={vi.fn().mockResolvedValue(result)} />);
|
||||
fireEvent.click(screen.getByRole("button", { name: "定位要素" }));
|
||||
await waitFor(() => expect(screen.getByRole("group", { name: "定位要素" })).toHaveAttribute("data-state", state));
|
||||
expect(screen.getByRole("status")).toHaveTextContent(text);
|
||||
});
|
||||
it("explains unsupported location", () => {
|
||||
render(<AnswerUiView block={location} />);
|
||||
expect(screen.getByRole("button", { name: "定位要素" })).toBeDisabled();
|
||||
expect(screen.getByText("当前视图不支持地图定位,可查看关联要素编号。")).toBeInTheDocument();
|
||||
});
|
||||
it("locks pending location and supports retry after rejection", async () => {
|
||||
let reject!: (error: Error) => void;
|
||||
const locate = vi.fn().mockImplementationOnce(() => new Promise((_, fail) => { reject = fail; })).mockResolvedValue({ located_ids: ["P1"], missing_ids: ["P2"] });
|
||||
render(<AnswerUiView block={location} onLocate={locate} />);
|
||||
fireEvent.click(screen.getByRole("button", { name: "定位要素" }));
|
||||
expect(screen.getByRole("group", { name: "定位要素" })).toHaveAttribute("aria-busy", "true");
|
||||
expect(screen.getByRole("button", { name: "正在定位…" })).toBeDisabled();
|
||||
await act(async () => reject(new Error("网络失败")));
|
||||
fireEvent.click(screen.getByRole("button", { name: "重试定位要素" }));
|
||||
await waitFor(() => expect(locate).toHaveBeenCalledTimes(2));
|
||||
});
|
||||
it("associates required errors, retains failed drafts and clears success when editing", async () => {
|
||||
let complete!: () => void;
|
||||
const submit = vi.fn().mockRejectedValueOnce(new Error("发送失败")).mockImplementationOnce(() => new Promise<void>((resolve) => { complete = resolve; }));
|
||||
render(<AnswerUiView block={form} onSubmit={submit} />);
|
||||
const editor = screen.getByLabelText("记录(必填)");
|
||||
const formElement = screen.getByRole("form", { name: "现场资料" });
|
||||
fireEvent.submit(formElement);
|
||||
expect(editor).toHaveAttribute("aria-invalid", "true");
|
||||
expect(editor).toHaveAccessibleDescription("请填写记录。");
|
||||
fireEvent.change(editor, { target: { value: "草稿" } });
|
||||
fireEvent.submit(formElement);
|
||||
await waitFor(() => expect(screen.getByRole("status")).toHaveTextContent("发送失败"));
|
||||
expect(editor).toHaveValue("草稿");
|
||||
fireEvent.submit(formElement);
|
||||
fireEvent.submit(formElement);
|
||||
expect(submit).toHaveBeenCalledTimes(2);
|
||||
expect(formElement).toHaveAttribute("aria-busy", "true");
|
||||
await act(async () => complete());
|
||||
expect(screen.getByRole("status")).toHaveTextContent("资料已发送");
|
||||
fireEvent.change(editor, { target: { value: "更新后的草稿" } });
|
||||
expect(screen.queryByRole("status")).not.toBeInTheDocument();
|
||||
expect(screen.getByRole("group", { name: "现场资料" })).toHaveAttribute("data-state", "idle");
|
||||
});
|
||||
it("confirms draft transfer without claiming submission", async () => {
|
||||
render(<AnswerUiView block={form} submitMode="draft" onSubmit={vi.fn()} />);
|
||||
fireEvent.change(screen.getByLabelText("记录(必填)"), { target: { value: "草稿" } });
|
||||
fireEvent.click(screen.getByRole("button", { name: "带入对话" }));
|
||||
await waitFor(() => expect(screen.getByRole("status")).toHaveTextContent("资料已带入对话,请确认后发送"));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user