Skip to content

Commit 50a2167

Browse files
test: add unit test for inline editing related functions
Remove visualBuilderInput.test.ts which tests the inline editing for single line and multi line fields using the VisualBuilder class, which requires extensive mocking of different code paths to get it right. Since it was written long back when things were simpler and now they are not, remove the tests there and instead expand on the tests recently written for handleIndividualFields. Extract functions defined inside the handleIndividualFields function closure into separate modules for unit testing.
1 parent 0ab602c commit 50a2167

9 files changed

Lines changed: 672 additions & 840 deletions

src/visualBuilder/__test__/visualBuilderInput.test.tsx

Lines changed: 0 additions & 664 deletions
This file was deleted.
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
import { throttle as throttleActual } from "lodash-es";
2+
import { MockedObject } from "vitest";
3+
import {
4+
generatePseudoEditableElement as generatePseudoEditableElementActual,
5+
isEllipsisActive as isEllipsisActiveActual,
6+
} from "../../generators/generatePseudoEditableField";
7+
import { VisualBuilder } from "../../index";
8+
import { VISUAL_BUILDER_FIELD_TYPE_ATTRIBUTE_KEY } from "../constants";
9+
import { enableInlineEditing } from "../enableInlineEditing";
10+
import { getMultilinePlaintext as getMultilinePlaintextActual } from "../getMultilinePlaintext";
11+
import { handleFieldInput, handleFieldKeyDown } from "../handleFieldMouseDown";
12+
import { pasteAsPlainText } from "../pasteAsPlainText";
13+
import { FieldDataType } from "../types/index.types";
14+
import { updateFocussedState as updateFocussedStateActual } from "../updateFocussedState";
15+
16+
const isEllipsisActive = vi.mocked(isEllipsisActiveActual);
17+
const generatePseudoEditableElement = vi.mocked(
18+
generatePseudoEditableElementActual
19+
);
20+
const getMultilinePlaintext = vi.mocked(getMultilinePlaintextActual);
21+
const updateFocussedState = vi.mocked(updateFocussedStateActual);
22+
const throttle = vi.mocked(throttleActual);
23+
24+
vi.mock("../../index", () => ({
25+
VisualBuilder: {
26+
VisualBuilderGlobalState: {
27+
value: {
28+
focusFieldValue: null,
29+
},
30+
},
31+
},
32+
}));
33+
34+
// Mock utility functions that are imported in the original file
35+
vi.mock("../getMultilinePlaintext", () => ({
36+
getMultilinePlaintext: vi.fn((element) => element.textContent || ""),
37+
}));
38+
39+
vi.mock("../../generators/generatePseudoEditableField", () => ({
40+
generatePseudoEditableElement: vi.fn(() => {
41+
const pseudoElement = document.createElement("div");
42+
pseudoElement.setAttribute("data-testid", "pseudo-element");
43+
return pseudoElement;
44+
}),
45+
isEllipsisActive: vi.fn(() => false),
46+
}));
47+
48+
vi.mock("../updateFocussedState", () => ({
49+
updateFocussedState: vi.fn(),
50+
}));
51+
52+
vi.mock("lodash-es", () => ({
53+
throttle: vi.fn((fn) => fn),
54+
}));
55+
56+
vi.mock("../handleFieldInput", () => ({
57+
handleFieldInput: vi.fn(),
58+
}));
59+
60+
vi.mock("../handleFieldKeyDown", () => ({
61+
handleFieldKeyDown: vi.fn(),
62+
}));
63+
64+
vi.mock("../pasteAsPlainText", () => ({
65+
pasteAsPlainText: vi.fn(),
66+
}));
67+
68+
describe("enableInlineEditing", () => {
69+
let editableElement: HTMLDivElement;
70+
let mockedEditableElement: MockedObject<HTMLDivElement>;
71+
let visualBuilderContainer: HTMLDivElement;
72+
let resizeObserver: ResizeObserver;
73+
74+
beforeEach(() => {
75+
// Set up the DOM elements
76+
editableElement = document.createElement("div");
77+
// after this point the editableElement is mocked
78+
// but we have reference to both just for the types
79+
mockedEditableElement = vi.mocked(editableElement);
80+
81+
editableElement.textContent = "Test content";
82+
editableElement.innerText = "Test content";
83+
84+
mockedEditableElement.focus = vi.fn();
85+
mockedEditableElement.addEventListener = vi.fn();
86+
87+
visualBuilderContainer = document.createElement("div");
88+
document.body.appendChild(visualBuilderContainer);
89+
90+
// Mock ResizeObserver
91+
resizeObserver = {
92+
observe: vi.fn(),
93+
unobserve: vi.fn(),
94+
disconnect: vi.fn(),
95+
} as unknown as ResizeObserver;
96+
97+
// clear mocks
98+
vi.clearAllMocks();
99+
100+
// @ts-expect-error only mocking required properties
101+
vi.spyOn(window, "getComputedStyle").mockReturnValue({
102+
display: "block",
103+
});
104+
});
105+
106+
afterEach(() => {
107+
VisualBuilder.VisualBuilderGlobalState.value.focusFieldValue = null;
108+
isEllipsisActive.mockReturnValue(false);
109+
document.body.removeChild(visualBuilderContainer);
110+
});
111+
112+
it("should make element editable and set focus", () => {
113+
enableInlineEditing({
114+
expectedFieldData: "Test content",
115+
editableElement,
116+
fieldType: FieldDataType.SINGLELINE,
117+
elements: {
118+
visualBuilderContainer,
119+
resizeObserver,
120+
lastEditedField: null,
121+
},
122+
});
123+
124+
expect(editableElement.getAttribute("contenteditable")).toBe("true");
125+
expect(editableElement.focus).toHaveBeenCalled();
126+
});
127+
128+
it("should handle multiline fields correctly", () => {
129+
enableInlineEditing({
130+
expectedFieldData: "Test content",
131+
editableElement,
132+
fieldType: FieldDataType.MULTILINE,
133+
elements: {
134+
visualBuilderContainer,
135+
resizeObserver,
136+
lastEditedField: null,
137+
},
138+
});
139+
140+
expect(getMultilinePlaintext).toHaveBeenCalledWith(editableElement);
141+
expect(editableElement.getAttribute("contenteditable")).toBe("true");
142+
143+
// Check event listeners
144+
const eventListeners =
145+
mockedEditableElement.addEventListener.mock.calls;
146+
expect(
147+
eventListeners.some(
148+
(call) => call[0] === "paste" && call[1] === pasteAsPlainText
149+
)
150+
).toBe(true);
151+
});
152+
153+
it("should create pseudo element when text content differs from expected", () => {
154+
enableInlineEditing({
155+
expectedFieldData: "Different content",
156+
editableElement,
157+
fieldType: FieldDataType.SINGLELINE,
158+
elements: {
159+
visualBuilderContainer,
160+
resizeObserver,
161+
lastEditedField: null,
162+
},
163+
});
164+
165+
expect(generatePseudoEditableElement).toHaveBeenCalled();
166+
expect(editableElement.style.visibility).toBe("hidden");
167+
expect(resizeObserver.observe).toHaveBeenCalled();
168+
});
169+
170+
it("should create pseudo element when ellipsis is active", () => {
171+
isEllipsisActive.mockReturnValue(true);
172+
173+
enableInlineEditing({
174+
expectedFieldData: "Test content",
175+
editableElement,
176+
fieldType: FieldDataType.SINGLELINE,
177+
elements: {
178+
visualBuilderContainer,
179+
resizeObserver,
180+
lastEditedField: null,
181+
},
182+
});
183+
184+
expect(generatePseudoEditableElement).toHaveBeenCalled();
185+
expect(editableElement.style.visibility).toBe("hidden");
186+
});
187+
188+
it("should set field type attribute on pseudo element", () => {
189+
enableInlineEditing({
190+
expectedFieldData: "Different content",
191+
editableElement,
192+
fieldType: FieldDataType.MULTILINE,
193+
elements: {
194+
visualBuilderContainer,
195+
resizeObserver,
196+
lastEditedField: null,
197+
},
198+
});
199+
200+
const pseudoElement =
201+
generatePseudoEditableElement.mock.results[0].value;
202+
expect(
203+
pseudoElement.getAttribute(VISUAL_BUILDER_FIELD_TYPE_ATTRIBUTE_KEY)
204+
).toBe(FieldDataType.MULTILINE);
205+
});
206+
207+
it("should handle inline elements correctly", () => {
208+
// @ts-expect-error mocking only required properties
209+
vi.spyOn(window, "getComputedStyle").mockReturnValue({
210+
display: "inline",
211+
});
212+
213+
enableInlineEditing({
214+
expectedFieldData: "Test content",
215+
editableElement,
216+
fieldType: FieldDataType.SINGLELINE,
217+
elements: {
218+
visualBuilderContainer,
219+
resizeObserver,
220+
lastEditedField: null,
221+
},
222+
});
223+
224+
expect(throttle).toHaveBeenCalled();
225+
226+
const onInlineElementInput =
227+
mockedEditableElement.addEventListener.mock.calls[0][1];
228+
229+
expect(onInlineElementInput).toBeInstanceOf(Function);
230+
(onInlineElementInput as () => void)();
231+
232+
expect(updateFocussedState).toHaveBeenCalled();
233+
});
234+
235+
it("should add event listeners for input and keydown", () => {
236+
enableInlineEditing({
237+
expectedFieldData: "Test content",
238+
editableElement,
239+
fieldType: FieldDataType.SINGLELINE,
240+
elements: {
241+
visualBuilderContainer,
242+
resizeObserver,
243+
lastEditedField: null,
244+
},
245+
});
246+
247+
const eventListeners =
248+
mockedEditableElement.addEventListener.mock.calls;
249+
expect(
250+
eventListeners.some(
251+
(call) => call[0] === "input" && call[1] === handleFieldInput
252+
)
253+
).toBe(true);
254+
expect(
255+
eventListeners.some(
256+
(call) =>
257+
call[0] === "keydown" && call[1] === handleFieldKeyDown
258+
)
259+
).toBe(true);
260+
});
261+
262+
it("should update global state with field value", () => {
263+
enableInlineEditing({
264+
expectedFieldData: "Test content",
265+
editableElement,
266+
fieldType: FieldDataType.SINGLELINE,
267+
elements: {
268+
visualBuilderContainer,
269+
resizeObserver,
270+
lastEditedField: null,
271+
},
272+
});
273+
274+
expect(
275+
VisualBuilder.VisualBuilderGlobalState.value.focusFieldValue
276+
).toBe("Test content");
277+
});
278+
});

0 commit comments

Comments
 (0)