Files
Jyotisha/frontend/tests/chart-library-normalize-20261027.test.ts
T
jesse-uxandGrok (agent loop) 5289afbbfe fix(home): fill missing saved-person fields before the home page reads them
Accounts that already saved another person crashed on the home page because the chart list left birthTimeClue out. The list entry now starts from an empty profile, then keeps every field the API sent.

Checks: tsc exit 0; lint 0 errors (122 existing warnings); chart-library-normalize 2 pass; related tests 38 pass; npm test 5268 tests, 5235 pass, 33 fail, 0 cancelled. Those 33 are the existing Windows set, including the 13 names in windows-known-failures.txt.

Co-Authored-By: Grok (agent loop)
2026-10-27 14:49:16 +08:00

119 lines
4.5 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { libraryProfileFromSubjectRow } from "../src/lib/chart-subject-write.ts";
import { normalizeChartLibraryApiRecord } from "../src/lib/home-cloud-sync.ts";
import { missingOtherProfileStep } from "../src/lib/home-profile.ts";
import { emptyProfile, type ChartLibraryApiRecord, type ChartLibraryRecord, type Profile } from "../src/lib/home-types.ts";
/** Fictional people. These dates and 杭州 are not anyone's birth data. */
const exactRow = {
name: "林遥",
birth_date: "1990-01-02",
reported_birth_time: "08:30",
birth_time_source: "family_exact",
birth_time_status: "reported",
uncertainty_before_minutes: 0,
uncertainty_after_minutes: 0,
birth_place_label: "杭州",
latitude: 30.27,
longitude: 120.15,
timezone_id: "Asia/Shanghai",
timezone_offset: 8,
relation: "friend",
profile: {},
};
const periodRow = {
name: "小舟",
birth_date: "1992-03-12",
birth_time_source: "period_only",
birth_time_period: "morning",
declared_window_start: "08:00",
declared_window_end: "11:59",
relation: "child",
profile: {},
};
const nameOnlyRow = {
name: "阿宁",
relation: "parent",
profile: {},
};
function apiRecord(id: string, row: Record<string, unknown>, chartRelationship?: Profile["chartRelationship"]): ChartLibraryApiRecord {
const profile = libraryProfileFromSubjectRow(row) as ChartLibraryApiRecord["profile"];
return {
id,
role: "other",
profile: chartRelationship ? { ...profile, chartRelationship } : profile,
updated_at: "2026-10-27T00:00:00.000Z",
};
}
function assertEmptyProfileKeys(profile: Profile) {
for (const key of Object.keys(emptyProfile) as (keyof typeof emptyProfile)[]) {
assert.equal(Object.prototype.hasOwnProperty.call(profile, key), true, String(key));
const sample = emptyProfile[key];
const actual = profile[key];
if (sample === null) {
assert.ok(actual === null || typeof actual === "number", String(key));
} else {
assert.equal(typeof actual, typeof sample, String(key));
}
}
}
/** Same predicate as pendingOther in use-profile-onboarding.ts when no intake hint is stored. */
function pendingOther(rows: readonly ChartLibraryRecord[]) {
return rows.find((row) => {
if (row.role === "self") return false;
return Boolean(row.profile.name.trim()) && missingOtherProfileStep(row.profile) !== null;
});
}
test("a saved person from the chart API keeps every profile key and does not crash onboarding", () => {
const exact = normalizeChartLibraryApiRecord(apiRecord("other-exact", exactRow));
const period = normalizeChartLibraryApiRecord(apiRecord("other-period", periodRow, "family"));
const nameOnly = normalizeChartLibraryApiRecord(apiRecord("other-name", nameOnlyRow));
for (const row of [exact, period, nameOnly]) assertEmptyProfileKeys(row.profile);
assert.equal(missingOtherProfileStep(exact.profile), null);
assert.equal(missingOtherProfileStep(period.profile), "place");
assert.equal(missingOtherProfileStep(nameOnly.profile), "birth");
assert.equal(period.profile.name, "小舟");
assert.equal(period.profile.date, "1992-03-12");
assert.equal(period.profile.birthTimePeriod, "morning");
assert.equal(period.profile.declaredWindowStart, "08:00");
assert.equal(period.profile.declaredWindowEnd, "11:59");
assert.equal(period.profile.relation, "child");
assert.equal(period.profile.chartRelationship, "family");
assert.equal(period.relationship, "family");
assert.equal(period.profile.birthTimeClue, "");
assert.equal(period.profile.rectificationCaseId, "");
assert.equal(exact.profile.name, "林遥");
assert.equal(exact.profile.date, "1990-01-02");
assert.equal(exact.profile.reportedTime, "08:30");
assert.equal(exact.profile.relation, "friend");
assert.equal(exact.profile.birthPlaceLabel, "杭州");
assert.equal(nameOnly.profile.name, "阿宁");
assert.equal(nameOnly.profile.relation, "parent");
assert.equal(nameOnly.profile.chartRelationship, "other");
const found = pendingOther([exact, period, nameOnly]);
assert.equal(found?.id, "other-period");
});
test("the API shape that crashed the home page throws before normalize and not after", () => {
const raw = libraryProfileFromSubjectRow(exactRow) as Profile;
assert.equal("birthTimeClue" in raw, false);
assert.throws(() => missingOtherProfileStep(raw), /length/);
const normalized = normalizeChartLibraryApiRecord(apiRecord("other-exact", exactRow));
assert.doesNotThrow(() => pendingOther([normalized]));
assert.equal(pendingOther([normalized]), undefined);
});