Port Angular CityCategoryService to feature-local cityCategory module

This commit is contained in:
2026-04-17 08:35:01 +03:00
parent 43f251e177
commit a61457bc90
2 changed files with 109 additions and 0 deletions
@@ -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);
});
});
+43
View File
@@ -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<string> = 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<string> = 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<string> = 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<string> = 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;
}