diff --git a/apps/fabro-web/app/components/interview-dock.test.tsx b/apps/fabro-web/app/components/interview-dock.test.tsx index 9ac272ee8..34a58269f 100644 --- a/apps/fabro-web/app/components/interview-dock.test.tsx +++ b/apps/fabro-web/app/components/interview-dock.test.tsx @@ -7,6 +7,7 @@ import { } from "@qltysh/fabro-api-client"; import { InterviewDock, displayLabel } from "./interview-dock"; +import { generatedAxios } from "../lib/api-client"; function render(node: React.ReactNode): TestRenderer.ReactTestRenderer { let tree: TestRenderer.ReactTestRenderer | undefined; @@ -81,6 +82,41 @@ describe("InterviewDock", () => { expect(buttons.No).toBeDefined(); }); + test("yes/no question submits typed yes and no answers", async () => { + const submitted: unknown[] = []; + const originalAdapter = generatedAxios.defaults.adapter; + generatedAxios.defaults.adapter = async (config) => { + submitted.push(JSON.parse(String(config.data))); + return { + data: undefined, + status: 204, + statusText: "No Content", + headers: {}, + config, + }; + }; + + try { + const tree = render( + , + ); + const buttons = buttonsByText(tree); + + await act(async () => { + buttons.Yes.props.onClick(); + await Promise.resolve(); + }); + await act(async () => { + buttons.No.props.onClick(); + await Promise.resolve(); + }); + + expect(submitted).toEqual([{ kind: "yes" }, { kind: "no" }]); + } finally { + generatedAxios.defaults.adapter = originalAdapter; + } + }); + test("multiple choice question renders option buttons with stripped accelerator prefixes", () => { const question = makeQuestion({ question_type: QuestionType.MULTIPLE_CHOICE, diff --git a/apps/fabro-web/app/components/interview-dock.tsx b/apps/fabro-web/app/components/interview-dock.tsx index c45c67492..35eb2fcf5 100644 --- a/apps/fabro-web/app/components/interview-dock.tsx +++ b/apps/fabro-web/app/components/interview-dock.tsx @@ -22,6 +22,7 @@ import { useSubmitInterviewAnswer, type SubmitInterviewAnswerArg, } from "../lib/mutations"; +import { ApiError } from "../lib/api-client"; import { ErrorMessage } from "./ui"; const PRIMARY_BUTTON = @@ -33,6 +34,8 @@ const CHOICE_BUTTON = const CHOICE_BUTTON_SELECTED = "inline-flex items-center justify-center gap-1.5 rounded-lg bg-teal-500/15 px-3.5 py-2 text-sm font-medium text-fg outline-1 -outline-offset-1 outline-teal-500/60 transition-colors hover:bg-teal-500/20 focus-visible:outline-2 focus-visible:-outline-offset-1 focus-visible:outline-teal-500"; +type SubmitInterviewAnswer = SubmitInterviewAnswerArg["answer"]; + export interface InterviewDockProps { runId: string; questions: ApiQuestion[]; @@ -52,15 +55,13 @@ export function InterviewDock({ runId, questions }: InterviewDockProps) { }, [question?.id, submitMutation.reset]); const submit = useCallback( - async (arg: Omit) => { + async (answer: SubmitInterviewAnswer) => { if (!question) return; setError(null); try { - await submitMutation.trigger({ ...arg, questionId: question.id }); + await submitMutation.trigger({ questionId: question.id, answer }); } catch (caught) { - setError( - caught instanceof Error ? caught.message : "Couldn't submit your answer.", - ); + setError(interviewSubmitErrorMessage(caught)); } }, [question, submitMutation], @@ -172,7 +173,7 @@ function QuestionBody({ }: { question: ApiQuestion; submitting: boolean; - onSubmit: (arg: Omit) => Promise; + onSubmit: (answer: SubmitInterviewAnswer) => Promise; }) { switch (question.question_type) { case QuestionType.YES_NO: @@ -216,14 +217,14 @@ function YesNoBody({ onSubmit, }: { submitting: boolean; - onSubmit: (arg: Omit) => Promise; + onSubmit: (answer: SubmitInterviewAnswer) => Promise; }) { return (