import { expect, test } from "bun:test"; import { nativeFlowProjectionSchema } from "./native-flow-session.js"; const projection = { renderer: "native", mode: "simulated", nodeId: "page", blocks: [ { kind: "radio", key: "route", label: "Route", required: true, options: [ { value: "detail", label: "Detail" }, { value: "finish", label: "Finish" }, ], }, ], answers: { route: null }, errors: {}, formErrors: [], status: "editing", canGoBack: true, canClose: true, terminal: [], }; const table = { fieldKey: "route", entries: [ { value: null, intent: "submit" }, { value: "", intent: "submit" }, { value: "detail", intent: "continue" }, { value: "finish", intent: "submit" }, ], }; test("new projection parser accepts legacy shape without adding the extension", () => { expect(nativeFlowProjectionSchema.parse(projection)).toEqual(projection); expect( Object.hasOwn( nativeFlowProjectionSchema.parse(projection), "radioDecisions", ), ).toBe(false); expect( nativeFlowProjectionSchema.safeParse({ ...projection, radioDecisions: table, }).success, ).toBe(true); }); test.each([ { ...table, fieldKey: "other" }, { ...table, entries: table.entries.slice(1) }, { ...table, entries: [...table.entries, table.entries[0]] }, { ...table, entries: table.entries.map((entry, index) => index === 2 ? { ...entry, value: "unknown" } : entry, ), }, { ...table, entries: table.entries.map((entry) => ({ ...entry, intent: "back" })), }, { ...table, entries: table.entries.map((entry) => ({ ...entry, value: [entry.value] })), }, { ...table, entries: Array.from({ length: 35 }, () => table.entries[0]) }, { ...table, extra: true }, ])("malformed radio mapping is rejected: %j", (radioDecisions) => { expect( nativeFlowProjectionSchema.safeParse({ ...projection, radioDecisions }) .success, ).toBe(false); }); test("table requires exactly one radio with unique options, and cannot accompany completion", () => { for (const blocks of [ [], [ ...projection.blocks, { kind: "text", key: "text", label: "Text", required: false }, ], [ { ...projection.blocks[0], options: [ projection.blocks[0].options[0], projection.blocks[0].options[0], ], }, ], ]) { expect( nativeFlowProjectionSchema.safeParse({ ...projection, blocks, radioDecisions: table, }).success, ).toBe(false); } expect( nativeFlowProjectionSchema.safeParse({ ...projection, status: "succeeded", radioDecisions: table, }).success, ).toBe(false); }); test("32 scalar options plus null and clear fit the bound; an empty option occurs once", () => { for (const first of ["", "value-0"]) { const values = Array.from({ length: 32 }, (_, i) => i === 0 ? first : `value-${i}`, ); const blocks = [ { ...projection.blocks[0], options: values.map((value) => ({ value, label: value })), }, ]; const radioDecisions = { fieldKey: "route", entries: [...new Set([null, "", ...values])].map((value) => ({ value, intent: "submit", })), }; expect( nativeFlowProjectionSchema.safeParse({ ...projection, blocks, radioDecisions, }).success, ).toBe(true); } });