feat(agent): render local interactive answers and fold completed process
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
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 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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user