You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

351 lines
11 KiB

import {
render,
screen,
cleanup,
fireEvent,
waitFor,
} from "@testing-library/react";
import { describe, it, expect, vi, afterEach } from "vitest";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { QuestionAnswersProvider } from "./question-answer-storage";
import { QuestionBirthplace } from "./question-birthplace";
import QuestionNumber from "./question-number";
import QuestionText from "./question-text";
vi.mock("@/translations/provider", () => ({
useI18n: vi.fn(() => ({ locale: "fa", dictionary: {} })),
}));
describe("UI Config based behavior", () => {
afterEach(() => {
cleanup();
});
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
it("should trigger GeoIP only when ui_config.enable_geoip is true", () => {
// With totally random title but ui_config.enable_geoip = true
const qWithGeo = {
id: "q1",
title: "Random Title Here",
type: "birthplace",
order: 1,
required: true,
isVisible: true,
description: "",
tooltip: "",
extras: {},
options: [],
ui_config: { enable_geoip: true },
} as any;
const { rerender } = render(
<QueryClientProvider client={queryClient}>
<QuestionAnswersProvider slug="test" questions={[qWithGeo]}>
<QuestionBirthplace question={qWithGeo} />
</QuestionAnswersProvider>
</QueryClientProvider>,
);
// Auto button is present when GeoIP is active
expect(screen.getByText("خودکار")).toBeDefined();
// With title "residence" but no ui_config
const qWithoutGeo = {
id: "q2",
title: "residence test",
type: "birthplace",
order: 1,
required: true,
isVisible: true,
description: "",
tooltip: "",
extras: {},
options: [],
ui_config: {},
} as any;
rerender(
<QueryClientProvider client={queryClient}>
<QuestionAnswersProvider slug="test" questions={[qWithoutGeo]}>
<QuestionBirthplace question={qWithoutGeo} />
</QuestionAnswersProvider>
</QueryClientProvider>,
);
expect(screen.queryByText("خودکار")).toBeNull();
});
it("should auto-detect and display city and country when ui_config.enable_geoip is true", async () => {
const qWithGeo = {
id: "q_residence",
title: "Current Residence",
type: "birthplace",
order: 1,
required: true,
isVisible: true,
description: "",
tooltip: "",
extras: {},
options: [],
ui_config: { enable_geoip: true },
} as any;
const { setStoredUserGeoRegion } = await import("@/lib/geo-region");
const { marriageQueryKeys } = await import("@/hooks/marriage/query-keys");
setStoredUserGeoRegion({
city: "Tehran",
country: "Iran",
countryCode: "IR",
phoneCode: "+98",
});
queryClient.setQueryData(marriageQueryKeys.sectionData("test-geoip"), {
status: "success",
data: [],
version: 1,
});
render(
<QueryClientProvider client={queryClient}>
<QuestionAnswersProvider slug="test-geoip" questions={[qWithGeo]}>
<QuestionBirthplace question={qWithGeo} />
</QuestionAnswersProvider>
</QueryClientProvider>,
);
await waitFor(() => {
expect(screen.getByText(/(ایران|Iran).*Tehran/i)).toBeDefined();
});
});
it("should trigger currency behavior only when ui_config.currency_enabled is true", () => {
// Title is random, but currency_enabled is true
const qWithCurrency = {
id: "q1",
title: "Random Income",
type: "number",
order: 1,
required: true,
isVisible: true,
description: "",
tooltip: "",
extras: { placeHolder: "", range: [0, 0] },
options: [],
ui_config: { currency_enabled: true },
} as any;
const { rerender } = render(
<QueryClientProvider client={queryClient}>
<QuestionAnswersProvider slug="test" questions={[qWithCurrency]}>
<QuestionNumber question={qWithCurrency} />
</QuestionAnswersProvider>
</QueryClientProvider>,
);
// Dropdown arrow should be rendered in currency mode (path is in SVG)
expect(screen.getByRole("img", { name: "Dropdown chevron" })).toBeDefined();
// Title is "Monthly Income", but currency_enabled is false
const qWithoutCurrency = {
id: "q2",
title: "Monthly Income",
type: "number",
order: 1,
required: true,
isVisible: true,
description: "",
tooltip: "",
extras: { placeHolder: "", range: [0, 0] },
options: [],
ui_config: {},
} as any;
rerender(
<QueryClientProvider client={queryClient}>
<QuestionAnswersProvider slug="test" questions={[qWithoutCurrency]}>
<QuestionNumber question={qWithoutCurrency} />
</QuestionAnswersProvider>
</QueryClientProvider>,
);
expect(screen.queryByRole("img", { name: "Dropdown chevron" })).toBeNull();
});
it("should render error message from validation.errorMessage or fallback to locale", async () => {
// Title is random, type is email, custom error message
const qCustomError = {
id: "q1",
title: "Random Email",
type: "email",
order: 1,
required: true,
isVisible: true,
description: "",
tooltip: "",
extras: { placeHolder: "" },
options: [],
validation: { errorMessage: "Custom Backend Error" },
} as any;
const { rerender } = render(
<QueryClientProvider client={queryClient}>
<QuestionAnswersProvider slug="test" questions={[qCustomError]}>
<QuestionText question={qCustomError} />
</QuestionAnswersProvider>
</QueryClientProvider>,
);
const input = screen.getByRole("textbox");
fireEvent.change(input, { target: { value: "invalid_email" } });
await waitFor(() => {
expect(screen.getByText("Custom Backend Error")).toBeDefined();
});
const qFallbackError = {
id: "q2",
title: "Random Email",
type: "email",
order: 1,
required: true,
isVisible: true,
description: "",
tooltip: "",
extras: { placeHolder: "" },
options: [],
validation: {},
} as any;
rerender(
<QueryClientProvider client={queryClient}>
<QuestionAnswersProvider slug="test" questions={[qFallbackError]}>
<QuestionText question={qFallbackError} />
</QuestionAnswersProvider>
</QueryClientProvider>,
);
const input2 = screen.getByRole("textbox");
fireEvent.change(input2, { target: { value: "invalid_email_again" } });
await waitFor(() => {
expect(screen.getByText("یک آدرس ایمیل معتبر وارد کنید.")).toBeDefined();
});
});
it("should preserve city input when country is selected or changed in birthplace", async () => {
const qBirthplace = {
id: "birthplace_test",
title: "Birthplace",
type: "birthplace",
order: 1,
required: true,
isVisible: true,
description: "",
tooltip: "",
extras: { placeHolder: "City, region" },
options: [],
ui_config: {},
} as any;
render(
<QueryClientProvider client={queryClient}>
<QuestionAnswersProvider slug="test" questions={[qBirthplace]}>
<QuestionBirthplace question={qBirthplace} />
</QuestionAnswersProvider>
</QueryClientProvider>,
);
// Type city into input
const cityInput = screen.getByPlaceholderText("City, region");
expect(document.activeElement).not.toBe(cityInput);
fireEvent.change(cityInput, { target: { value: "Tehran" } });
expect((cityInput as HTMLInputElement).value).toBe("Tehran");
// Open country dropdown
const countryButton = screen.getByText("انتخاب کشور");
fireEvent.click(countryButton);
expect(document.body.classList.contains("dropdown-open")).toBe(true);
// Select Iran
const iranOption = screen.getByRole("button", { name: "ایران" });
fireEvent.click(iranOption);
// City should still be Tehran!
expect((cityInput as HTMLInputElement).value).toBe("Tehran");
await waitFor(() => {
expect(document.activeElement).toBe(cityInput);
expect(document.body.classList.contains("dropdown-open")).toBe(false);
});
});
it("should preserve country when typing city after selecting country", async () => {
const qBirthplace = {
id: "birthplace_select_first",
title: "محل تولد",
type: "birthplace",
order: 1,
required: true,
isVisible: true,
description: "",
tooltip: "",
extras: { placeHolder: "شهر، منطقه یا محله" },
options: [],
ui_config: {},
} as any;
render(
<QueryClientProvider client={queryClient}>
<QuestionAnswersProvider slug="test-select-first" questions={[qBirthplace]}>
<QuestionBirthplace question={qBirthplace} />
</QuestionAnswersProvider>
</QueryClientProvider>,
);
// 1. Open country dropdown first
const countryButton = screen.getByText("انتخاب کشور");
fireEvent.click(countryButton);
// 2. Select Germany (آلمان)
const germanyOption = screen.getByRole("button", { name: "آلمان" });
fireEvent.click(germanyOption);
await waitFor(() => {
expect(screen.queryByRole("dialog")).toBeNull();
});
// Country button should show Germany
expect(screen.getByText("آلمان")).toBeDefined();
// 3. Type city in the input field
const cityInput = screen.getByPlaceholderText("شهر، منطقه یا محله");
fireEvent.change(cityInput, { target: { value: "برلین" } });
// Country button MUST still show Germany and city must show Berlin
expect(screen.getByText("آلمان")).toBeDefined();
expect((cityInput as HTMLInputElement).value).toBe("برلین");
// 4. Type more characters into city field
fireEvent.change(cityInput, { target: { value: "برلین مرکزی" } });
expect(screen.getByText("آلمان")).toBeDefined();
expect((cityInput as HTMLInputElement).value).toBe("برلین مرکزی");
// 5. Change country to France (فرانسه)
const updatedCountryButton = screen.getByText("آلمان");
fireEvent.click(updatedCountryButton);
const franceOption = screen.getByRole("button", { name: "فرانسه" });
fireEvent.click(franceOption);
await waitFor(() => {
expect(screen.queryByRole("dialog")).toBeNull();
});
expect(screen.getByText("فرانسه")).toBeDefined();
expect((cityInput as HTMLInputElement).value).toBe("برلین مرکزی");
});
});