// @vitest-environment node // No DOM needed — codeBlockText is a pure string extractor. It lives in // block-renderer.tsx (exported for this test); the component's React import // chain is mocked at the boundary. import { describe, expect, it, vi } from "vitest"; // The renderer pulls in a large component tree (next/link, react-query, // lucide, the database view, Tiptap). None of it matters to codeBlockText, // so stub the heavy leaves to keep the import light + deterministic. vi.mock("next/link", () => ({ default: (p: object) => p })); vi.mock("@tanstack/react-query", () => ({ useQuery: () => ({ data: undefined }), useInfiniteQuery: () => ({ data: undefined }), useQueryClient: () => ({ invalidateQueries: vi.fn() }), })); vi.mock("@/lib/api", () => ({ apiFetch: vi.fn(async () => ({ data: [] })), blockApi: { update: vi.fn(async () => ({})) }, uploadFile: vi.fn(), })); import { codeBlockText } from "@/components/editor/block-renderer"; describe("codeBlockText (code block content → editable plain text)", () => { it("returns a raw string content verbatim (web edits)", () => { expect(codeBlockText("const x = 1;")).toBe("const x = 1;"); expect(codeBlockText("")).toBe(""); }); it("unwraps the API/AI canonical { text } envelope (slash-pick / AI apply)", () => { expect(codeBlockText({ text: "function f() {}\n" })).toBe("function f() {}\n"); expect(codeBlockText({ text: "" })).toBe(""); }); it("falls back to pretty JSON for other object content", () => { expect(codeBlockText({ a: 1 })).toBe(JSON.stringify({ a: 1 }, null, 2)); }); it("handles null / undefined content as empty string", () => { expect(codeBlockText(null)).toBe(""); expect(codeBlockText(undefined)).toBe(""); expect(codeBlockText({})).toBe(JSON.stringify({}, null, 2)); }); it("does not confuse an object with a non-string .text field for empty", () => { // { text: 42 } is not a string envelope → falls back to JSON. expect(codeBlockText({ text: 42 })).toBe(JSON.stringify({ text: 42 }, null, 2)); }); });