import { describe, expect, it } from "vitest"; import { ASK_AI_ROWS, BACKGROUND_COLOR_TOKENS, buildBlockMenu, convertToRows, moreRows, turnIntoId, turnIntoRows, TEXT_BLOCK_TYPES, TEXT_COLOR_TOKENS, } from "./context-menu.js"; describe("context menu (PRODUCT-SPEC §7 shape)", () => { it("builds the exact top-level row order", () => { const rows = buildBlockMenu({ blockIds: ["b1"], type: "paragraph" }); expect(rows.map((r) => r.id)).toEqual([ "turn_into", "duplicate", "copy", "copy_link", "move_to", "delete", "sep-1", "comment", "ask_ai", "sep-2", "color", "background", "sep-3", "convert_to", "sep-4", "more", ]); }); it("pluralizes the duplicate label for a selection", () => { const single = buildBlockMenu({ blockIds: ["b1"], type: "paragraph" }); const many = buildBlockMenu({ blockIds: ["a", "b", "c"], type: "paragraph" }); expect(single.find((r) => r.id === "duplicate")?.label).toBe("Duplicate"); expect(many.find((r) => r.id === "duplicate")?.label).toBe("Duplicate 3 blocks"); }); it("disables mutating rows in read-only mode but keeps read rows", () => { const rows = buildBlockMenu({ blockIds: ["b1"], type: "paragraph", readOnly: true }); const byId = new Map(rows.map((r) => [r.id, r])); expect(byId.get("duplicate")?.disabled).toBe(true); expect(byId.get("delete")?.disabled).toBe(true); expect(byId.get("turn_into")?.disabled).toBe(true); expect(byId.get("copy")?.disabled).toBe(false); expect(byId.get("copy_link")?.disabled).toBe(false); }); it("Turn into lists every text-capable type and disables the current one", () => { const rows = turnIntoRows("heading_2"); expect(rows.map((r) => r.id)).toEqual(TEXT_BLOCK_TYPES.map(turnIntoId)); expect(rows.find((r) => r.id === turnIntoId("heading_2"))?.disabled).toBe(true); expect(rows.find((r) => r.id === turnIntoId("paragraph"))?.disabled).toBe(false); }); it("Convert to is the quick set (h1–3 / quote / callout / todo)", () => { expect(convertToRows().map((r) => r.id)).toEqual([ "convert_to:heading_1", "convert_to:heading_2", "convert_to:heading_3", "convert_to:quote", "convert_to:callout", "convert_to:todo", ]); }); it("Ask AI is the frozen Phase-12 shape (all disabled, same tooltip)", () => { expect(ASK_AI_ROWS).toHaveLength(4); for (const row of ASK_AI_ROWS) { expect(row.disabled).toBe(true); expect(row.tooltip).toBe("Available in Phase 12"); } }); it("color/background submenus have 9 swatches each (closed token palette)", () => { expect(TEXT_COLOR_TOKENS).toHaveLength(9); expect(BACKGROUND_COLOR_TOKENS).toHaveLength(9); const rows = buildBlockMenu({ blockIds: ["b1"], type: "paragraph" }); const color = rows.find((r) => r.id === "color")?.submenu; const bg = rows.find((r) => r.id === "background")?.submenu; expect(color).toHaveLength(9); expect(bg).toHaveLength(9); // swatch rows are enabled and carry a run expect(color?.every((r) => r.run !== undefined)).toBe(true); }); it("More rows: synced + template disabled; permissions gated on page root", () => { expect(moreRows(false).find((r) => r.id === "more:permissions")?.disabled).toBe(true); expect(moreRows(true).find((r) => r.id === "more:permissions")?.disabled).toBe(false); expect(moreRows(true).find((r) => r.id === "more:synced")?.disabled).toBe(true); expect(moreRows(true).find((r) => r.id === "more:template")?.disabled).toBe(true); }); });