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: "我适合什么时候换工作?", }; function controller(overrides: Partial = {}): ConversationalRectificationController { return { turn, draft: "", selectedDomain: null, correctionTarget: null, pending: false, error: "", getSnapshot: () => ({ turn, 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, pause: async () => turn, abandon: async () => turn, confirm: async () => turn, ...overrides, }; } test("rectification is a language-first exchange with one free-text answer path", () => { const markup = renderToStaticMarkup(React.createElement( ConversationalRectificationSurface, { controller: controller() }, )); assert.match(markup, /已记录:2021 年 7 月 · 开始第一份长期工作/); assert.match(markup, /目前已经形成一个待确认候选/); assert.ok(markup.indexOf("待确认候选") < markup.indexOf("当前候选 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(/