import { describe, expect, it } from "vitest"; import { parseAnswerUiBlock } from "./contract"; const block = { version: "answer-ui@1", block_id: "reading", fallback_text: "仍待核对", spec: { root: "note", elements: { note: { type: "Text", props: { text: "仍待核对", tone: "neutral" }, children: [] } } } }; describe("untrusted answer components", () => { it("accepts a line with seven sparse history series while enforcing sample timestamp alignment", () => { const x_data = Array.from({ length: 13 }, (_, index) => String(index)); const series = [{ name: "当前", type: "line", data: x_data.map(() => 31) }, ...Array.from({ length: 7 }, (_, index) => ({ name: `前${index + 1}日`, type: "scatter", data: [...Array(10).fill(null), 33, 34, 35], sample_times: [...Array(10).fill(null), ...[30, 45, 59].map((minute) => `2026-06-${String(13 - index).padStart(2, "0")}T11:${minute}:00+08:00`)] }))]; const props = { title: "压力(m)", source: "合成样例", chart_type: "line", x_data, series }; const make = (value: unknown) => ({ ...block, spec: { root: "chart", elements: { chart: { type: "Chart", props: value, children: [] } } } }); expect(parseAnswerUiBlock(make(props))).not.toBeNull(); expect(parseAnswerUiBlock(make({ ...props, series: [...series, series[0]] }))).toBeNull(); expect(parseAnswerUiBlock(make({ ...props, series: [{ ...series[1], sample_times: [null] }] }))).toBeNull(); expect(parseAnswerUiBlock(make({ ...props, series: [{ ...series[1], sample_times: Array(13).fill("2026-06-13T12:00:00+08:00") }] }))).toBeNull(); }); it("keeps explicit UI phases and rejects invalid phase values", () => { expect(parseAnswerUiBlock(block)?.phase).toBeUndefined(); expect(parseAnswerUiBlock({ ...block, phase: "process" })?.phase).toBe("process"); expect(parseAnswerUiBlock({ ...block, phase: "final" })?.phase).toBe("final"); expect(parseAnswerUiBlock({ ...block, phase: "other" })).toBeNull(); expect(parseAnswerUiBlock({ ...block, phase: null })).toBeNull(); }); it("accepts controlled task semantics and result states without accepting arbitrary presentation code", () => { const result = { type: "Result", props: { title: "定位结果", summary: "3 根管道已定位,高亮未确认。", status: "partial", source: "地图工具回执" }, children: [] }; const spec = { root: "root", elements: { root: { type: "Stack", props: { columns: "one", gap: "normal", intent: "response", density: "compact" }, children: ["section"] }, section: { type: "Section", props: { title: "本次结果", description: null, role: "result" }, children: ["result"] }, result } }; expect(parseAnswerUiBlock({ ...block, spec })).not.toBeNull(); for (const props of [ { ...result.props, status: "running" }, { ...result.props, css: "display:none" }, { ...result.props, onClick: "fetch('/write')" } ]) expect(parseAnswerUiBlock({ ...block, spec: { ...spec, elements: { ...spec.elements, result: { ...result, props } } } })).toBeNull(); expect(parseAnswerUiBlock({ ...block, spec: { root: "note", elements: { note: { ...block.spec.elements.note, props: { text: "高亮操作未提供。", tone: "warning", title: "能力限制" } } } } })).not.toBeNull(); }); it("accepts registered text and rejects arbitrary code, URLs and unknown components", () => { expect(parseAnswerUiBlock(block)).not.toBeNull(); for (const node of [ { type: "iframe", props: { src: "https://example.com" }, children: [] }, { ...block.spec.elements.note, props: { ...block.spec.elements.note.props, script: "alert(1)" } }, { ...block.spec.elements.note, on: { click: { action: "fetch" } } } ]) expect( parseAnswerUiBlock({ ...block, spec: { root: "note", elements: { note: node } } }) ).toBeNull(); }); it("rejects missing, duplicate, cyclic, unreachable and excessive trees", () => { const stack = { type: "Stack", props: { columns: "one", gap: "normal" }, children: ["note"] }; for (const children of [["missing"], ["note", "note"], ["root"]]) expect( parseAnswerUiBlock({ ...block, spec: { root: "root", elements: { root: { ...stack, children }, ...block.spec.elements } } }) ).toBeNull(); expect( parseAnswerUiBlock({ ...block, spec: { ...block.spec, elements: { ...block.spec.elements, unused: block.spec.elements.note } } }) ).toBeNull(); }); it("requires aligned, finite chart values and unambiguous form fields", () => { const chart = { type: "Chart", props: { title: "演练压力", chart_type: "line", source: "演练数据", x_data: ["08:00"], series: [{ name: "压力", data: [0.3] }] }, children: [] }; const withNode = (node: unknown) => ({ ...block, spec: { root: "note", elements: { note: node } } }); expect(parseAnswerUiBlock(withNode(chart))).not.toBeNull(); for (const data of [[0.3, 0.2], [Infinity], [NaN]]) expect( parseAnswerUiBlock( withNode({ ...chart, props: { ...chart.props, series: [{ name: "压力", data }] } }) ) ).toBeNull(); const field = { name: "location", label: "位置", kind: "text", required: true }; expect( parseAnswerUiBlock( withNode({ type: "FollowUpForm", props: { title: "核验", description: "用户补充", fields: [field, field] }, children: [] }) ) ).toBeNull(); }); });