From c2fad426047f87eb3137e3e0b511f4be90fd9b7a Mon Sep 17 00:00:00 2001 From: mortezaei Date: Fri, 21 Aug 2026 21:25:01 +0330 Subject: [PATCH] fix(birthplace): guarantee strict country and city independence with test suite --- .../Componentes/question-birthplace.test.ts | 70 +++++++++++++++++++ .../Componentes/question-birthplace.tsx | 27 ++++--- 2 files changed, 86 insertions(+), 11 deletions(-) create mode 100644 src/components/Componentes/question-birthplace.test.ts diff --git a/src/components/Componentes/question-birthplace.test.ts b/src/components/Componentes/question-birthplace.test.ts new file mode 100644 index 0000000..60cfccd --- /dev/null +++ b/src/components/Componentes/question-birthplace.test.ts @@ -0,0 +1,70 @@ +import { describe, expect, it } from "vitest"; +import { parseValue } from "./question-birthplace"; + +describe("QuestionBirthplace parseValue", () => { + it("parses empty and null values safely", () => { + expect(parseValue(null)).toEqual({ country: "", city: "" }); + expect(parseValue(undefined)).toEqual({ country: "", city: "" }); + expect(parseValue("")).toEqual({ country: "", city: "" }); + }); + + it("parses structured objects", () => { + expect(parseValue({ country: "Iran", city: "Tehran" })).toEqual({ + country: "Iran", + city: "Tehran", + }); + }); + + it("parses standard 'City, Country' strings", () => { + expect(parseValue("Tehran, Iran")).toEqual({ + city: "Tehran", + country: "Iran", + }); + expect(parseValue("شیراز, ایران")).toEqual({ + city: "شیراز", + country: "ایران", + }); + }); + + it("never swaps city and country even when city equals country name", () => { + // City typed as 'albania' and country selected as 'Afghanistan' + expect(parseValue("albania, Afghanistan")).toEqual({ + city: "albania", + country: "Afghanistan", + }); + + // City typed as 'Albania' and country selected as 'Albania' + expect(parseValue("Albania, Albania")).toEqual({ + city: "Albania", + country: "Albania", + }); + + // City typed as 'Georgia' and country selected as 'United States (US)' + expect(parseValue("Georgia, United States (US)")).toEqual({ + city: "Georgia", + country: "United States (US)", + }); + }); + + it("parses single country string", () => { + expect(parseValue("Iran")).toEqual({ + country: "Iran", + city: "", + }); + expect(parseValue("ایران")).toEqual({ + country: "ایران", + city: "", + }); + expect(parseValue("Afghanistan")).toEqual({ + country: "Afghanistan", + city: "", + }); + }); + + it("parses single custom city string without matching country", () => { + expect(parseValue("Rey")).toEqual({ + country: "", + city: "Rey", + }); + }); +}); diff --git a/src/components/Componentes/question-birthplace.tsx b/src/components/Componentes/question-birthplace.tsx index dfc8f4f..a8ed693 100644 --- a/src/components/Componentes/question-birthplace.tsx +++ b/src/components/Componentes/question-birthplace.tsx @@ -24,7 +24,7 @@ type BirthplaceValue = { city?: string; }; -function parseValue(rawValue: unknown): { country: string; city: string } { +export function parseValue(rawValue: unknown): { country: string; city: string } { if (!rawValue) return { country: "", city: "" }; if (typeof rawValue === "object" && rawValue !== null) { @@ -105,10 +105,11 @@ export function QuestionBirthplace({ const storedRegion = isResidence ? getStoredUserGeoRegion() : null; const initial = parseValue(rawValue); - const localizedInitialCountry = resolveCountryName( - initial.country || storedRegion?.country, - locale, - ); + const localizedInitialCountry = + resolveCountryName(initial.country || storedRegion?.country, locale) || + initial.country || + storedRegion?.country || + ""; const initialCity = initial.city || storedRegion?.city || ""; const initialLoc = localizedInitialCountry || initialCity @@ -193,7 +194,7 @@ export function QuestionBirthplace({ // If there is already a saved answer and we are not forcing, display it if (rawValue && !force) { const parsed = parseValue(rawValue); - const cName = resolveCountryName(parsed.country, locale); + const cName = resolveCountryName(parsed.country, locale) || parsed.country; if (cName || parsed.city) { const loc = [parsed.city, cName].filter(Boolean).join(", "); if (cName) setSelectedCountry(cName); @@ -254,12 +255,16 @@ export function QuestionBirthplace({ } setMode("manual"); const parsed = parseValue(rawValue); - const resolvedC = resolveCountryName(selectedCountry || parsed.country, locale); - const country = resolvedC || selectedCountry || parsed.country; + const resolvedC = + resolveCountryName(selectedCountry || parsed.country, locale) || + selectedCountry || + parsed.country; + const country = resolvedC; const city = cityInput || parsed.city; setSelectedCountry(country); setCityInput(city); updateAnswers(country, city); + setDetectedLocation([city, country].filter(Boolean).join(", ")); }; // Synchronize state ONLY if rawValue changes externally (e.g. draft fetch or reset) @@ -268,11 +273,11 @@ export function QuestionBirthplace({ return; } const updated = parseValue(rawValue); - const resolvedC = resolveCountryName(updated.country, locale); - if (resolvedC && resolvedC !== selectedCountry) { + const resolvedC = resolveCountryName(updated.country, locale) || updated.country; + if (resolvedC !== selectedCountry) { setSelectedCountry(resolvedC); } - if (updated.city && updated.city.trim() !== cityInput.trim()) { + if (updated.city !== cityInput) { setCityInput(updated.city); } if (resolvedC || updated.city) {