@node-edit-utils/core 2.3.3 → 2.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/lib/viewport/label/getViewportLabelOverlay.d.ts +1 -0
  2. package/dist/lib/viewport/label/index.d.ts +5 -3
  3. package/dist/lib/viewport/label/isViewportDragging.d.ts +2 -0
  4. package/dist/lib/viewport/label/refreshViewportLabel.d.ts +8 -0
  5. package/dist/lib/viewport/label/removeViewportLabel.d.ts +5 -0
  6. package/dist/lib/viewport/label/setupViewportDrag.d.ts +1 -0
  7. package/dist/node-edit-utils.cjs.js +100 -62
  8. package/dist/node-edit-utils.esm.js +100 -62
  9. package/dist/node-edit-utils.umd.js +100 -62
  10. package/dist/node-edit-utils.umd.min.js +1 -1
  11. package/dist/styles.css +1 -1
  12. package/package.json +7 -2
  13. package/src/lib/canvas/createCanvasObserver.test.ts +242 -0
  14. package/src/lib/canvas/disableCanvasKeyboard.test.ts +53 -0
  15. package/src/lib/canvas/disableCanvasKeyboard.ts +1 -1
  16. package/src/lib/canvas/disableCanvasTextMode.test.ts +53 -0
  17. package/src/lib/canvas/disableCanvasTextMode.ts +1 -1
  18. package/src/lib/canvas/enableCanvasKeyboard.test.ts +53 -0
  19. package/src/lib/canvas/enableCanvasKeyboard.ts +1 -1
  20. package/src/lib/canvas/enableCanvasTextMode.test.ts +53 -0
  21. package/src/lib/canvas/enableCanvasTextMode.ts +1 -1
  22. package/src/lib/canvas/helpers/applyCanvasState.test.ts +119 -0
  23. package/src/lib/canvas/helpers/applyCanvasState.ts +1 -1
  24. package/src/lib/canvas/helpers/getCanvasContainer.test.ts +62 -0
  25. package/src/lib/canvas/helpers/getCanvasContainerOrBody.test.ts +51 -0
  26. package/src/lib/canvas/helpers/getCanvasWindowValue.test.ts +116 -0
  27. package/src/lib/helpers/adjustForZoom.test.ts +65 -0
  28. package/src/lib/helpers/createDragHandler.test.ts +325 -0
  29. package/src/lib/helpers/getNodeProvider.test.ts +71 -0
  30. package/src/lib/helpers/getNodeTools.test.ts +50 -0
  31. package/src/lib/helpers/getViewportDimensions.test.ts +93 -0
  32. package/src/lib/helpers/observer/connectMutationObserver.test.ts +127 -0
  33. package/src/lib/helpers/observer/connectResizeObserver.test.ts +147 -0
  34. package/src/lib/helpers/parseTransform.test.ts +117 -0
  35. package/src/lib/helpers/toggleClass.test.ts +71 -0
  36. package/src/lib/helpers/withRAF.test.ts +439 -0
  37. package/src/lib/node-tools/createNodeTools.test.ts +373 -0
  38. package/src/lib/node-tools/events/click/handleNodeClick.test.ts +109 -0
  39. package/src/lib/node-tools/events/setupEventListener.test.ts +136 -0
  40. package/src/lib/node-tools/highlight/clearHighlightFrame.test.ts +88 -0
  41. package/src/lib/node-tools/highlight/createCornerHandles.test.ts +150 -0
  42. package/src/lib/node-tools/highlight/createHighlightFrame.test.ts +237 -0
  43. package/src/lib/node-tools/highlight/createTagLabel.test.ts +135 -0
  44. package/src/lib/node-tools/highlight/createToolsContainer.test.ts +97 -0
  45. package/src/lib/node-tools/highlight/helpers/getElementBounds.test.ts +158 -0
  46. package/src/lib/node-tools/highlight/helpers/getHighlightFrameElement.test.ts +78 -0
  47. package/src/lib/node-tools/highlight/helpers/getScreenBounds.test.ts +133 -0
  48. package/src/lib/node-tools/highlight/highlightNode.test.ts +213 -0
  49. package/src/lib/node-tools/highlight/refreshHighlightFrame.test.ts +323 -0
  50. package/src/lib/node-tools/highlight/updateHighlightFrameVisibility.test.ts +110 -0
  51. package/src/lib/node-tools/select/helpers/getElementsFromPoint.test.ts +109 -0
  52. package/src/lib/node-tools/select/helpers/isInsideComponent.test.ts +81 -0
  53. package/src/lib/node-tools/select/helpers/isInsideViewport.test.ts +82 -0
  54. package/src/lib/node-tools/select/helpers/targetSameCandidates.test.ts +81 -0
  55. package/src/lib/node-tools/select/selectNode.test.ts +238 -0
  56. package/src/lib/node-tools/text/events/setupKeydownHandler.test.ts +91 -0
  57. package/src/lib/node-tools/text/events/setupMutationObserver.test.ts +213 -0
  58. package/src/lib/node-tools/text/events/setupNodeListeners.test.ts +133 -0
  59. package/src/lib/node-tools/text/helpers/enterTextEditMode.test.ts +50 -0
  60. package/src/lib/node-tools/text/helpers/handleTextChange.test.ts +201 -0
  61. package/src/lib/node-tools/text/helpers/hasTextContent.test.ts +101 -0
  62. package/src/lib/node-tools/text/helpers/insertLineBreak.test.ts +96 -0
  63. package/src/lib/node-tools/text/helpers/makeNodeEditable.test.ts +56 -0
  64. package/src/lib/node-tools/text/helpers/makeNodeNonEditable.test.ts +57 -0
  65. package/src/lib/node-tools/text/helpers/shouldEnterTextEditMode.test.ts +61 -0
  66. package/src/lib/node-tools/text/nodeText.test.ts +233 -0
  67. package/src/lib/post-message/processPostMessage.test.ts +218 -0
  68. package/src/lib/post-message/sendPostMessage.test.ts +120 -0
  69. package/src/lib/styles/styles.css +2 -2
  70. package/src/lib/viewport/createViewport.test.ts +267 -0
  71. package/src/lib/viewport/createViewport.ts +7 -4
  72. package/src/lib/viewport/events/setupEventListener.test.ts +103 -0
  73. package/src/lib/viewport/label/getViewportLabelOverlay.test.ts +77 -0
  74. package/src/lib/viewport/label/{getViewportLabelsOverlay.ts → getViewportLabelOverlay.ts} +2 -1
  75. package/src/lib/viewport/label/helpers/getLabelPosition.test.ts +51 -0
  76. package/src/lib/viewport/label/helpers/getTransformValues.test.ts +59 -0
  77. package/src/lib/viewport/label/helpers/getZoomValue.test.ts +53 -0
  78. package/src/lib/viewport/label/helpers/selectFirstViewportNode.test.ts +105 -0
  79. package/src/lib/viewport/label/helpers/selectFirstViewportNode.ts +8 -0
  80. package/src/lib/viewport/label/index.ts +5 -3
  81. package/src/lib/viewport/label/isViewportDragging.test.ts +35 -0
  82. package/src/lib/viewport/label/isViewportDragging.ts +9 -0
  83. package/src/lib/viewport/label/refreshViewportLabel.test.ts +105 -0
  84. package/src/lib/viewport/label/refreshViewportLabel.ts +50 -0
  85. package/src/lib/viewport/label/refreshViewportLabels.test.ts +107 -0
  86. package/src/lib/viewport/label/refreshViewportLabels.ts +17 -50
  87. package/src/lib/viewport/label/removeViewportLabel.test.ts +67 -0
  88. package/src/lib/viewport/label/removeViewportLabel.ts +20 -0
  89. package/src/lib/viewport/label/setupViewportDrag.test.ts +249 -0
  90. package/src/lib/viewport/label/{setupViewportLabelDrag.ts → setupViewportDrag.ts} +14 -14
  91. package/src/lib/viewport/resize/createResizeHandle.test.ts +37 -0
  92. package/src/lib/viewport/resize/createResizePresets.test.ts +75 -0
  93. package/src/lib/viewport/resize/updateActivePreset.test.ts +92 -0
  94. package/src/lib/viewport/width/calcConstrainedWidth.test.ts +47 -0
  95. package/src/lib/viewport/width/calcWidth.test.ts +68 -0
  96. package/src/lib/viewport/width/updateWidth.test.ts +78 -0
  97. package/src/lib/window/bindToWindow.test.ts +166 -0
  98. package/dist/lib/viewport/label/getViewportLabelsOverlay.d.ts +0 -1
  99. package/dist/lib/viewport/label/isViewportLabelDragging.d.ts +0 -2
  100. package/dist/lib/viewport/label/setupViewportLabelDrag.d.ts +0 -1
  101. package/src/lib/viewport/label/isViewportLabelDragging.ts +0 -9
