From a61457bc90d4f2552dc7824d62345c037bdac382 Mon Sep 17 00:00:00 2001 From: gnezim Date: Fri, 17 Apr 2026 08:35:01 +0300 Subject: [PATCH] Port Angular CityCategoryService to feature-local cityCategory module --- src/features/flights-map/cityCategory.test.ts | 66 +++++++++++++++++++ src/features/flights-map/cityCategory.ts | 43 ++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 src/features/flights-map/cityCategory.test.ts create mode 100644 src/features/flights-map/cityCategory.ts diff --git a/src/features/flights-map/cityCategory.test.ts b/src/features/flights-map/cityCategory.test.ts new file mode 100644 index 00000000..8451a2be --- /dev/null +++ b/src/features/flights-map/cityCategory.test.ts @@ -0,0 +1,66 @@ +import { describe, it, expect } from "vitest"; +import { + POPULATION_1KK, + POPULATION_500K, + POPULATION_100K, + POPULAR_RESORTS, + getCityZoomLevel, +} from "./cityCategory.js"; + +describe("cityCategory — set sizes", () => { + it("POPULATION_1KK has 30 entries (Angular parity)", () => { + expect(POPULATION_1KK.size).toBe(30); + }); + + it("POPULATION_500K has 23 entries", () => { + expect(POPULATION_500K.size).toBe(23); + }); + + it("POPULATION_100K has 39 entries", () => { + expect(POPULATION_100K.size).toBe(39); + }); + + it("POPULAR_RESORTS has 15 entries", () => { + expect(POPULAR_RESORTS.size).toBe(15); + }); +}); + +describe("cityCategory — membership spot checks", () => { + it("MOW is in POPULATION_1KK", () => { + expect(POPULATION_1KK.has("MOW")).toBe(true); + }); + + it("DXB is in POPULAR_RESORTS", () => { + expect(POPULAR_RESORTS.has("DXB")).toBe(true); + }); + + it("TJM is in POPULATION_500K", () => { + expect(POPULATION_500K.has("TJM")).toBe(true); + }); + + it("MMK is in POPULATION_100K", () => { + expect(POPULATION_100K.has("MMK")).toBe(true); + }); +}); + +describe("getCityZoomLevel", () => { + it("returns 2 for a 1kk-population city", () => { + expect(getCityZoomLevel("MOW")).toBe(2); + }); + + it("returns 2 for a popular resort", () => { + expect(getCityZoomLevel("DXB")).toBe(2); + }); + + it("returns 5 for a 500k-population city", () => { + expect(getCityZoomLevel("TJM")).toBe(5); + }); + + it("returns 6 for a 100k-population city", () => { + expect(getCityZoomLevel("MMK")).toBe(6); + }); + + it("returns 6 for an unknown city code", () => { + expect(getCityZoomLevel("XXX")).toBe(6); + }); +}); diff --git a/src/features/flights-map/cityCategory.ts b/src/features/flights-map/cityCategory.ts new file mode 100644 index 00000000..6465beeb --- /dev/null +++ b/src/features/flights-map/cityCategory.ts @@ -0,0 +1,43 @@ +/** + * City category data + zoom-level lookup. + * + * Direct port of Angular `CityCategoryService`. Cities are partitioned into + * four population/resort tiers; `getCityZoomLevel` maps a city code to the + * minimum map zoom at which its marker should render. + * + * Keep the Sets exported for regression tests — editing them should be an + * intentional product decision. + */ + +export const POPULATION_1KK: ReadonlySet = new Set([ + "QIN", "THR", "FRU", "KZN", "LED", "IST", "HKG", "PEE", "UFA", + "OVB", "CEK", "CAI", "SVX", "EVN", "DEL", "MSQ", + "KJA", "KUF", "NQZ", "OMS", "ALA", "SHA", "VOG", + "BAK", "BKK", "BJS", "MOW", "GOJ", "VVO", "KHV", +]); + +export const POPULATION_500K: ReadonlySet = new Set([ + "TJM", "TOF", "SKD", "ULY", "PEZ", "MCX", "KEJ", + "IJK", "REN", "ASF", "NOZ", "BAX", "RTW", "DOH", + "AUH", "SSH", "TAS", "CIT", "SGN", "CAN", "HRB", + "VRA", "KGF", +]); + +export const POPULATION_100K: ReadonlySet = new Set([ + "MMK", "GDX", "SGC", "DYR", "ESL", "OSS", "KVD", "KVK", "GUW", "SCW", + "SCO", "NJC", "NBC", "UGC", "UUD", "YKS", "MJZ", "SKX", "STW", "KSN", + "KGD", "HMA", "HTA", "RGK", "GRV", "ABA", "PKC", "NAL", "MQF", "MRV", + "OSW", "ARH", "UUS", "NUX", "BHK", "PYJ", "CSY", "BQS", "DLM", +]); + +export const POPULAR_RESORTS: ReadonlySet = new Set([ + "AER", "AYT", "BJV", "CMB", "DPS", "DXB", "GOI", + "HAV", "HKT", "HRG", "MLE", "NHA", "SEZ", "SYX", "IKT", +]); + +export function getCityZoomLevel(code: string): number { + if (POPULATION_1KK.has(code) || POPULAR_RESORTS.has(code)) return 2; + if (POPULATION_500K.has(code)) return 5; + if (POPULATION_100K.has(code)) return 6; + return 6; +}