@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,267 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as getCanvasContainerModule from "../canvas/helpers/getCanvasContainer";
3
+ import type { DragState } from "../helpers/createDragHandler";
4
+ import * as createDragHandlerModule from "../helpers/createDragHandler";
5
+ import * as getNodeProviderModule from "../helpers/getNodeProvider";
6
+ import * as getNodeToolsModule from "../helpers/getNodeTools";
7
+ import * as refreshHighlightFrameModule from "../node-tools/highlight/refreshHighlightFrame";
8
+ import { DEFAULT_WIDTH } from "./constants";
9
+ import { createViewport } from "./createViewport";
10
+ import * as refreshViewportLabelModule from "./label/refreshViewportLabel";
11
+ import * as removeViewportLabelModule from "./label/removeViewportLabel";
12
+ import * as createResizeHandleModule from "./resize/createResizeHandle";
13
+ import * as createResizePresetsModule from "./resize/createResizePresets";
14
+ import * as calcWidthModule from "./width/calcWidth";
15
+ import * as updateWidthModule from "./width/updateWidth";
16
+
17
+ vi.mock("../canvas/helpers/getCanvasContainer");
18
+ vi.mock("../helpers/createDragHandler");
19
+ vi.mock("../helpers/getNodeProvider");
20
+ vi.mock("../helpers/getNodeTools");
21
+ vi.mock("../node-tools/highlight/refreshHighlightFrame");
22
+ vi.mock("./label/refreshViewportLabel");
23
+ vi.mock("./label/removeViewportLabel");
24
+ vi.mock("./resize/createResizeHandle");
25
+ vi.mock("./resize/createResizePresets");
26
+ vi.mock("./width/calcWidth");
27
+ vi.mock("./width/updateWidth");
28
+
29
+ describe("createViewport", () => {
30
+ let container: HTMLElement;
31
+ let canvas: HTMLElement;
32
+ let resizeHandle: HTMLElement;
33
+ let mockDragCleanup: ReturnType<typeof vi.fn>;
34
+ let mockNodeTools: {
35
+ getSelectedNode: ReturnType<typeof vi.fn>;
36
+ };
37
+ let mockNodeProvider: HTMLElement;
38
+
39
+ beforeEach(() => {
40
+ container = document.createElement("div");
41
+ container.classList.add("viewport");
42
+ document.body.appendChild(container);
43
+
44
+ canvas = document.createElement("div");
45
+ canvas.id = "canvas";
46
+ document.body.appendChild(canvas);
47
+
48
+ resizeHandle = document.createElement("div");
49
+ resizeHandle.classList.add("resize-handle");
50
+
51
+ mockDragCleanup = vi.fn();
52
+ mockNodeTools = {
53
+ getSelectedNode: vi.fn().mockReturnValue(null),
54
+ };
55
+ mockNodeProvider = document.createElement("div");
56
+
57
+ vi.mocked(getCanvasContainerModule.getCanvasContainer).mockReturnValue(canvas);
58
+ vi.mocked(createDragHandlerModule.createDragHandler).mockReturnValue(mockDragCleanup);
59
+ vi.mocked(getNodeProviderModule.getNodeProvider).mockReturnValue(mockNodeProvider);
60
+ vi.mocked(getNodeToolsModule.getNodeTools).mockReturnValue(
61
+ mockNodeTools as unknown as ReturnType<typeof getNodeToolsModule.getNodeTools>
62
+ );
63
+ vi.mocked(refreshHighlightFrameModule.refreshHighlightFrame).mockImplementation(() => {});
64
+ vi.mocked(refreshViewportLabelModule.refreshViewportLabel).mockImplementation(() => {});
65
+ vi.mocked(removeViewportLabelModule.removeViewportLabel).mockImplementation(() => {});
66
+ vi.mocked(createResizeHandleModule.createResizeHandle).mockReturnValue(resizeHandle);
67
+ vi.mocked(createResizePresetsModule.createResizePresets).mockReturnValue(document.createElement("div"));
68
+ vi.mocked(calcWidthModule.calcWidth).mockReturnValue(500);
69
+ vi.mocked(updateWidthModule.updateWidth).mockImplementation(() => {});
70
+ });
71
+
72
+ afterEach(() => {
73
+ document.body.removeChild(container);
74
+ document.body.removeChild(canvas);
75
+ vi.clearAllMocks();
76
+ });
77
+
78
+ it("should create viewport with default width", () => {
79
+ const viewport = createViewport(container);
80
+
81
+ expect(container.style.getPropertyValue("--container-width")).toBe(`${DEFAULT_WIDTH}px`);
82
+ expect(viewport).toHaveProperty("setWidth");
83
+ expect(viewport).toHaveProperty("cleanup");
84
+ });
85
+
86
+ it("should create viewport with initial width", () => {
87
+ const initialWidth = 600;
88
+ createViewport(container, initialWidth);
89
+
90
+ expect(container.style.getPropertyValue("--container-width")).toBe(`${initialWidth}px`);
91
+ });
92
+
93
+ it("should remove existing resize handle before creating new one", () => {
94
+ const existingHandle = document.createElement("div");
95
+ existingHandle.classList.add("resize-handle");
96
+ container.appendChild(existingHandle);
97
+
98
+ createViewport(container);
99
+
100
+ expect(container.querySelector(".resize-handle")).not.toBe(existingHandle);
101
+ });
102
+
103
+ it("should create resize handle", () => {
104
+ createViewport(container);
105
+
106
+ expect(createResizeHandleModule.createResizeHandle).toHaveBeenCalledWith(container);
107
+ });
108
+
109
+ it("should create resize presets", () => {
110
+ createViewport(container);
111
+
112
+ expect(createResizePresetsModule.createResizePresets).toHaveBeenCalledWith(resizeHandle, container, updateWidthModule.updateWidth);
113
+ });
114
+
115
+ it("should refresh viewport label on creation", () => {
116
+ createViewport(container);
117
+
118
+ expect(refreshViewportLabelModule.refreshViewportLabel).toHaveBeenCalledWith(container);
119
+ });
120
+
121
+ it("should set cursor to ew-resize during drag", () => {
122
+ let onDragCallback: ((event: MouseEvent, state: DragState & { deltaX: number; deltaY: number }) => void) | undefined;
123
+
124
+ vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
125
+ onDragCallback = callbacks.onDrag;
126
+ return mockDragCleanup;
127
+ });
128
+
129
+ createViewport(container);
130
+
131
+ const mockEvent = new MouseEvent("mousemove");
132
+ const mockState: DragState & { deltaX: number; deltaY: number } = {
133
+ isDragging: true,
134
+ hasDragged: true,
135
+ startX: 0,
136
+ startY: 0,
137
+ deltaX: 0,
138
+ deltaY: 0,
139
+ };
140
+ onDragCallback?.(mockEvent, mockState);
141
+
142
+ expect(canvas.style.cursor).toBe("ew-resize");
143
+ });
144
+
145
+ it("should calculate and update width during drag", () => {
146
+ let onDragCallback: ((event: MouseEvent, state: DragState & { deltaX: number; deltaY: number }) => void) | undefined;
147
+
148
+ vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
149
+ onDragCallback = callbacks.onDrag;
150
+ return mockDragCleanup;
151
+ });
152
+
153
+ createViewport(container);
154
+
155
+ const mockEvent = new MouseEvent("mousemove");
156
+ const mockState: DragState & { deltaX: number; deltaY: number } = {
157
+ isDragging: true,
158
+ hasDragged: true,
159
+ startX: 0,
160
+ startY: 0,
161
+ deltaX: 0,
162
+ deltaY: 0,
163
+ };
164
+ onDragCallback?.(mockEvent, mockState);
165
+
166
+ expect(calcWidthModule.calcWidth).toHaveBeenCalled();
167
+ expect(updateWidthModule.updateWidth).toHaveBeenCalled();
168
+ });
169
+
170
+ it("should reset cursor to default on drag stop", () => {
171
+ let onStopCallback: ((event: MouseEvent, state: DragState) => void) | undefined;
172
+
173
+ vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
174
+ onStopCallback = callbacks.onStop;
175
+ return mockDragCleanup;
176
+ });
177
+
178
+ createViewport(container);
179
+ canvas.style.cursor = "ew-resize";
180
+
181
+ const mockEvent = new MouseEvent("mouseup");
182
+ const mockState: DragState = { isDragging: false, hasDragged: false, startX: 0, startY: 0 };
183
+ onStopCallback?.(mockEvent, mockState);
184
+
185
+ expect(canvas.style.cursor).toBe("default");
186
+ });
187
+
188
+ it("should reset cursor to default on drag cancel", () => {
189
+ let onCancelCallback: ((state: DragState) => void) | undefined;
190
+
191
+ vi.mocked(createDragHandlerModule.createDragHandler).mockImplementation((_element, callbacks) => {
192
+ onCancelCallback = callbacks.onCancel;
193
+ return mockDragCleanup;
194
+ });
195
+
196
+ createViewport(container);
197
+ canvas.style.cursor = "ew-resize";
198
+
199
+ const mockState: DragState = { isDragging: false, hasDragged: false, startX: 0, startY: 0 };
200
+ onCancelCallback?.(mockState);
201
+
202
+ expect(canvas.style.cursor).toBe("default");
203
+ });
204
+
205
+ it("should handle mouseleave when leaving window", () => {
206
+ createViewport(container);
207
+ canvas.style.cursor = "ew-resize";
208
+
209
+ const event = new MouseEvent("mouseleave", {
210
+ bubbles: true,
211
+ relatedTarget: null,
212
+ });
213
+ Object.defineProperty(event, "target", { value: document, writable: false });
214
+ document.dispatchEvent(event);
215
+
216
+ expect(canvas.style.cursor).toBe("default");
217
+ });
218
+
219
+ it("should cleanup all listeners and remove handle on cleanup", () => {
220
+ const viewport = createViewport(container);
221
+
222
+ viewport.cleanup();
223
+
224
+ expect(mockDragCleanup).toHaveBeenCalled();
225
+ expect(removeViewportLabelModule.removeViewportLabel).toHaveBeenCalledWith(container);
226
+ expect(container.querySelector(".resize-handle")).toBeNull();
227
+ });
228
+
229
+ it("should update width via setWidth", () => {
230
+ const viewport = createViewport(container);
231
+ const newWidth = 800;
232
+
233
+ viewport.setWidth(newWidth);
234
+
235
+ expect(updateWidthModule.updateWidth).toHaveBeenCalledWith(container, newWidth);
236
+ expect(refreshViewportLabelModule.refreshViewportLabel).toHaveBeenCalledWith(container);
237
+ });
238
+
239
+ it("should refresh highlight frame when node is selected and setWidth is called", () => {
240
+ const selectedNode = document.createElement("div");
241
+ mockNodeTools.getSelectedNode.mockReturnValue(selectedNode);
242
+
243
+ const viewport = createViewport(container);
244
+ viewport.setWidth(800);
245
+
246
+ expect(refreshHighlightFrameModule.refreshHighlightFrame).toHaveBeenCalledWith(selectedNode, mockNodeProvider);
247
+ });
248
+
249
+ it("should not refresh highlight frame when no node is selected", () => {
250
+ mockNodeTools.getSelectedNode.mockReturnValue(null);
251
+
252
+ const viewport = createViewport(container);
253
+ viewport.setWidth(800);
254
+
255
+ expect(refreshHighlightFrameModule.refreshHighlightFrame).not.toHaveBeenCalled();
256
+ });
257
+
258
+ it("should handle cleanup when canvas is null", () => {
259
+ vi.mocked(getCanvasContainerModule.getCanvasContainer).mockReturnValue(null);
260
+
261
+ const viewport = createViewport(container);
262
+
263
+ expect(() => {
264
+ viewport.cleanup();
265
+ }).not.toThrow();
266
+ });
267
+ });
@@ -4,7 +4,8 @@ import { getNodeProvider } from "../helpers/getNodeProvider";
4
4
  import { getNodeTools } from "../helpers/getNodeTools";
