import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { cleanup, fireEvent, render, screen, waitFor, } from "@testing-library/react"; import { afterEach, describe, expect, it, vi } from "vitest"; import type { QuestionField } from "@/lib/schema-adapter"; import { QuestionAnswersProvider } from "./question-answer-storage"; import { QuestionSheet } from "./question-sheet"; vi.mock("@/translations/provider", () => ({ useI18n: vi.fn(() => ({ locale: "fa", dictionary: { Confirm: "تایید" } })), })); vi.mock("@/hooks/marriage/use-section-data", () => ({ applyProfilePatchResultToCache: vi.fn(), useMarriageSectionDataQuery: vi.fn(() => ({ data: undefined, isLoading: false, })), useUpdateMarriageSectionDataMutation: vi.fn(() => ({ mutateAsync: vi.fn().mockResolvedValue({}), isPending: false, })), })); describe("QuestionSheet component", () => { afterEach(() => { cleanup(); window.history.replaceState(null, "", window.location.href); document.body.classList.remove("dropdown-open"); document.body.classList.remove("question-sheet-open"); document.body.classList.remove("question-keyboard-open"); }); const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, }); it("renders trigger and opens bottom sheet on click", async () => { const qDropdown = { id: "q_nat", title: "تابعیت و شهروندی", type: "dropdown", order: 1, required: true, isVisible: true, description: "", tooltip: "", extras: { placeHolder: "انتخاب کشور" }, options: [ { id: "iran", value: "Iran", label: "ایران", order: 1 }, { id: "germany", value: "Germany", label: "آلمان", order: 2 }, { id: "canada", value: "Canada", label: "کانادا", order: 3 }, ], ui_config: {}, } as QuestionField; render(
, ); // Initial placeholder displayed in trigger expect(screen.getByText("انتخاب کشور")).toBeDefined(); // Click trigger to open bottom sheet const trigger = screen.getByRole("button", { name: /انتخاب کشور/i }); fireEvent.click(trigger); // Bottom sheet dialog should be visible expect(screen.getByRole("dialog")).toBeDefined(); expect(document.body.classList.contains("dropdown-open")).toBe(true); expect(document.body.classList.contains("question-sheet-open")).toBe(true); expect( (document.querySelector(".app-shell") as HTMLElement).style.overflowY, ).toBe("hidden"); expect(screen.getByRole("button", { name: "ایران" })).toBeDefined(); expect(screen.getByRole("button", { name: "آلمان" })).toBeDefined(); // Select Iran const iranBtn = screen.getByRole("button", { name: "ایران" }); fireEvent.click(iranBtn); // Trigger should now show Iran await waitFor(() => { expect(screen.getByText("ایران")).toBeDefined(); expect(document.body.classList.contains("dropdown-open")).toBe(false); expect(document.body.classList.contains("question-sheet-open")).toBe( false, ); expect( (document.querySelector(".app-shell") as HTMLElement).style.overflowY, ).toBe(""); }); }); it("supports multi-selection with confirm button", async () => { const qMulti = { id: "q_lang", title: "سایر زبان‌ها", type: "dropdown", order: 2, required: false, isVisible: true, description: "", tooltip: "", extras: { placeHolder: "انتخاب زبان‌ها", range: [0, 5] }, options: [ { id: "en", value: "English", label: "انگلیسی", order: 1 }, { id: "ar", value: "Arabic", label: "عربی", order: 2 }, { id: "fr", value: "French", label: "فرانسوی", order: 3 }, ], ui_config: {}, } as QuestionField; render( , ); // Click trigger const trigger = screen.getByRole("button", { name: /انتخاب زبان‌ها/i }); fireEvent.click(trigger); // Select English and French const enBtn = screen.getByRole("button", { name: "انگلیسی" }); const frBtn = screen.getByRole("button", { name: "فرانسوی" }); fireEvent.click(enBtn); fireEvent.click(frBtn); // Confirm button should show count const confirmBtn = screen.getByRole("button", { name: /تایید \(2\)/i }); expect(confirmBtn).toBeDefined(); fireEvent.click(confirmBtn); // Trigger displays selected items await waitFor(() => { expect(screen.getByText("انگلیسی, فرانسوی")).toBeDefined(); }); }); it("closes the inline selector with Escape", async () => { const question = { id: "q_drag", title: "کشور محل اقامت", type: "dropdown", required: true, extras: { placeHolder: "انتخاب کشور" }, options: [{ id: "iran", value: "Iran", label: "ایران", order: 1 }], ui_config: {}, } as QuestionField; render( , ); fireEvent.click(screen.getByRole("button", { name: /انتخاب کشور/i })); fireEvent.keyDown(screen.getByRole("dialog"), { key: "Escape" }); await waitFor(() => { expect(screen.queryByRole("dialog")).toBeNull(); expect(document.body.classList.contains("dropdown-open")).toBe(false); expect(document.body.classList.contains("question-sheet-open")).toBe( false, ); }); }); it("closes the sheet instead of leaving the page on browser Back", async () => { const question = { id: "q_back", title: "کشور", type: "dropdown", required: true, extras: { placeHolder: "انتخاب کشور" }, options: [{ id: "iran", value: "Iran", label: "ایران", order: 1 }], ui_config: {}, } as QuestionField; const pushState = vi.spyOn(window.history, "pushState"); render( , ); fireEvent.click(screen.getByRole("button", { name: /انتخاب کشور/i })); await waitFor(() => expect(pushState).toHaveBeenCalledTimes(1)); window.history.replaceState(null, "", window.location.href); window.dispatchEvent(new PopStateEvent("popstate")); await waitFor(() => { expect(screen.queryByRole("dialog")).toBeNull(); }); pushState.mockRestore(); }); it("opens a searchable sheet for 7+ options without focusing search", () => { const question = { id: "q_country", title: "کشور", type: "dropdown", required: true, extras: { placeHolder: "انتخاب کشور" }, options: Array.from({ length: 7 }, (_, index) => ({ id: `country-${index}`, value: `country-${index}`, label: `کشور ${index + 1}`, order: index + 1, })), ui_config: {}, } as QuestionField; render( , ); fireEvent.click(screen.getByRole("button", { name: /انتخاب کشور/i })); expect(screen.getByPlaceholderText("جستجو...")).not.toHaveFocus(); expect(screen.getByRole("dialog").querySelector("section")).toHaveClass( "h-[82svh]", ); }); it("sizes a short options sheet to its content (4 options)", () => { const question = { id: "q_short", title: "انتخاب کوتاه", type: "dropdown", required: true, extras: { placeHolder: "انتخاب" }, options: Array.from({ length: 4 }, (_, index) => ({ id: `option-${index}`, value: `option-${index}`, label: `گزینه ${index + 1}`, order: index + 1, })), ui_config: {}, } as QuestionField; render( , ); fireEvent.click(screen.getByRole("button", { name: "انتخاب" })); expect(screen.getByRole("dialog").querySelector("section")).toHaveClass( "h-auto", "max-h-[82svh]", ); }); it("compact boundary: 5 options → compact, no search", () => { const question = { id: "q_boundary_5", title: "پنج گزینه", type: "dropdown", required: true, extras: { placeHolder: "انتخاب" }, options: Array.from({ length: 5 }, (_, i) => ({ id: `opt-${i}`, value: `opt-${i}`, label: `گزینه ${i + 1}`, order: i + 1, })), ui_config: {}, } as QuestionField; render( , ); fireEvent.click(screen.getByRole("button", { name: "انتخاب" })); const section = screen.getByRole("dialog").querySelector("section"); expect(section).toHaveClass("h-auto", "max-h-[82svh]"); expect(screen.queryByPlaceholderText("جستجو...")).toBeNull(); }); it("compact boundary: 6 options → compact, no search", () => { const question = { id: "q_boundary_6", title: "شش گزینه", type: "dropdown", required: true, extras: { placeHolder: "انتخاب" }, options: Array.from({ length: 6 }, (_, i) => ({ id: `opt-${i}`, value: `opt-${i}`, label: `گزینه ${i + 1}`, order: i + 1, })), ui_config: {}, } as QuestionField; render( , ); fireEvent.click(screen.getByRole("button", { name: "انتخاب" })); const section = screen.getByRole("dialog").querySelector("section"); expect(section).toHaveClass("h-auto", "max-h-[82svh]"); expect(section).not.toHaveClass("h-[82svh]"); expect(screen.queryByPlaceholderText("جستجو...")).toBeNull(); }); it("compact boundary: 7 options → large sheet with search", () => { const question = { id: "q_boundary_7", title: "هفت گزینه", type: "dropdown", required: true, extras: { placeHolder: "انتخاب" }, options: Array.from({ length: 7 }, (_, i) => ({ id: `opt-${i}`, value: `opt-${i}`, label: `گزینه ${i + 1}`, order: i + 1, })), ui_config: {}, } as QuestionField; render( , ); fireEvent.click(screen.getByRole("button", { name: "انتخاب" })); const section = screen.getByRole("dialog").querySelector("section"); expect(section).toHaveClass("h-[82svh]"); expect(section).not.toHaveClass("h-auto"); expect(screen.getByPlaceholderText("جستجو...")).toBeDefined(); }); });