Files
Jyotisha/frontend/tests/birth-time-consultation-consent.test.ts
T
Jesse_Chen 54269fcfcc
Independent Staging Quality Gate / validate (push) Has been cancelled
Independent Staging Quality Gate / publish (push) Has been cancelled
refactor(chat): extract home helpers, chart library, and starter surfaces
page.tsx still owns the chat main chain, but the first product surfaces now
live in their own modules so later splits can land without editing the 4k-line
Home. Source-lock tests follow the moved tokens; the orphan user-data contract
is aligned and added to the quick gate.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 22:58:02 +08:00

249 lines
10 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import {
canUseUnverifiedBirthTime,
birthTimeConsultationOptionsCopy,
consultationModeForSession,
createLatestAccountRequestGuard,
createBirthTimeConsultationConsentState,
grantBirthTimeConsultationConsent,
hasBirthTimeConsultationConsent,
parseRectificationPriceCredits,
requiresBirthTimeConsent,
resolveBirthTimeConsultationRoute,
resolveRectificationCardAction,
unverifiedBirthTime,
} from "../src/lib/birth-time-consultation-consent.ts";
import { homeSurface } from "./home-surface.ts";
import type { BirthTimeDraft } from "../src/lib/birth-time-intake-model.ts";
const reportedExactTime = {
date: "1997-08-08",
time: "",
reportedTime: "05:30",
birthTimeSource: "family_exact",
birthTimePeriod: "",
declaredWindowStart: "",
declaredWindowEnd: "",
birthTimeClue: "",
uncertaintyBeforeMinutes: 10,
uncertaintyAfterMinutes: 10,
birthTimeStatus: "reported",
} satisfies BirthTimeDraft;
test("an unverified concrete time is usable immediately without a consent gate", () => {
const initial = createBirthTimeConsultationConsentState();
assert.equal(canUseUnverifiedBirthTime(reportedExactTime), true);
assert.equal(requiresBirthTimeConsent(reportedExactTime), false);
assert.equal(hasBirthTimeConsultationConsent(initial, "chat-a"), false);
assert.deepEqual(resolveBirthTimeConsultationRoute(reportedExactTime, initial, "chat-a"), {
kind: "consult",
mode: "unverified_birth_time",
time: "05:30",
});
const consented = grantBirthTimeConsultationConsent(initial, "chat-a");
assert.equal(hasBirthTimeConsultationConsent(consented, "chat-a"), true);
assert.equal(hasBirthTimeConsultationConsent(consented, "chat-b"), false);
assert.equal(hasBirthTimeConsultationConsent(initial, "chat-a"), false);
});
test("period-only and unknown declarations never pretend to provide an unverified minute", () => {
const periodOnly = {
...reportedExactTime,
reportedTime: "",
birthTimeSource: "period_only",
birthTimePeriod: "early_morning",
} satisfies BirthTimeDraft;
const unknown = {
...reportedExactTime,
reportedTime: "",
birthTimeSource: "unknown",
uncertaintyBeforeMinutes: null,
uncertaintyAfterMinutes: null,
} satisfies BirthTimeDraft;
assert.equal(canUseUnverifiedBirthTime(periodOnly), false);
assert.equal(canUseUnverifiedBirthTime(unknown), false);
assert.equal(requiresBirthTimeConsent(periodOnly), false);
assert.equal(requiresBirthTimeConsent(unknown), false);
assert.match(birthTimeConsultationOptionsCopy(periodOnly), /日期与地点.*可选增强/);
assert.match(birthTimeConsultationOptionsCopy(unknown), /日期与地点.*可选增强/);
assert.doesNotMatch(birthTimeConsultationOptionsCopy(periodOnly), /使用.*原始填报时间|具体原始时间/);
assert.doesNotMatch(birthTimeConsultationOptionsCopy(unknown), /使用.*原始填报时间|具体原始时间/);
assert.match(birthTimeConsultationOptionsCopy(reportedExactTime), /直接使用你填报的时间.*可选增强/);
});
test("the current reported minute wins over an old candidate and never falls back to it", () => {
const editedCandidate = {
...reportedExactTime,
time: "05:18",
reportedTime: "06:10",
birthTimeStatus: "candidate",
} satisfies BirthTimeDraft;
const periodCandidate = {
...editedCandidate,
reportedTime: "",
birthTimeSource: "period_only",
birthTimePeriod: "early_morning",
} satisfies BirthTimeDraft;
const missingReportedCandidate = {
...editedCandidate,
reportedTime: "",
birthTimeSource: "approximate",
} satisfies BirthTimeDraft;
assert.equal(unverifiedBirthTime(editedCandidate), "06:10");
assert.equal(unverifiedBirthTime(periodCandidate), null);
assert.equal(unverifiedBirthTime(missingReportedCandidate), null);
const page = homeSurface;
assert.match(page, /persistedReportedTime \|\| \(source === "legacy_import" \? time : ""\)/);
});
test("period-only users enter minute-free consultation directly without a blocking choice", () => {
const periodOnly = {
...reportedExactTime,
reportedTime: "",
birthTimeSource: "period_only",
birthTimePeriod: "early_morning",
} satisfies BirthTimeDraft;
const initial = createBirthTimeConsultationConsentState();
assert.deepEqual(resolveBirthTimeConsultationRoute(periodOnly, initial, "chat-a"), {
kind: "consult",
mode: "declared_birth_window",
time: null,
});
const general = grantBirthTimeConsultationConsent(initial, "chat-a", "declared_birth_window");
assert.equal(consultationModeForSession(general, "chat-a"), "declared_birth_window");
assert.equal(consultationModeForSession(general, "chat-b"), null);
assert.deepEqual(resolveBirthTimeConsultationRoute(periodOnly, general, "chat-a"), {
kind: "consult",
mode: "declared_birth_window",
time: null,
});
assert.deepEqual(resolveBirthTimeConsultationRoute(periodOnly, general, "chat-b"), {
kind: "consult",
mode: "declared_birth_window",
time: null,
});
});
test("unverified consent resolves to a chart request and confirmed profiles need no consent", () => {
const consented = grantBirthTimeConsultationConsent(
createBirthTimeConsultationConsentState(),
"chat-a",
"unverified_birth_time",
);
assert.deepEqual(resolveBirthTimeConsultationRoute(reportedExactTime, consented, "chat-a"), {
kind: "consult",
mode: "unverified_birth_time",
time: "05:30",
});
assert.deepEqual(resolveBirthTimeConsultationRoute({
...reportedExactTime,
time: "05:28",
birthTimeStatus: "confirmed",
}, createBirthTimeConsultationConsentState(), "chat-b"), {
kind: "consult",
mode: "verified_chart",
time: "05:28",
});
});
test("a stale minute-free preference cannot suppress the user's current reported time", () => {
const general = grantBirthTimeConsultationConsent(
createBirthTimeConsultationConsentState(),
"chat-a",
"general_no_birth_time",
);
assert.deepEqual(resolveBirthTimeConsultationRoute(reportedExactTime, general, "chat-a"), {
kind: "consult",
mode: "unverified_birth_time",
time: "05:30",
});
});
test("confirmed time does not request unverified-use consent", () => {
const confirmed = {
...reportedExactTime,
time: "05:28",
birthTimeStatus: "confirmed",
} satisfies BirthTimeDraft;
assert.equal(canUseUnverifiedBirthTime(confirmed), false);
assert.equal(requiresBirthTimeConsent(confirmed), false);
});
test("card action resumes an existing Agentic session and otherwise starts or revises", () => {
assert.equal(resolveRectificationCardAction({ hasRectificationSession: false, hasUsableBirthTime: false }), "start");
assert.equal(resolveRectificationCardAction({ hasRectificationSession: true, hasUsableBirthTime: true }), "resume");
assert.equal(resolveRectificationCardAction({ hasRectificationSession: false, hasUsableBirthTime: true }), "revise");
});
test("fixed rectification price uses a checked default and rejects invalid configured values", () => {
assert.equal(parseRectificationPriceCredits(undefined), 1);
assert.equal(parseRectificationPriceCredits(" 7 "), 7);
for (const invalid of ["", "0", "101", "1.5", "1e1", "free"]) {
assert.throws(() => parseRectificationPriceCredits(invalid), /RECTIFICATION_PRICE_CREDITS/);
}
});
test("account refresh identities reject an older response after a newer case request starts", () => {
const guard = createLatestAccountRequestGuard();
const oldCaseRequest = guard.begin();
const newCaseRequest = guard.begin();
assert.equal(guard.isCurrent(oldCaseRequest), false);
assert.equal(guard.isCurrent(newCaseRequest), true);
});
test("unverified birth time no longer emits a modal or toast gate", () => {
const page = homeSurface;
assert.doesNotMatch(page, /<BirthTimeSoftNotice|setBirthTimeSoftNotice/);
assert.doesNotMatch(page, /role="alertdialog"[\s\S]{0,300}出生时间还没有完成校正/);
});
test("birth time intake starts with exact or uncertain choices and keeps rectification optional", () => {
const intake = readFileSync(new URL("../src/components/birth-time-intake.tsx", import.meta.url), "utf8");
const model = readFileSync(new URL("../src/lib/birth-time-intake-model.ts", import.meta.url), "utf8");
assert.match(model, /我知道准确出生时间/);
assert.match(model, /我不确定准确时间/);
assert.match(intake, /你对出生时间了解多少?/);
assert.match(intake, /不用猜具体分钟/);
assert.match(intake, /aria-label=\{ariaLabel\}/);
assert.match(intake, /ariaLabel = "选择出生时间"/);
assert.doesNotMatch(intake, /source === "family_exact" \|\| source === "approximate"/);
assert.match(intake, /选择你记得的开始和结束时间/);
assert.match(intake, /完全不清楚,跳过出生时间/);
assert.match(intake, /生时校正以后需要时再做/);
assert.doesNotMatch(intake, /请选择最接近的时间范围|补充描述/);
});
test("homepage and profile result copy use the source-aware consultation options", () => {
const page = homeSurface;
const intake = readFileSync(new URL("../src/components/birth-time-intake.tsx", import.meta.url), "utf8");
assert.match(page, /birthTimeConsultationOptionsCopy\(savedProfile\)/);
assert.doesNotMatch(page, /birthTimeConsultationOptionsCopy\(profile\)/);
assert.match(intake, /birthTimeConsultationOptionsCopy\(value\)/);
});
test("onboarding form steps hide the dead composer and keep source choices in a single column", () => {
const page = homeSurface;
const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
assert.match(page, /const onboardingFormActive = !profileComplete && onboardingStep !== "name"/);
assert.match(page, /!rectificationSurfaceOpen && !onboardingFormActive && <div className=\{`composer-wrap/);
assert.match(page, /birthTimeDraftReadyHint\(profileDraft\)/);
assert.match(css, /\.birth-time-source-list \{ grid-template-columns: 1fr/);
assert.doesNotMatch(css, /\.birth-time-source-list \{ grid-template-columns: repeat\(2/);
assert.doesNotMatch(css, /\.birth-time-source-option input \{[^}]*clip-path: inset\(50%\)/);
});