5
5
  import { refreshHighlightFrame } from "../node-tools/highlight/refreshHighlightFrame";
6
6
  import { DEFAULT_WIDTH } from "./constants";
7
- import { refreshViewportLabels } from "./label/refreshViewportLabels";
7
+ import { refreshViewportLabel } from "./label/refreshViewportLabel";
8
+ import { removeViewportLabel } from "./label/removeViewportLabel";
8
9
  import { createResizeHandle } from "./resize/createResizeHandle";
9
10
  import { createResizePresets } from "./resize/createResizePresets";
10
11
  import type { Viewport } from "./types";
@@ -69,20 +70,22 @@ export const createViewport = (container: HTMLElement, initialWidth?: number): V
69
70
 
70
71
  document.addEventListener("mouseleave", handleMouseLeave);
71
72
 
72
- refreshViewportLabels();
73
+ // Create/refresh the label for this viewport
74
+ refreshViewportLabel(container);
73
75
 
74
76
  const cleanup = (): void => {
75
77
  removeDragListeners();
76
78
  document.removeEventListener("mouseleave", handleMouseLeave);
77
79
  resizeHandle.remove();
78
80
 
79
- refreshViewportLabels();
81
+ // Remove the label for this viewport
82
+ removeViewportLabel(container);
80
83
  };
