323 lines · 8.9 KB · TypeScript
RawDownload
import { describe, expect, it } from "vite-plus/test"; import {  buildPendingUserInputAnswers,  countAnsweredPendingUserInputQuestions,  derivePendingUserInputProgress,  resolvePendingUserInputAnswer,  setPendingUserInputCustomAnswer,  togglePendingUserInputOptionSelection,} from "./pendingUserInput"; const singleSelectQuestion = {  id: "scope",  header: "Scope",  question: "What should the plan target first?",  options: [    {      label: "Orchestration-first",      description: "Focus on orchestration first",    },  ],  multiSelect: false,} as const; const multiSelectQuestion = {  id: "areas",  header: "Areas",  question: "Which areas should this change cover?",  options: [    {      label: "Server",      description: "Server",    },    {      label: "Web",      description: "Web",    },  ],  multiSelect: true,} as const; const nativeChoiceQuestion = {  id: "result",  header: "Result",  question: "Which result should be used?",  options: [    { value: " first\t", label: "Result", description: "First result" },    { value: "second", label: "Result", description: "Second result" },  ],  allowCustomAnswer: false,  multiSelect: false,} as const; describe("resolvePendingUserInputAnswer", () => {  it("prefers a custom answer over selected options", () => {    expect(      resolvePendingUserInputAnswer(singleSelectQuestion, {        selectedOptionValues: ["Orchestration-first"],        customAnswer: "Keep the existing envelope for one release",      }),    ).toBe("Keep the existing envelope for one release");  });   it("falls back to the selected option for single-select questions", () => {    expect(      resolvePendingUserInputAnswer(singleSelectQuestion, {        selectedOptionValues: ["Orchestration-first"],      }),    ).toBe("Orchestration-first");  });   it("returns all selected labels for multi-select questions", () => {    expect(      resolvePendingUserInputAnswer(multiSelectQuestion, {        selectedOptionValues: ["Server", "Web"],      }),    ).toEqual(["Server", "Web"]);  });   it("clears the preset selection when a custom answer is entered", () => {    expect(      setPendingUserInputCustomAnswer(        {          selectedOptionValues: ["Server", "Web"],        },        "doesn't matter",      ),    ).toEqual({      customAnswer: "doesn't matter",    });  });   it("does not replace a required choice with a custom answer", () => {    expect(      resolvePendingUserInputAnswer(nativeChoiceQuestion, {        selectedOptionValues: ["second"],        customAnswer: "Use another result",      }),    ).toBe("second");  });   it("does not submit labels or unknown values when an option has a value", () => {    expect(      resolvePendingUserInputAnswer(nativeChoiceQuestion, {        selectedOptionValues: ["Result", "unknown"],      }),    ).toBeNull();  });}); describe("togglePendingUserInputOptionSelection", () => {  it("toggles options for multi-select questions", () => {    expect(togglePendingUserInputOptionSelection(multiSelectQuestion, undefined, "Server")).toEqual(      {        customAnswer: "",        selectedOptionValues: ["Server"],      },    );     expect(      togglePendingUserInputOptionSelection(        multiSelectQuestion,        {          selectedOptionValues: ["Server", "Web"],        },        "Server",      ),    ).toEqual({      customAnswer: "",      selectedOptionValues: ["Web"],    });  });   it("selects and removes options with the same label independently", () => {    const question = { ...nativeChoiceQuestion, multiSelect: true };    const firstSelected = togglePendingUserInputOptionSelection(question, undefined, " first\t");    const bothSelected = togglePendingUserInputOptionSelection(question, firstSelected, "second");     expect(buildPendingUserInputAnswers([question], { result: bothSelected })).toEqual({      result: [" first\t", "second"],    });     const secondSelected = togglePendingUserInputOptionSelection(      question,      bothSelected,      " first\t",    );    expect(buildPendingUserInputAnswers([question], { result: secondSelected })).toEqual({      result: ["second"],    });  });}); describe("buildPendingUserInputAnswers", () => {  it("returns a canonical answer map for complete prompts", () => {    expect(      buildPendingUserInputAnswers(        [          singleSelectQuestion,          {            id: "compat",            header: "Compat",            question: "How strict should compatibility be?",            options: [              {                label: "Keep current envelope",                description: "Preserve current wire format",              },            ],            multiSelect: false,          },        ],        {          scope: {            selectedOptionValues: ["Orchestration-first"],          },          compat: {            customAnswer: "Keep the current envelope for one release window",          },        },      ),    ).toEqual({      scope: "Orchestration-first",      compat: "Keep the current envelope for one release window",    });  });   it("returns arrays for answered multi-select prompts", () => {    expect(      buildPendingUserInputAnswers([multiSelectQuestion], {        areas: {          selectedOptionValues: ["Server", "Web"],        },      }),    ).toEqual({      areas: ["Server", "Web"],    });  });   it("returns null when any question is unanswered", () => {    expect(buildPendingUserInputAnswers([singleSelectQuestion], {})).toBeNull();  });   it.each([" first\t", ""])("preserves the exact selected option value %j", (value) => {    const question = {      ...nativeChoiceQuestion,      options: [{ ...nativeChoiceQuestion.options[0], value }, nativeChoiceQuestion.options[1]],    };    const draft = togglePendingUserInputOptionSelection(question, undefined, value);     expect(buildPendingUserInputAnswers([question], { result: draft })).toEqual({ result: value });    expect(derivePendingUserInputProgress([question], { result: draft }, 0)).toMatchObject({      selectedOptionValues: [value],      answeredQuestionCount: 1,      canAdvance: true,      isComplete: true,    });  });}); describe("pending user input question progress", () => {  const questions = [    singleSelectQuestion,    {      id: "compat",      header: "Compat",      question: "How strict should compatibility be?",      options: [        {          label: "Keep current envelope",          description: "Preserve current wire format",        },      ],      multiSelect: false,    },  ] as const;   it("counts only answered questions", () => {    expect(      countAnsweredPendingUserInputQuestions(questions, {        scope: {          selectedOptionValues: ["Orchestration-first"],        },      }),    ).toBe(1);  });   it("derives the active question and advancement state", () => {    expect(      derivePendingUserInputProgress(        questions,        {          scope: {            selectedOptionValues: ["Orchestration-first"],          },        },        0,      ),    ).toMatchObject({      questionIndex: 0,      activeQuestion: questions[0],      selectedOptionValues: ["Orchestration-first"],      customAnswer: "",      resolvedAnswer: "Orchestration-first",      answeredQuestionCount: 1,      isLastQuestion: false,      isComplete: false,      canAdvance: true,    });  });   it("treats multi-select questions as answered when they have selected options", () => {    expect(      derivePendingUserInputProgress(        [multiSelectQuestion],        {          areas: {            selectedOptionValues: ["Server", "Web"],          },        },        0,      ),    ).toMatchObject({      selectedOptionValues: ["Server", "Web"],      resolvedAnswer: ["Server", "Web"],      canAdvance: true,      isComplete: true,    });  });   it("requires an option when custom answers are disabled", () => {    const drafts = { result: { customAnswer: "Use another result" } };     expect(buildPendingUserInputAnswers([nativeChoiceQuestion], drafts)).toBeNull();    expect(derivePendingUserInputProgress([nativeChoiceQuestion], drafts, 0)).toMatchObject({      customAnswer: "",      usingCustomAnswer: false,      resolvedAnswer: null,      answeredQuestionCount: 0,      canAdvance: false,      isComplete: false,    });  });}); it("accepts attachment-only answers after every upload finishes", () => {  const questions = [    { id: "spec", header: "Spec", question: "Provide a spec", options: [], multiSelect: false },  ];  expect(buildPendingUserInputAnswers(questions, { spec: { attachmentCount: 1 } })).toEqual({    spec: "",  });  expect(    buildPendingUserInputAnswers(questions, {      spec: { attachmentCount: 1, attachmentsBlocked: true },    }),  ).toBeNull();  expect(    buildPendingUserInputAnswers([{ ...questions[0]!, allowCustomAnswer: false }], {      spec: { attachmentCount: 1 },    }),  ).toBeNull();});