/** * THE PRACTICE SURFACE — SSR render pins (the render-suite discipline: * pure bodies, renderToStaticMarkup, testids). The dock (the active * problem replaces the composer: statement above, "Your thinking" * composer-twin below, the TWO submit lanes — Nudge without a commit, * Commit with one; ⌘/Ctrl+Enter commits), the transcript cards (the * problem's compact-expandable statement — commitment NEVER renders; * the conclusion's outcome + verdict + reveal; the learner's thinking * register cards), and the view projection (the thinking marker's * data part; the dismissal's silence). */ import React from "react"; (globalThis as Record).React = React; import assert from "node:assert/strict"; import { renderToStaticMarkup } from "react-dom/server"; import { describe, it } from "node:test"; import { PracticeDock } from "../components/practice-dock.tsx"; import { ConcludeCard, ProblemCard, ThinkingCard } from "../components/practice-cards.tsx"; import { GraphContextCard } from "../components/graph-context-card.tsx"; import { newGraphContextMessage } from "../src/core/native-messages.ts"; import { projectView } from "../src/core/view.ts"; import type { ArtifactPartProps } from "../src/core"; import { newThinkingMessage, PRACTICE_PROBLEM_TOOL, type LhMessage, } from "../src/core/native-messages.ts"; const part = (over: Partial): ArtifactPartProps["part"] => ({ type: "tool-problem", state: "input-available", toolCallId: "call_p1", ...over, }); // ── the dock ───────────────────────────────────────────────────────────────── describe("PracticeDock", () => { it("expanded: statement renders, the two lanes sit in the thinking field", () => { const html = renderToStaticMarkup( {}} onSubmit={() => {}} />, ); assert.match(html, /Show that the sum is even\./); // The 'Problem' eyebrow (owner round, 2026-10-05): the quiz cards' // label row, mirrored — dock and transcript card read identically. assert.match(html, /Problem/); // The measured column cap (the old fixed 40vh is only the fallback). assert.match(html, /--dock-max-h/); assert.match(html, /Your thinking/); assert.match(html, /data-testid="practice-nudge"/); assert.match(html, /data-testid="practice-commit"/); // The buttons carry their lane in the disabled gate; both start // disabled (empty field). assert.match(html, /disabled/); }); it("collapsed: the slim bar carries the statement excerpt", () => { const html = renderToStaticMarkup( {}} onSubmit={() => {}} />, ); assert.match(html, /data-testid="practice-dock-bar"/); assert.match(html, /Show that the sum is even\./); }); }); // ── the transcript cards ───────────────────────────────────────────────────── describe("the practice transcript cards", () => { it("ProblemCard: ALWAYS OPEN, the 'Problem' eyebrow + the statement — the commitment NEVER renders", () => { const html = renderToStaticMarkup( , ); assert.match(html, /data-testid="problem-card"/); assert.match(html, /Problem/); assert.match(html, /Prove the parity claim\./); assert.doesNotMatch(html, /Pair them\./, "the commitment stays hidden"); // No chevron, no collapsed bar (owner round, 2026-10-05). assert.doesNotMatch(html, /lucide-chevron|