Files
Jyotisha/frontend/tests/composer-ime-contract.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

44 lines
2.2 KiB
TypeScript

import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import test from "node:test";
import { rectificationChatSurface } from "./rectification-chat-surface.ts";
const page = readFileSync(new URL("../src/app/(app)/page.tsx", import.meta.url), "utf8");
// 原值: 读 rectification-agentic-chat.tsx 单文件。新值: rectificationChatSurface(容器 + 拆出的 hook / 参数函数 / 块,见 rectification-chat-surface.ts)。原因: TASK-rectification-code-split-20260926 只搬不改,整文件断言跟着代码走;切片断言另行改为调用函数。
const rectification = rectificationChatSurface;
const guide = readFileSync(new URL("../src/components/birth-time-guide-turn.tsx", import.meta.url), "utf8");
function onKeyDownBlock(source: string, marker: string) {
const start = source.indexOf(marker);
assert.notEqual(start, -1, `missing onKeyDown near ${marker}`);
const slice = source.slice(start, start + 600);
const handler = slice.match(/onKeyDown=\{\(event\) => \{[\s\S]*?\n\s*\}\}/)
?? slice.match(/function handleComposerKeyDown[\s\S]*?\n \}/);
assert.ok(handler, `could not isolate onKeyDown from ${marker}`);
return handler[0];
}
test("all three composers ignore Enter while an IME is composing", () => {
const main = page.slice(
page.indexOf("function handleComposerKeyDown"),
page.indexOf("\n\n if (!hydrated"),
);
const rectificationHandler = onKeyDownBlock(rectification, "onKeyDown={(event) => {");
const guideHandler = onKeyDownBlock(guide, "onKeyDown={(event) => {");
for (const [name, handler] of [
["main composer", main],
["rectification composer", rectificationHandler],
["birth-time guide", guideHandler],
] as const) {
assert.match(handler, /event\.nativeEvent\.isComposing/, `${name} must ignore composing Enter`);
}
});
test("the guide turn sends with Enter and wraps with Shift+Enter, matching the other two", () => {
const handler = onKeyDownBlock(guide, "onKeyDown={(event) => {");
assert.match(handler, /event\.key === "Enter" && !event\.shiftKey/);
assert.doesNotMatch(handler, /metaKey \|\| event\.ctrlKey/);
assert.match(guide, /Enter 发送,Shift\+Enter 换行/);
});