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("rich narrative precedes 2–4 domain choices while free text remains available", () => { const markup = renderToStaticMarkup(React.createElement( ConversationalRectificationSurface, { controller: controller() }, )); assert.match(markup, /

当前判断<\/h2>/); assert.match(markup, /05:18<\/strong>/); assert.ok(markup.indexOf("候选时间") < markup.indexOf("当前判断")); assert.ok(markup.indexOf("当前判断") < markup.indexOf('data-evidence-domain="relationship"')); assert.equal((markup.match(/data-evidence-domain=/g) ?? []).length, 3); assert.match(markup, /aria-label="重要关系,下一步建议"/); assert.match(markup, /aria-label="事业与身份,已提供,可继续补充"/); assert.ok( markup.indexOf('data-evidence-domain="relationship"') < markup.indexOf('data-evidence-domain="career"'), ); assert.match(markup, /]+id="conversational-rectification-answer"/); assert.match(markup, /aria-label="经历发生年份"/); assert.match(markup, /aria-label="经历发生月份"/); assert.match(markup, /