diff --git a/src/visualBuilder/utils/__test__/getPsuedoEditableEssentialStyles.test.ts b/src/visualBuilder/utils/__test__/getPsuedoEditableEssentialStyles.test.ts new file mode 100644 index 00000000..bc0a098c --- /dev/null +++ b/src/visualBuilder/utils/__test__/getPsuedoEditableEssentialStyles.test.ts @@ -0,0 +1,118 @@ +import { getPsuedoEditableEssentialStyles } from "../getPsuedoEditableEssentialStyles"; + +describe("getPsuedoEditableEssentialStyles", () => { + const mockRect: DOMRect = { + top: 50, + left: 30, + width: 200, + height: 100, + bottom: 150, + right: 230, + x: 30, + y: 50, + toJSON: () => ({}), + }; + + const mockScrollX = 100; + const mockScrollY = 200; + + beforeEach(() => { + // Mock window.scrollX and window.scrollY using vitest spies + vi.spyOn(window, "scrollX", "get").mockReturnValue(mockScrollX); + vi.spyOn(window, "scrollY", "get").mockReturnValue(mockScrollY); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + test("returns styles with kebab-case properties when camelCase is false", () => { + const result = getPsuedoEditableEssentialStyles({ + rect: mockRect, + camelCase: false, + }); + + expect(result).toEqual({ + position: "absolute", + top: `${mockRect.top + mockScrollY}px`, + left: `${mockRect.left + mockScrollX}px`, + height: "auto", + "min-height": `${Math.abs(mockRect.height)}px`, + "white-space": "normal", + "text-transform": "none", + "text-wrap-mode": "wrap", + "text-overflow": "visible", + }); + }); + + test("returns styles with kebab-case properties when camelCase is undefined", () => { + const result = getPsuedoEditableEssentialStyles({ + rect: mockRect, + camelCase: undefined, + }); + + expect(result).toEqual({ + position: "absolute", + top: `${mockRect.top + mockScrollY}px`, + left: `${mockRect.left + mockScrollX}px`, + height: "auto", + "min-height": `${Math.abs(mockRect.height)}px`, + "white-space": "normal", + "text-transform": "none", + "text-wrap-mode": "wrap", + "text-overflow": "visible", + }); + }); + + test("returns styles with camelCase properties when camelCase is true", () => { + const result = getPsuedoEditableEssentialStyles({ + rect: mockRect, + camelCase: true, + }); + + expect(result).toEqual({ + position: "absolute", + top: `${mockRect.top + mockScrollY}px`, + left: `${mockRect.left + mockScrollX}px`, + height: "auto", + minHeight: `${Math.abs(mockRect.height)}px`, + whiteSpace: "normal", + textTransform: "none", + textWrapMode: "wrap", + textOverflow: "visible", + }); + }); + + test("calculates correct positioning with scroll offset", () => { + const customScrollX = 50; + const customScrollY = 150; + + // Override the default mock values for this test + vi.spyOn(window, "scrollX", "get").mockReturnValue(customScrollX); + vi.spyOn(window, "scrollY", "get").mockReturnValue(customScrollY); + + const result = getPsuedoEditableEssentialStyles({ + rect: mockRect, + camelCase: false, + }); + + expect(result.top).toBe(`${mockRect.top + customScrollY}px`); + expect(result.left).toBe(`${mockRect.left + customScrollX}px`); + }); + + test("handles negative rect heights correctly", () => { + const negativeHeightRect: DOMRect = { + ...mockRect, + height: -50, + }; + + const result = getPsuedoEditableEssentialStyles({ + rect: negativeHeightRect, + camelCase: false, + }); + + expect(result["min-height"]).toBe( + `${Math.abs(negativeHeightRect.height)}px` + ); + }); +}); diff --git a/src/visualBuilder/utils/__test__/getPsuedoEditableStylesElement.test.ts b/src/visualBuilder/utils/__test__/getPsuedoEditableStylesElement.test.ts index 43427177..cb904326 100644 --- a/src/visualBuilder/utils/__test__/getPsuedoEditableStylesElement.test.ts +++ b/src/visualBuilder/utils/__test__/getPsuedoEditableStylesElement.test.ts @@ -2,57 +2,78 @@ import { describe, it, expect, vi, Mock } from "vitest"; import { getPsuedoEditableElementStyles } from "../getPsuedoEditableStylesElement"; import getCamelCaseStyles from "../getCamelCaseStyles"; import getStyleOfAnElement from "../getStyleOfAnElement"; +import { getPsuedoEditableEssentialStyles } from "../getPsuedoEditableEssentialStyles"; vi.mock("../getCamelCaseStyles"); vi.mock("../getStyleOfAnElement"); +vi.mock("../getPsuedoEditableEssentialStyles"); describe("getPsuedoEditableElementStyles", () => { - it("should return styles with absolute position and correct top and left values", () => { + it("should return merged styles from getStyleOfAnElement and getPsuedoEditableEssentialStyles", () => { const mockElement = { getBoundingClientRect: vi.fn().mockReturnValue({ top: 100, left: 200, + height: 50, }), } as unknown as HTMLElement; - window.scrollY = 50; - window.scrollX = 30; - const mockStyles = { color: "red", - fontSize: "16px", + "font-size": "16px", + }; + + const mockEssentialStyles = { + position: "absolute", + top: "150px", + left: "230px", + height: "auto", + "white-space": "normal", + "text-transform": "none", + "text-overflow": "visible", + "text-wrap-mode": "wrap", + "min-height": "50px", }; (getStyleOfAnElement as Mock).mockReturnValue(mockStyles); + (getPsuedoEditableEssentialStyles as Mock).mockReturnValue( + mockEssentialStyles + ); const result = getPsuedoEditableElementStyles(mockElement); + expect(getPsuedoEditableEssentialStyles).toHaveBeenCalledWith({ + rect: { top: 100, left: 200, height: 50 }, + camelCase: undefined, + }); + expect(result).toEqual({ color: "red", - fontSize: "16px", + "font-size": "16px", position: "absolute", top: "150px", left: "230px", height: "auto", - whiteSpace: "pre-line", - textTransform: "none", + "white-space": "normal", + "text-transform": "none", + "text-overflow": "visible", + "text-wrap-mode": "wrap", + "min-height": "50px", }); }); - it("should return camel case styles if camelCase is true", () => { + it("should apply camelCase conversion when camelCase is true", () => { const mockElement = { getBoundingClientRect: vi.fn().mockReturnValue({ top: 100, left: 200, + height: 50, }), } as unknown as HTMLElement; - window.scrollY = 50; - window.scrollX = 30; - const mockStyles = { color: "red", - fontSize: "16px", + "font-size": "16px", }; const mockCamelCaseStyles = { @@ -60,11 +81,32 @@ describe("getPsuedoEditableElementStyles", () => { fontSize: "16px", }; + const mockEssentialStyles = { + position: "absolute", + top: "150px", + left: "230px", + height: "auto", + whiteSpace: "normal", + textTransform: "none", + textOverflow: "visible", + textWrapMode: "wrap", + minHeight: "50px", + }; + (getStyleOfAnElement as Mock).mockReturnValue(mockStyles); (getCamelCaseStyles as Mock).mockReturnValue(mockCamelCaseStyles); + (getPsuedoEditableEssentialStyles as Mock).mockReturnValue( + mockEssentialStyles + ); const result = getPsuedoEditableElementStyles(mockElement, true); + expect(getCamelCaseStyles).toHaveBeenCalledWith(mockStyles); + expect(getPsuedoEditableEssentialStyles).toHaveBeenCalledWith({ + rect: { top: 100, left: 200, height: 50 }, + camelCase: true, + }); + expect(result).toEqual({ color: "red", fontSize: "16px", @@ -72,8 +114,51 @@ describe("getPsuedoEditableElementStyles", () => { top: "150px", left: "230px", height: "auto", - whiteSpace: "pre-line", + whiteSpace: "normal", textTransform: "none", + textOverflow: "visible", + textWrapMode: "wrap", + minHeight: "50px", + }); + }); + + it("should handle merging where essential styles override element styles", () => { + const mockElement = { + getBoundingClientRect: vi.fn().mockReturnValue({ + top: 100, + left: 200, + height: 50, + }), + } as unknown as HTMLElement; + + const mockStyles = { + color: "red", + position: "relative", // This should be overridden by essential styles + height: "100px", // This should be overridden by essential styles + }; + + const mockEssentialStyles = { + position: "absolute", + top: "150px", + left: "230px", + height: "auto", + "min-height": "50px", + }; + + (getStyleOfAnElement as Mock).mockReturnValue(mockStyles); + (getPsuedoEditableEssentialStyles as Mock).mockReturnValue( + mockEssentialStyles + ); + + const result = getPsuedoEditableElementStyles(mockElement); + + expect(result).toEqual({ + color: "red", + position: "absolute", // Overridden by essential styles + top: "150px", + left: "230px", + height: "auto", // Overridden by essential styles + "min-height": "50px", }); }); -}); \ No newline at end of file +}); diff --git a/src/visualBuilder/utils/getPsuedoEditableEssentialStyles.ts b/src/visualBuilder/utils/getPsuedoEditableEssentialStyles.ts new file mode 100644 index 00000000..fe7a6cad --- /dev/null +++ b/src/visualBuilder/utils/getPsuedoEditableEssentialStyles.ts @@ -0,0 +1,22 @@ +import getCamelCaseStyles from "./getCamelCaseStyles"; + +export function getPsuedoEditableEssentialStyles({ + rect, + camelCase, +}: { + rect: DOMRect; + camelCase: boolean | undefined; +}) { + const overrides = { + position: "absolute", + top: `${rect.top + window.scrollY}px`, + left: `${rect.left + window.scrollX}px`, + height: "auto", + "min-height": `${Math.abs(rect.height)}px`, + "white-space": "normal", + "text-transform": "none", + "text-wrap-mode": "wrap", + "text-overflow": "visible", + }; + return camelCase ? getCamelCaseStyles(overrides) : overrides; +} diff --git a/src/visualBuilder/utils/getPsuedoEditableStylesElement.ts b/src/visualBuilder/utils/getPsuedoEditableStylesElement.ts index e2369d64..5ff44ee9 100644 --- a/src/visualBuilder/utils/getPsuedoEditableStylesElement.ts +++ b/src/visualBuilder/utils/getPsuedoEditableStylesElement.ts @@ -1,4 +1,5 @@ import getCamelCaseStyles from "./getCamelCaseStyles"; +import { getPsuedoEditableEssentialStyles } from "./getPsuedoEditableEssentialStyles"; import getStyleOfAnElement from "./getStyleOfAnElement"; export function getPsuedoEditableElementStyles( @@ -6,21 +7,13 @@ export function getPsuedoEditableElementStyles( camelCase?: boolean ): { [key: string]: string } { let styles = getStyleOfAnElement(psuedoEditableElement); - if (camelCase) { - styles = getCamelCaseStyles(styles); - } // Get the offsetTop and offsetLeft of the editable element and set the position of the pseudo editable element // The pseudo editable element is positioned absolutely at the same location as the editable element const rect = psuedoEditableElement.getBoundingClientRect(); - styles.position = "absolute"; - styles.top = `${rect.top + window.scrollY}px`; - styles.left = `${rect.left + window.scrollX}px`; - // setting height to auto so that the element can grow based on the content - // and the resize observer can detect the change in height - styles.height = "auto"; - styles.whiteSpace = "pre-line"; - styles.textTransform = "none"; - - return styles; + if (camelCase) { + styles = getCamelCaseStyles(styles); + } + const overrides = getPsuedoEditableEssentialStyles({ rect, camelCase }); + return { ...styles, ...overrides }; }