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: () =>
})); function block(type: string, props: Record) { 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(); 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(); 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(); 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((resolve) => { complete = resolve; })); render(); 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(); fireEvent.change(screen.getByLabelText("记录(必填)"), { target: { value: "草稿" } }); fireEvent.click(screen.getByRole("button", { name: "带入对话" })); await waitFor(() => expect(screen.getByRole("status")).toHaveTextContent("资料已带入对话,请确认后发送")); }); });