import assert from "node:assert/strict"; import { spawn, type ChildProcess } from "node:child_process"; import { existsSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync, } from "node:fs"; import { homedir, tmpdir } from "node:os"; import { join } from "node:path"; import test from "node:test"; import { fileURLToPath, pathToFileURL } from "node:url"; import { build } from "esbuild"; import React from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { ConversationalRectificationSurface, } from "../src/components/conversational-birth-time-rectification.tsx"; import type { ConversationalRectificationController } from "../src/hooks/use-conversational-rectification.ts"; import type { ConversationalRectificationTurn } from "../src/lib/conversational-rectification/contracts.ts"; // The repository keeps JSX in preserve mode for Next.js; direct Node SSR needs the classic global. Object.assign(globalThis, { React }); const turn: ConversationalRectificationTurn = { caseId: "00000000-0000-4000-8000-000000000821", journeyProtocol: "conversational-evidence-v3", status: "confirming", turnVersion: 6, narrative: "## 当前判断\n\n**05:18** 只是待验证候选;D9 与 D10 仍需真实经历交叉验证。", candidate: { status: "ready_for_confirmation", representativeTime: "05:18", rangeStart: "05:16", rangeEnd: "05:20", }, technicalReceipt: { calculationVersion: "rectification-technical-v1", stableLayers: ["D1"], sensitiveLayers: ["D9", "D10"], candidateDifferenceRefs: ["consult-d9", "consult-d10"], }, evidenceRequest: { domains: ["relationship", "career", "relocation"], datePrecision: "month_preferred", freeTextAllowed: true, }, evidenceRecap: [{ id: "00000000-0000-4000-8000-000000000822", summary: "开始第一份长期工作", dateLabel: "2021 年 7 月", domain: "career", isCorrection: false, }], actions: ["answer", "pause", "abandon", "confirm"], pendingConsultationQuestion: "我适合什么时候换工作?", }; const messages = [{ role: "user" as const, text: "2021 年 7 月开始第一份长期工作。", renderKey: "user-history-1", }, { role: "assistant" as const, text: "这段事业经历很有区分度。目前已经形成一个待确认候选,下一步请说说一段重要关系经历。", renderKey: "assistant-history-1", }]; function controller(overrides: Partial = {}): ConversationalRectificationController { return { turn, messages, draft: "", selectedDomain: null, correctionTarget: null, pending: false, error: "", getSnapshot: () => ({ turn, messages, draft: "", selectedDomain: null, correctionTarget: null, pending: false, error: "", }), subscribe: () => () => undefined, synchronizeInitialTurn: () => undefined, setDraft: () => undefined, selectDomain: () => undefined, beginEvidenceCorrection: () => undefined, cancelEvidenceCorrection: () => undefined, start: async () => turn, resume: async () => turn, answer: async () => turn, regenerate: async () => turn, pause: async () => turn, abandon: async () => turn, confirm: async () => turn, ...overrides, }; } function surfaceProps(value: ConversationalRectificationController) { return { controller: value, models: [{ id: "deepseek-chat", label: "DeepSeek", description: "", creditCost: 1, isDefault: true }] as const, selectedModelId: "deepseek-chat", onSelectModel: () => undefined, }; } test("rectification is a language-first exchange with one free-text answer path", () => { const markup = renderToStaticMarkup(React.createElement( ConversationalRectificationSurface, surfaceProps(controller()), )); assert.match(markup, /2021 年 7 月开始第一份长期工作/); assert.match(markup, /这段事业经历很有区分度/); assert.match(markup, /目前已经形成一个待确认候选/); assert.doesNotMatch(markup, /当前候选 05:18|校正进度 ·/); assert.doesNotMatch(markup, /D9 与 D10|当前判断/); assert.match(markup, /]+id="conversational-rectification-answer"/); assert.match(markup, /像聊天一样回答即可/); assert.match(markup, /2018 年 6 月去了上海工作/); assert.equal((markup.match(/