Files
Jyotisha/frontend/tests/birth-place-picker.test.ts
T

149 lines
7.7 KiB
TypeScript

import assert from "node:assert/strict";
import { existsSync, readFileSync } from "node:fs";
import test from "node:test";
import {
birthPlaceDraftFromLevels,
formatBirthPlaceCoordinate,
formatBirthPlaceTimezone,
hasVirtualCityLevel,
findProvinceNode,
resolveBirthPlaceNode,
} from "../src/lib/china-birth-place.ts";
const pickerSource = readFileSync(new URL("../src/components/birth-place-picker.tsx", import.meta.url), "utf8");
const pageSource = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
test("resolves a province, city and district to the district centre", () => {
const resolved = resolveBirthPlaceNode({ provinceCode: "130000", cityCode: "130400", districtCode: "130402" });
assert.ok(resolved);
assert.equal(resolved.label, "河北省 · 邯郸市 · 邯山区");
assert.equal(resolved.placeType, "district");
assert.equal(resolved.node.code, "130402");
assert.equal(resolved.latitude, 36.603196);
assert.equal(resolved.longitude, 114.484989);
});
test("falls back to the city centre when the district is left unnamed", () => {
const resolved = resolveBirthPlaceNode({ provinceCode: "130000", cityCode: "130400", districtCode: "" });
assert.ok(resolved);
assert.equal(resolved.label, "河北省 · 邯郸市");
assert.equal(resolved.placeType, "city");
assert.equal(resolved.node.code, "130400");
assert.equal(resolved.latitude, 36.612273);
});
test("collapses the repeated city level of municipalities and special regions", () => {
assert.equal(hasVirtualCityLevel(findProvinceNode("110000")!), true);
assert.equal(hasVirtualCityLevel(findProvinceNode("130000")!), false);
// The city code may be absent because the picker never shows that level.
const resolved = resolveBirthPlaceNode({ provinceCode: "110000", cityCode: "", districtCode: "110105" });
assert.ok(resolved);
assert.equal(resolved.label, "北京市 · 朝阳区");
assert.equal(resolved.placeType, "district");
});
test("completes at the city for prefecture cities that have no districts", () => {
const resolved = resolveBirthPlaceNode({ provinceCode: "440000", cityCode: "441900", districtCode: "" });
assert.ok(resolved);
assert.equal(resolved.label, "广东省 · 东莞市");
assert.equal(resolved.placeType, "city");
assert.equal(resolved.node.code, "441900");
});
test("completes at the province when neither lower level offers a choice", () => {
const resolved = resolveBirthPlaceNode({ provinceCode: "710000", cityCode: "", districtCode: "" });
assert.ok(resolved);
assert.equal(resolved.label, "台湾省");
assert.equal(resolved.placeType, "province");
});
test("rejects codes that do not belong together", () => {
assert.equal(resolveBirthPlaceNode({ provinceCode: "999999", cityCode: "", districtCode: "" }), null);
assert.equal(resolveBirthPlaceNode({ provinceCode: "130000", cityCode: "440100", districtCode: "" }), null);
assert.equal(resolveBirthPlaceNode({ provinceCode: "130000", cityCode: "130400", districtCode: "110105" }), null);
});
test("re-syncing a saved profile keeps the same draft so the picker cannot clear itself", () => {
const saved = { provinceCode: "130000", cityCode: "130400", districtCode: "" };
const draft = birthPlaceDraftFromLevels(saved);
assert.deepEqual(draft, { provinceCode: "130000", cityCode: "130400", districtCode: "", cityCentre: true });
assert.deepEqual(birthPlaceDraftFromLevels(draft), draft);
// A city without districts is complete without being marked as an unsure centre.
assert.deepEqual(birthPlaceDraftFromLevels({ provinceCode: "440000", cityCode: "441900", districtCode: "" }), {
provinceCode: "440000",
cityCode: "441900",
districtCode: "",
cityCentre: false,
});
// A municipality resumes with its virtual city filled in.
assert.deepEqual(birthPlaceDraftFromLevels({ provinceCode: "110000", cityCode: "", districtCode: "110105" }), {
provinceCode: "110000",
cityCode: "110000-city",
districtCode: "110105",
cityCentre: false,
});
});
test("states coordinates and the resolved offset in plain Chinese", () => {
assert.equal(formatBirthPlaceCoordinate(36.612273, 114.490686), "东经 114.49° · 北纬 36.61°");
assert.equal(formatBirthPlaceTimezone("Asia/Shanghai", 8), "Asia/Shanghai · UTC+8");
assert.equal(formatBirthPlaceTimezone("Asia/Kathmandu", 5.75), "Asia/Kathmandu · UTC+5:45");
assert.equal(formatBirthPlaceTimezone("Asia/Shanghai", null), "Asia/Shanghai");
});
test("the picker offers three cascading levels instead of a free-text search", () => {
assert.equal(existsSync(new URL("../src/components/location-search-combobox.tsx", import.meta.url)), false);
assert.doesNotMatch(pickerSource, /role="combobox"/);
assert.match(pickerSource, /aria-label="出生省份"/);
assert.match(pickerSource, /aria-label="出生城市"/);
assert.match(pickerSource, /aria-label="出生区县"/);
// The level is hidden rather than shown empty when it carries no choice.
assert.match(pickerSource, /\{isChina && province && !virtualCity && \(/);
assert.match(pickerSource, /\{isChina && city && districts\.length > 0 && \(/);
assert.match(pickerSource, /不确定,用市区中心/);
assert.match(pickerSource, /精确到区 \/ 县就足够/);
});
test("timezone is resolved once for the chosen place rather than during browsing", () => {
assert.equal(existsSync(new URL("../src/app/api/locations/timezone/route.ts", import.meta.url)), true);
assert.match(pickerSource, /fetch\("\/api\/locations\/timezone"/);
// Browsing the levels cannot spend a lookup: only a resolved place gets a sync key.
assert.match(pickerSource, /if \(syncKey === "empty"\)/);
assert.match(pickerSource, /: \{ status: "loading" \};/);
// An unresolved zone must not be reported upward as a usable birth place.
assert.match(pickerSource, /setOutcome\(\{ key: syncKey, state: \{ status: "error" \} \}\);\s*onChangeRef\.current\(null\);/);
const service = readFileSync(new URL("../src/lib/birth-location-timezone-service.ts", import.meta.url), "utf8");
assert.match(service, /\/api\/location\/timezone/);
assert.match(service, /timezoneSource: "iana_historical"/);
});
test("mounting a saved birth place neither refetches nor clears the stored location", () => {
// Reporting null on mount would look like a location edit and would drop a
// rectified birth-time candidate just because the profile dialog was opened.
assert.match(pickerSource, /const savedIsCurrent = resolved !== null/);
assert.match(pickerSource, /if \(syncKey === "saved"\) return;/);
assert.match(pickerSource, /if \(saved\.countryCode !== "CN" \|\| saved\.provinceCode \|\| saved\.timezoneId\) onChangeRef\.current\(null\);/);
// A changed birth date invalidates the stored offset, so the zone is resolved again.
assert.match(pickerSource, /mountedMoment === birthMoment/);
});
test("the profile stores administrative codes alongside coordinates and the IANA zone", () => {
assert.match(pageSource, /<BirthPlacePicker/);
assert.match(pageSource, /provinceCode: location\?\.provinceCode \|\| ""/);
assert.match(pageSource, /cityCode: location\?\.cityCode \|\| ""/);
assert.match(pageSource, /districtCode: location\?\.districtCode \|\| ""/);
assert.match(pageSource, /birthPlaceProvider: location\?\.provider \|\| ""/);
assert.match(pageSource, /timezoneSource: location \? "iana_historical" : ""/);
assert.match(pageSource, /birth_place_provider_id:\s*nextProfile\.birthPlaceProviderId/);
assert.match(pageSource, /timezone_id:\s*nextProfile\.timezoneId/);
});
test("profile accepts a selected IANA location before a numeric offset is resolved", () => {
assert.match(pageSource, /profile\.timezoneId\.trim\(\)/);
assert.match(pageSource, /profile\.timezoneOffset === null \|\| Number\.isFinite\(profile\.timezoneOffset\)/);
});