Files
Jyotisha/frontend/tests/rectification-adopt-cards-stay-20260909.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

82 lines
4.5 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 { rectificationChatSurface } from "./rectification-chat-surface.ts";
import { deriveRectificationChatView } from "../src/lib/rectification-chat-view.ts";
import { chatViewInput, offerableCandidateResult, settledAssistant } from "./rectification-chat-run-fixtures.ts";
const read = (relativePath: string) => readFileSync(new URL(relativePath, import.meta.url), "utf8");
// 原值: 读 rectification-agentic-chat.tsx 单文件。新值: rectificationChatSurface(容器 + 拆出的 hook / 参数函数 / 块,见 rectification-chat-surface.ts)。原因: TASK-rectification-code-split-20260926 只搬不改,整文件断言跟着代码走;切片断言另行改为调用函数。
const chat = rectificationChatSurface;
const delivery = read("../src/components/rectification-range-delivery.tsx");
const styles = read("../src/app/globals.css");
const messageLoop = chat.slice(
chat.indexOf("{messages.map((message) => {"),
chat.indexOf("{savedTime &&"),
);
test("clicking 更像这个 does not unmount the range card for busy", () => {
// 原值: 从 chat 源码切 `const keepSelectionCardsWhileBusy` 到 `const showReadonlyRange`,匹配
// `acceptingCandidateId || candidateResult?.selectedTime`、`!busy || keepSelectionCardsWhileBusy`,
// 且不含 `&& !busy && selectionCardMessageKey`。
// 新值: 调用 deriveRectificationChatView:可交付结果挂在最新已结算回复上——空闲出卡;busy 且没在采用、
// 没采用则收卡;busy 但正在采用(acceptingCandidateId)或已采用(带持久 offer)时卡仍在。
// 原因: TASK-rectification-code-split-20260926 派生逻辑原样搬到 rectification-chat-view.ts(纯函数);
// 行为断言覆盖原三条 token 的意图,且更强。
const result = offerableCandidateResult();
const messages = [settledAssistant("offer-1")];
const idle = deriveRectificationChatView(chatViewInput({ messages, candidateResult: result }));
assert.equal(idle.showSelectionCards, true);
assert.equal(idle.selectionCardMessageKey, "offer-1");
const busyTurn = deriveRectificationChatView(chatViewInput({ messages, candidateResult: result, busy: true }));
assert.equal(busyTurn.showSelectionCards, false);
const adopting = deriveRectificationChatView(chatViewInput({
messages,
candidateResult: result,
busy: true,
acceptingCandidateId: result.candidates[0].candidateId,
}));
assert.equal(adopting.showSelectionCards, true);
assert.equal(adopting.selectionCardMessageKey, "offer-1");
const adopted = deriveRectificationChatView(chatViewInput({
messages: [settledAssistant("offer-1", { candidateOffer: { resultId: result.resultId } })],
candidateResult: { ...result, selectedTime: "04:53" },
busy: true,
}));
assert.equal(adopted.showSelectionCards, true);
assert.equal(adopted.selectionCardMessageKey, "offer-1");
});
test("after adopt the card stays on the locked offering message", () => {
assert.match(chat, /resolveSelectionCardMessageKey/);
assert.match(chat, /nextSelectionCardLock/);
// 原值:selectionOfferLockRef.current = nextSelectionCardLock(...)
// 新值:useState + 渲染期 setSelectionOfferLock(与 seededTurns 同一模式)
// 原因:react-hooks/refs 禁止渲染期读写 ref;门禁 lint 0 error(BUG-622)
assert.match(chat, /const \[selectionOfferLock, setSelectionOfferLock\] = useState/);
assert.match(chat, /if \(nextOfferLock !== selectionOfferLock\) \{\s*setSelectionOfferLock\(nextOfferLock\);/);
assert.doesNotMatch(chat, /selectionOfferLockRef/);
assert.match(messageLoop, /showSelectionCards && candidateResult && message\.renderKey === selectionCardMessageKey/);
});
test("the closing line sits under the card on the assistant column", () => {
assert.match(messageLoop, /verifiedIdleCopy && \(/);
assert.match(messageLoop, /className="rectification-pending-note"/);
assert.match(
chat,
/questionGap === "verified_idle" && verifiedIdleCopy && !showSelectionCards/,
);
assert.match(
styles,
/\.rectification-pending-note \{[\s\S]*margin-inline-start: var\(--assistant-content-inset\)/,
);
assert.doesNotMatch(chat, /rectification-adopt-status/);
});
test("the adopted column button is pressed and disabled; others stay 更像这个", () => {
assert.match(delivery, /disabled=\{busy \|\| adopted\}/);
assert.match(delivery, /rangeDeliveryAdopted/);
assert.match(delivery, /rangeDeliveryMoreLikeThis/);
});