@@ -0,0 +1,373 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as connectResizeObserverModule from "../helpers/observer/connectResizeObserver";
3
+ import * as sendPostMessageModule from "../post-message/sendPostMessage";
4
+ import * as bindToWindowModule from "../window/bindToWindow";
5
+ import { createNodeTools } from "./createNodeTools";
6
+ import * as setupEventListenerModule from "./events/setupEventListener";
7
+ import * as clearHighlightFrameModule from "./highlight/clearHighlightFrame";
8
+ import * as highlightNodeModule from "./highlight/highlightNode";
9
+ import * as refreshHighlightFrameModule from "./highlight/refreshHighlightFrame";
10
+ import * as updateHighlightFrameVisibilityModule from "./highlight/updateHighlightFrameVisibility";
11
+ import * as nodeTextModule from "./text/nodeText";
12
+
13
+ vi.mock("../window/bindToWindow");
14
+ vi.mock("../post-message/sendPostMessage");
15
+ vi.mock("./events/setupEventListener");
16
+ vi.mock("./highlight/clearHighlightFrame");
17
+ vi.mock("./highlight/highlightNode");
18
+ vi.mock("./highlight/refreshHighlightFrame");
19
+ vi.mock("./highlight/updateHighlightFrameVisibility");
20
+ vi.mock("./text/nodeText");
21
+ vi.mock("../helpers/observer/connectResizeObserver");
22
+
23
+ // Mock ResizeObserver for jsdom
24
+ global.ResizeObserver = class ResizeObserver {
25
+ constructor(public callback: ResizeObserverCallback) {}
26
+ observe() {}
27
+ unobserve() {}
28
+ disconnect() {}
29
+ } as unknown as typeof ResizeObserver;
30
+
31
+ describe("createNodeTools", () => {
32
+ let nodeProvider: HTMLElement;
33
+ let node: HTMLElement;
34
+ let mockText: {
35
+ enableEditMode: ReturnType<typeof vi.fn>;
36
+ blurEditMode: ReturnType<typeof vi.fn>;
37
+ getEditableNode: ReturnType<typeof vi.fn>;
38
+ isEditing: ReturnType<typeof vi.fn>;
39
+ };
40
+ let mockRemoveListeners: ReturnType<typeof vi.fn>;
41
+ let mockResizeObserver: ResizeObserver;
42
+ let mockMutationObserver: MutationObserver;
43
+ let mockParentMutationObserver: MutationObserver;
44
+
45
+ beforeEach(() => {
46
+ nodeProvider = document.createElement("div");
47
+ nodeProvider.setAttribute("data-role", "node-provider");
48
+ document.body.appendChild(nodeProvider);
49
+
50
+ node = document.createElement("div");
51
+ node.setAttribute("data-node-id", "test-node-1");
52
+ nodeProvider.appendChild(node);
53
+
54
+ mockText = {
55
+ enableEditMode: vi.fn(),
56
+ blurEditMode: vi.fn(),
57
+ getEditableNode: vi.fn().mockReturnValue(null),
58
+ isEditing: vi.fn().mockReturnValue(false),
59
+ };
60
+
61
+ mockRemoveListeners = vi.fn();
62
+ mockResizeObserver = new ResizeObserver(() => {});
63
+ mockResizeObserver.disconnect = vi.fn();
64
+ mockMutationObserver = new MutationObserver(() => {});
65
+ mockMutationObserver.disconnect = vi.fn();
66
+ mockParentMutationObserver = new MutationObserver(() => {});
67
+ mockParentMutationObserver.disconnect = vi.fn();
68
+
69
+ vi.mocked(bindToWindowModule.bindToWindow).mockImplementation(() => {});
70
+ vi.mocked(sendPostMessageModule.sendPostMessage).mockImplementation(() => {});
71
+ vi.mocked(setupEventListenerModule.setupEventListener).mockReturnValue(mockRemoveListeners);
72
+ vi.mocked(clearHighlightFrameModule.clearHighlightFrame).mockImplementation(() => {});
73
+ vi.mocked(highlightNodeModule.highlightNode).mockImplementation(() => {});
74
+ vi.mocked(refreshHighlightFrameModule.refreshHighlightFrame).mockImplementation(() => {});
75
+ vi.mocked(updateHighlightFrameVisibilityModule.updateHighlightFrameVisibility).mockImplementation(() => {});
76
+ vi.mocked(nodeTextModule.nodeText).mockReturnValue(mockText as unknown as ReturnType<typeof nodeTextModule.nodeText>);
77
+ vi.mocked(connectResizeObserverModule.connectResizeObserver).mockReturnValue(mockResizeObserver);
78
+ });
79
+
80
+ afterEach(() => {
81
+ if (document.body.contains(nodeProvider)) {
82
+ document.body.removeChild(nodeProvider);
83
+ }
84
+ vi.clearAllMocks();
85
+ });
86
+
87
+ it("should return NodeTools interface", () => {
88
+ const nodeTools = createNodeTools(nodeProvider);
89
+
90
+ expect(nodeTools).toHaveProperty("selectNode");
91
+ expect(nodeTools).toHaveProperty("getSelectedNode");
92
+ expect(nodeTools).toHaveProperty("getEditableNode");
93
+ expect(nodeTools).toHaveProperty("refreshHighlightFrame");
94
+ expect(nodeTools).toHaveProperty("clearSelectedNode");
95
+ expect(nodeTools).toHaveProperty("cleanup");
96
+ });
97
+
98
+ it("should bind nodeTools to window", () => {
99
+ createNodeTools(nodeProvider);
100
+
101
+ expect(bindToWindowModule.bindToWindow).toHaveBeenCalledWith("nodeTools", expect.any(Object));
102
+ });
103
+
104
+ it("should setup event listeners", () => {
105
+ createNodeTools(nodeProvider);
106
+
107
+ expect(setupEventListenerModule.setupEventListener).toHaveBeenCalledWith(
108
+ nodeProvider,
109
+ expect.any(Function),
110
+ expect.any(Function),
111
+ mockText
112
+ );
113
+ });
114
+
115
+ it("should initialize with no selected node", () => {
116
+ const nodeTools = createNodeTools(nodeProvider);
117
+
118
+ expect(nodeTools.getSelectedNode()).toBeNull();
119
+ });
120
+
121
+ it("should select a node", () => {
122
+ const nodeTools = createNodeTools(nodeProvider);
123
+
124
+ nodeTools.selectNode(node);
125
+
126
+ expect(nodeTools.getSelectedNode()).toBe(node);
127
+ expect(highlightNodeModule.highlightNode).toHaveBeenCalledWith(node);
128
+ expect(sendPostMessageModule.sendPostMessage).toHaveBeenCalledWith("selectedNodeChanged", "test-node-1");
129
+ });
130
+
131
+ it("should not select same node twice", () => {
132
+ const nodeTools = createNodeTools(nodeProvider);
133
+
134
+ nodeTools.selectNode(node);
135
+ vi.clearAllMocks();
136
+
137
+ nodeTools.selectNode(node);
138
+
139
+ // Should not call highlightNode again
140
+ expect(highlightNodeModule.highlightNode).not.toHaveBeenCalled();
141
+ });
142
+
143
+ it("should clear previous selection when selecting new node", () => {
144
+ const nodeTools = createNodeTools(nodeProvider);
145
+ const node2 = document.createElement("div");
146
+ node2.setAttribute("data-node-id", "test-node-2");
147
+ nodeProvider.appendChild(node2);
148
+
149
+ nodeTools.selectNode(node);
150
+ vi.clearAllMocks();
151
+
152
+ nodeTools.selectNode(node2);
153
+
154
+ expect(nodeTools.getSelectedNode()).toBe(node2);
155
+ expect(mockResizeObserver.disconnect).toHaveBeenCalled();
156
+ });
157
+
158
+ it("should clear selection when selecting null", () => {
159
+ const nodeTools = createNodeTools(nodeProvider);
160
+
161
+ nodeTools.selectNode(node);
162
+ nodeTools.selectNode(null);
163
+
164
+ expect(nodeTools.getSelectedNode()).toBeNull();
165
+ expect(clearHighlightFrameModule.clearHighlightFrame).toHaveBeenCalled();
166
+ });
167
+
168
+ it("should blur edit mode when selecting different node", () => {
169
+ const nodeTools = createNodeTools(nodeProvider);
170
+ const node2 = document.createElement("div");
171
+ node2.setAttribute("data-node-id", "test-node-2");
172
+ nodeProvider.appendChild(node2);
173
+
174
+ mockText.getEditableNode.mockReturnValue(node);
175
+ mockText.isEditing.mockReturnValue(true);
176
+
177
+ nodeTools.selectNode(node);
178
+ vi.clearAllMocks();
179
+
180
+ nodeTools.selectNode(node2);
181
+
182
+ expect(mockText.blurEditMode).toHaveBeenCalled();
183
+ });
184
+
185
+ it("should not blur edit mode when selecting same editable node", () => {
186
+ const nodeTools = createNodeTools(nodeProvider);
187
+
188
+ mockText.getEditableNode.mockReturnValue(node);
189
+ mockText.isEditing.mockReturnValue(true);
190
+
191
+ nodeTools.selectNode(node);
192
+ vi.clearAllMocks();
193
+
194
+ nodeTools.selectNode(node);
195
+
196
+ expect(mockText.blurEditMode).not.toHaveBeenCalled();
197
+ });
198
+
199
+ it("should setup observers when selecting node", () => {
200
+ const nodeTools = createNodeTools(nodeProvider);
201
+
202
+ nodeTools.selectNode(node);
203
+
204
+ expect(connectResizeObserverModule.connectResizeObserver).toHaveBeenCalled();
205
+ });
206
+
207
+ it("should handle escape key when editing", () => {
208
+ const nodeTools = createNodeTools(nodeProvider);
209
+
210
+ mockText.isEditing.mockReturnValue(true);
211
+ nodeTools.selectNode(node);
212
+
213
+ // Get the escape handler from setupEventListener
214
+ const escapeHandler = vi.mocked(setupEventListenerModule.setupEventListener).mock.calls[0][2];
215
+ escapeHandler();
216
+
217
+ expect(mockText.blurEditMode).toHaveBeenCalled();
218
+ });
219
+
220
+ it("should handle escape key when node is selected", () => {
221
+ const nodeTools = createNodeTools(nodeProvider);
222
+
223
+ nodeTools.selectNode(node);
224
+
225
+ const escapeHandler = vi.mocked(setupEventListenerModule.setupEventListener).mock.calls[0][2];
226
+ escapeHandler();
227
+
228
+ expect(clearHighlightFrameModule.clearHighlightFrame).toHaveBeenCalled();
229
+ expect(nodeTools.getSelectedNode()).toBeNull();
230
+ });
231
+
232
+ it("should handle escape key when no node is selected", () => {
233
+ createNodeTools(nodeProvider);
234
+
235
+ const escapeHandler = vi.mocked(setupEventListenerModule.setupEventListener).mock.calls[0][2];
236
+ escapeHandler();
237
+
238
+ expect(clearHighlightFrameModule.clearHighlightFrame).not.toHaveBeenCalled();
239
+ });
240
+
241
+ it("should refresh highlight frame", () => {
242
+ const nodeTools = createNodeTools(nodeProvider);
243
+
244
+ nodeTools.selectNode(node);
245
+ vi.clearAllMocks();
246
+
247
+ nodeTools.refreshHighlightFrame();
248
+
249
+ expect(refreshHighlightFrameModule.refreshHighlightFrame).toHaveBeenCalledWith(node, nodeProvider, "canvas");
250
+ expect(updateHighlightFrameVisibilityModule.updateHighlightFrameVisibility).toHaveBeenCalledWith(node);
251
+ });
252
+
253
+ it("should not refresh highlight frame when no node is selected", () => {
254
+ const nodeTools = createNodeTools(nodeProvider);
255
+
256
+ nodeTools.refreshHighlightFrame();
257
+
258
+ expect(refreshHighlightFrameModule.refreshHighlightFrame).not.toHaveBeenCalled();
259
+ });
260
+
261
+ it("should clear selected node", () => {
262
+ const nodeTools = createNodeTools(nodeProvider);
263
+
264
+ nodeTools.selectNode(node);
265
+ nodeTools.clearSelectedNode();
266
+
267
+ expect(nodeTools.getSelectedNode()).toBeNull();
268
+ expect(clearHighlightFrameModule.clearHighlightFrame).toHaveBeenCalled();
269
+ });
270
+
271
+ it("should get editable node", () => {
272
+ const nodeTools = createNodeTools(nodeProvider);
273
+
274
+ mockText.getEditableNode.mockReturnValue(node);
275
+
276
+ expect(nodeTools.getEditableNode()).toBe(node);
277
+ });
278
+
279
+ it("should cleanup all resources", () => {
280
+ const nodeTools = createNodeTools(nodeProvider);
281
+
282
+ nodeTools.selectNode(node);
283
+ nodeTools.cleanup();
284
+
285
+ expect(mockRemoveListeners).toHaveBeenCalled();
286
+ expect(mockText.blurEditMode).toHaveBeenCalled();
287
+ expect(clearHighlightFrameModule.clearHighlightFrame).toHaveBeenCalled();
288
+ expect(sendPostMessageModule.sendPostMessage).toHaveBeenCalledWith("selectedNodeChanged", null);
289
+ });
290
+
291
+ it("should work with null nodeProvider", () => {
292
+ const nodeTools = createNodeTools(null);
293
+
294
+ expect(() => {
295
+ nodeTools.selectNode(node);
296
+ }).not.toThrow();
297
+ });
298
+
299
+ it("should handle node removal detection", async () => {
300
+ const nodeTools = createNodeTools(nodeProvider);
301
+ const testNode = document.createElement("div");
302
+ testNode.setAttribute("data-node-id", "test-node-removal");
303
+ nodeProvider.appendChild(testNode);
304
+
305
+ nodeTools.selectNode(testNode);
306
+ expect(nodeTools.getSelectedNode()).toBe(testNode);
307
+
308
+ // Simulate node removal
309
+ nodeProvider.removeChild(testNode);
310
+
311
+ // Wait for mutation observer to process
312
+ await new Promise((resolve) => setTimeout(resolve, 10));
313
+
314
+ // The node should be cleared after removal
315
+ // Note: The mutation observer should detect the removal and clear the selection
316
+ expect(nodeTools.getSelectedNode()).toBeNull();
317
+ });
318
+
319
+ it("should use custom canvas name", () => {
320
+ const nodeTools = createNodeTools(nodeProvider, "custom-canvas");
321
+
322
+ nodeTools.selectNode(node);
323
+
324
+ expect(nodeTextModule.nodeText).toHaveBeenCalledWith("custom-canvas");
325
+ });
326
+
327
+ it("should send postMessage with node ID when selecting", () => {
328
+ const nodeTools = createNodeTools(nodeProvider);
329
+
330
+ nodeTools.selectNode(node);
331
+
332
+ expect(sendPostMessageModule.sendPostMessage).toHaveBeenCalledWith("selectedNodeChanged", "test-node-1");
333
+ });
334
+
335
+ it("should send postMessage with null when clearing selection", () => {
336
+ const nodeTools = createNodeTools(nodeProvider);
337
+
338
+ nodeTools.selectNode(node);
339
+ nodeTools.selectNode(null);
340
+
341
+ expect(sendPostMessageModule.sendPostMessage).toHaveBeenLastCalledWith("selectedNodeChanged", null);
342
+ });
343
+
344
+ it("should update highlight frame visibility when selecting node", () => {
345
+ const nodeTools = createNodeTools(nodeProvider);
346
+
347
+ nodeTools.selectNode(node);
348
+
349
+ expect(updateHighlightFrameVisibilityModule.updateHighlightFrameVisibility).toHaveBeenCalledWith(node);
350
+ });
351
+
352
+ it("should handle multiple node selections", () => {
353
+ const nodeTools = createNodeTools(nodeProvider);
354
+ const node2 = document.createElement("div");
355
+ node2.setAttribute("data-node-id", "test-node-2");
356
+ nodeProvider.appendChild(node2);
357
+ const node3 = document.createElement("div");
358
+ node3.setAttribute("data-node-id", "test-node-3");
359
+ nodeProvider.appendChild(node3);
360
+
361
+ nodeTools.selectNode(node);
362
+ expect(nodeTools.getSelectedNode()).toBe(node);
363
+
364
+ nodeTools.selectNode(node2);
365
+ expect(nodeTools.getSelectedNode()).toBe(node2);
366
+
367
+ nodeTools.selectNode(node3);
368
+ expect(nodeTools.getSelectedNode()).toBe(node3);
369
+
370
+ nodeTools.selectNode(null);
371
+ expect(nodeTools.getSelectedNode()).toBeNull();
372
+ });
373
+ });
@@ -0,0 +1,109 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as clearHighlightFrameModule from "../../highlight/clearHighlightFrame";
3
+ import * as selectNodeModule from "../../select/selectNode";
4
+ import type { NodeText } from "../../text/types";
5
+ import { handleNodeClick } from "./handleNodeClick";
6
+
7
+ vi.mock("../../highlight/clearHighlightFrame");
8
+ vi.mock("../../select/selectNode");
9
+
10
+ describe("handleNodeClick", () => {
11
+ let nodeProvider: HTMLElement;
12
+ let node: HTMLElement;
13
+ let mockText: NodeText;
14
+ let onNodeSelected: ReturnType<typeof vi.fn>;
15
+ let mockEvent: MouseEvent;
16
+
17
+ beforeEach(() => {
18
+ nodeProvider = document.createElement("div");
19
+ nodeProvider.setAttribute("data-role", "node-provider");
20
+ document.body.appendChild(nodeProvider);
21
+
22
+ node = document.createElement("div");
23
+ node.setAttribute("data-node-id", "test-node");
24
+ nodeProvider.appendChild(node);
25
+
26
+ mockText = {
27
+ getEditableNode: vi.fn(),
28
+ enableEditMode: vi.fn(),
29
+ blurEditMode: vi.fn(),
30
+ isEditing: vi.fn(),
31
+ };
32
+
33
+ onNodeSelected = vi.fn();
34
+
35
+ mockEvent = new MouseEvent("click", {
36
+ bubbles: true,
37
+ cancelable: true,
38
+ });
39
+ Object.defineProperty(mockEvent, "target", { value: node, writable: false });
40
+
41
+ vi.mocked(clearHighlightFrameModule.clearHighlightFrame).mockImplementation(() => {});
42
+ vi.mocked(selectNodeModule.selectNode).mockReturnValue(node);
43
+ });
44
+
45
+ afterEach(() => {
46
+ if (document.body.contains(nodeProvider)) {
47
+ document.body.removeChild(nodeProvider);
48
+ }
49
+ vi.clearAllMocks();
50
+ });
51
+
52
+ it("should prevent default and stop propagation", () => {
53
+ const preventDefaultSpy = vi.spyOn(mockEvent, "preventDefault");
54
+ const stopPropagationSpy = vi.spyOn(mockEvent, "stopPropagation");
55
+
56
+ handleNodeClick(mockEvent, nodeProvider, mockText, onNodeSelected);
57
+
58
+ expect(preventDefaultSpy).toHaveBeenCalled();
59
+ expect(stopPropagationSpy).toHaveBeenCalled();
60
+ });
61
+
62
+ it("should clear highlight and call onNodeSelected with null when click is outside nodeProvider", () => {
63
+ const outsideElement = document.createElement("div");
64
+ document.body.appendChild(outsideElement);
65
+ const outsideEvent = new MouseEvent("click", {
66
+ bubbles: true,
67
+ cancelable: true,
68
+ });
69
+ Object.defineProperty(outsideEvent, "target", { value: outsideElement, writable: false, configurable: true });
70
+
71
+ handleNodeClick(outsideEvent, nodeProvider, mockText, onNodeSelected);
72
+
73
+ expect(clearHighlightFrameModule.clearHighlightFrame).toHaveBeenCalled();
74
+ expect(onNodeSelected).toHaveBeenCalledWith(null);
75
+ expect(selectNodeModule.selectNode).not.toHaveBeenCalled();
76
+
77
+ document.body.removeChild(outsideElement);
78
+ });
79
+
80
+ it("should select node when click is inside nodeProvider", () => {
81
+ handleNodeClick(mockEvent, nodeProvider, mockText, onNodeSelected);
82
+
83
+ expect(selectNodeModule.selectNode).toHaveBeenCalledWith(mockEvent, nodeProvider, mockText);
84
+ expect(onNodeSelected).toHaveBeenCalledWith(node);
85
+ });
86
+
87
+ it("should work with null nodeProvider", () => {
88
+ handleNodeClick(mockEvent, null, mockText, onNodeSelected);
89
+
90
+ expect(selectNodeModule.selectNode).toHaveBeenCalledWith(mockEvent, null, mockText);
91
+ });
92
+
93
+ it("should handle when selectNode returns null", () => {
94
+ vi.mocked(selectNodeModule.selectNode).mockReturnValue(null);
95
+
96
+ handleNodeClick(mockEvent, nodeProvider, mockText, onNodeSelected);
97
+
98
+ expect(onNodeSelected).toHaveBeenCalledWith(null);
99
+ });
100
+
101
+ it("should handle when selectNode returns different node", () => {
102
+ const node2 = document.createElement("div");
103
+ vi.mocked(selectNodeModule.selectNode).mockReturnValue(node2);
104
+
105
+ handleNodeClick(mockEvent, nodeProvider, mockText, onNodeSelected);
106
+
107
+ expect(onNodeSelected).toHaveBeenCalledWith(node2);
108
+ });
109
+ });
@@ -0,0 +1,136 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as processPostMessageModule from "@/lib/post-message/processPostMessage";
3
+ import * as handleNodeClickModule from "./click/handleNodeClick";
4
+ import { setupEventListener } from "./setupEventListener";
5
+ import type { NodeText } from "../../text/types";
6
+
7
+ vi.mock("@/lib/post-message/processPostMessage");
8
+ vi.mock("./click/handleNodeClick");
9
+
10
+ describe("setupEventListener", () => {
11
+ let nodeProvider: HTMLElement;
12
+ let onNodeSelected: ReturnType<typeof vi.fn>;
13
+ let onEscapePressed: ReturnType<typeof vi.fn>;
14
+ let mockText: NodeText;
15
+ let cleanup: () => void;
16
+
17
+ beforeEach(() => {
18
+ nodeProvider = document.createElement("div");
19
+ nodeProvider.setAttribute("data-role", "node-provider");
20
+ document.body.appendChild(nodeProvider);
21
+
22
+ onNodeSelected = vi.fn();
23
+ onEscapePressed = vi.fn();
24
+ mockText = {
25
+ getEditableNode: vi.fn(),
26
+ enableEditMode: vi.fn(),
27
+ blurEditMode: vi.fn(),
28
+ isEditing: vi.fn(),
29
+ };
30
+
31
+ vi.mocked(processPostMessageModule.processPostMessage).mockImplementation(() => {});
32
+ vi.mocked(handleNodeClickModule.handleNodeClick).mockImplementation(() => {});
33
+ });
34
+
35
+ afterEach(() => {
36
+ if (cleanup) {
37
+ cleanup();
38
+ }
39
+ if (document.body.contains(nodeProvider)) {
40
+ document.body.removeChild(nodeProvider);
41
+ }
42
+ vi.clearAllMocks();
43
+ });
44
+
45
+ it("should return cleanup function", () => {
46
+ cleanup = setupEventListener(nodeProvider, onNodeSelected, onEscapePressed, mockText);
47
+
48
+ expect(typeof cleanup).toBe("function");
49
+ });
50
+
51
+ it("should setup message event listener", () => {
52
+ cleanup = setupEventListener(nodeProvider, onNodeSelected, onEscapePressed, mockText);
53
+
54
+ const event = new MessageEvent("message", {
55
+ data: { source: "application", action: "selectedNodeChanged", data: "test" },
56
+ });
57
+ window.dispatchEvent(event);
58
+
59
+ expect(processPostMessageModule.processPostMessage).toHaveBeenCalledWith(event, onNodeSelected);
60
+ });
61
+
62
+ it("should setup click event listener", () => {
63
+ cleanup = setupEventListener(nodeProvider, onNodeSelected, onEscapePressed, mockText);
64
+
65
+ const event = new MouseEvent("click", { bubbles: true });
66
+ document.dispatchEvent(event);
67
+
68
+ expect(handleNodeClickModule.handleNodeClick).toHaveBeenCalledWith(event, nodeProvider, mockText, onNodeSelected);
69
+ });
70
+
71
+ it("should setup keydown event listener for Escape key", () => {
72
+ cleanup = setupEventListener(nodeProvider, onNodeSelected, onEscapePressed, mockText);
73
+
74
+ const event = new KeyboardEvent("keydown", { key: "Escape", bubbles: true, cancelable: true });
75
+ const preventDefaultSpy = vi.spyOn(event, "preventDefault");
76
+ const stopPropagationSpy = vi.spyOn(event, "stopPropagation");
77
+
78
+ document.dispatchEvent(event);
79
+
80
+ expect(preventDefaultSpy).toHaveBeenCalled();
81
+ expect(stopPropagationSpy).toHaveBeenCalled();
82
+ expect(onEscapePressed).toHaveBeenCalled();
83
+ });
84
+
85
+ it("should not call onEscapePressed for other keys", () => {
86
+ cleanup = setupEventListener(nodeProvider, onNodeSelected, onEscapePressed, mockText);
87
+
88
+ const event = new KeyboardEvent("keydown", { key: "Enter", bubbles: true });
89
+ document.dispatchEvent(event);
90
+
91
+ expect(onEscapePressed).not.toHaveBeenCalled();
92
+ });
93
+
94
+ it("should remove all event listeners on cleanup", () => {
95
+ cleanup = setupEventListener(nodeProvider, onNodeSelected, onEscapePressed, mockText);
96
+
97
+ cleanup();
98
+ vi.clearAllMocks();
99
+
100
+ const messageEvent = new MessageEvent("message", {
101
+ data: { source: "application", action: "selectedNodeChanged", data: "test" },
102
+ });
103
+ window.dispatchEvent(messageEvent);
104
+
105
+ const clickEvent = new MouseEvent("click", { bubbles: true });
106
+ document.dispatchEvent(clickEvent);
107
+
108
+ const keydownEvent = new KeyboardEvent("keydown", { key: "Escape", bubbles: true });
109
+ document.dispatchEvent(keydownEvent);
110
+
111
+ expect(processPostMessageModule.processPostMessage).not.toHaveBeenCalled();
112
+ expect(handleNodeClickModule.handleNodeClick).not.toHaveBeenCalled();
113
+ expect(onEscapePressed).not.toHaveBeenCalled();
114
+ });
115
+
116
+ it("should work with null nodeProvider", () => {
117
+ cleanup = setupEventListener(null, onNodeSelected, onEscapePressed, mockText);
118
+
119
+ const event = new MouseEvent("click", { bubbles: true });
120
+ document.dispatchEvent(event);
121
+
122
+ expect(handleNodeClickModule.handleNodeClick).toHaveBeenCalledWith(event, null, mockText, onNodeSelected);
123
+ });
124
+
125
+ it("should handle multiple escape presses", () => {
126
+ cleanup = setupEventListener(nodeProvider, onNodeSelected, onEscapePressed, mockText);
127
+
128
+ const event1 = new KeyboardEvent("keydown", { key: "Escape", bubbles: true });
129
+ const event2 = new KeyboardEvent("keydown", { key: "Escape", bubbles: true });
130
+ document.dispatchEvent(event1);
131
+ document.dispatchEvent(event2);
132
+
133
+ expect(onEscapePressed).toHaveBeenCalledTimes(2);
134
+ });
135
+ });
136
+