Files
Jyotisha/frontend/tests/conversational-rectification-component.test.ts
T

146 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { rectificationV4ChatMessages } from "../src/components/rectification-v4-panel.tsx";
import type { RectificationV4ApiResponse } from "../src/lib/rectification-v4/contracts.ts";
const id = "00000000-0000-4000-8000-000000000901";
const questionId = "00000000-0000-4000-8000-000000000902";
const now = "2026-07-27T00:00:00.000Z";
function response(overrides: Record<string, unknown> = {}): RectificationV4ApiResponse {
return {
case: {
id,
userId: "00000000-0000-4000-8000-000000000900",
protocol: "rectification-evidence-v4",
version: 2,
status: "awaiting_answer",
phase: "collecting_evidence",
calculationSpec: {
version: "rectification-calculation-spec-v4",
birthDate: "1997-08-08",
candidateRange: { start: "04:50", end: "05:10" },
latitude: 36.419,
longitude: 114.213,
timezoneOffsetHours: 8,
ayanamsa: "lahiri",
nodeMode: "mean",
minuteStep: 1,
},
calculationSpecHash: "a".repeat(64),
evidenceSetHash: "b".repeat(64),
currentQuestion: {
id: questionId,
domain: "relocation",
targetEventId: null,
prompt: "你提到复读后再次毕业,这段连续变化很清楚。后来还有哪一次环境变化让你印象很深?",
recallCost: "low",
reason: "根据对话选择下一条高信息量追问。",
},
latestSnapshot: null,
acceptedRange: null,
createdAt: now,
updatedAt: now,
...overrides,
},
job: null,
events: [],
turns: [{
id: "00000000-0000-4000-8000-000000000903",
caseId: id,
caseVersion: 1,
questionId: "00000000-0000-4000-8000-000000000904",
questionDomain: "other",
questionTargetEventId: null,
question: "请从你最确定的一段人生经历开始说。",
answer: "2015年毕业后复读,2016年再次毕业。",
modelId: "gpt-5.5",
actionId: "00000000-0000-4000-8000-000000000905",
createdAt: now,
}],
};
}
test("v4 rectification reuses the ordinary session message list, composer, and model selector", () => {
const component = readFileSync(new URL("../src/components/rectification-v4-panel.tsx", import.meta.url), "utf8");
const wrapper = readFileSync(new URL("../src/components/conversational-birth-time-rectification.tsx", import.meta.url), "utf8");
const page = readFileSync(new URL("../src/app/page.tsx", import.meta.url), "utf8");
const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
assert.match(
component,
/<>\s*<section className="conversation"[\s\S]*?<\/section>\s*\{showControls && \(\s*<div className="composer-wrap"/,
);
assert.match(component, /className="message-list"/);
assert.match(component, /<ChatMessageRow/);
assert.match(component, /className="composer-wrap"/);
assert.match(component, /className="composer"/);
assert.match(component, /<Textarea/);
assert.match(component, /<ModelSelector/);
assert.match(component, /controller\.answer\(answer, props\.selectedModelId \|\| null\)/);
assert.match(wrapper, /<RectificationV4Panel \{\.\.\.props\} \/>/);
assert.match(page, /\{!rectificationSurfaceOpen && \(\s*<div className=\{`conversation/);
assert.match(page, /\{rectificationSurfaceOpen && \(\s*<ConversationalBirthTimeRectification/);
assert.doesNotMatch(page, /is-rectification/);
assert.doesNotMatch(component, /rectification-chat/);
assert.doesNotMatch(css, /\.rectification-chat/);
});
test("turn history and the context-aware next question render as one chat timeline", () => {
const messages = rectificationV4ChatMessages(response(), false);
assert.deepEqual(messages.map(({ role, text }) => [role, text]), [
["assistant", "请从你最确定的一段人生经历开始说。"],
["user", "2015年毕业后复读,2016年再次毕业。"],
["assistant", "你提到复读后再次毕业,这段连续变化很清楚。后来还有哪一次环境变化让你印象很深?"],
]);
});
test("processing is an ordinary assistant thinking message after the saved answer", () => {
const messages = rectificationV4ChatMessages(response({ currentQuestion: null, status: "processing" }), true);
assert.equal(messages.at(-1)?.role, "assistant");
assert.equal(messages.at(-1)?.state, "thinking");
});
test("range messages never claim an exact confirmed birth minute", () => {
const rangeReady = response({
status: "range_ready",
phase: "complete",
currentQuestion: null,
latestSnapshot: {
clusters: [{ startTime: "05:26", endTime: "05:30" }],
canAcceptRange: true,
},
});
const candidateText = rectificationV4ChatMessages(rangeReady, false).at(-1)?.text ?? "";
assert.match(candidateText, /05:2605:30/);
assert.match(candidateText, /候选范围/);
assert.match(candidateText, /不是已确认的出生分钟/);
const acceptedText = rectificationV4ChatMessages(response({
status: "range_ready",
phase: "complete",
currentQuestion: null,
acceptedRange: { start: "05:26", end: "05:30" },
}), false).at(-1)?.text ?? "";
assert.match(acceptedText, /原出生时间没有被自动改写/);
});
test("the retired questionnaire panel and fixed-domain controls are absent", () => {
const component = readFileSync(new URL("../src/components/rectification-v4-panel.tsx", import.meta.url), "utf8");
const wrapper = readFileSync(new URL("../src/components/conversational-birth-time-rectification.tsx", import.meta.url), "utf8");
const css = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
for (const retired of [
"生时校正 · 事件证据法",
"rectification-v4-evidence-grid",
"支持这个范围的经历",
"ConversationalRectificationSurface",
]) {
assert.doesNotMatch(component, new RegExp(retired));
assert.doesNotMatch(wrapper, new RegExp(retired));
}
assert.doesNotMatch(css, /Birth-time rectification V4|\.rectification-v4-panel/);
});