@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,133 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as setupKeydownHandlerModule from "./setupKeydownHandler";
3
+ import * as setupMutationObserverModule from "./setupMutationObserver";
4
+ import { setupNodeListeners } from "./setupNodeListeners";
5
+
6
+ vi.mock("./setupKeydownHandler");
7
+ vi.mock("./setupMutationObserver");
8
+
9
+ describe("setupNodeListeners", () => {
10
+ let node: HTMLElement;
11
+ let nodeProvider: HTMLElement;
12
+ let blurHandler: ReturnType<typeof vi.fn>;
13
+ let keydownCleanup: ReturnType<typeof vi.fn>;
14
+ let mutationCleanup: ReturnType<typeof vi.fn>;
15
+
16
+ beforeEach(() => {
17
+ node = document.createElement("div");
18
+ node.contentEditable = "true";
19
+ document.body.appendChild(node);
20
+
21
+ nodeProvider = document.createElement("div");
22
+ document.body.appendChild(nodeProvider);
23
+
24
+ blurHandler = vi.fn();
25
+ keydownCleanup = vi.fn();
26
+ mutationCleanup = vi.fn();
27
+
28
+ vi.mocked(setupKeydownHandlerModule.setupKeydownHandler).mockReturnValue(keydownCleanup);
29
+ vi.mocked(setupMutationObserverModule.setupMutationObserver).mockReturnValue(mutationCleanup);
30
+ });
31
+
32
+ afterEach(() => {
33
+ if (document.body.contains(node)) {
34
+ document.body.removeChild(node);
35
+ }
36
+ if (document.body.contains(nodeProvider)) {
37
+ document.body.removeChild(nodeProvider);
38
+ }
39
+ vi.clearAllMocks();
40
+ });
41
+
42
+ it("should return cleanup function", () => {
43
+ const cleanup = setupNodeListeners(node, nodeProvider, blurHandler);
44
+
45
+ expect(typeof cleanup).toBe("function");
46
+ });
47
+
48
+ it("should return no-op cleanup when nodeProvider is null", () => {
49
+ const cleanup = setupNodeListeners(node, null, blurHandler);
50
+
51
+ expect(typeof cleanup).toBe("function");
52
+ expect(setupKeydownHandlerModule.setupKeydownHandler).not.toHaveBeenCalled();
53
+ expect(setupMutationObserverModule.setupMutationObserver).not.toHaveBeenCalled();
54
+ });
55
+
56
+ it("should setup blur listener", () => {
57
+ setupNodeListeners(node, nodeProvider, blurHandler);
58
+
59
+ const blurEvent = new FocusEvent("blur");
60
+ node.dispatchEvent(blurEvent);
61
+
62
+ expect(blurHandler).toHaveBeenCalled();
63
+ });
64
+
65
+ it("should setup keydown handler", () => {
66
+ setupNodeListeners(node, nodeProvider, blurHandler);
67
+
68
+ expect(setupKeydownHandlerModule.setupKeydownHandler).toHaveBeenCalledWith(node);
69
+ });
70
+
71
+ it("should setup mutation observer", () => {
72
+ setupNodeListeners(node, nodeProvider, blurHandler);
73
+
74
+ expect(setupMutationObserverModule.setupMutationObserver).toHaveBeenCalledWith(node, nodeProvider, "canvas");
75
+ });
76
+
77
+ it("should use custom canvas name", () => {
78
+ setupNodeListeners(node, nodeProvider, blurHandler, "custom-canvas");
79
+
80
+ expect(setupMutationObserverModule.setupMutationObserver).toHaveBeenCalledWith(node, nodeProvider, "custom-canvas");
81
+ });
82
+
83
+ it("should remove blur listener on cleanup", () => {
84
+ const cleanup = setupNodeListeners(node, nodeProvider, blurHandler);
85
+
86
+ cleanup();
87
+
88
+ const blurEvent = new FocusEvent("blur");
89
+ node.dispatchEvent(blurEvent);
90
+
91
+ expect(blurHandler).not.toHaveBeenCalled();
92
+ });
93
+
94
+ it("should call keydown cleanup on cleanup", () => {
95
+ const cleanup = setupNodeListeners(node, nodeProvider, blurHandler);
96
+
97
+ cleanup();
98
+
99
+ expect(keydownCleanup).toHaveBeenCalled();
100
+ });
101
+
102
+ it("should call mutation cleanup on cleanup", () => {
103
+ const cleanup = setupNodeListeners(node, nodeProvider, blurHandler);
104
+
105
+ cleanup();
106
+
107
+ expect(mutationCleanup).toHaveBeenCalled();
108
+ });
109
+
110
+ it("should handle cleanup when mutation cleanup is undefined", () => {
111
+ vi.mocked(setupMutationObserverModule.setupMutationObserver).mockReturnValue(undefined);
112
+
113
+ const cleanup = setupNodeListeners(node, nodeProvider, blurHandler);
114
+
115
+ expect(() => {
116
+ cleanup();
117
+ }).not.toThrow();
118
+ });
119
+
120
+ it("should cleanup all listeners", () => {
121
+ const cleanup = setupNodeListeners(node, nodeProvider, blurHandler);
122
+
123
+ cleanup();
124
+
125
+ expect(keydownCleanup).toHaveBeenCalled();
126
+ expect(mutationCleanup).toHaveBeenCalled();
127
+
128
+ const blurEvent = new FocusEvent("blur");
129
+ node.dispatchEvent(blurEvent);
130
+ expect(blurHandler).not.toHaveBeenCalled();
131
+ });
132
+ });
133
+
@@ -0,0 +1,50 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import type { NodeText } from "../types";
3
+ import { enterTextEditMode } from "./enterTextEditMode";
4
+
5
+ describe("enterTextEditMode", () => {
6
+ let node: HTMLElement;
7
+ let nodeProvider: HTMLElement;
8
+ let mockText: NodeText;
9
+
10
+ beforeEach(() => {
11
+ node = document.createElement("div");
12
+ nodeProvider = document.createElement("div");
13
+ nodeProvider.setAttribute("data-role", "node-provider");
14
+
15
+ mockText = {
16
+ enableEditMode: vi.fn(),
17
+ blurEditMode: vi.fn(),
18
+ getEditableNode: vi.fn(),
19
+ isEditing: vi.fn(),
20
+ };
21
+ });
22
+
23
+ afterEach(() => {
24
+ vi.clearAllMocks();
25
+ });
26
+
27
+ it("should call enableEditMode when node and nodeProvider are provided", () => {
28
+ enterTextEditMode(node, nodeProvider, mockText);
29
+
30
+ expect(mockText.enableEditMode).toHaveBeenCalledWith(node, nodeProvider);
31
+ });
32
+
33
+ it("should not call enableEditMode when node is null", () => {
34
+ enterTextEditMode(null as unknown as HTMLElement, nodeProvider, mockText);
35
+
36
+ expect(mockText.enableEditMode).not.toHaveBeenCalled();
37
+ });
38
+
39
+ it("should not call enableEditMode when nodeProvider is null", () => {
40
+ enterTextEditMode(node, null, mockText);
41
+
42
+ expect(mockText.enableEditMode).not.toHaveBeenCalled();
43
+ });
44
+
45
+ it("should not call enableEditMode when both are null", () => {
46
+ enterTextEditMode(null as unknown as HTMLElement, null, mockText);
47
+
48
+ expect(mockText.enableEditMode).not.toHaveBeenCalled();
49
+ });
50
+ });
@@ -0,0 +1,201 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import * as sendPostMessageModule from "../../../post-message/sendPostMessage";
3
+ import { handleTextChange } from "./handleTextChange";
4
+
5
+ vi.mock("../../../post-message/sendPostMessage");
6
+
7
+ describe("handleTextChange", () => {
8
+ let node: HTMLElement;
9
+
10
+ beforeEach(() => {
11
+ node = document.createElement("div");
12
+ node.setAttribute("data-node-id", "test-node-1");
13
+ node.textContent = "Initial text";
14
+ document.body.appendChild(node);
15
+
16
+ vi.mocked(sendPostMessageModule.sendPostMessage).mockImplementation(() => {});
17
+ });
18
+
19
+ afterEach(() => {
20
+ if (document.body.contains(node)) {
21
+ document.body.removeChild(node);
22
+ }
23
+ vi.clearAllMocks();
24
+ });
25
+
26
+ it("should send postMessage when text content changes", () => {
27
+ node.textContent = "Updated text";
28
+ const mutations: MutationRecord[] = [
29
+ {
30
+ type: "characterData",
31
+ target: node.firstChild!,
32
+ addedNodes: [] as unknown as NodeList,
33
+ removedNodes: [] as unknown as NodeList,
34
+ previousSibling: null,
35
+ nextSibling: null,
36
+ attributeName: null,
37
+ attributeNamespace: null,
38
+ oldValue: null,
39
+ } as MutationRecord,
40
+ ];
41
+
42
+ handleTextChange(node, mutations);
43
+
44
+ expect(sendPostMessageModule.sendPostMessage).toHaveBeenCalledWith("textContentChanged", {
45
+ nodeId: "test-node-1",
46
+ textContent: "Updated text",
47
+ final: false,
48
+ });
49
+ });
50
+
51
+ it("should send postMessage with final flag when final is true", () => {
52
+ node.textContent = "Final text";
53
+ const mutations: MutationRecord[] = [];
54
+
55
+ handleTextChange(node, mutations, true);
56
+
57
+ expect(sendPostMessageModule.sendPostMessage).toHaveBeenCalledWith("textContentChanged", {
58
+ nodeId: "test-node-1",
59
+ textContent: "Final text",
60
+ final: true,
61
+ });
62
+ });
63
+
64
+ it("should not send postMessage when no text change and final is false", () => {
65
+ const mutations: MutationRecord[] = [
66
+ {
67
+ type: "attributes",
68
+ target: node,
69
+ addedNodes: [] as unknown as NodeList,
70
+ removedNodes: [] as unknown as NodeList,
71
+ previousSibling: null,
72
+ nextSibling: null,
73
+ attributeName: "class",
74
+ attributeNamespace: null,
75
+ oldValue: null,
76
+ } as MutationRecord,
77
+ ];
78
+
79
+ handleTextChange(node, mutations, false);
80
+
81
+ expect(sendPostMessageModule.sendPostMessage).not.toHaveBeenCalled();
82
+ });
83
+
84
+ it("should send postMessage when mutations include childList changes", () => {
85
+ const newChild = document.createTextNode("New child");
86
+ const mutations: MutationRecord[] = [
87
+ {
88
+ type: "childList",
89
+ target: node,
90
+ addedNodes: [newChild] as unknown as NodeList,
91
+ removedNodes: [] as unknown as NodeList,
92
+ previousSibling: null,
93
+ nextSibling: null,
94
+ attributeName: null,
95
+ attributeNamespace: null,
96
+ oldValue: null,
97
+ } as MutationRecord,
98
+ ];
99
+
100
+ handleTextChange(node, mutations);
101
+
102
+ expect(sendPostMessageModule.sendPostMessage).toHaveBeenCalled();
103
+ });
104
+
105
+ it("should send postMessage when mutations include removed nodes", () => {
106
+ const removedChild = document.createTextNode("Removed");
107
+ const mutations: MutationRecord[] = [
108
+ {
109
+ type: "childList",
110
+ target: node,
111
+ addedNodes: [] as unknown as NodeList,
112
+ removedNodes: [removedChild] as unknown as NodeList,
113
+ previousSibling: null,
114
+ nextSibling: null,
115
+ attributeName: null,
116
+ attributeNamespace: null,
117
+ oldValue: null,
118
+ } as MutationRecord,
119
+ ];
120
+
121
+ handleTextChange(node, mutations);
122
+
123
+ expect(sendPostMessageModule.sendPostMessage).toHaveBeenCalled();
124
+ });
125
+
126
+ it("should handle empty textContent", () => {
127
+ node.textContent = "";
128
+ const mutations: MutationRecord[] = [
129
+ {
130
+ type: "characterData",
131
+ target: node.firstChild!,
132
+ addedNodes: [] as unknown as NodeList,
133
+ removedNodes: [] as unknown as NodeList,
134
+ previousSibling: null,
135
+ nextSibling: null,
136
+ attributeName: null,
137
+ attributeNamespace: null,
138
+ oldValue: null,
139
+ } as MutationRecord,
140
+ ];
141
+
142
+ handleTextChange(node, mutations);
143
+
144
+ expect(sendPostMessageModule.sendPostMessage).toHaveBeenCalledWith("textContentChanged", {
145
+ nodeId: "test-node-1",
146
+ textContent: "",
147
+ final: false,
148
+ });
149
+ });
150
+
151
+ it("should handle null textContent", () => {
152
+ node.textContent = null as unknown as string;
153
+ const mutations: MutationRecord[] = [
154
+ {
155
+ type: "characterData",
156
+ target: node.firstChild!,
157
+ addedNodes: [] as unknown as NodeList,
158
+ removedNodes: [] as unknown as NodeList,
159
+ previousSibling: null,
160
+ nextSibling: null,
161
+ attributeName: null,
162
+ attributeNamespace: null,
163
+ oldValue: null,
164
+ } as MutationRecord,
165
+ ];
166
+
167
+ handleTextChange(node, mutations);
168
+
169
+ expect(sendPostMessageModule.sendPostMessage).toHaveBeenCalledWith("textContentChanged", {
170
+ nodeId: "test-node-1",
171
+ textContent: "",
172
+ final: false,
173
+ });
174
+ });
175
+
176
+ it("should handle missing data-node-id", () => {
177
+ node.removeAttribute("data-node-id");
178
+ node.textContent = "Text without id";
179
+ const mutations: MutationRecord[] = [
180
+ {
181
+ type: "characterData",
182
+ target: node.firstChild!,
183
+ addedNodes: [] as unknown as NodeList,
184
+ removedNodes: [] as unknown as NodeList,
185
+ previousSibling: null,
186
+ nextSibling: null,
187
+ attributeName: null,
188
+ attributeNamespace: null,
189
+ oldValue: null,
190
+ } as MutationRecord,
191
+ ];
192
+
193
+ handleTextChange(node, mutations);
194
+
195
+ expect(sendPostMessageModule.sendPostMessage).toHaveBeenCalledWith("textContentChanged", {
196
+ nodeId: null,
197
+ textContent: "Text without id",
198
+ final: false,
199
+ });
200
+ });
201
+ });
@@ -0,0 +1,101 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { hasTextContent } from "./hasTextContent";
3
+
4
+ describe("hasTextContent", () => {
5
+ let node: HTMLElement;
6
+
7
+ beforeEach(() => {
8
+ node = document.createElement("div");
9
+ });
10
+
11
+ afterEach(() => {
12
+ if (document.body.contains(node)) {
13
+ document.body.removeChild(node);
14
+ }
15
+ });
16
+
17
+ it("should return true when node has text content", () => {
18
+ node.textContent = "Hello World";
19
+
20
+ const result = hasTextContent(node);
21
+
22
+ expect(result).toBe(true);
23
+ });
24
+
25
+ it("should return true when node has text node child", () => {
26
+ const textNode = document.createTextNode("Hello");
27
+ node.appendChild(textNode);
28
+
29
+ const result = hasTextContent(node);
30
+
31
+ expect(result).toBe(true);
32
+ });
33
+
34
+ it("should return false when node has no text content", () => {
35
+ const result = hasTextContent(node);
36
+
37
+ expect(result).toBe(false);
38
+ });
39
+
40
+ it("should return false when node only has whitespace", () => {
41
+ node.textContent = " \n\t ";
42
+
43
+ const result = hasTextContent(node);
44
+
45
+ expect(result).toBe(false);
46
+ });
47
+
48
+ it("should return true when node has text with whitespace", () => {
49
+ node.textContent = " Hello ";
50
+
51
+ const result = hasTextContent(node);
52
+
53
+ expect(result).toBe(true);
54
+ });
55
+
56
+ it("should return false when node only has element children", () => {
57
+ const child = document.createElement("span");
58
+ node.appendChild(child);
59
+
60
+ const result = hasTextContent(node);
61
+
62
+ expect(result).toBe(false);
63
+ });
64
+
65
+ it("should return true when node has both text and element children", () => {
66
+ node.textContent = "Hello";
67
+ const child = document.createElement("span");
68
+ node.appendChild(child);
69
+
70
+ const result = hasTextContent(node);
71
+
72
+ expect(result).toBe(true);
73
+ });
74
+
75
+ it("should return true when node has text node with content", () => {
76
+ const textNode = document.createTextNode("Test");
77
+ node.appendChild(textNode);
78
+
79
+ const result = hasTextContent(node);
80
+
81
+ expect(result).toBe(true);
82
+ });
83
+
84
+ it("should return false when node has empty text node", () => {
85
+ const textNode = document.createTextNode("");
86
+ node.appendChild(textNode);
87
+
88
+ const result = hasTextContent(node);
89
+
90
+ expect(result).toBe(false);
91
+ });
92
+
93
+ it("should return false when node has whitespace-only text node", () => {
94
+ const textNode = document.createTextNode(" ");
95
+ node.appendChild(textNode);
96
+
97
+ const result = hasTextContent(node);
98
+
99
+ expect(result).toBe(false);
100
+ });
101
+ });
@@ -0,0 +1,96 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { insertLineBreak } from "./insertLineBreak";
3
+
4
+ describe("insertLineBreak", () => {
5
+ let node: HTMLElement;
6
+
7
+ beforeEach(() => {
8
+ node = document.createElement("div");
9
+ node.textContent = "Hello World";
10
+ document.body.appendChild(node);
11
+ });
12
+
13
+ afterEach(() => {
14
+ if (document.body.contains(node)) {
15
+ document.body.removeChild(node);
16
+ }
17
+ });
18
+
19
+ it("should insert br element at cursor position", () => {
20
+ const range = document.createRange();
21
+ const selection = window.getSelection();
22
+
23
+ range.setStart(node.firstChild!, 5);
24
+ range.setEnd(node.firstChild!, 5);
25
+ selection?.removeAllRanges();
26
+ selection?.addRange(range);
27
+
28
+ insertLineBreak();
29
+
30
+ expect(node.innerHTML).toContain("<br>");
31
+ });
32
+
33
+ it("should delete contents at cursor position before inserting br", () => {
34
+ const range = document.createRange();
35
+ const selection = window.getSelection();
36
+
37
+ range.setStart(node.firstChild!, 5);
38
+ range.setEnd(node.firstChild!, 11); // Select "World"
39
+ selection?.removeAllRanges();
40
+ selection?.addRange(range);
41
+
42
+ insertLineBreak();
43
+
44
+ expect(node.innerHTML).toContain("<br>");
45
+ expect(node.textContent).not.toContain("World");
46
+ });
47
+
48
+ it("should move cursor after the br element", () => {
49
+ const range = document.createRange();
50
+ const selection = window.getSelection();
51
+
52
+ range.setStart(node.firstChild!, 5);
53
+ range.setEnd(node.firstChild!, 5);
54
+ selection?.removeAllRanges();
55
+ selection?.addRange(range);
56
+
57
+ insertLineBreak();
58
+
59
+ const newRange = selection?.getRangeAt(0);
60
+ expect(newRange?.startContainer).toBe(node);
61
+ expect(newRange?.startOffset).toBeGreaterThan(0);
62
+ });
63
+
64
+ it("should not throw when no selection exists", () => {
65
+ window.getSelection()?.removeAllRanges();
66
+
67
+ expect(() => {
68
+ insertLineBreak();
69
+ }).not.toThrow();
70
+ });
71
+
72
+ it("should not throw when selection has no ranges", () => {
73
+ const selection = window.getSelection();
74
+ selection?.removeAllRanges();
75
+
76
+ expect(() => {
77
+ insertLineBreak();
78
+ }).not.toThrow();
79
+ });
80
+
81
+ it("should work with empty node", () => {
82
+ node.textContent = "";
83
+ const range = document.createRange();
84
+ const selection = window.getSelection();
85
+
86
+ range.setStart(node, 0);
87
+ range.setEnd(node, 0);
88
+ selection?.removeAllRanges();
89
+ selection?.addRange(range);
90
+
91
+ expect(() => {
92
+ insertLineBreak();
93
+ }).not.toThrow();
94
+ expect(node.innerHTML).toContain("<br>");
95
+ });
96
+ });
@@ -0,0 +1,56 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { makeNodeEditable } from "./makeNodeEditable";
3
+
4
+ describe("makeNodeEditable", () => {
5
+ let node: HTMLElement;
6
+
7
+ beforeEach(() => {
8
+ node = document.createElement("div");
9
+ document.body.appendChild(node);
10
+ });
11
+
12
+ afterEach(() => {
13
+ if (document.body.contains(node)) {
14
+ document.body.removeChild(node);
15
+ }
16
+ });
17
+
18
+ it("should set contentEditable to true", () => {
19
+ makeNodeEditable(node);
20
+
21
+ expect(node.contentEditable).toBe("true");
22
+ });
23
+
24
+ it("should add is-editable class", () => {
25
+ makeNodeEditable(node);
26
+
27
+ expect(node.classList.contains("is-editable")).toBe(true);
28
+ });
29
+
30
+ it("should set outline to none", () => {
31
+ makeNodeEditable(node);
32
+
33
+ expect(node.style.outline).toBe("none");
34
+ });
35
+
36
+ it("should work on node that already has contentEditable set", () => {
37
+ node.contentEditable = "false";
38
+ makeNodeEditable(node);
39
+
40
+ expect(node.contentEditable).toBe("true");
41
+ });
42
+
43
+ it("should work on node that already has is-editable class", () => {
44
+ node.classList.add("is-editable");
45
+ makeNodeEditable(node);
46
+
47
+ expect(node.classList.contains("is-editable")).toBe(true);
48
+ });
49
+
50
+ it("should override existing outline style", () => {
51
+ node.style.outline = "2px solid red";
52
+ makeNodeEditable(node);
53
+
54
+ expect(node.style.outline).toBe("none");
55
+ });
56
+ });
@@ -0,0 +1,57 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { makeNodeNonEditable } from "./makeNodeNonEditable";
3
+
4
+ describe("makeNodeNonEditable", () => {
5
+ let node: HTMLElement;
6
+
7
+ beforeEach(() => {
8
+ node = document.createElement("div");
9
+ document.body.appendChild(node);
10
+ });
11
+
12
+ afterEach(() => {
13
+ if (document.body.contains(node)) {
14
+ document.body.removeChild(node);
15
+ }
16
+ });
17
+
18
+ it("should set contentEditable to false", () => {
19
+ node.contentEditable = "true";
20
+ makeNodeNonEditable(node);
21
+
22
+ expect(node.contentEditable).toBe("false");
23
+ });
24
+
25
+ it("should remove is-editable class", () => {
26
+ node.classList.add("is-editable");
27
+ makeNodeNonEditable(node);
28
+
29
+ expect(node.classList.contains("is-editable")).toBe(false);
30
+ });
31
+
32
+ it("should set outline to none", () => {
33
+ makeNodeNonEditable(node);
34
+
35
+ expect(node.style.outline).toBe("none");
36
+ });
37
+
38
+ it("should work on node that is not editable", () => {
39
+ node.contentEditable = "false";
40
+ makeNodeNonEditable(node);
41
+
42
+ expect(node.contentEditable).toBe("false");
43
+ });
44
+
45
+ it("should work on node that does not have is-editable class", () => {
46
+ makeNodeNonEditable(node);
47
+
48
+ expect(node.classList.contains("is-editable")).toBe(false);
49
+ });
50
+
51
+ it("should override existing outline style", () => {
52
+ node.style.outline = "2px solid red";
53
+ makeNodeNonEditable(node);
54
+
55
+ expect(node.style.outline).toBe("none");
56
+ });
57
+ });