mirror of
https://github.com/backnotprop/plannotator.git
synced 2026-09-14 14:17:26 +08:00
64062af9a1
A Guided Review can now leave Plannotator: as a single self-contained HTML file that renders exactly like the in-app guide, as an encrypted-by-default share link on guides.show, or authored by any agent through the new guide CLI. Highlights: packages/guide-viewer extracted from review-editor at the injection seam (read-only host, no third renderer); guides.show Worker with R2-backed share storage, per-IP rate limiting on creation, delete tokens hashed at rest, and 128-bit ids; portable exports pin the viewer by SRI hash with budget and manifest gates in PR CI and at deploy; two-runtime parity across Bun and Pi verified; v0.27.x saved guides load unchanged. Retention is indefinite by explicit decision, to revisit with the lean sharing refactor. Decision record: adr/decisions/007-portable-guided-reviews-20260815.md
46 lines
2.2 KiB
TypeScript
46 lines
2.2 KiB
TypeScript
import { describe, it, expect } from "bun:test";
|
|
import { renderInlineMarkdown } from "./renderInlineMarkdown";
|
|
|
|
const isElement = (node: unknown): node is { type: string; props: { children?: unknown } } =>
|
|
typeof node === "object" && node !== null && "type" in node && "props" in node;
|
|
|
|
const types = (nodes: unknown[]) => nodes.map((node) => (isElement(node) ? node.type : typeof node));
|
|
|
|
describe("renderInlineMarkdown", () => {
|
|
it("renders underscore emphasis", () => {
|
|
const nodes = renderInlineMarkdown("_text_");
|
|
expect(nodes).toHaveLength(1);
|
|
expect(isElement(nodes[0])).toBe(true);
|
|
expect((nodes[0] as { type: string; props: { children?: unknown } }).type).toBe("em");
|
|
expect((nodes[0] as { type: string; props: { children?: unknown } }).props.children).toBe("text");
|
|
});
|
|
|
|
it("renders underscore emphasis in context", () => {
|
|
const nodes = renderInlineMarkdown("foo _bar_ baz");
|
|
expect(nodes.filter(isElement)).toHaveLength(1);
|
|
expect(types(nodes)).toContain("em");
|
|
expect((nodes.find(isElement) as { type: string; props: { children?: unknown } }).props.children).toBe("bar");
|
|
expect(nodes.filter((node) => typeof node === "string").join("")).toBe("foo baz");
|
|
});
|
|
|
|
it("keeps intraword underscores literal", () => {
|
|
expect(renderInlineMarkdown("snake_case")).toEqual(["snake_case"]);
|
|
expect(renderInlineMarkdown("foo_bar_baz")).toEqual(["foo_bar_baz"]);
|
|
expect(renderInlineMarkdown("__init__")).toEqual(["__init__"]);
|
|
});
|
|
|
|
it("renders underscore emphasis after other inline tokens", () => {
|
|
const boldNodes = renderInlineMarkdown("**bold**_italic_");
|
|
expect(types(boldNodes)).toEqual(["strong", "em"]);
|
|
expect((boldNodes[1] as { type: string; props: { children?: unknown } }).props.children).toBe("italic");
|
|
|
|
const codeNodes = renderInlineMarkdown("`code`_italic_");
|
|
expect(types(codeNodes)).toEqual(["code", "em"]);
|
|
expect((codeNodes[1] as { type: string; props: { children?: unknown } }).props.children).toBe("italic");
|
|
|
|
const linkNodes = renderInlineMarkdown("[link](https://example.com)_italic_");
|
|
expect(types(linkNodes)).toEqual(["a", "em"]);
|
|
expect((linkNodes[1] as { type: string; props: { children?: unknown } }).props.children).toBe("italic");
|
|
});
|
|
});
|