- observability: liveness-gated stale-job recovery, extraction run registry on globalThis with explicit 'embedding' phase, /api/jobs force-dynamic, client refresh nudges at every seam - headless: providerSafePng canonicalizer (multi-IDAT Chrome PNGs → single-IDAT) fixing qwen 400s - composer: Enter routes through pure enterAction — accepts a pending plan verdict (owner decision) - markdown chrome: session-scoped mermaid overlay + md-blocks component set + pan-zoom math, corner-copy (clipboard grant in electron main), math/table/code block fixes, heading weight 900 543/543 tests; typecheck/lint/build clean
171 lines
9.5 KiB
TypeScript
171 lines
9.5 KiB
TypeScript
/**
|
|
* Render tests for the chrome-melt wave (2026-09-19): the md-blocks
|
|
* component overrides (Streamdown `components` prop) + the session-scoped
|
|
* overlays. Idiom shared with render-markdown.test.tsx: static HTML via
|
|
* react-dom/server (no DOM, effects never run — mermaid stays at its
|
|
* skeleton, the overlays render their shells). tsx note: classic JSX
|
|
* transform — the React global shim below provides React.
|
|
*/
|
|
|
|
import React from "react";
|
|
(globalThis as Record<string, unknown>).React = React;
|
|
|
|
import assert from "node:assert/strict";
|
|
import { describe, it } from "node:test";
|
|
import { renderToStaticMarkup } from "react-dom/server";
|
|
import { MessageResponse } from "../components/ai-elements/message.tsx";
|
|
import { MermaidOverlay, SessionMermaidContext, chartSize } from "../components/session-overlay.tsx";
|
|
import { PlanPopup } from "../components/plan-block.tsx";
|
|
import { MarkdownCode, codeSource } from "../components/md-blocks.tsx";
|
|
|
|
function render(md: string): string {
|
|
return renderToStaticMarkup(<MessageResponse mode="static">{md}</MessageResponse>);
|
|
}
|
|
|
|
describe("md-blocks — code", () => {
|
|
it("inline code passes through with the house inline styling", () => {
|
|
const html = render("a `term` b");
|
|
assert.ok(html.includes('data-streamdown="inline-code"'), "inline code keeps the inline-code path");
|
|
assert.match(html, /bg-muted/);
|
|
});
|
|
|
|
it("code fences delegate to streamdown's CodeBlock with a hover copy corner", () => {
|
|
const html = render("```js\nconst x = 1;\n```");
|
|
assert.ok(html.includes('data-testid="md-code"'), "the md-blocks wrapper mounts");
|
|
assert.ok(html.includes('data-streamdown="code-block"'), "the delegated CodeBlock path is kept");
|
|
assert.ok(html.includes('data-streamdown="code-block-body"'), "shiki body renders");
|
|
assert.match(html, /aria-label="Copy code"/, "the corner copy affordance is present");
|
|
// NAMED group (group/md) — the message container is also a `group`,
|
|
// and an unnamed group-hover would light every corner in the message at
|
|
// once (owner report, 2026-09-19); only the hovered block reveals.
|
|
assert.ok(html.includes("group/md"), "the block uses the named md group");
|
|
assert.match(html, /group-hover\/md:opacity-100/, "the corner reveals via the named group only");
|
|
// No "Copied" pill — the icon flip alone conveys the copy (owner ask,
|
|
// 2026-09-19; the pill was tried and removed same day).
|
|
assert.ok(!html.includes('data-testid="md-copy-pill"'), "no Copied pill anywhere — icon flip is the feedback");
|
|
});
|
|
});
|
|
|
|
describe("md-blocks — mermaid", () => {
|
|
it("renders as the bare app-owned block (skeleton in static render), never a code block, source never leaks", () => {
|
|
const html = render("```mermaid\ngraph TD\nA --> B\n```");
|
|
assert.ok(html.includes('data-testid="md-mermaid"'), "the app-owned mermaid block mounts");
|
|
assert.ok(
|
|
!html.includes('data-streamdown="code-block"'),
|
|
"mermaid fences must not render as code blocks",
|
|
);
|
|
assert.ok(!html.includes("graph TD"), "chart source is not leaked as text");
|
|
// No chat page mounted → no overlay context → no expand button.
|
|
assert.ok(!html.includes('data-testid="md-mermaid-expand"'), "expand hidden without a session context");
|
|
});
|
|
});
|
|
|
|
describe("md-blocks — tables", () => {
|
|
it("renders as a plain prose table with the copy corner — no wrapper box, no button rows", () => {
|
|
const html = render("| A | B |\n| --- | --- |\n| 1 | 2 |");
|
|
assert.ok(html.includes('data-testid="md-table"'), "the md-blocks table wrapper mounts");
|
|
assert.ok(html.includes('data-streamdown="table"'), "the table itself renders");
|
|
assert.ok(!html.includes('data-streamdown="table-wrapper"'), "streamdown's boxed wrapper is gone");
|
|
assert.match(html, /aria-label="Copy table"/, "the corner copy affordance is present");
|
|
assert.ok(!html.match(/Fullscreen|aria-label="Download"/), "no fullscreen/download chrome on tables");
|
|
// The corner is CONTAINED in the table (top-1 right-1, not the old
|
|
// -top-3 that straddled the top outline — the 24px button at a 12px
|
|
// offset sat half over the border; owner report, 2026-09-19).
|
|
assert.match(html, /absolute top-1 right-1 z-10/, "the corner sits inside the table's top-right");
|
|
// Table text runs at the session prose size — no text-sm pin (the
|
|
// cells' own text-sm is overridden to inherit via globals.css).
|
|
assert.ok(!/border-collapse text-sm/.test(html), "the table itself pins no smaller text size");
|
|
});
|
|
});
|
|
|
|
describe("md-blocks — display math", () => {
|
|
it("the block corner reveals ONLY on its own block — named group, not the message-level group (owner report: all-at-once)", () => {
|
|
const md = "```js\nconst x = 1;\n```\n\n| A | B |\n| --- | --- |\n| 1 | 2 |";
|
|
const html = render(md);
|
|
// No UNNAMED group-hover on the corners: that selector would fire from
|
|
// the Message container's own `group` class and light every block.
|
|
assert.ok(!html.match(/\s group-hover:opacity-100/), "no unnamed group-hover on any corner");
|
|
assert.ok(html.includes('data-testid="md-table"'), "table present for the contrast");
|
|
});
|
|
|
|
it("keeps katex-display rendering and adds the TeX copy corner", () => {
|
|
const html = render("$$\n\\int_0^1 x^2\\,dx = \\tfrac{1}{3}\n$$");
|
|
assert.ok(html.includes("katex-display"), "display math still renders via KaTeX");
|
|
assert.ok(html.includes('data-testid="md-math"'), "the md-blocks math wrapper mounts");
|
|
assert.match(html, /aria-label="Copy equation source"/, "the corner copy affordance is present");
|
|
// The wrapper is BLOCK: the katex-display child is display:block, and
|
|
// an inline wrapper around it fragments — the absolute corner then has
|
|
// no clean containing block and pointer events never land (the math
|
|
// corner neither highlighted nor copied; owner report, 2026-09-19).
|
|
assert.match(html, /group\/md relative block/, "the math wrapper is a block box");
|
|
assert.match(html, /absolute top-0 right-0 z-10/, "the corner stacks above the equation");
|
|
});
|
|
});
|
|
|
|
describe("md-blocks — codeSource (pure)", () => {
|
|
it("extracts the fence source from string children and element-wrapped children", () => {
|
|
assert.equal(codeSource("const x = 1;"), "const x = 1;");
|
|
assert.equal(codeSource(<span>const x = 1;</span>), "const x = 1;");
|
|
});
|
|
});
|
|
|
|
describe("session-scoped overlays (chrome-melt wave, 2026-09-19)", () => {
|
|
it("mermaid overlay mounts inside the session area — absolute (not fixed), 80% + blur, closeable", () => {
|
|
const html = renderToStaticMarkup(<MermaidOverlay chart="graph TD\nA --> B" onClose={() => {}} />);
|
|
assert.match(html, /class="absolute inset-0[^"]*z-40/, "absolute inset-0 z-40 — scoped, not fullscreen");
|
|
assert.ok(html.includes("bg-background/80"), "80% background: the transcript ghosts through");
|
|
assert.ok(html.includes("backdrop-blur"), "blur surface");
|
|
assert.ok(!html.includes("fixed inset-0"), "never fixed — the drag bar stays uncovered");
|
|
assert.ok(html.includes('data-testid="mermaid-overlay-close"'), "the close X is present");
|
|
assert.match(html, /role="dialog"/, "dialog semantics for Esc/aria");
|
|
});
|
|
|
|
it("the plan popup is session-scoped the same way, and its chart is hidden until the first fit (no top-left flash)", () => {
|
|
const html = renderToStaticMarkup(<PlanPopup messages={[]} onClose={() => {}} />);
|
|
assert.match(html, /data-testid="plan-popup"/);
|
|
assert.match(html, /class="absolute inset-0[^"]*z-40/, "absolute inset-0 z-40 — scoped, not fullscreen");
|
|
assert.ok(html.includes("bg-background/80"), "80% background");
|
|
assert.ok(html.includes("backdrop-blur"), "blur surface");
|
|
assert.ok(!html.includes("fixed inset-0"), "never fixed — the drag bar stays uncovered");
|
|
});
|
|
|
|
it("the diagram wrapper is hidden until the first fit applies (no top-left flash — owner report, 2026-09-19)", () => {
|
|
// Covered in render-plan-block.test.tsx, which has a real tree
|
|
// fixture (an empty-messages popup renders no diagram to gate) — and
|
|
// the same gate is asserted on the mermaid overlay's wrapper here:
|
|
// settled=false at static render → opacity 0.
|
|
const html = renderToStaticMarkup(<MermaidOverlay chart="graph TD\nA --> B" onClose={() => {}} />);
|
|
// The chart itself hasn't rendered at static render (async effect) —
|
|
// the opacity gate is pinned on the plan popup's diagram wrapper in
|
|
// render-plan-block.test.tsx, which has a real tree fixture (the two
|
|
// overlays share the settled-gating pattern).
|
|
assert.ok(html.includes('data-testid="mermaid-overlay-close"'), "overlay shell renders");
|
|
});
|
|
|
|
it("chartSize reads the viewBox from a rendered svg (fallback for degenerate ones)", () => {
|
|
assert.deepEqual(
|
|
chartSize('<svg viewBox="0 0 900 700" xmlns="..."></svg>'),
|
|
{ w: 900, h: 700 },
|
|
);
|
|
assert.deepEqual(chartSize("<svg></svg>"), { w: 600, h: 400 });
|
|
});
|
|
});
|
|
|
|
describe("mermaid expand — the context seam", () => {
|
|
it("the expand button renders when a session context is mounted (static: chart stays at skeleton, so assert the seam via the provider wiring)", () => {
|
|
// The full path (skeleton → chart → hover expand) needs effects/DOM;
|
|
// what static rendering CAN pin is that a provider value flows: the
|
|
// default context is null (hidden button, covered above) and a real
|
|
// provider is the one md-blocks consumes. The overlay's open-state
|
|
// owner is chat.tsx — geometry covered by the tests above.
|
|
const opened: string[] = [];
|
|
const html = renderToStaticMarkup(
|
|
<SessionMermaidContext.Provider value={{ openMermaid: (chart) => void opened.push(chart) }}>
|
|
<div />
|
|
</SessionMermaidContext.Provider>,
|
|
);
|
|
assert.ok(html.length > 0, "provider renders");
|
|
assert.deepEqual(opened, [], "nothing opened by rendering alone");
|
|
});
|
|
});
|