Files
Jyotisha/frontend/tests/rectification-step-state-20260906.test.ts
T
Jesse_ChenandClaude Opus 5.5 3a66c39f48 refactor(rectification): split the chat component into hooks, parameter functions and blocks
Move-only (TASK-rectification-code-split-20260926 T1/T4). No behavior, API,
copy, DB, Skill or scoring change.

- rectification-agentic-chat.tsx: 2043 -> 737 lines; component body
  1625 -> 630; useState 35 -> 12, useRef 16 -> 9, effects 9 -> 5,
  useCallback 12 -> 6.
- Snapshot sync -> hooks/use-rectification-case-snapshot.ts; board layout
  and live label clock -> two small hooks.
- send / submitStructuredChoice / acceptCandidate bodies ->
  lib/rectification-chat-{turn,choice,accept}-run.ts parameter functions
  (bodies verbatim, deps destructured to the same names); copy/regenerate,
  question repair, pure transcript and snapshot helpers and the per-render
  view derivation -> lib/rectification-chat-*.ts (no React hooks).
- Question-gap blocks and the read-only range line ->
  components/rectification-question-gap-notices.tsx.
- Dependency arrays kept exactly as before (lint warnings +3, listed in
  PROGRESS); no dep added to appease the linter.
- Tests: whole-source contracts read tests/rectification-chat-surface.ts
  (container + split files, like home-surface.ts); slices of moved code now
  call the extracted functions or render the extracted block, each with a
  原值/新值/原因 comment. New tests/rectification-growth-contract.test.ts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-09-26 19:23:52 +08:00

79 lines
3.3 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { PROBE_EXPLAIN_COPY } from "../src/lib/rectification-agentic/v9/probe-explain.ts";
import { projectRectificationStepState, STEP_STATE_COPY } from "../src/lib/rectification-agentic/v9/step-state.ts";
import { parseRectificationStepState } from "../src/lib/rectification-candidate-result.ts";
import { rectificationChatSurface } from "./rectification-chat-surface.ts";
test("step state covers collect, discriminate, deliver, and post-adopt", () => {
const collect = projectRectificationStepState({
nextAction: "ask_fact_collection",
methodsCovered: false,
});
assert.equal(collect.stage, "collect");
assert.equal(collect.index, 1);
assert.match(collect.headline, /第 1 步/);
const discriminate = projectRectificationStepState({
nextAction: "ask_candidate_discriminator",
methodsCovered: true,
});
assert.equal(discriminate.stage, "discriminate");
assert.equal(discriminate.index, 2);
assert.match(discriminate.next, /先这样/);
const exhausted = projectRectificationStepState({
nextAction: "complete_with_range",
methodsCovered: true,
stopReason: "probe_pool_exhausted",
});
assert.equal(exhausted.stage, "deliver");
assert.match(exhausted.reason, /已经问完/);
const uncertain = projectRectificationStepState({
nextAction: "complete_with_range",
stopReason: "user_uncertainty_too_high",
});
assert.equal(uncertain.stage, "deliver");
assert.match(uncertain.reason, /说不好/);
const adopted = projectRectificationStepState({
accepted: true,
nextAction: "ask_holdout_validation",
});
assert.equal(adopted.stage, "post_adopt");
assert.equal(adopted.index, 4);
});
test("step state parser rejects forbidden certainty words", () => {
const parsed = parseRectificationStepState(projectRectificationStepState({
nextAction: "offer_provisional_range",
}));
assert.ok(parsed);
const blob = `${parsed.headline}${parsed.reason}${parsed.next}`;
assert.doesNotMatch(blob, /概率|置信度|确定/);
});
test("chat does not render the step-state strip above the composer", () => {
// 原值: 读 rectification-agentic-chat.tsx 单文件。新值: rectificationChatSurface(容器 + 拆出的 hook / 参数函数 / 块,见 rectification-chat-surface.ts)。原因: TASK-rectification-code-split-20260926 只搬不改,整文件断言跟着代码走;切片断言另行改为调用函数。
const chat = rectificationChatSurface;
const route = readFileSync(new URL("../src/lib/rectification-agentic/v9/case-dossier-response.ts", import.meta.url), "utf8");
assert.doesNotMatch(chat, /rectification-step-state/);
assert.doesNotMatch(chat, /下一步:\{stepState\.next\}/);
// 原值: 「再补一件经历」后 composer-meta 显示收集提示
// 新值: 无 composer-meta;再补经历动作已删
// 原因: BUG-595 决策 1/2
assert.doesNotMatch(chat, /rectification-composer-meta/);
assert.match(route, /step_state: stepStateFromCaseDossier/);
});
test("explain-layer copy avoids probability words", () => {
const blob = [
...Object.values(PROBE_EXPLAIN_COPY),
...Object.values(STEP_STATE_COPY).flatMap((item) => Object.values(item)),
].join("\n");
assert.doesNotMatch(blob, /概率|置信度|确定/);
});