Skip to content

Commit 908ec43

Browse files
authored
Merge pull request #451 from contentstack/VE-6459-fix-psuedoeditable-height-collapse
fix(VE-6459): psuedo-editable height collapse
2 parents 83cfac3 + e8388d0 commit 908ec43

4 files changed

Lines changed: 246 additions & 28 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { getPsuedoEditableEssentialStyles } from "../getPsuedoEditableEssentialStyles";
2+
3+
describe("getPsuedoEditableEssentialStyles", () => {
4+
const mockRect: DOMRect = {
5+
top: 50,
6+
left: 30,
7+
width: 200,
8+
height: 100,
9+
bottom: 150,
10+
right: 230,
11+
x: 30,
12+
y: 50,
13+
toJSON: () => ({}),
14+
};
15+
16+
const mockScrollX = 100;
17+
const mockScrollY = 200;
18+
19+
beforeEach(() => {
20+
// Mock window.scrollX and window.scrollY using vitest spies
21+
vi.spyOn(window, "scrollX", "get").mockReturnValue(mockScrollX);
22+
vi.spyOn(window, "scrollY", "get").mockReturnValue(mockScrollY);
23+
});
24+
25+
afterEach(() => {
26+
vi.restoreAllMocks();
27+
});
28+
29+
test("returns styles with kebab-case properties when camelCase is false", () => {
30+
const result = getPsuedoEditableEssentialStyles({
31+
rect: mockRect,
32+
camelCase: false,
33+
});
34+
35+
expect(result).toEqual({
36+
position: "absolute",
37+
top: `${mockRect.top + mockScrollY}px`,
38+
left: `${mockRect.left + mockScrollX}px`,
39+
height: "auto",
40+
"min-height": `${Math.abs(mockRect.height)}px`,
41+
"white-space": "normal",
42+
"text-transform": "none",
43+
"text-wrap-mode": "wrap",
44+
"text-overflow": "visible",
45+
});
46+
});
47+
48+
test("returns styles with kebab-case properties when camelCase is undefined", () => {
49+
const result = getPsuedoEditableEssentialStyles({
50+
rect: mockRect,
51+
camelCase: undefined,
52+
});
53+
54+
expect(result).toEqual({
55+
position: "absolute",
56+
top: `${mockRect.top + mockScrollY}px`,
57+
left: `${mockRect.left + mockScrollX}px`,
58+
height: "auto",
59+
"min-height": `${Math.abs(mockRect.height)}px`,
60+
"white-space": "normal",
61+
"text-transform": "none",
62+
"text-wrap-mode": "wrap",
63+
"text-overflow": "visible",
64+
});
65+
});
66+
67+
test("returns styles with camelCase properties when camelCase is true", () => {
68+
const result = getPsuedoEditableEssentialStyles({
69+
rect: mockRect,
70+
camelCase: true,
71+
});
72+
73+
expect(result).toEqual({
74+
position: "absolute",
75+
top: `${mockRect.top + mockScrollY}px`,
76+
left: `${mockRect.left + mockScrollX}px`,
77+
height: "auto",
78+
minHeight: `${Math.abs(mockRect.height)}px`,
79+
whiteSpace: "normal",
80+
textTransform: "none",
81+
textWrapMode: "wrap",
82+
textOverflow: "visible",
83+
});
84+
});
85+
86+
test("calculates correct positioning with scroll offset", () => {
87+
const customScrollX = 50;
88+
const customScrollY = 150;
89+
90+
// Override the default mock values for this test
91+
vi.spyOn(window, "scrollX", "get").mockReturnValue(customScrollX);
92+
vi.spyOn(window, "scrollY", "get").mockReturnValue(customScrollY);
93+
94+
const result = getPsuedoEditableEssentialStyles({
95+
rect: mockRect,
96+
camelCase: false,
97+
});
98+
99+
expect(result.top).toBe(`${mockRect.top + customScrollY}px`);
100+
expect(result.left).toBe(`${mockRect.left + customScrollX}px`);
101+
});
102+
103+
test("handles negative rect heights correctly", () => {
104+
const negativeHeightRect: DOMRect = {
105+
...mockRect,
106+
height: -50,
107+
};
108+
109+
const result = getPsuedoEditableEssentialStyles({
110+
rect: negativeHeightRect,
111+
camelCase: false,
112+
});
113+
114+
expect(result["min-height"]).toBe(
115+
`${Math.abs(negativeHeightRect.height)}px`
116+
);
117+
});
118+
});

