Skip to content

Commit de64cd8

Browse files
authored
Merge pull request #398 from contentstack/VE-5478-input-unit-tests
test(VE-5478): add unit test for inline editing related functions
2 parents 73e46ae + 50a2167 commit de64cd8

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)