import assert from "node:assert/strict"; import test from "node:test"; import { formatAnswers, normalizeQuestions, orderedAnswers } from "../src/normalize.ts"; import type { QuestionInput, UserAnswer } from "../src/types.ts"; test("normalizes select and text questions with bounded defaults", () => { const questions = normalizeQuestions([ { id: "language", label: " Language ", prompt: " Choose a language ", type: "select", options: [{ value: "ts", label: " TypeScript " }], }, { id: "notes", prompt: "Anything else?", type: "text", required: false, }, ]); assert.deepEqual(questions[0], { id: "language", label: "Language", prompt: "Choose a language", type: "select", options: [{ value: "ts", label: "TypeScript" }], required: true, }); assert.equal(questions[1]?.label, "Q2"); assert.equal(questions[1]?.required, false); }); test("ignores the removed allowCustom input from legacy callers", () => { const legacyQuestion = { id: "language", prompt: "Choose a language", type: "select", options: [{ value: "ts", label: "TypeScript" }], allowCustom: false, } satisfies QuestionInput & { allowCustom: boolean }; const [question] = normalizeQuestions([legacyQuestion]); assert.ok(question); assert.equal("allowCustom" in question, false); }); test("rejects invalid ids, duplicate ids, invalid option combinations, and duplicate values", () => { assert.throws(() => normalizeQuestions([{ id: "bad id", prompt: "Bad", type: "text" }]), /must start with a letter/); assert.throws(() => normalizeQuestions([null as never]), /must be an object/); assert.throws(() => normalizeQuestions([{ id: "bad_flag", prompt: "Bad", type: "text", required: "yes" as never }]), /must be a boolean/); assert.throws(() => normalizeQuestions([ { id: "same", prompt: "One", type: "text" }, { id: "same", prompt: "Two", type: "text" }, ]), /duplicate question id/); assert.throws(() => normalizeQuestions([{ id: "pick", prompt: "Pick", type: "select" }]), /at least one option/); assert.throws(() => normalizeQuestions([{ id: "text", prompt: "Write", type: "text", options: [{ value: "x", label: "X" }] }]), /must not define options/); assert.throws(() => normalizeQuestions([{ id: "pick", prompt: "Pick", type: "select", options: [{ value: "x", label: "X" }, { value: "x", label: "Again" }], }]), /duplicate option value/); }); test("orders and formats answers by question order", () => { const questions = normalizeQuestions([ { id: "first", prompt: "First?", type: "text" }, { id: "second", prompt: "Second?", type: "select", options: [{ value: "yes", label: "Yes" }] }, { id: "third", prompt: "Third?", type: "text", required: false }, ]); const answer = (id: string, overrides: Partial): UserAnswer => ({ id, question: `${id}?`, type: "text", answer: id, value: id, label: id, custom: false, skipped: false, ...overrides, }); const answers = new Map([ ["third", answer("third", { answer: "", value: "", label: "Skipped", skipped: true })], ["second", answer("second", { type: "select", answer: "Yes", value: "yes", label: "Yes" })], ["first", answer("first", { answer: "hello", value: "hello", label: "hello", custom: true })], ]); const ordered = orderedAnswers(questions, answers); assert.deepEqual(ordered.map((item) => item.id), ["first", "second", "third"]); assert.equal(formatAnswers(ordered), [ "first: user wrote: hello", "second: user selected: Yes (value: yes)", "third: skipped", ].join("\n")); });