src/visualBuilder/utils/__test__/getPsuedoEditableStylesElement.test.ts

Lines changed: 100 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,163 @@ import { describe, it, expect, vi, Mock } from "vitest";
22
import { getPsuedoEditableElementStyles } from "../getPsuedoEditableStylesElement";
33
import getCamelCaseStyles from "../getCamelCaseStyles";
44
import getStyleOfAnElement from "../getStyleOfAnElement";
5+
import { getPsuedoEditableEssentialStyles } from "../getPsuedoEditableEssentialStyles";
56

67
vi.mock("../getCamelCaseStyles");
78
vi.mock("../getStyleOfAnElement");
9+
vi.mock("../getPsuedoEditableEssentialStyles");
810

911
describe("getPsuedoEditableElementStyles", () => {
10-
it("should return styles with absolute position and correct top and left values", () => {
12+
it("should return merged styles from getStyleOfAnElement and getPsuedoEditableEssentialStyles", () => {
1113
const mockElement = {
1214
getBoundingClientRect: vi.fn().mockReturnValue({
1315
top: 100,
1416
left: 200,
17+
height: 50,
1518
}),
1619
} as unknown as HTMLElement;
1720

18-
window.scrollY = 50;
19-
window.scrollX = 30;
20-
2121
const mockStyles = {
2222
color: "red",
23-
fontSize: "16px",
23+
"font-size": "16px",
24+
};
25+
26+
const mockEssentialStyles = {
27+
position: "absolute",
28+
top: "150px",
29+
left: "230px",
30+
height: "auto",
31+
"white-space": "normal",
32+
"text-transform": "none",
33+
"text-overflow": "visible",
34+
"text-wrap-mode": "wrap",
35+
"min-height": "50px",
2436
};
2537

2638
(getStyleOfAnElement as Mock).mockReturnValue(mockStyles);
39+
(getPsuedoEditableEssentialStyles as Mock).mockReturnValue(
40+
mockEssentialStyles
41+
);
2742

2843
const result = getPsuedoEditableElementStyles(mockElement);
2944

45+
expect(getPsuedoEditableEssentialStyles).toHaveBeenCalledWith({
46+
rect: { top: 100, left: 200, height: 50 },
47+
camelCase: undefined,
48+
});
49+
3050
expect(result).toEqual({
3151
color: "red",
32-
fontSize: "16px",
52+
"font-size": "16px",
3353
position: "absolute",
3454
top: "150px",
3555
left: "230px",
3656
height: "auto",
37-
whiteSpace: "pre-line",
38-
textTransform: "none",
57+
"white-space": "normal",
58+
"text-transform": "none",
59+
"text-overflow": "visible",
60+
"text-wrap-mode": "wrap",
61+
"min-height": "50px",
3962
});
4063
});
4164

42-
it("should return camel case styles if camelCase is true", () => {
65+
it("should apply camelCase conversion when camelCase is true", () => {
4366
const mockElement = {
4467
getBoundingClientRect: vi.fn().mockReturnValue({
4568
top: 100,
4669
left: 200,
70+
height: 50,
4771
}),
4872
} as unknown as HTMLElement;
4973

50-
window.scrollY = 50;
51-
window.scrollX = 30;
52-
5374
const mockStyles = {
5475
color: "red",
55-
fontSize: "16px",
76+
"font-size": "16px",
5677
};
5778

5879
const mockCamelCaseStyles = {
5980
color: "red",
6081
fontSize: "16px",
6182
};
6283

84+
const mockEssentialStyles = {
85+
position: "absolute",
86+
top: "150px",
87+
left: "230px",
88+
height: "auto",
89+
whiteSpace: "normal",
90+
textTransform: "none",
91+
textOverflow: "visible",
92+
textWrapMode: "wrap",
93+
minHeight: "50px",
94+
};
95+
6396
(getStyleOfAnElement as Mock).mockReturnValue(mockStyles);
6497
(getCamelCaseStyles as Mock).mockReturnValue(mockCamelCaseStyles);
98+
(getPsuedoEditableEssentialStyles as Mock).mockReturnValue(
99+
mockEssentialStyles
100+
);
65101

66102
const result = getPsuedoEditableElementStyles(mockElement, true);
67103

104+
expect(getCamelCaseStyles).toHaveBeenCalledWith(mockStyles);
105+
expect(getPsuedoEditableEssentialStyles).toHaveBeenCalledWith({
106+
rect: { top: 100, left: 200, height: 50 },
107+
camelCase: true,
108+
});
109+
68110
expect(result).toEqual({
69111
color: "red",
70112
fontSize: "16px",
71113
position: "absolute",
72114
top: "150px",
73115
left: "230px",
74116
height: "auto",
75-
whiteSpace: "pre-line",
117+
whiteSpace: "normal",
76118
textTransform: "none",
119+
textOverflow: "visible",
120+
textWrapMode: "wrap",
121+
minHeight: "50px",
122+
});
123+
});
124+
125+
it("should handle merging where essential styles override element styles", () => {
126+
const mockElement = {
127+
getBoundingClientRect: vi.fn().mockReturnValue({
128+
top: 100,
129+
left: 200,
130+
height: 50,
131+
}),
132+
} as unknown as HTMLElement;
133+
134+
const mockStyles = {
135+
color: "red",
136+
position: "relative", // This should be overridden by essential styles
137+
height: "100px", // This should be overridden by essential styles
138+
};
139+
140+
const mockEssentialStyles = {
141+
position: "absolute",
142+
top: "150px",
143+
left: "230px",
144+
height: "auto",
145+
"min-height": "50px",
146+
};
147+
148+
(getStyleOfAnElement as Mock).mockReturnValue(mockStyles);
149+
(getPsuedoEditableEssentialStyles as Mock).mockReturnValue(
150+
mockEssentialStyles
151+
);
152+
153+
const result = getPsuedoEditableElementStyles(mockElement);
154+
155+
expect(result).toEqual({
156+
color: "red",
157+
position: "absolute", // Overridden by essential styles
158+
top: "150px",
159+
left: "230px",
160+
height: "auto", // Overridden by essential styles
161+
"min-height": "50px",
77162
});
78163
});
79-
});
164+
});
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import getCamelCaseStyles from "./getCamelCaseStyles";
2+
3+
export function getPsuedoEditableEssentialStyles({
4+
rect,
5+
camelCase,
6+
}: {
7+
rect: DOMRect;
8+
camelCase: boolean | undefined;
9+
}) {
10+
const overrides = {
11+
position: "absolute",
12+
top: `${rect.top + window.scrollY}px`,
13+
left: `${rect.left + window.scrollX}px`,
14+
height: "auto",
15+
"min-height": `${Math.abs(rect.height)}px`,
16+
"white-space": "normal",
17+
"text-transform": "none",
18+
"text-wrap-mode": "wrap",
19+
"text-overflow": "visible",
20+
};
21+
return camelCase ? getCamelCaseStyles(overrides) : overrides;
22+
}
Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,19 @@
11
import getCamelCaseStyles from "./getCamelCaseStyles";
2+
import { getPsuedoEditableEssentialStyles } from "./getPsuedoEditableEssentialStyles";
23
import getStyleOfAnElement from "./getStyleOfAnElement";
34

45
export function getPsuedoEditableElementStyles(
56
psuedoEditableElement: HTMLElement,
67
camelCase?: boolean
78
): { [key: string]: string } {
89
let styles = getStyleOfAnElement(psuedoEditableElement);
9-
if (camelCase) {
10-
styles = getCamelCaseStyles(styles);
11-
}
1210
// Get the offsetTop and offsetLeft of the editable element and set the position of the pseudo editable element
1311
// The pseudo editable element is positioned absolutely at the same location as the editable element
1412
const rect = psuedoEditableElement.getBoundingClientRect();
1513

16-
styles.position = "absolute";
17-
styles.top = `${rect.top + window.scrollY}px`;
18-
styles.left = `${rect.left + window.scrollX}px`;
19-
// setting height to auto so that the element can grow based on the content
20-
// and the resize observer can detect the change in height
21-
styles.height = "auto";
22-
styles.whiteSpace = "pre-line";
23-
styles.textTransform = "none";
24-
25-
return styles;
14+
if (camelCase) {
15+
styles = getCamelCaseStyles(styles);
16+
}
17+
const overrides = getPsuedoEditableEssentialStyles({ rect, camelCase });
18+
return { ...styles, ...overrides };
2619
}

0 commit comments

Comments
 (0)