81
84
 
82
85
  return {
83
86
  setWidth: (width: number): void => {
84
87
  updateWidth(container, width);
85
- refreshViewportLabels();
88
+ refreshViewportLabel(container);
86
89
 
87
90
  const nodeTools = getNodeTools();
88
91
  const selectedNode = nodeTools?.getSelectedNode?.();
@@ -0,0 +1,103 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { setupEventListener } from "./setupEventListener";
3
+
4
+ describe("setupEventListener", () => {
5
+ let resizeHandle: HTMLElement;
6
+ let startResize: ReturnType<typeof vi.fn>;
7
+ let handleResize: ReturnType<typeof vi.fn>;
8
+ let stopResize: ReturnType<typeof vi.fn>;
9
+ let blurResize: ReturnType<typeof vi.fn>;
10
+ let cleanup: () => void;
11
+
12
+ beforeEach(() => {
13
+ resizeHandle = document.createElement("div");
14
+ document.body.appendChild(resizeHandle);
15
+
16
+ startResize = vi.fn();
17
+ handleResize = vi.fn();
18
+ stopResize = vi.fn();
19
+ blurResize = vi.fn();
20
+
21
+ cleanup = setupEventListener(resizeHandle, startResize, handleResize, stopResize, blurResize);
22
+ });
23
+
24
+ afterEach(() => {
25
+ cleanup();
26
+ document.body.removeChild(resizeHandle);
27
+ vi.clearAllMocks();
28
+ });
29
+
30
+ it("should call startResize on mousedown", () => {
31
+ const event = new MouseEvent("mousedown", { bubbles: true });
32
+ resizeHandle.dispatchEvent(event);
33
+
34
+ expect(startResize).toHaveBeenCalledTimes(1);
35
+ expect(startResize).toHaveBeenCalledWith(expect.any(MouseEvent));
36
+ });
37
+
38
+ it("should call handleResize on mousemove", () => {
39
+ const event = new MouseEvent("mousemove", { bubbles: true });
40
+ document.dispatchEvent(event);
41
+
42
+ expect(handleResize).toHaveBeenCalledTimes(1);
43
+ expect(handleResize).toHaveBeenCalledWith(expect.any(MouseEvent));
44
+ });
45
+
46
+ it("should call stopResize on mouseup", () => {
47
+ const event = new MouseEvent("mouseup", { bubbles: true });
48
+ document.dispatchEvent(event);
49
+
50
+ expect(stopResize).toHaveBeenCalledTimes(1);
51
+ expect(stopResize).toHaveBeenCalledWith(expect.any(MouseEvent));
52
+ });
53
+
54
+ it("should call blurResize on window blur", () => {
55
+ const event = new Event("blur");
56
+ window.dispatchEvent(event);
57
+
58
+ expect(blurResize).toHaveBeenCalledTimes(1);
59
+ });
60
+
61
+ it("should call blurResize on mouseleave when leaving window", () => {
62
+ const event = new MouseEvent("mouseleave", {
63
+ bubbles: true,
64
+ relatedTarget: null,
65
+ });
66
+ Object.defineProperty(event, "target", { value: document, writable: false });
67
+ document.dispatchEvent(event);
68
+
69
+ expect(blurResize).toHaveBeenCalledTimes(1);
70
+ });
71
+
72
+ it("should not call blurResize on mouseleave when relatedTarget exists", () => {
73
+ const relatedTarget = document.createElement("div");
74
+ const event = new MouseEvent("mouseleave", {
75
+ bubbles: true,
76
+ relatedTarget,
77
+ });
78
+ Object.defineProperty(event, "target", { value: document, writable: false });
79
+ document.dispatchEvent(event);
80
+
81
+ expect(blurResize).not.toHaveBeenCalled();
82
+ });
83
+
84
+ it("should remove all event listeners when cleanup is called", () => {
85
+ cleanup();
86
+
87
+ const mousedownEvent = new MouseEvent("mousedown", { bubbles: true });
88
+ resizeHandle.dispatchEvent(mousedownEvent);
89
+ expect(startResize).not.toHaveBeenCalled();
90
+
91
+ const mousemoveEvent = new MouseEvent("mousemove", { bubbles: true });
92
+ document.dispatchEvent(mousemoveEvent);
93
+ expect(handleResize).not.toHaveBeenCalled();
94
+
95
+ const mouseupEvent = new MouseEvent("mouseup", { bubbles: true });
96
+ document.dispatchEvent(mouseupEvent);
97
+ expect(stopResize).not.toHaveBeenCalled();
98
+
99
+ const blurEvent = new Event("blur");
100
+ window.dispatchEvent(blurEvent);
101
+ expect(blurResize).not.toHaveBeenCalled();
102
+ });
103
+ });
@@ -0,0 +1,77 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as getCanvasContainerOrBodyModule from "../../canvas/helpers/getCanvasContainerOrBody";
3
+ import * as getViewportDimensionsModule from "../../helpers/getViewportDimensions";
4
+ import { getViewportLabelOverlay } from "./getViewportLabelOverlay";
5
+
6
+ vi.mock("../../canvas/helpers/getCanvasContainerOrBody");
7
+ vi.mock("../../helpers/getViewportDimensions");
8
+
9
+ describe("getViewportLabelOverlay", () => {
10
+ let container: HTMLElement;
11
+
12
+ beforeEach(() => {
13
+ container = document.createElement("div");
14
+ document.body.appendChild(container);
15
+ vi.mocked(getCanvasContainerOrBodyModule.getCanvasContainerOrBody).mockReturnValue(container);
16
+ vi.mocked(getViewportDimensionsModule.getViewportDimensions).mockReturnValue({
17
+ width: 1920,
18
+ height: 1080,
19
+ });
20
+ });
21
+
22
+ afterEach(() => {
23
+ // Clean up any overlays
24
+ const overlays = container.querySelectorAll(".viewport-labels-overlay");
25
+ overlays.forEach((overlay) => {
26
+ overlay.remove();
27
+ });
28
+ document.body.removeChild(container);
29
+ vi.clearAllMocks();
30
+ });
31
+
32
+ it("should create a new SVG overlay if it doesn't exist", () => {
33
+ const overlay = getViewportLabelOverlay();
34
+
35
+ expect(overlay).toBeInstanceOf(SVGSVGElement);
36
+ expect(overlay.classList.contains("viewport-labels-overlay")).toBe(true);
37
+ expect(container.contains(overlay)).toBe(true);
38
+ });
39
+
40
+ it("should return existing overlay if it already exists", () => {
41
+ const overlay1 = getViewportLabelOverlay();
42
+ const overlay2 = getViewportLabelOverlay();
43
+
44
+ expect(overlay1).toBe(overlay2);
45
+ });
46
+
47
+ it("should set correct SVG attributes", () => {
48
+ const overlay = getViewportLabelOverlay();
49
+
50
+ expect(overlay.getAttribute("width")).toBe("1920");
51
+ expect(overlay.getAttribute("height")).toBe("1080");
52
+ });
53
+
54
+ it("should set correct CSS styles", () => {
55
+ const overlay = getViewportLabelOverlay();
56
+
57
+ expect(overlay.style.position).toBe("absolute");
58
+ expect(overlay.style.top).toBe("0px");
59
+ expect(overlay.style.left).toBe("0px");
60
+ expect(overlay.style.width).toBe("100vw");
61
+ expect(overlay.style.height).toBe("100vh");
62
+ expect(overlay.style.pointerEvents).toBe("none");
63
+ expect(overlay.style.zIndex).toBe("500");
64
+ });
65
+
66
+ it("should use viewport dimensions from getViewportDimensions", () => {
67
+ vi.mocked(getViewportDimensionsModule.getViewportDimensions).mockReturnValue({
68
+ width: 2560,
69
+ height: 1440,
70
+ });
71
+
72
+ const overlay = getViewportLabelOverlay();
73
+
74
+ expect(overlay.getAttribute("width")).toBe("2560");
75
+ expect(overlay.getAttribute("height")).toBe("1440");
76
+ });
77
+ });
@@ -1,7 +1,7 @@
1
1
  import { getCanvasContainerOrBody } from "../../canvas/helpers/getCanvasContainerOrBody";
2
2
  import { getViewportDimensions } from "../../helpers/getViewportDimensions";
3
3
 
4
- export const getViewportLabelsOverlay = (): SVGSVGElement => {
4
+ export const getViewportLabelOverlay = (): SVGSVGElement => {
5
5
  const container = getCanvasContainerOrBody();
6
6
 
7
7
  // Check if overlay already exists
@@ -30,3 +30,4 @@ export const getViewportLabelsOverlay = (): SVGSVGElement => {
30
30
 
31
31
  return overlay;
32
32
  };
33
+
@@ -0,0 +1,51 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getLabelPosition } from "./getLabelPosition";
3
+
4
+ describe("getLabelPosition", () => {
5
+ it("should parse translate transform with positive values", () => {
6
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
7
+ group.setAttribute("transform", "translate(100, 200)");
8
+
9
+ const result = getLabelPosition(group as unknown as SVGGElement);
10
+ expect(result).toEqual({ x: 100, y: 200 });
11
+ });
12
+
13
+ it("should parse translate transform with negative values", () => {
14
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
15
+ group.setAttribute("transform", "translate(-50, -100)");
16
+
17
+ const result = getLabelPosition(group as unknown as SVGGElement);
18
+ expect(result).toEqual({ x: -50, y: -100 });
19
+ });
20
+
21
+ it("should parse translate transform with decimal values", () => {
22
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
23
+ group.setAttribute("transform", "translate(123.45, 678.90)");
24
+
25
+ const result = getLabelPosition(group as unknown as SVGGElement);
26
+ expect(result).toEqual({ x: 123.45, y: 678.9 });
27
+ });
28
+
29
+ it("should return { x: 0, y: 0 } when transform is null", () => {
30
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
31
+
32
+ const result = getLabelPosition(group as unknown as SVGGElement);
33
+ expect(result).toEqual({ x: 0, y: 0 });
34
+ });
35
+
36
+ it("should return { x: 0, y: 0 } when transform doesn't match pattern", () => {
37
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
38
+ group.setAttribute("transform", "rotate(45)");
39
+
40
+ const result = getLabelPosition(group as unknown as SVGGElement);
41
+ expect(result).toEqual({ x: 0, y: 0 });
42
+ });
43
+
44
+ it("should handle transform with spaces", () => {
45
+ const group = document.createElementNS("http://www.w3.org/2000/svg", "g");
46
+ group.setAttribute("transform", "translate(100, 200)");
47
+
48
+ const result = getLabelPosition(group as unknown as SVGGElement);
49
+ expect(result).toEqual({ x: 100, y: 200 });
50
+ });
51
+ });
@@ -0,0 +1,59 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { getTransformValues } from "./getTransformValues";
3
+
4
+ describe("getTransformValues", () => {
5
+ it("should parse translate3d transform with positive values", () => {
6
+ const element = document.createElement("div");
7
+ element.style.transform = "translate3d(100px, 200px, 0px)";
8
+
9
+ const result = getTransformValues(element);
10
+ expect(result).toEqual({ x: 100, y: 200 });
11
+ });
12
+
13
+ it("should parse translate3d transform with negative values", () => {
14
+ const element = document.createElement("div");
15
+ element.style.transform = "translate3d(-50px, -100px, 0px)";
16
+
17
+ const result = getTransformValues(element);
18
+ expect(result).toEqual({ x: -50, y: -100 });
19
+ });
20
+
21
+ it("should parse translate3d transform with decimal values", () => {
22
+ const element = document.createElement("div");
23
+ element.style.transform = "translate3d(123.45px, 678.90px, 0px)";
24
+
25
+ const result = getTransformValues(element);
26
+ expect(result).toEqual({ x: 123.45, y: 678.9 });
27
+ });
28
+
29
+ it("should return { x: 0, y: 0 } when transform is empty", () => {
30
+ const element = document.createElement("div");
31
+
32
+ const result = getTransformValues(element);
33
+ expect(result).toEqual({ x: 0, y: 0 });
34
+ });
35
+
36
+ it("should return { x: 0, y: 0 } when transform doesn't match pattern", () => {
37
+ const element = document.createElement("div");
38
+ element.style.transform = "rotate(45deg)";
39
+
40
+ const result = getTransformValues(element);
41
+ expect(result).toEqual({ x: 0, y: 0 });
42
+ });
43
+
44
+ it("should handle transform with spaces", () => {
45
+ const element = document.createElement("div");
46
+ element.style.transform = "translate3d(100px, 200px, 0px)";
47
+
48
+ const result = getTransformValues(element);
49
+ expect(result).toEqual({ x: 100, y: 200 });
50
+ });
51
+
52
+ it("should handle non-zero z value", () => {
53
+ const element = document.createElement("div");
54
+ element.style.transform = "translate3d(100px, 200px, 50px)";
55
+
56
+ const result = getTransformValues(element);
57
+ expect(result).toEqual({ x: 100, y: 200 });
58
+ });
59
+ });
@@ -0,0 +1,53 @@
1
+ import { afterEach, describe, expect, it, vi } from "vitest";
2
+ import { getZoomValue } from "./getZoomValue";
3
+
4
+ describe("getZoomValue", () => {
5
+ afterEach(() => {
6
+ // Reset CSS custom property
7
+ document.body.style.setProperty("--zoom", "");
8
+ });
9
+
10
+ it("should return 1 when --zoom is not set", () => {
11
+ document.body.style.removeProperty("--zoom");
12
+ const result = getZoomValue();
13
+ expect(result).toBe(1);
14
+ });
15
+
16
+ it("should return parsed float value from --zoom CSS variable", () => {
17
+ document.body.style.setProperty("--zoom", "1.5");
18
+ const result = getZoomValue();
19
+ expect(result).toBe(1.5);
20
+ });
21
+
22
+ it("should handle integer zoom values", () => {
23
+ document.body.style.setProperty("--zoom", "2");
24
+ const result = getZoomValue();
25
+ expect(result).toBe(2);
26
+ });
27
+
28
+ it("should handle decimal zoom values", () => {
29
+ document.body.style.setProperty("--zoom", "0.75");
30
+ const result = getZoomValue();
31
+ expect(result).toBe(0.75);
32
+ });
33
+
34
+ it("should trim whitespace from zoom value", () => {
35
+ // Note: CSS custom properties don't typically have whitespace, but we test the trim logic
36
+ // We'll mock getComputedStyle to return a value with whitespace
37
+ const mockGetComputedStyle = vi.spyOn(window, "getComputedStyle");
38
+ mockGetComputedStyle.mockReturnValue({
39
+ getPropertyValue: () => " 1.5 ",
40
+ } as unknown as CSSStyleDeclaration);
41
+
42
+ const result = getZoomValue();
43
+ expect(result).toBe(1.5);
44
+
45
+ mockGetComputedStyle.mockRestore();
46
+ });
47
+
48
+ it("should return 1 when --zoom is empty string", () => {
49
+ document.body.style.setProperty("--zoom", "");
50
+ const result = getZoomValue();
51
+ expect(result).toBe(1);
52
+ });
53
+ });