@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,97 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as toggleClassModule from "@/lib/helpers/toggleClass";
3
+ import * as createTagLabelModule from "./createTagLabel";
4
+ import { createToolsContainer } from "./createToolsContainer";
5
+
6
+ vi.mock("@/lib/helpers/toggleClass");
7
+ vi.mock("./createTagLabel");
8
+
9
+ describe("createToolsContainer", () => {
10
+ let node: HTMLElement;
11
+ let highlightFrame: HTMLElement;
12
+
13
+ beforeEach(() => {
14
+ node = document.createElement("div");
15
+ node.setAttribute("data-node-id", "test-node");
16
+ highlightFrame = document.createElement("div");
17
+
18
+ vi.mocked(toggleClassModule.toggleClass).mockImplementation(() => {});
19
+ vi.mocked(createTagLabelModule.createTagLabel).mockImplementation(() => {});
20
+ });
21
+
22
+ afterEach(() => {
23
+ vi.clearAllMocks();
24
+ });
25
+
26
+ it("should create node-tools element", () => {
27
+ createToolsContainer(node, highlightFrame);
28
+
29
+ const nodeTools = highlightFrame.querySelector(".node-tools");
30
+ expect(nodeTools).not.toBeNull();
31
+ expect(nodeTools?.className).toBe("node-tools");
32
+ });
33
+
34
+ it("should append node-tools to highlight frame", () => {
35
+ createToolsContainer(node, highlightFrame);
36
+
37
+ const nodeTools = highlightFrame.querySelector(".node-tools");
38
+ expect(nodeTools?.parentNode).toBe(highlightFrame);
39
+ });
40
+
41
+ it("should toggle is-instance class when isInstance is true", () => {
42
+ createToolsContainer(node, highlightFrame, true, false);
43
+
44
+ expect(toggleClassModule.toggleClass).toHaveBeenCalledWith(
45
+ expect.any(HTMLElement),
46
+ "is-instance",
47
+ true
48
+ );
49
+ });
50
+
51
+ it("should toggle is-text-edit class when isTextEdit is true", () => {
52
+ createToolsContainer(node, highlightFrame, false, true);
53
+
54
+ expect(toggleClassModule.toggleClass).toHaveBeenCalledWith(
55
+ expect.any(HTMLElement),
56
+ "is-text-edit",
57
+ true
58
+ );
59
+ });
60
+
61
+ it("should toggle both classes when both flags are true", () => {
62
+ createToolsContainer(node, highlightFrame, true, true);
63
+
64
+ expect(toggleClassModule.toggleClass).toHaveBeenCalledTimes(2);
65
+ });
66
+
67
+ it("should not toggle classes when both flags are false", () => {
68
+ createToolsContainer(node, highlightFrame, false, false);
69
+
70
+ expect(toggleClassModule.toggleClass).toHaveBeenCalledTimes(2);
71
+ expect(toggleClassModule.toggleClass).toHaveBeenCalledWith(
72
+ expect.any(HTMLElement),
73
+ "is-instance",
74
+ false
75
+ );
76
+ expect(toggleClassModule.toggleClass).toHaveBeenCalledWith(
77
+ expect.any(HTMLElement),
78
+ "is-text-edit",
79
+ false
80
+ );
81
+ });
82
+
83
+ it("should create tag label", () => {
84
+ createToolsContainer(node, highlightFrame);
85
+
86
+ const nodeTools = highlightFrame.querySelector(".node-tools");
87
+ expect(createTagLabelModule.createTagLabel).toHaveBeenCalledWith(node, nodeTools as HTMLElement);
88
+ });
89
+
90
+ it("should pass correct nodeTools element to createTagLabel", () => {
91
+ createToolsContainer(node, highlightFrame);
92
+
93
+ const nodeTools = highlightFrame.querySelector(".node-tools");
94
+ expect(createTagLabelModule.createTagLabel).toHaveBeenCalledWith(node, nodeTools as HTMLElement);
95
+ });
96
+ });
97
+
@@ -0,0 +1,158 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as adjustForZoomModule from "@/lib/helpers/adjustForZoom";
3
+ import * as getCanvasWindowValueModule from "@/lib/canvas/helpers/getCanvasWindowValue";
4
+ import { getElementBounds } from "./getElementBounds";
5
+
6
+ vi.mock("@/lib/helpers/adjustForZoom");
7
+ vi.mock("@/lib/canvas/helpers/getCanvasWindowValue");
8
+
9
+ describe("getElementBounds", () => {
10
+ let element: HTMLElement;
11
+ let nodeProvider: HTMLElement;
12
+
13
+ beforeEach(() => {
14
+ nodeProvider = document.createElement("div");
15
+ nodeProvider.style.position = "relative";
16
+ nodeProvider.style.left = "100px";
17
+ nodeProvider.style.top = "200px";
18
+ document.body.appendChild(nodeProvider);
19
+
20
+ element = document.createElement("div");
21
+ element.style.position = "absolute";
22
+ element.style.left = "50px";
23
+ element.style.top = "75px";
24
+ element.style.width = "200px";
25
+ element.style.height = "150px";
26
+ nodeProvider.appendChild(element);
27
+
28
+ vi.mocked(getCanvasWindowValueModule.getCanvasWindowValue).mockReturnValue(1);
29
+ vi.mocked(adjustForZoomModule.adjustForZoom).mockImplementation((value) => value);
30
+ });
31
+
32
+ afterEach(() => {
33
+ if (document.body.contains(nodeProvider)) {
34
+ document.body.removeChild(nodeProvider);
35
+ }
36
+ vi.clearAllMocks();
37
+ });
38
+
39
+ it("should calculate relative bounds", () => {
40
+ const result = getElementBounds(element, nodeProvider);
41
+
42
+ expect(result).toHaveProperty("top");
43
+ expect(result).toHaveProperty("left");
44
+ expect(result).toHaveProperty("width");
45
+ expect(result).toHaveProperty("height");
46
+ expect(typeof result.top).toBe("number");
47
+ expect(typeof result.left).toBe("number");
48
+ expect(typeof result.width).toBe("number");
49
+ expect(typeof result.height).toBe("number");
50
+ });
51
+
52
+ it("should adjust bounds for zoom", () => {
53
+ vi.mocked(getCanvasWindowValueModule.getCanvasWindowValue).mockReturnValue(2);
54
+ vi.mocked(adjustForZoomModule.adjustForZoom).mockImplementation((value) => value / 2);
55
+
56
+ const result = getElementBounds(element, nodeProvider);
57
+
58
+ expect(adjustForZoomModule.adjustForZoom).toHaveBeenCalled();
59
+ });
60
+
61
+ it("should use default zoom of 1 when zoom is undefined", () => {
62
+ vi.mocked(getCanvasWindowValueModule.getCanvasWindowValue).mockReturnValue(undefined);
63
+
64
+ getElementBounds(element, nodeProvider);
65
+
66
+ expect(adjustForZoomModule.adjustForZoom).toHaveBeenCalled();
67
+ });
68
+
69
+ it("should ensure minimum width of 4", () => {
70
+ element.style.width = "2px";
71
+ vi.mocked(adjustForZoomModule.adjustForZoom).mockImplementation((value) => {
72
+ // Return very small width
73
+ if (value < 10) return 2;
74
+ return value;
75
+ });
76
+
77
+ const result = getElementBounds(element, nodeProvider);
78
+
79
+ expect(result.width).toBeGreaterThanOrEqual(4);
80
+ });
81
+
82
+ it("should use custom canvas name", () => {
83
+ getElementBounds(element, nodeProvider, "custom-canvas");
84
+
85
+ expect(getCanvasWindowValueModule.getCanvasWindowValue).toHaveBeenCalledWith(["zoom", "current"], "custom-canvas");
86
+ });
87
+
88
+ it("should calculate relative position correctly", () => {
89
+ const elementRect = {
90
+ top: 275,
91
+ left: 150,
92
+ width: 200,
93
+ height: 150,
94
+ bottom: 425,
95
+ right: 350,
96
+ x: 150,
97
+ y: 275,
98
+ toJSON: vi.fn(),
99
+ };
100
+ const providerRect = {
101
+ top: 200,
102
+ left: 100,
103
+ width: 500,
104
+ height: 400,
105
+ bottom: 600,
106
+ right: 600,
107
+ x: 100,
108
+ y: 200,
109
+ toJSON: vi.fn(),
110
+ };
111
+
112
+ vi.spyOn(element, "getBoundingClientRect").mockReturnValue(elementRect as DOMRect);
113
+ vi.spyOn(nodeProvider, "getBoundingClientRect").mockReturnValue(providerRect as DOMRect);
114
+
115
+ const result = getElementBounds(element, nodeProvider);
116
+
117
+ // Relative top = 275 - 200 = 75
118
+ // Relative left = 150 - 100 = 50
119
+ expect(adjustForZoomModule.adjustForZoom).toHaveBeenCalledWith(75, expect.any(Number));
120
+ expect(adjustForZoomModule.adjustForZoom).toHaveBeenCalledWith(50, expect.any(Number));
121
+ });
122
+
123
+ it("should handle negative relative positions", () => {
124
+ const elementRect = {
125
+ top: 150,
126
+ left: 50,
127
+ width: 200,
128
+ height: 150,
129
+ bottom: 300,
130
+ right: 250,
131
+ x: 50,
132
+ y: 150,
133
+ toJSON: vi.fn(),
134
+ };
135
+ const providerRect = {
136
+ top: 200,
137
+ left: 100,
138
+ width: 500,
139
+ height: 400,
140
+ bottom: 600,
141
+ right: 600,
142
+ x: 100,
143
+ y: 200,
144
+ toJSON: vi.fn(),
145
+ };
146
+
147
+ vi.spyOn(element, "getBoundingClientRect").mockReturnValue(elementRect as DOMRect);
148
+ vi.spyOn(nodeProvider, "getBoundingClientRect").mockReturnValue(providerRect as DOMRect);
149
+
150
+ const result = getElementBounds(element, nodeProvider);
151
+
152
+ // Relative top = 150 - 200 = -50
153
+ // Relative left = 50 - 100 = -50
154
+ expect(adjustForZoomModule.adjustForZoom).toHaveBeenCalledWith(-50, expect.any(Number));
155
+ expect(adjustForZoomModule.adjustForZoom).toHaveBeenCalledWith(-50, expect.any(Number));
156
+ });
157
+ });
158
+
@@ -0,0 +1,78 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as getCanvasContainerOrBodyModule from "@/lib/canvas/helpers/getCanvasContainerOrBody";
3
+ import { getHighlightFrameElement } from "./getHighlightFrameElement";
4
+
5
+ vi.mock("@/lib/canvas/helpers/getCanvasContainerOrBody");
6
+
7
+ describe("getHighlightFrameElement", () => {
8
+ let container: HTMLElement;
9
+ let highlightFrame: SVGSVGElement;
10
+
11
+ beforeEach(() => {
12
+ container = document.createElement("div");
13
+ document.body.appendChild(container);
14
+
15
+ highlightFrame = document.createElementNS("http://www.w3.org/2000/svg", "svg");
16
+ highlightFrame.classList.add("highlight-frame-overlay");
17
+ });
18
+
19
+ afterEach(() => {
20
+ if (document.body.contains(container)) {
21
+ document.body.removeChild(container);
22
+ }
23
+ vi.clearAllMocks();
24
+ });
25
+
26
+ it("should return highlight frame element when it exists", () => {
27
+ container.appendChild(highlightFrame);
28
+ vi.mocked(getCanvasContainerOrBodyModule.getCanvasContainerOrBody).mockReturnValue(container);
29
+
30
+ const result = getHighlightFrameElement();
31
+
32
+ expect(result).toBe(highlightFrame);
33
+ });
34
+
35
+ it("should return null when highlight frame does not exist", () => {
36
+ vi.mocked(getCanvasContainerOrBodyModule.getCanvasContainerOrBody).mockReturnValue(container);
37
+
38
+ const result = getHighlightFrameElement();
39
+
40
+ expect(result).toBeNull();
41
+ });
42
+
43
+ it("should return first highlight frame when multiple exist", () => {
44
+ const frame1 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
45
+ frame1.classList.add("highlight-frame-overlay");
46
+ const frame2 = document.createElementNS("http://www.w3.org/2000/svg", "svg");
47
+ frame2.classList.add("highlight-frame-overlay");
48
+ container.appendChild(frame1);
49
+ container.appendChild(frame2);
50
+ vi.mocked(getCanvasContainerOrBodyModule.getCanvasContainerOrBody).mockReturnValue(container);
51
+
52
+ const result = getHighlightFrameElement();
53
+
54
+ expect(result).toBe(frame1);
55
+ });
56
+
57
+ it("should return null after frame is removed", () => {
58
+ container.appendChild(highlightFrame);
59
+ vi.mocked(getCanvasContainerOrBodyModule.getCanvasContainerOrBody).mockReturnValue(container);
60
+
61
+ const result1 = getHighlightFrameElement();
62
+ expect(result1).toBe(highlightFrame);
63
+
64
+ container.removeChild(highlightFrame);
65
+ const result2 = getHighlightFrameElement();
66
+ expect(result2).toBeNull();
67
+ });
68
+
69
+ it("should return SVG element", () => {
70
+ container.appendChild(highlightFrame);
71
+ vi.mocked(getCanvasContainerOrBodyModule.getCanvasContainerOrBody).mockReturnValue(container);
72
+
73
+ const result = getHighlightFrameElement();
74
+
75
+ expect(result).toBeInstanceOf(SVGSVGElement);
76
+ });
77
+ });
78
+
@@ -0,0 +1,133 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { getScreenBounds } from "./getScreenBounds";
3
+
4
+ describe("getScreenBounds", () => {
5
+ let element: HTMLElement;
6
+
7
+ beforeEach(() => {
8
+ element = document.createElement("div");
9
+ document.body.appendChild(element);
10
+ });
11
+
12
+ afterEach(() => {
13
+ if (document.body.contains(element)) {
14
+ document.body.removeChild(element);
15
+ }
16
+ });
17
+
18
+ it("should return bounds from getBoundingClientRect", () => {
19
+ // Mock getBoundingClientRect
20
+ const mockRect = {
21
+ top: 100,
22
+ left: 200,
23
+ width: 300,
24
+ height: 400,
25
+ bottom: 500,
26
+ right: 500,
27
+ x: 200,
28
+ y: 100,
29
+ toJSON: vi.fn(),
30
+ };
31
+ vi.spyOn(element, "getBoundingClientRect").mockReturnValue(mockRect as DOMRect);
32
+
33
+ const result = getScreenBounds(element);
34
+
35
+ expect(result.top).toBe(100);
36
+ expect(result.left).toBe(200);
37
+ expect(result.width).toBe(300);
38
+ expect(result.height).toBe(400);
39
+ });
40
+
41
+ it("should handle zero dimensions", () => {
42
+ const mockRect = {
43
+ top: 0,
44
+ left: 0,
45
+ width: 0,
46
+ height: 0,
47
+ bottom: 0,
48
+ right: 0,
49
+ x: 0,
50
+ y: 0,
51
+ toJSON: vi.fn(),
52
+ };
53
+ vi.spyOn(element, "getBoundingClientRect").mockReturnValue(mockRect as DOMRect);
54
+
55
+ const result = getScreenBounds(element);
56
+
57
+ expect(result.top).toBe(0);
58
+ expect(result.left).toBe(0);
59
+ expect(result.width).toBe(0);
60
+ expect(result.height).toBe(0);
61
+ });
62
+
63
+ it("should handle negative positions", () => {
64
+ const mockRect = {
65
+ top: -50,
66
+ left: -100,
67
+ width: 200,
68
+ height: 150,
69
+ bottom: 100,
70
+ right: 100,
71
+ x: -100,
72
+ y: -50,
73
+ toJSON: vi.fn(),
74
+ };
75
+ vi.spyOn(element, "getBoundingClientRect").mockReturnValue(mockRect as DOMRect);
76
+
77
+ const result = getScreenBounds(element);
78
+
79
+ expect(result.top).toBe(-50);
80
+ expect(result.left).toBe(-100);
81
+ expect(result.width).toBe(200);
82
+ expect(result.height).toBe(150);
83
+ });
84
+
85
+ it("should handle decimal values", () => {
86
+ const mockRect = {
87
+ top: 123.45,
88
+ left: 678.90,
89
+ width: 234.56,
90
+ height: 789.01,
91
+ bottom: 912.46,
92
+ right: 913.46,
93
+ x: 678.90,
94
+ y: 123.45,
95
+ toJSON: vi.fn(),
96
+ };
97
+ vi.spyOn(element, "getBoundingClientRect").mockReturnValue(mockRect as DOMRect);
98
+
99
+ const result = getScreenBounds(element);
100
+
101
+ expect(result.top).toBe(123.45);
102
+ expect(result.left).toBe(678.9);
103
+ expect(result.width).toBe(234.56);
104
+ expect(result.height).toBe(789.01);
105
+ });
106
+
107
+ it("should return object with all required properties", () => {
108
+ const mockRect = {
109
+ top: 100,
110
+ left: 200,
111
+ width: 300,
112
+ height: 400,
113
+ bottom: 500,
114
+ right: 500,
115
+ x: 200,
116
+ y: 100,
117
+ toJSON: vi.fn(),
118
+ };
119
+ vi.spyOn(element, "getBoundingClientRect").mockReturnValue(mockRect as DOMRect);
120
+
121
+ const result = getScreenBounds(element);
122
+
123
+ expect(result).toHaveProperty("top");
124
+ expect(result).toHaveProperty("left");
125
+ expect(result).toHaveProperty("width");
126
+ expect(result).toHaveProperty("height");
127
+ expect(typeof result.top).toBe("number");
128
+ expect(typeof result.left).toBe("number");
129
+ expect(typeof result.width).toBe("number");
130
+ expect(typeof result.height).toBe("number");
131
+ });
132
+ });
133
+
@@ -0,0 +1,213 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as getCanvasContainerOrBodyModule from "@/lib/canvas/helpers/getCanvasContainerOrBody";
3
+ import * as toggleClassModule from "@/lib/helpers/toggleClass";
4
+ import * as isComponentInstanceModule from "../select/helpers/isComponentInstance";
5
+ import * as createHighlightFrameModule from "./createHighlightFrame";
6
+ import * as createToolsContainerModule from "./createToolsContainer";
7
+ import * as getHighlightFrameElementModule from "./helpers/getHighlightFrameElement";
8
+ import * as getScreenBoundsModule from "./helpers/getScreenBounds";
9
+ import { highlightNode } from "./highlightNode";
10
+
11
+ vi.mock("@/lib/canvas/helpers/getCanvasContainerOrBody");
12
+ vi.mock("@/lib/helpers/toggleClass");
13
+ vi.mock("../select/helpers/isComponentInstance");
14
+ vi.mock("./createHighlightFrame");
15
+ vi.mock("./createToolsContainer");
16
+ vi.mock("./helpers/getHighlightFrameElement");
17
+ vi.mock("./helpers/getScreenBounds");
18
+
19
+ describe("highlightNode", () => {
20
+ let node: HTMLElement;
21
+ let container: HTMLElement;
22
+ let existingFrame: SVGSVGElement;
23
+ let existingToolsWrapper: HTMLElement;
24
+ let newFrame: SVGSVGElement;
25
+
26
+ beforeEach(() => {
27
+ node = document.createElement("div");
28
+ node.setAttribute("data-node-id", "test-node");
29
+ document.body.appendChild(node);
30
+
31
+ container = document.createElement("div");
32
+ document.body.appendChild(container);
33
+
34
+ existingFrame = document.createElementNS("http://www.w3.org/2000/svg", "svg");
35
+ existingFrame.classList.add("highlight-frame-overlay");
36
+ container.appendChild(existingFrame);
37
+
38
+ existingToolsWrapper = document.createElement("div");
39
+ existingToolsWrapper.classList.add("highlight-frame-tools-wrapper");
40
+ container.appendChild(existingToolsWrapper);
41
+
42
+ newFrame = document.createElementNS("http://www.w3.org/2000/svg", "svg");
43
+ newFrame.classList.add("highlight-frame-overlay");
44
+
45
+ vi.mocked(getCanvasContainerOrBodyModule.getCanvasContainerOrBody).mockReturnValue(container);
46
+ vi.mocked(getHighlightFrameElementModule.getHighlightFrameElement).mockReturnValue(existingFrame);
47
+ vi.mocked(isComponentInstanceModule.isComponentInstance).mockReturnValue(false);
48
+ vi.mocked(getScreenBoundsModule.getScreenBounds).mockReturnValue({
49
+ top: 100,
50
+ left: 200,
51
+ width: 300,
52
+ height: 400,
53
+ });
54
+ vi.mocked(createHighlightFrameModule.createHighlightFrame).mockReturnValue(newFrame);
55
+ vi.mocked(toggleClassModule.toggleClass).mockImplementation(() => {});
56
+ vi.mocked(createToolsContainerModule.createToolsContainer).mockImplementation(() => {});
57
+ });
58
+
59
+ afterEach(() => {
60
+ if (document.body.contains(node)) {
61
+ document.body.removeChild(node);
62
+ }
63
+ if (document.body.contains(container)) {
64
+ document.body.removeChild(container);
65
+ }
66
+ vi.clearAllMocks();
67
+ });
68
+
69
+ it("should do nothing when node is null", () => {
70
+ highlightNode(null);
71
+
72
+ expect(createHighlightFrameModule.createHighlightFrame).not.toHaveBeenCalled();
73
+ });
74
+
75
+ it("should remove existing highlight frame", () => {
76
+ highlightNode(node);
77
+
78
+ expect(existingFrame.parentNode).toBeNull();
79
+ });
80
+
81
+ it("should remove existing tools wrapper", () => {
82
+ highlightNode(node);
83
+
84
+ expect(existingToolsWrapper.parentNode).toBeNull();
85
+ });
86
+
87
+ it("should create new highlight frame", () => {
88
+ highlightNode(node);
89
+
90
+ expect(createHighlightFrameModule.createHighlightFrame).toHaveBeenCalledWith(node, false, false);
91
+ });
92
+
93
+ it("should create highlight frame with instance flag", () => {
94
+ vi.mocked(isComponentInstanceModule.isComponentInstance).mockReturnValue(true);
95
+
96
+ highlightNode(node);
97
+
98
+ expect(createHighlightFrameModule.createHighlightFrame).toHaveBeenCalledWith(node, true, false);
99
+ });
100
+
101
+ it("should create highlight frame with text edit flag", () => {
102
+ node.contentEditable = "true";
103
+
104
+ highlightNode(node);
105
+
106
+ expect(createHighlightFrameModule.createHighlightFrame).toHaveBeenCalledWith(node, false, true);
107
+ });
108
+
109
+ it("should add is-editable class when node is contentEditable", () => {
110
+ node.contentEditable = "true";
111
+
112
+ highlightNode(node);
113
+
114
+ expect(newFrame.classList.contains("is-editable")).toBe(true);
115
+ });
116
+
117
+ it("should create tools wrapper with correct styles", () => {
118
+ highlightNode(node);
119
+
120
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
121
+ expect(toolsWrapper).not.toBeNull();
122
+ expect((toolsWrapper as HTMLElement).style.position).toBe("absolute");
123
+ expect((toolsWrapper as HTMLElement).style.transform).toBe("translate(200px, 500px)");
124
+ expect((toolsWrapper as HTMLElement).style.transformOrigin).toBe("left center");
125
+ expect((toolsWrapper as HTMLElement).style.pointerEvents).toBe("none");
126
+ expect((toolsWrapper as HTMLElement).style.zIndex).toBe("500");
127
+ });
128
+
129
+ it("should toggle classes on tools wrapper", () => {
130
+ highlightNode(node);
131
+
132
+ expect(toggleClassModule.toggleClass).toHaveBeenCalled();
133
+ });
134
+
135
+ it("should create tools container", () => {
136
+ highlightNode(node);
137
+
138
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
139
+ expect(createToolsContainerModule.createToolsContainer).toHaveBeenCalledWith(
140
+ node,
141
+ toolsWrapper as HTMLElement,
142
+ false,
143
+ false
144
+ );
145
+ });
146
+
147
+ it("should append tools wrapper to container", () => {
148
+ highlightNode(node);
149
+
150
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
151
+ expect(toolsWrapper?.parentNode).toBe(container);
152
+ });
153
+
154
+ it("should handle instance node", () => {
155
+ vi.mocked(isComponentInstanceModule.isComponentInstance).mockReturnValue(true);
156
+
157
+ highlightNode(node);
158
+
159
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
160
+ expect(createToolsContainerModule.createToolsContainer).toHaveBeenCalledWith(
161
+ node,
162
+ toolsWrapper as HTMLElement,
163
+ true,
164
+ false
165
+ );
166
+ });
167
+
168
+ it("should handle text edit node", () => {
169
+ node.contentEditable = "true";
170
+
171
+ highlightNode(node);
172
+
173
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
174
+ expect(createToolsContainerModule.createToolsContainer).toHaveBeenCalledWith(
175
+ node,
176
+ toolsWrapper as HTMLElement,
177
+ false,
178
+ true
179
+ );
180
+ });
181
+
182
+ it("should calculate bottomY correctly", () => {
183
+ vi.mocked(getScreenBoundsModule.getScreenBounds).mockReturnValue({
184
+ top: 50,
185
+ left: 100,
186
+ width: 200,
187
+ height: 150,
188
+ });
189
+
190
+ highlightNode(node);
191
+
192
+ const toolsWrapper = container.querySelector(".highlight-frame-tools-wrapper");
193
+ expect((toolsWrapper as HTMLElement).style.transform).toBe("translate(100px, 200px)");
194
+ });
195
+
196
+ it("should work when no existing frame exists", () => {
197
+ vi.mocked(getHighlightFrameElementModule.getHighlightFrameElement).mockReturnValue(null);
198
+ container.removeChild(existingFrame);
199
+
200
+ expect(() => {
201
+ highlightNode(node);
202
+ }).not.toThrow();
203
+ });
204
+
205
+ it("should work when no existing tools wrapper exists", () => {
206
+ container.removeChild(existingToolsWrapper);
207
+
208
+ expect(() => {
209
+ highlightNode(node);
210
+ }).not.toThrow();
211
+ });
212
+ });
213
+