Files
Jyotisha/frontend/tests/onboarding-presentation.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

79 lines
4.2 KiB
TypeScript

import assert from "node:assert/strict";
import test from "node:test";
import { readFileSync } from "node:fs";
import {
createOnboardingFallbackGreeting,
createStartGreeting,
createStartGreetingParts,
isCurrentOnboardingRequest,
onboardingProfileFingerprint,
onboardingRequestIdentity,
} from "../src/lib/onboarding-client.ts";
import { homeSurface } from "./home-surface.ts";
const completeProfile = {
name: "林遥", date: "1990-06-15", time: "12:30", reportedTime: "12:30",
birthTimeSource: "hospital_record", birthTimePeriod: "", declaredWindowStart: "", declaredWindowEnd: "", birthTimeClue: "出生证明", birthTimeStatus: "confirmed",
uncertaintyBeforeMinutes: 0, uncertaintyAfterMinutes: 0, rectificationCaseId: "", countryCode: "CN", provinceCode: "110000", cityCode: "110000-city", districtCode: "110101",
} as const;
test("rejects stale A presentation and derives terminal and fallback greetings from profile B", () => {
// Given: one account starts onboarding for a complete persisted profile A.
const firstIdentity = onboardingRequestIdentity("account-1", onboardingProfileFingerprint(completeProfile));
// When: the name changes before that request completes and profile B is presented.
const changedProfile = { ...completeProfile, name: "周宁" };
const currentIdentity = onboardingRequestIdentity("account-1", onboardingProfileFingerprint(changedProfile));
const terminalGreeting = createStartGreeting(changedProfile.name, new Date(2026, 6, 19, 8), 0);
const fallbackGreeting = createOnboardingFallbackGreeting(changedProfile.name);
// Then: stale work is rejected and neither presentation path can retain A's name.
assert.notEqual(currentIdentity, firstIdentity);
assert.equal(isCurrentOnboardingRequest(currentIdentity, firstIdentity), false);
for (const greeting of [terminalGreeting, fallbackGreeting]) {
assert.match(greeting, /周宁/);
assert.doesNotMatch(greeting, /林遥/);
}
assert.equal(fallbackGreeting, "周宁,从你此刻最关心的问题开始吧。");
});
test("the starter hero splits one time-aware greeting instead of drawing from a separate pool", () => {
// Given: the same daypart variant, asked for as parts and as one line.
const noon = new Date(2026, 6, 19, 12);
const parts = createStartGreetingParts("周宁", noon, 0);
const whole = createStartGreeting("周宁", noon, 0);
// Then: the salutation carries the name and the question carries no name, and they recompose exactly.
assert.equal(parts.salutation, "中午好,周宁。");
assert.equal(parts.question, "现在最想理清哪件事?");
assert.equal(`${parts.salutation}${parts.question}`, whole);
assert.doesNotMatch(parts.question, /周宁/);
// And: the extremes stay inside the variant list rather than falling off the end.
assert.equal(createStartGreetingParts("周宁", noon, 1).question, "事业、关系或选择,想先聊哪个?");
assert.equal(createStartGreetingParts("周宁", noon, 0.999999).question, "事业、关系或选择,想先聊哪个?");
// And: every daypart is reachable, so the heading is not one fixed line in disguise.
const questions = new Set([0, 8, 12, 16, 20, 23].flatMap((hour) =>
[0, 1 / 3, 2 / 3].map((selection) =>
createStartGreetingParts("周宁", new Date(2026, 6, 19, hour), selection).question)));
assert.ok(questions.size >= 12);
});
test("the starter hero stays at a salutation and a question", () => {
const source = homeSurface;
// Given: the hero heading is the question half of the time-aware greeting.
assert.match(source, /<p className="starter-greeting">\{starterGreeting\.salutation\}<\/p>/);
assert.match(source, /<h1 id="starter-heading">\{starterGreeting\.question\}<\/h1>/);
// Then: no third line repeats the welcome the two lines above already carry.
assert.doesNotMatch(source, /starter-hero-note/);
const styles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
assert.doesNotMatch(styles, /starter-hero-note/);
// And: the separate static heading pool is gone for good.
assert.doesNotMatch(source, /starterPrompt|createStarterPrompt|greetingForHour/);
assert.doesNotMatch(source, /greeting: createStartGreeting\(/);
});