Skip to content
File

Blob: tests/client/components/editor/controllers/table-menu.dom.test.tsx

typescript128 lines
1import { act, type ReactNode } from "react";
2import { createRoot, type Root } from "react-dom/client";
3import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4 
5const openMenu = { kind: "row", tableKey: "table-1", index: 0 } as const;
6let rowCount = 2;
7const buildRowMenuSections = vi.fn();
8 
9beforeEach(() => {
10 vi.resetModules();
11 rowCount = 2;
12 vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
13 buildRowMenuSections.mockImplementation(() => [
14 [
15 {
16 key: "row-count",
17 icon: null,
18 label: `Rows ${rowCount}`,
19 onSelect: vi.fn(),
20 },
21 ],
22 ]);
23 vi.stubGlobal("requestAnimationFrame", (callback: FrameRequestCallback) => {
24 callback(0);
25 return 1;
26 });
27 vi.stubGlobal("cancelAnimationFrame", vi.fn());
28 const editor = {
29 view: {
30 dispatch: vi.fn(),
31 state: {
32 tr: {
33 setMeta: vi.fn(() => ({})),
34 },
35 },
36 },
37 };
38 
39 vi.doMock("@tiptap/react", () => ({
40 useTiptap: () => ({ editor }),
41 useTiptapState: () => ({
42 openMenu,
43 rowCount,
44 colCount: 1,
45 canResetWidths: false,
46 }),
47 }));
48 vi.doMock("@floating-ui/react", () => ({
49 FloatingPortal: ({ children }: { children: ReactNode }) => <>{children}</>,
50 autoUpdate: vi.fn(),
51 flip: vi.fn(() => ({})),
52 offset: vi.fn(() => ({})),
53 shift: vi.fn(() => ({})),
54 useDismiss: vi.fn(() => ({})),
55 useFloating: vi.fn(() => ({
56 context: {},
57 refs: {
58 setFloating: vi.fn(),
59 setReference: vi.fn(),
60 },
61 floatingStyles: {},
62 })),
63 useInteractions: vi.fn(() => ({
64 getFloatingProps: (props: Record<string, unknown>) => props,
65 })),
66 }));
67 vi.doMock("@/client/components/editor/controllers/table-menu-actions", () => ({
68 buildColumnMenuSections: vi.fn(() => []),
69 buildRowMenuSections,
70 buildTableMenuSections: vi.fn(() => []),
71 }));
72 vi.doMock("@/client/components/editor/extensions/table/state", () => ({
73 resolveOpenMenuState: vi.fn(),
74 tableHandleSelector: vi.fn(() => "[data-table-trigger]"),
75 tableHandlesKey: {
76 getState: vi.fn(() => ({ openMenu })),
77 },
78 }));
79 vi.doMock("@/client/components/editor/extensions/table/widths", () => ({
80 hasExplicitColumnWidths: vi.fn(() => false),
81 }));
82});
83 
84afterEach(() => {
85 vi.restoreAllMocks();
86 vi.unstubAllGlobals();
87 document.body.innerHTML = "";
88});
89 
90describe("TableMenu", () => {
91 it("refreshes section availability when row or column counts change under the same open menu", async () => {
92 const trigger = document.createElement("button");
93 trigger.setAttribute("data-table-trigger", "");
94 document.body.appendChild(trigger);
95 
96 const { TableMenu } = await import("@/client/components/editor/controllers/table-menu");
97 
98 const host = document.createElement("div");
99 document.body.appendChild(host);
100 let root: Root | null = createRoot(host);
101 
102 try {
103 await act(async () => {
104 root!.render(<TableMenu />);
105 });
106 
107 expect(host.textContent).toContain("Rows 2");
108 const callsAfterInitialOpen = buildRowMenuSections.mock.calls.length;
109 
110 rowCount = 3;
111 
112 await act(async () => {
113 root!.render(<TableMenu />);
114 });
115 
116 expect(host.textContent).toContain("Rows 3");
117 expect(buildRowMenuSections.mock.calls.length).toBeGreaterThan(callsAfterInitialOpen);
118 } finally {
119 await act(async () => {
120 root?.unmount();
121 });
122 root = null;
123 host.remove();
124 trigger.remove();
125 }
126 });
127});