import { expect, test } from "bun:test"; import { createElement } from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { QuestionView } from "./question-view"; import { AUTH } from "./question-view.test-fixtures"; import type { QuestionViewProps } from "./question-view"; let MULTIPLE = { questions: [{ ...AUTH.questions[0]!, multiple: true }] }; let LEGACY = { questions: [AUTH.questions[0]!, { id: "scope", header: "Scope", question: "What belongs in the first cut?", multiple: true, options: [{ id: "anchors", label: "Anchors", description: "" }], }], }; function render(props: QuestionViewProps) { return renderToStaticMarkup(createElement(QuestionView, props)); } for (let [name, definition] of [["single", AUTH], ["legacy multi", LEGACY]] as const) { test(`${name} discarded card names its questions and resolver without actions`, () => { let markup = render({ definition, drafts: {}, status: "discarded", resolver: "bea", onReopen: () => {}, onDiscard: () => {}, }); expect(markup).toContain("Discarded by @bea"); for (let question of definition.questions) expect(markup).toContain(question.question); expect(markup).not.toContain("Cancelled"); expect(markup).not.toContain("Saved decision"); expect(markup).not.toContain(" { for (let resolver of [undefined, "system"]) { let markup = render({ definition: AUTH, drafts: {}, status: "discarded", resolver }); expect(markup).toContain("Discarded"); expect(markup).toContain(AUTH.questions[0]!.question); expect(markup).not.toContain("@system"); expect(markup).not.toContain("Answered"); } }); test("genuine cancellation keeps its existing summary and has no resolved actions", () => { let markup = render({ definition: AUTH, drafts: {}, status: "cancelled", resolver: "bea", onReopen: () => {}, onDiscard: () => {}, }); expect(markup).toContain("Cancelled by @bea"); expect(markup).not.toContain("Discarded"); expect(markup).not.toContain(" { let drafts = { q: { mode: "choices" as const, choice: null, options: { a: false, b: false }, custom: "" }, }; let before = structuredClone(drafts); let markup = render({ definition: AUTH, drafts, suggested: { optionId: "b", revision: 7 } }); expect(markup.match(/from chat/g)).toHaveLength(1); expect(markup).toMatch(/GitHub Apps[\s\S]*?from chat/); expect(markup.replace(/<[^>]*>/g, "").replace(/\s+/g, " ")).toContain("GitHub Apps from chat"); expect(markup).toMatch(/]*checked=""[^>]*\/>/); expect(drafts).toEqual(before); }); test("human choices and custom answers do not inherit suggestion provenance", () => { for ( let draft of [ { mode: "choices" as const, choice: "a", options: { a: true, b: false }, custom: "" }, { mode: "custom" as const, choice: null, options: { a: false, b: false }, custom: "A third approach", }, ] ) { let before = structuredClone(draft); let markup = render({ definition: AUTH, drafts: { q: draft }, suggested: { optionId: "b", revision: 7 }, }); expect(markup).not.toContain("from chat"); expect(draft).toEqual(before); } }); test("invalid and multi-question suggestions never gain a from chat label", () => { for (let [definition, optionId] of [[AUTH, "missing"], [LEGACY, "b"], [MULTIPLE, "b"]] as const) { let markup = render({ definition, drafts: {}, suggested: { optionId, revision: 7 } }); expect(markup).not.toContain("from chat"); } });