Resolve popular-request codes to city/airport names

useCityName was a 'phase 2 stub' that returned the IATA code
unchanged, so the popular-requests panel read 'Маршрут: SVO - LED'
instead of 'Маршрут: Шереметьево - Санкт-Петербург'. Rewire the hook
to read the current locale and look up cityByCode / airportByCode
from the loaded dictionaries, falling back to the code only while
dictionaries are loading or for unknown codes (matches Angular's
DictionariesService.getCityOrAirport).

Tests expanded to cover the city/airport resolutions and the
loading / unknown fallback paths (5 total, 1258 suite green).
This commit is contained in:
2026-04-18 11:02:12 +03:00
parent 51f997e642
commit db163f5645
2 changed files with 84 additions and 19 deletions
+64 -8
View File
@@ -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("");
});
});
+20 -11
View File
@@ -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<code, CityModel>.
* Angular equivalent: DictionariesService.getCityOrAirport, which walks
* a Map<code, CityModel | AirportModel>.
*/
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;
}