import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { cleanup, fireEvent, render, screen, waitFor, } from "@testing-library/react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useFormSchemaQuery } from "@/hooks/marriage/use-form-schema"; import { useMarriageProfileQuery } from "@/hooks/marriage/use-profile-main"; import { useMarriageSectionDataQuery, useUpdateMarriageSectionDataMutation, } from "@/hooks/marriage/use-section-data"; import { convertSchemaToFrontendItems } from "@/lib/schema-adapter"; import { QuestionAnswersProvider, useQuestionAnswers, } from "./question-answer-storage"; vi.mock("@/hooks/marriage/use-form-schema", () => ({ useFormSchemaQuery: vi.fn(), })); vi.mock("@/hooks/marriage/use-profile-main", () => ({ useMarriageProfileQuery: vi.fn(), })); vi.mock("@/hooks/marriage/use-section-data", () => ({ applyProfilePatchResultToCache: vi.fn(), useMarriageSectionDataQuery: vi.fn(), useUpdateMarriageSectionDataMutation: vi.fn(), })); // Dummy component to interact with the context function TestComponent({ slug }: { slug: string }) { const { setAnswerValue, flushAnswers } = useQuestionAnswers(); return (
); } describe("Question Answer & Schema Integration", () => { let capturedPayload: any = null; beforeEach(() => { capturedPayload = null; const updateMutateAsync = vi.fn(async (payload) => { capturedPayload = payload; return payload; }); (useUpdateMarriageSectionDataMutation as any).mockReturnValue({ mutateAsync: updateMutateAsync, isPending: false, }); (useMarriageProfileQuery as any).mockReturnValue({ data: { can_edit_profile: true }, }); (useMarriageSectionDataQuery as any).mockReturnValue({ data: [], isLoading: false, }); (useFormSchemaQuery as any).mockReturnValue({ data: { is_completed: false }, isLoading: false, isFetching: false, }); }); afterEach(() => { cleanup(); }); it("should send option_id instead of label/value for radio", async () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); render( , ); fireEvent.click(screen.getByTestId("set-radio")); fireEvent.click(screen.getByTestId("save")); await waitFor(() => { expect(capturedPayload).not.toBeNull(); const field = capturedPayload.fields.find((f: any) => f.key === "q1"); expect(field.option_id).toBe("opt1"); expect(field.value).toBe("opt1"); // ui value is option_id }); }); it("should send array of option_ids for checkbox", async () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); render( , ); fireEvent.click(screen.getByTestId("set-checkbox")); fireEvent.click(screen.getByTestId("save")); await waitFor(() => { expect(capturedPayload).not.toBeNull(); const field = capturedPayload.fields.find((f: any) => f.key === "q2"); expect(field.option_id).toEqual(["opt2", "opt3"]); }); }); it("batches all dirty answers into one mutation", async () => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); render( , ); fireEvent.click(screen.getByTestId("set-radio")); fireEvent.click(screen.getByTestId("set-checkbox")); fireEvent.click(screen.getByTestId("save")); await waitFor(() => expect(capturedPayload?.fields).toHaveLength(2)); }); it("should sort schema questions and options by order correctly", () => { const mockSchema = { sections: [ { id: "sec1", title: "Section", order: 1, icon: "user-circle", is_required: true, estimated_minutes: 5, cards: [ { id: "card1", title: "Card", order: 2, questions: [ { id: "q1", title: "Q1", type: "text", order: 10, required: true, is_visible: true, ui_config: {}, options: [], }, { id: "q2", title: "Q2", type: "text", order: 5, required: true, is_visible: true, ui_config: {}, options: [ { id: "opt1", value: "A", label: "Option A", order: 2 }, { id: "opt2", value: "B", label: "Option B", order: 1 }, ], }, ], }, { id: "card2", title: "Card 2", order: 1, questions: [ { id: "q3", title: "Q3", type: "text", order: 1, required: true, is_visible: true, ui_config: {}, options: [], }, ], }, ], }, ], progress: { sections_progress: {} }, } as any; const frontendItems = convertSchemaToFrontendItems(mockSchema, "en"); const questions = frontendItems[0].questions; expect(questions[0].id).toBe("q3"); expect(questions[1].id).toBe("q2"); expect(questions[2].id).toBe("q1"); expect(questions[1].options[0].id).toBe("opt2"); expect(questions[1].options[1].id).toBe("opt1"); }); it("should hydrate radio/dropdown/checkbox with option_id instead of canonical value", async () => { // Mock the backend sending canonical value but keeping option_id (useMarriageSectionDataQuery as any).mockReturnValue({ data: { slug: "test_slug", data: [ { key: "q_radio", type: "radio", value: "Server Canonical Value", option_id: "opt_radio", }, { key: "q_check", type: "checkbox", value: ["Server Val 1", "Server Val 2"], option_id: ["opt_check1", "opt_check2"], }, ], }, isLoading: false, }); const capturedValues: any = {}; function HydrationTestComponent() { const { getAnswerValue } = useQuestionAnswers(); capturedValues.radio = getAnswerValue({ id: "q_radio" } as any); capturedValues.check = getAnswerValue({ id: "q_check" } as any); return
; } const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); render( , ); // Give it a moment to reconcile useEffect in QuestionAnswersProvider await waitFor(() => { expect(capturedValues.radio).toBe("opt_radio"); expect(capturedValues.check).toEqual(["opt_check1", "opt_check2"]); }); }); it("should respect bq.is_required over bq.required for required state", () => { const mockSchema = { sections: [ { id: "sec1", title: "Section", order: 1, icon: "user-circle", is_required: true, estimated_minutes: 5, cards: [ { id: "card1", title: "Card", order: 1, questions: [ { id: "q1", title: "Q1", type: "text", order: 1, required: false, is_required: true, is_visible: true, ui_config: {}, options: [], }, ], }, ], }, ], progress: { sections_progress: {} }, } as any; const frontendItems = convertSchemaToFrontendItems(mockSchema, "en"); const question = frontendItems[0].questions[0]; expect(question.baseRequired).toBe(false); expect(question.required).toBe(true); }); });