import React from "react"; (globalThis as Record).React = React; import { renderToStaticMarkup } from "react-dom/server"; import { describe, it } from "node:test"; import assert from "node:assert/strict"; import { RenameRow, SessionRow } from "../components/sidebar.tsx"; import { DeleteSessionBody, DeleteSessionDialog, TRACES_COPY } from "../components/delete-session-dialog.tsx"; import type { ChatListItem } from "../src/core/index.ts"; /** * Wave 6 chunk A — the deletion + context-menu seams on the sidebar: * SessionRow's right-click seam (always carries the row testid now), the * inline RenameRow (same geometry, select-all entry), and the two-option * delete dialog's plain-language copy (the locked design: both options * name their reach, the graph-touching one says so). */ const chat: ChatListItem = { id: "c1", title: "Wave mechanics", updatedAt: new Date().toISOString(), busy: false, } as unknown as ChatListItem; describe("SessionRow — the right-click seam", () => { it("rows carry the session-row testid (context menu anchors to it)", () => { const html = renderToStaticMarkup( {}} />); assert.ok(html.includes('data-testid="session-row"')); }); }); describe("RenameRow — the inline rename surface", () => { it("renders the draft input with the current title, keeping the meta line", () => { const html = renderToStaticMarkup( {}} onCancel={() => {}} />, ); assert.ok(html.includes('data-testid="session-rename-input"')); // controlled draft starts from the current title assert.ok(html.includes(`value="${chat.title}"`)); // the meta line rides along (row never changes height mid-edit) assert.ok(html.includes("session-rename-row")); // (owner, polish round) NOTHING MOVES: no status spacer, no input // border/padding — the draft text sits at the idle row's title x, // and the input is the ONLY shift-proof editing cue (underline). assert.ok(!html.includes("w-3.5 shrink-0"), "no always-on spacer (idle rows have none)"); const input = html.split('data-testid="session-rename-input"')[1]?.split("/>")[0] ?? ""; assert.ok(input.includes("px-0"), "input carries no left padding"); assert.ok(input.includes("border-b"), "the underline is the editing cue"); assert.ok(!input.includes("bg-background"), "no filled field box — text stays put"); }); }); describe("DeleteSessionDialog — the node-dialog shape (owner flow rework 2026-09-24; portals no-op in SSR; pin the pure body)", () => { it("closed (chat=null) renders nothing", () => { const html = renderToStaticMarkup( {}} onDeleted={() => {}} />, ); assert.equal(html, ""); }); it("red serif title 'Delete session?', the session name below in mono, sized up, no code-block chrome", () => { const html = renderToStaticMarkup( {}} onClose={() => {}} onRun={() => {}} />, ); assert.match(html, /Delete session\?/); assert.match(html, /text-destructive[^"]*">Delete session\?]*>/)?.[0] ?? ""; assert.match(name, /font-mono text-lg/); assert.ok(!name.includes("bg-muted"), "no code-block chrome"); assert.match(html, /data-testid="session-name-code"[^>]*>Orbital mechanics { const html = renderToStaticMarkup( {}} onClose={() => {}} onRun={() => {}} />, ); // unchecked by default (preserve-graph) — React SSR omits the // attribute when false, so pin its ABSENCE on the rendered input assert.ok(!/"checked"/.test(html), "unchecked by default"); // plain-language consequences on the checkbox body assert.ok(html.includes(TRACES_COPY.title)); assert.ok(html.includes("leave the graph too")); assert.ok(html.includes("survive")); // the old two-button design is gone assert.ok(!html.includes("delete-option-preserve")); assert.ok(!html.includes("delete-option-nodes")); }); it("bottom row matches the node dialog: quiet Cancel + destructive-red Delete", () => { const html = renderToStaticMarkup( {}} onClose={() => {}} onRun={() => {}} />, ); assert.match(html, /data-testid="delete-cancel"[^>]*>Cancel]*>/)?.[0] ?? ""; assert.match(del, /border-destructive\/50/); assert.match(html, /data-testid="delete-confirm"[^>]*>Deleting…]*disabled=""/); }); });