Files
Jyotisha/frontend/tests/birth-time-intake.test.ts
Jesse_Chen 8ed6118b9a fix(web): persist uncertain birth time as a start/end clock window
Users pick a clock range instead of a coarse period plus notes, so
rectification and window consult scan that range instead of a leftover afternoon bucket.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-22 20:48:01 +08:00

463 lines
16 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
applyBirthTimeDraftPatch,
applyPersistedBirthTime,
assistantIntentCopy,
birthTimeDisplayState,
birthTimeDraftReadyHint,
birthTimePersistenceValues,
birthTimeSourceDefaults,
birthTimeSourceOptions,
describeBirthTimeDraft,
formatBirthDate,
isDeclaredBirthProfileComplete,
isBirthTimeReadyForConsultation,
isBirthTimeDraftReady,
normalizePersistedBirthDate,
parseBirthDate,
type BirthTimeDraft,
} from "../src/lib/birth-time-intake-model.ts";
const emptyDraft: BirthTimeDraft = {
date: "1993-04-17",
time: "",
reportedTime: "",
birthTimeSource: "",
birthTimePeriod: "",
declaredWindowStart: "",
declaredWindowEnd: "",
birthTimeClue: "",
uncertaintyBeforeMinutes: null,
uncertaintyAfterMinutes: null,
birthTimeStatus: "",
};
test("birth time intake requires only the fields selected by the source", () => {
const hospital = {
...emptyDraft,
birthTimeSource: "hospital_record",
reportedTime: "08:16",
uncertaintyBeforeMinutes: 2,
uncertaintyAfterMinutes: 2,
} satisfies BirthTimeDraft;
const period = {
...emptyDraft,
birthTimeSource: "period_only",
birthTimePeriod: "evening",
} satisfies BirthTimeDraft;
const customWindow = {
...emptyDraft,
birthTimeSource: "period_only",
declaredWindowStart: "14:30",
declaredWindowEnd: "15:00",
} satisfies BirthTimeDraft;
const incompleteApproximate = {
...emptyDraft,
birthTimeSource: "approximate",
reportedTime: "14:30",
} satisfies BirthTimeDraft;
assert.equal(isBirthTimeDraftReady(hospital), true);
assert.equal(isBirthTimeDraftReady(period), true);
assert.equal(isBirthTimeDraftReady(customWindow), true);
assert.equal(isBirthTimeDraftReady({
...customWindow,
declaredWindowEnd: "14:30",
}), false);
assert.equal(isBirthTimeDraftReady(incompleteApproximate), true);
assert.equal(isBirthTimeDraftReady({ ...emptyDraft, birthTimeSource: "unknown" }), true);
});
test("family exact intake defaults and persists as reported 0/0 without engine confirmation", () => {
assert.deepEqual(birthTimeSourceDefaults.family_exact, {
birthTimePeriod: "",
declaredWindowStart: "",
declaredWindowEnd: "",
birthTimeClue: "",
uncertaintyBeforeMinutes: 0,
uncertaintyAfterMinutes: 0,
});
const declaredExact = {
...emptyDraft,
reportedTime: "05:00",
birthTimeSource: "family_exact",
uncertaintyBeforeMinutes: 0,
uncertaintyAfterMinutes: 0,
birthTimeStatus: "reported",
} satisfies BirthTimeDraft;
assert.equal(isBirthTimeDraftReady(declaredExact), true);
assert.equal(isBirthTimeReadyForConsultation(declaredExact), false);
assert.deepEqual(birthTimePersistenceValues(declaredExact), {
reported_birth_time: "05:00",
birth_time_source: "family_exact",
birth_time_period: null,
declared_window_start: null,
declared_window_end: null,
birth_time_clue: null,
uncertainty_before_minutes: 0,
uncertainty_after_minutes: 0,
});
});
test("an unconfirmed candidate working time remains in rectification onboarding", () => {
const candidate = {
...emptyDraft,
time: "04:53",
birthTimeStatus: "candidate",
} satisfies BirthTimeDraft;
assert.equal(isBirthTimeReadyForConsultation(candidate), false);
assert.equal(isBirthTimeReadyForConsultation({ ...candidate, time: "" }), false);
assert.equal(isBirthTimeReadyForConsultation({ ...candidate, birthTimeStatus: "rectifying" }), false);
assert.equal(isBirthTimeReadyForConsultation({ ...candidate, birthTimeStatus: "confirmed" }), true);
});
test("declared birth data completes onboarding without an active or confirmed minute", () => {
const declaredExact = {
...emptyDraft,
reportedTime: "05:30",
birthTimeSource: "family_exact",
uncertaintyBeforeMinutes: 0,
uncertaintyAfterMinutes: 0,
birthTimeStatus: "reported",
} satisfies BirthTimeDraft;
const declaredPeriod = {
...emptyDraft,
birthTimeSource: "period_only",
birthTimePeriod: "early_morning",
birthTimeStatus: "reported",
} satisfies BirthTimeDraft;
assert.equal(isDeclaredBirthProfileComplete(declaredExact), true);
assert.equal(isBirthTimeReadyForConsultation(declaredExact), false);
assert.equal(isDeclaredBirthProfileComplete(declaredPeriod), true);
assert.equal(isBirthTimeReadyForConsultation(declaredPeriod), false);
});
test("declared completeness validates the actual calendar date, clock, source fields, and bounds", () => {
const exact = {
...emptyDraft,
birthTimeSource: "family_exact",
reportedTime: "05:30",
uncertaintyBeforeMinutes: 0,
uncertaintyAfterMinutes: 0,
} satisfies BirthTimeDraft;
assert.equal(isDeclaredBirthProfileComplete({ ...exact, date: "2000-02-29" }), true);
assert.equal(isDeclaredBirthProfileComplete({ ...exact, date: "2001-02-29" }), false);
assert.equal(isDeclaredBirthProfileComplete({ ...exact, date: "1899-12-31" }), false);
assert.equal(isDeclaredBirthProfileComplete({ ...exact, date: "2101-01-01" }), false);
assert.equal(isDeclaredBirthProfileComplete({ ...exact, reportedTime: "24:00" }), false);
assert.equal(isDeclaredBirthProfileComplete({ ...exact, reportedTime: "5:30" }), false);
assert.equal(isDeclaredBirthProfileComplete({ ...exact, uncertaintyBeforeMinutes: 7 }), false);
assert.equal(isDeclaredBirthProfileComplete({
...emptyDraft,
birthTimeSource: "hospital_record",
reportedTime: "05:30",
}), false);
assert.equal(isDeclaredBirthProfileComplete({
...emptyDraft,
birthTimeSource: "period_only",
birthTimePeriod: "",
}), false);
assert.equal(isDeclaredBirthProfileComplete({
...emptyDraft,
birthTimeSource: "period_only",
birthTimePeriod: "morning",
uncertaintyBeforeMinutes: 30,
}), false);
assert.equal(isDeclaredBirthProfileComplete({
...emptyDraft,
birthTimeSource: "unknown",
reportedTime: "05:30",
}), false);
assert.equal(isDeclaredBirthProfileComplete({
...emptyDraft,
birthTimeSource: "unknown",
birthTimeClue: "x".repeat(241),
}), false);
assert.equal(isDeclaredBirthProfileComplete(exact, {
label: "中国 · 河北省 · 邯郸市",
lat: 36.62,
lon: 114.49,
tz: 8,
}), true);
assert.equal(isDeclaredBirthProfileComplete(exact, {
label: "中国 · 河北省 · 邯郸市 · 峰峰矿区",
lat: 36.420487,
lon: 114.209936,
tz: null,
timezoneId: "Asia/Shanghai",
}), true);
assert.equal(isDeclaredBirthProfileComplete(exact, {
label: "缺少时区的地点",
lat: 36.420487,
lon: 114.209936,
tz: null,
timezoneId: "",
}), false);
assert.equal(isDeclaredBirthProfileComplete(exact, {
label: "越界地点",
lat: 91,
lon: 114.49,
tz: 8,
}), false);
assert.equal(isDeclaredBirthProfileComplete(exact, null), false);
});
test("editing an unconfirmed declaration invalidates an old candidate but preserves confirmed active time", () => {
const candidate = {
...emptyDraft,
time: "05:18",
reportedTime: "05:30",
birthTimeSource: "approximate",
uncertaintyBeforeMinutes: 30,
uncertaintyAfterMinutes: 30,
birthTimeStatus: "candidate",
} satisfies BirthTimeDraft;
assert.deepEqual(applyBirthTimeDraftPatch(candidate, { reportedTime: "06:10" }), {
...candidate,
time: "",
reportedTime: "06:10",
birthTimeStatus: "reported",
});
assert.deepEqual(applyBirthTimeDraftPatch(candidate, {
uncertaintyBeforeMinutes: 60,
uncertaintyAfterMinutes: 60,
}), {
...candidate,
time: "",
uncertaintyBeforeMinutes: 60,
uncertaintyAfterMinutes: 60,
birthTimeStatus: "reported",
});
const confirmed = { ...candidate, birthTimeStatus: "confirmed" } satisfies BirthTimeDraft;
assert.deepEqual(applyBirthTimeDraftPatch(confirmed, { reportedTime: "06:10" }), {
...confirmed,
reportedTime: "06:10",
});
});
test("a persisted candidate working time takes precedence over the reported range", () => {
// Given: rectification saved a candidate minute while preserving the user's original period.
const candidate = {
...emptyDraft,
time: "04:53",
birthTimeSource: "period_only",
birthTimePeriod: "early_morning",
birthTimeStatus: "candidate",
} satisfies BirthTimeDraft;
// When: a profile surface asks what birth-time state to display.
const display = birthTimeDisplayState(candidate);
// Then: the candidate minute is primary and the original clock window remains secondary.
assert.deepEqual(display, {
kind: "candidate",
activeTime: "04:53",
reportedLabel: "04:00—07:59",
});
});
test("birth time declaration payload cannot write deterministic application fields", () => {
const draft = {
...emptyDraft,
time: "14:24",
reportedTime: "14:30",
birthTimeSource: "approximate",
uncertaintyBeforeMinutes: 30,
uncertaintyAfterMinutes: 30,
birthTimeStatus: "confirmed",
} satisfies BirthTimeDraft;
assert.deepEqual(birthTimePersistenceValues(draft), {
reported_birth_time: "14:30",
birth_time_source: "approximate",
birth_time_period: null,
declared_window_start: null,
declared_window_end: null,
birth_time_clue: null,
uncertainty_before_minutes: 30,
uncertainty_after_minutes: 30,
});
});
test("birth time intake describes uncertainty without claiming false precision", () => {
const approximate = {
...emptyDraft,
birthTimeSource: "approximate",
reportedTime: "14:30",
uncertaintyBeforeMinutes: 30,
uncertaintyAfterMinutes: 30,
} satisfies BirthTimeDraft;
assert.equal(describeBirthTimeDraft(approximate), "1993年4月17日,约 14:30");
assert.equal(
describeBirthTimeDraft({
...emptyDraft,
birthTimeSource: "period_only",
declaredWindowStart: "14:30",
declaredWindowEnd: "15:00",
}),
"1993年4月17日,14:30—15:00",
);
assert.equal(
describeBirthTimeDraft({
...emptyDraft,
birthTimeSource: "period_only",
birthTimePeriod: "evening",
}),
"1993年4月17日,18:00—22:59",
);
assert.equal(
assistantIntentCopy("present_saved_candidate_range"),
"目前只能保存候选范围,还没有足够证据应用到具体分钟。",
);
});
test("birth date parsing preserves the local calendar day", () => {
const parsed = parseBirthDate("1993-04-17");
assert.equal(parsed?.getFullYear(), 1993);
assert.equal(parsed?.getMonth(), 3);
assert.equal(parsed?.getDate(), 17);
});
test("birth date values round trip leap days and reject invalid input", () => {
const leapDay = parseBirthDate("2000-02-29");
assert.equal(leapDay === undefined ? undefined : formatBirthDate(leapDay), "2000-02-29");
assert.equal(parseBirthDate(""), undefined);
assert.equal(parseBirthDate("2001-02-29"), undefined);
});
test("persisted birth dates normalize database ISO values without accepting invalid dates", () => {
assert.equal(normalizePersistedBirthDate(new Date("1997-08-08T00:00:00.000Z")), "1997-08-08");
assert.equal(normalizePersistedBirthDate("1997-08-08T00:00:00.000Z"), "1997-08-08");
assert.equal(normalizePersistedBirthDate("1997-08-08"), "1997-08-08");
assert.equal(normalizePersistedBirthDate(new Date("invalid")), "");
assert.equal(normalizePersistedBirthDate("1997-02-30T00:00:00.000Z"), "");
assert.equal(normalizePersistedBirthDate("1997-08-08junk"), "");
});
test("fresh intake copy stays user-facing and explains the next missing field", () => {
assert.equal(
birthTimeSourceOptions.find((option) => option.value === "family_exact")?.hint,
"按医院记录或家人记得的时间填写,精确到分钟",
);
assert.equal(
birthTimeSourceOptions.find((option) => option.value === "period_only")?.hint,
"选择一段开始到结束的时间,或完全不清楚",
);
assert.equal(birthTimeSourceOptions.every((option) => !/引擎确认|初始化填报/.test(option.hint)), true);
assert.equal(birthTimeDraftReadyHint({ ...emptyDraft, date: "" }), "请先选择出生日期");
assert.equal(birthTimeDraftReadyHint(emptyDraft), "请选择你对出生时间了解多少");
assert.equal(birthTimeDraftReadyHint({
...emptyDraft,
birthTimeSource: "family_exact",
uncertaintyBeforeMinutes: 0,
uncertaintyAfterMinutes: 0,
}), "请填写时和分");
assert.equal(birthTimeDraftReadyHint({
...emptyDraft,
birthTimeSource: "period_only",
}), "请选择开始和结束时间");
assert.equal(birthTimeDraftReadyHint({
...emptyDraft,
birthTimeSource: "period_only",
declaredWindowStart: "14:30",
declaredWindowEnd: "14:30",
}), "开始和结束不能是同一分钟");
});
test("fresh intake preserves exact, approximate-period, and unknown-time paths without auto-confirming", () => {
const source = readFileSync(new URL("../src/components/birth-time-intake.tsx", import.meta.url), "utf8");
assert.doesNotMatch(source, /引擎确认/);
assert.deepEqual(
birthTimeSourceOptions.map((option) => option.value),
["family_exact", "period_only"],
);
assert.doesNotMatch(source, /已用于当前排盘/);
assert.doesNotMatch(source, /birthTimePeriodOptions/);
assert.match(source, /birthTimeSource: "unknown"/);
assert.match(source, /选择你记得的开始和结束时间/);
assert.match(source, /完全不清楚,跳过出生时间/);
assert.match(source, /我可以选一段时间范围/);
assert.doesNotMatch(source, /补充描述|请选择最接近的时间范围|我可以描述一个时间范围/);
assert.match(source, /birthTimeStatus: "reported"/);
assert.match(source, /birthTimeConsultationOptionsCopy\(value\)/);
});
test("a custom clock window persists as the scan bounds without a leftover period bucket", () => {
const draft = {
...emptyDraft,
birthTimeSource: "period_only",
birthTimePeriod: "afternoon",
declaredWindowStart: "14:30",
declaredWindowEnd: "15:00",
birthTimeStatus: "reported",
} satisfies BirthTimeDraft;
assert.deepEqual(birthTimePersistenceValues(draft), {
reported_birth_time: null,
birth_time_source: "period_only",
birth_time_period: null,
declared_window_start: "14:30",
declared_window_end: "15:00",
birth_time_clue: null,
uncertainty_before_minutes: null,
uncertainty_after_minutes: null,
});
});
test("a saved declaration adopts the birth-time truth the account write returned", () => {
const exactDeclaration = {
...emptyDraft,
birthTimeSource: "family_exact",
reportedTime: "05:00",
uncertaintyBeforeMinutes: 0,
uncertaintyAfterMinutes: 0,
birthTimeStatus: "reported",
} as const satisfies BirthTimeDraft;
// The server accepts a zero-uncertainty exact time as the active minute, so the
// submitted draft must not keep claiming the time is still only reported.
assert.deepEqual(
applyPersistedBirthTime(exactDeclaration, { status: "accepted", activeTime: "05:00" }),
{ ...exactDeclaration, time: "05:00", birthTimeStatus: "accepted" },
);
assert.deepEqual(
applyPersistedBirthTime({ ...exactDeclaration, time: "05:00", birthTimeStatus: "accepted" }, {
status: "reported",
activeTime: null,
}),
exactDeclaration,
);
assert.deepEqual(
applyPersistedBirthTime(exactDeclaration, { status: "accepted", activeTime: "05:00:00" }),
{ ...exactDeclaration, time: "05:00", birthTimeStatus: "accepted" },
);
// A response without usable birth-time truth must leave the draft untouched.
for (const applied of [
undefined,
null,
{},
{ status: "unknown_status", activeTime: "05:00" },
{ status: 7, activeTime: "05:00" },
]) {
assert.deepEqual(applyPersistedBirthTime(exactDeclaration, applied), exactDeclaration);
}
assert.deepEqual(
applyPersistedBirthTime(exactDeclaration, { status: "accepted", activeTime: "5:0" }),
{ ...exactDeclaration, time: "", birthTimeStatus: "accepted" },
);
});