diff --git a/src/shared/hooks/useDictionaries.test.ts b/src/shared/hooks/useDictionaries.test.ts index 47a55cb9..a121b93f 100644 --- a/src/shared/hooks/useDictionaries.test.ts +++ b/src/shared/hooks/useDictionaries.test.ts @@ -1,18 +1,74 @@ -import { describe, it, expect } from "vitest"; +// @vitest-environment jsdom +import { describe, it, expect, vi } from "vitest"; +import { renderHook } from "@testing-library/react"; import { useCityName } from "./useDictionaries.js"; +vi.mock("@modern-js/runtime/router", () => ({ + useParams: () => ({ lang: "ru" }), +})); + +const mockDictionariesState = vi.fn(); +vi.mock("@/shared/dictionaries/useDictionaries.js", () => ({ + useDictionaries: () => mockDictionariesState(), +})); + describe("useCityName", () => { - it("returns the code itself (Phase 2 passthrough)", () => { - expect(useCityName("SVO")).toBe("SVO"); + it("returns the code when dictionaries are still loading", () => { + mockDictionariesState.mockReturnValue({ + dictionaries: null, + loading: true, + error: null, + }); + const { result } = renderHook(() => useCityName("SVO")); + expect(result.current).toBe("SVO"); }); - it("returns the code for any input", () => { - expect(useCityName("LED")).toBe("LED"); - expect(useCityName("MOW")).toBe("MOW"); - expect(useCityName("JFK")).toBe("JFK"); + it("resolves a city code to the localized name", () => { + mockDictionariesState.mockReturnValue({ + dictionaries: { + cityByCode: new Map([["MOW", { name: "Москва" }]]), + airportByCode: new Map(), + }, + loading: false, + error: null, + }); + const { result } = renderHook(() => useCityName("MOW")); + expect(result.current).toBe("Москва"); + }); + + it("resolves an airport code to the localized name", () => { + mockDictionariesState.mockReturnValue({ + dictionaries: { + cityByCode: new Map(), + airportByCode: new Map([["SVO", { name: "Шереметьево" }]]), + }, + loading: false, + error: null, + }); + const { result } = renderHook(() => useCityName("SVO")); + expect(result.current).toBe("Шереметьево"); + }); + + it("falls back to the code for unknown lookups", () => { + mockDictionariesState.mockReturnValue({ + dictionaries: { + cityByCode: new Map(), + airportByCode: new Map(), + }, + loading: false, + error: null, + }); + const { result } = renderHook(() => useCityName("ZZZ")); + expect(result.current).toBe("ZZZ"); }); it("handles empty string", () => { - expect(useCityName("")).toBe(""); + mockDictionariesState.mockReturnValue({ + dictionaries: null, + loading: true, + error: null, + }); + const { result } = renderHook(() => useCityName("")); + expect(result.current).toBe(""); }); }); diff --git a/src/shared/hooks/useDictionaries.ts b/src/shared/hooks/useDictionaries.ts index 760f0eb6..983348dc 100644 --- a/src/shared/hooks/useDictionaries.ts +++ b/src/shared/hooks/useDictionaries.ts @@ -1,22 +1,31 @@ /** * Airport/city dictionary hooks. * - * Phase 2 stub: returns the IATA code itself as the city name. - * The real implementation will call the customer's dictionary API - * (similar to Angular DictionariesService.getCityOrAirport). + * Resolves an IATA code to its human-readable city/airport name using the + * loaded dictionaries. Falls back to the code itself while dictionaries + * are loading or when the code is unknown. * - * TODO: Replace passthrough with actual API call once the dictionary - * endpoint is provided by the customer. The Angular app loads dictionaries - * via networkService.getDictionary('cities') / getDictionary('airports') - * and builds an in-memory Map. + * Angular equivalent: DictionariesService.getCityOrAirport, which walks + * a Map. */ +import { useParams } from "@modern-js/runtime/router"; +import { useDictionaries as useDictionariesState } from "@/shared/dictionaries/useDictionaries.js"; + /** - * Returns the city name for a given IATA airport/city code. - * - * Phase 2: passthrough — returns the code itself. + * Returns the city/airport name for a given IATA code. Uses the locale + * from the current URL. Returns the code itself until dictionaries load + * or if the code isn't in the dictionary (matches Angular's fallback — + * see DictionariesService.getCityOrAirport). */ export function useCityName(code: string): string { - // TODO: Look up from dictionary cache/API when available + const { lang } = useParams<{ lang: string }>(); + const { dictionaries } = useDictionariesState(lang ?? "ru"); + if (!code || !dictionaries) return code; + const upper = code.toUpperCase(); + const city = dictionaries.cityByCode.get(upper); + if (city) return city.name; + const airport = dictionaries.airportByCode.get(upper); + if (airport) return airport.name; return code; }