From 5b7333923c1b6808db8b16fd9488b4015eb04ece Mon Sep 17 00:00:00 2001 From: Mridul Sharma Date: Wed, 27 May 2026 01:49:44 +0530 Subject: [PATCH 1/2] test: added test cases for remaining live preivew and post message funtionality --- src/livePreview/__test__/live-preview.test.ts | 225 ++++++++++++++++++ .../__test__/postMessageEvent.hooks.test.ts | 28 ++- 2 files changed, 252 insertions(+), 1 deletion(-) diff --git a/src/livePreview/__test__/live-preview.test.ts b/src/livePreview/__test__/live-preview.test.ts index 57c58f85..c4ef5398 100644 --- a/src/livePreview/__test__/live-preview.test.ts +++ b/src/livePreview/__test__/live-preview.test.ts @@ -10,6 +10,7 @@ import Config from "../../configManager/configManager"; import { PublicLogger } from "../../logger/logger"; import { ILivePreviewWindowType } from "../../types/types"; import { addLivePreviewQueryTags } from '../../utils/addLivePreviewQueryTags'; +import * as utils from "../../utils"; import livePreviewPostMessage from "../eventManager/livePreviewEventManager"; import { LIVE_PREVIEW_POST_MESSAGE_EVENTS } from "../eventManager/livePreviewEventManager.constant"; import { @@ -17,8 +18,11 @@ import { OnChangeLivePreviewPostMessageEventData, } from "../eventManager/types/livePreviewPostMessageEvent.type"; import LivePreview from "../live-preview"; +import * as postMessageHooks from "../eventManager/postMessageEvent.hooks"; +import { LivePreviewEditButton } from "../editButton/editButton"; import { mockLivePreviewInitEventListener } from "./mock"; + vi.mock("../../utils/addLivePreviewQueryTags", () => ({ addLivePreviewQueryTags: vi.fn(), })); @@ -543,3 +547,224 @@ describe("testing window event listeners", () => { expect(addLivePreviewQueryTags).toBeCalled(); }); }); + +describe("enable=false early return", () => { + beforeEach(() => { + Config.reset(); + livePreviewPostMessage?.destroy({ soft: true }); + livePreviewPostMessage?.on( + LIVE_PREVIEW_POST_MESSAGE_EVENTS.INIT, + mockLivePreviewInitEventListener + ); + }); + + afterEach(() => { + document.getElementsByTagName("html")[0].innerHTML = ""; + vi.restoreAllMocks(); + }); + + afterAll(() => { + Config.reset(); + livePreviewPostMessage?.destroy({ soft: true }); + }); + + test("should not call sendInitializeLivePreviewPostMessageEvent when enable is false", () => { + const sendInitSpy = vi.spyOn( + postMessageHooks, + "sendInitializeLivePreviewPostMessageEvent" + ); + Config.replace({ enable: false, cleanCslpOnProduction: false }); + + new LivePreview(); + + expect(sendInitSpy).not.toHaveBeenCalled(); + }); + + test("should call sendInitializeLivePreviewPostMessageEvent when enable is true", () => { + const sendInitSpy = vi.spyOn( + postMessageHooks, + "sendInitializeLivePreviewPostMessageEvent" + ); + Config.replace({ enable: true }); + + new LivePreview(); + + expect(sendInitSpy).toHaveBeenCalled(); + }); + + test("should not create LivePreviewEditButton when enable is false", () => { + Config.replace({ + enable: false, + editButton: { enable: true }, + }); + + new LivePreview(); + + expect(document.getElementById("cslp-tooltip")).toBeNull(); + }); + + test("should not create LivePreviewEditButton when enable is false even if mode is builder", () => { + Config.replace({ + enable: false, + mode: "builder", + stackDetails: { environment: "preview", apiKey: "test-key" }, + }); + + new LivePreview(); + + expect(document.getElementById("cslp-tooltip")).toBeNull(); + }); +}); + +describe("LivePreview edit button condition", () => { + beforeEach(() => { + Config.reset(); + LivePreviewEditButton.livePreviewEditButton = undefined as any; + livePreviewPostMessage?.destroy({ soft: true }); + livePreviewPostMessage?.on( + LIVE_PREVIEW_POST_MESSAGE_EVENTS.INIT, + mockLivePreviewInitEventListener + ); + vi.spyOn(utils, "isOpeningInTimeline").mockReturnValue(false); + }); + + afterEach(() => { + document.getElementsByTagName("html")[0].innerHTML = ""; + vi.restoreAllMocks(); + }); + + afterAll(() => { + Config.reset(); + livePreviewPostMessage?.destroy({ soft: true }); + }); + + test("should not create LivePreviewEditButton when editButton.enable is false and mode is preview", () => { + Config.replace({ + enable: true, + editButton: { enable: false }, + mode: "preview", + }); + + new LivePreview(); + + expect(LivePreviewEditButton.livePreviewEditButton).toBeUndefined(); + }); + + test("should instantiate LivePreviewEditButton when mode is builder even if editButton.enable is false", () => { + LivePreviewEditButton.livePreviewEditButton = undefined as any; + Config.replace({ + enable: true, + editButton: { enable: false }, + mode: "builder", + stackDetails: { environment: "preview", apiKey: "test-key" }, + }); + + new LivePreview(); + + expect(LivePreviewEditButton.livePreviewEditButton).toBeDefined(); + }); + + test("should not create LivePreviewEditButton when isOpeningInTimeline returns true", () => { + vi.spyOn(utils, "isOpeningInTimeline").mockReturnValue(true); + Config.replace({ + enable: true, + editButton: { enable: true }, + }); + + new LivePreview(); + + expect(document.getElementById("cslp-tooltip")).toBeNull(); + }); +}); + +describe("multiple LivePreview inits", () => { + beforeEach(() => { + Config.reset(); + livePreviewPostMessage?.destroy({ soft: true }); + livePreviewPostMessage?.on( + LIVE_PREVIEW_POST_MESSAGE_EVENTS.INIT, + mockLivePreviewInitEventListener + ); + }); + + afterEach(() => { + document.getElementsByTagName("html")[0].innerHTML = ""; + vi.restoreAllMocks(); + }); + + afterAll(() => { + Config.reset(); + livePreviewPostMessage?.destroy({ soft: true }); + }); + + test("should not throw when constructed multiple times", () => { + Config.replace({ enable: true }); + + expect(() => { + new LivePreview(); + new LivePreview(); + }).not.toThrow(); + }); + + test("should have at most one edit button in the DOM after multiple inits", () => { + vi.spyOn(utils, "isOpeningInTimeline").mockReturnValue(false); + Config.replace({ + enable: true, + editButton: { enable: true }, + }); + + new LivePreview(); + new LivePreview(); + + const buttons = document.querySelectorAll("#cslp-tooltip"); + expect(buttons.length).toBe(1); + }); +}); + +describe("LivePreview init with partial stackDetails", () => { + beforeEach(() => { + Config.reset(); + livePreviewPostMessage?.destroy({ soft: true }); + livePreviewPostMessage?.on( + LIVE_PREVIEW_POST_MESSAGE_EVENTS.INIT, + mockLivePreviewInitEventListener + ); + }); + + afterEach(() => { + document.getElementsByTagName("html")[0].innerHTML = ""; + vi.restoreAllMocks(); + }); + + afterAll(() => { + Config.reset(); + livePreviewPostMessage?.destroy({ soft: true }); + }); + + test("should not throw when constructed with partial stackDetails", () => { + Config.replace({ + enable: true, + stackDetails: { + apiKey: "partial-key", + } as any, + }); + + expect(() => new LivePreview()).not.toThrow(); + }); + + test("should preserve provided stackDetails fields and retain defaults for omitted ones", () => { + Config.replace({ + enable: true, + stackDetails: { + apiKey: "my-api-key", + } as any, + }); + + new LivePreview(); + + const { stackDetails } = Config.get(); + expect(stackDetails.apiKey).toBe("my-api-key"); + expect(stackDetails.locale).toBe("en-us"); + expect(stackDetails.masterLocale).toBe("en-us"); + }); +}); diff --git a/src/livePreview/eventManager/__test__/postMessageEvent.hooks.test.ts b/src/livePreview/eventManager/__test__/postMessageEvent.hooks.test.ts index 80b6b4cf..cf20856c 100644 --- a/src/livePreview/eventManager/__test__/postMessageEvent.hooks.test.ts +++ b/src/livePreview/eventManager/__test__/postMessageEvent.hooks.test.ts @@ -17,7 +17,8 @@ import { useHistoryPostMessageEvent, sendInitializeLivePreviewPostMessageEvent, } from "../postMessageEvent.hooks"; -import { isOpeningInNewTab } from "../../../common/inIframe"; +import { isOpeningInNewTab, inVisualEditor } from "../../../common/inIframe"; +import { addParamsToUrl } from "../../../utils"; import { ILivePreviewWindowType } from "../../../types/types"; // Mock dependencies @@ -678,6 +679,31 @@ describe("postMessageEvent.hooks", () => { expect(Config.set).not.toHaveBeenCalled(); }); + it("should return early and skip post-init setup when inVisualEditor is true", async () => { + mockConfig = { + ssr: true, + mode: 1, + }; + (Config.get as any).mockReturnValue(mockConfig); + (livePreviewPostMessage as any).send.mockResolvedValue({ + windowType: ILivePreviewWindowType.PREVIEW, + contentTypeUid: "blog", + entryUid: "entry-123", + }); + (inVisualEditor as any).mockReturnValueOnce(true); + + await sendInitializeLivePreviewPostMessageEvent(); + await Promise.resolve(); + + expect(setConfigFromParams).not.toHaveBeenCalled(); + expect(Config.set).not.toHaveBeenCalled(); + expect(addParamsToUrl).not.toHaveBeenCalled(); + expect(livePreviewPostMessage?.on).not.toHaveBeenCalledWith( + LIVE_PREVIEW_POST_MESSAGE_EVENTS.ON_CHANGE, + expect.any(Function), + ); + }); + it("should start CHECK_ENTRY_PAGE interval when ssr is false", async () => { vi.useFakeTimers(); try { From d968e051e74bafca19183fc51bfb3c998afb3a07 Mon Sep 17 00:00:00 2001 From: Mridul Sharma Date: Wed, 27 May 2026 17:30:46 +0530 Subject: [PATCH 2/2] test: added remaining gap tests from VB-1504 --- .../eventManager/__test__/useCollab.test.ts | 48 +++++++++++++++++++ .../listeners/__test__/mouseClick.test.ts | 48 +++++++++++++++++-- 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/src/visualBuilder/eventManager/__test__/useCollab.test.ts b/src/visualBuilder/eventManager/__test__/useCollab.test.ts index 1d33696c..f7bdbeed 100644 --- a/src/visualBuilder/eventManager/__test__/useCollab.test.ts +++ b/src/visualBuilder/eventManager/__test__/useCollab.test.ts @@ -410,3 +410,51 @@ describe("cleanup", () => { }); }); }); + +// Integration tests using real Config to document the intentional read-only isolation design. +// Share recipients receive COLLAB_ENABLE with fromShare:true, which sets pauseFeedback and +// isFeedbackMode but intentionally does NOT set collab.enable. As a result, share recipients +// never enter the collab block in mouseClick.ts (guarded by `collab.enable === true`) and +// bypass the full collab flow by design. +describe("read-only isolation — fromShare integration", () => { + let realConfig: { get: () => any; set: (key: string, value: any) => void; reset: () => void }; + let collabEnableHandler: (data: any) => void; + let collabDisableHandler: (data: any) => void; + + beforeEach(async () => { + vi.clearAllMocks(); + const realConfigModule = await vi.importActual<{ default: typeof realConfig }>( + "../../../configManager/configManager" + ); + realConfig = realConfigModule.default; + realConfig.reset(); + vi.mocked(Config.set).mockImplementation((key, value) => realConfig.set(key, value)); + vi.mocked(Config.get).mockImplementation(() => realConfig.get()); + useCollab(); + collabEnableHandler = getHandler(0); + collabDisableHandler = getHandler(2); + }); + + afterEach(() => { + realConfig.reset(); + vi.mocked(Config.get).mockReset(); + vi.mocked(Config.set).mockReset(); + }); + + it("should set pauseFeedback but not enable after COLLAB_ENABLE with fromShare:true", () => { + collabEnableHandler({ + data: { collab: { fromShare: true, pauseFeedback: true, isFeedbackMode: true } }, + }); + + expect(realConfig.get().collab.pauseFeedback).toBe(true); + expect(realConfig.get().collab.enable).not.toBe(true); + }); + + it("should set collab.pauseFeedback when COLLAB_DISABLE fires with fromShare:true", () => { + collabDisableHandler({ + data: { collab: { fromShare: true, pauseFeedback: true } }, + }); + + expect(realConfig.get().collab.pauseFeedback).toBe(true); + }); +}); diff --git a/src/visualBuilder/listeners/__test__/mouseClick.test.ts b/src/visualBuilder/listeners/__test__/mouseClick.test.ts index c203cb8c..cb65241a 100644 --- a/src/visualBuilder/listeners/__test__/mouseClick.test.ts +++ b/src/visualBuilder/listeners/__test__/mouseClick.test.ts @@ -73,11 +73,15 @@ vi.mock("../../components/FieldRevert/FieldRevertComponent", () => ({ vi.mock("get-xpath", () => ({ default: vi.fn().mockReturnValue("/div") })); -vi.mock("../../configManager/configManager", () => ({ +const hoistedConfigMocks = vi.hoisted(() => ({ + configGet: vi.fn().mockReturnValue({ + collab: { enable: false, isFeedbackMode: false, pauseFeedback: false }, + }), +})); + +vi.mock("../../../configManager/configManager", () => ({ default: { - get: vi.fn().mockReturnValue({ - collab: { enable: false, isFeedbackMode: false, pauseFeedback: false }, - }), + get: hoistedConfigMocks.configGet, set: vi.fn(), }, })); @@ -110,6 +114,7 @@ const { getCsDataOfElement } = await import("../../utils/getCsDataOfElement"); const { addFocusOverlay } = await import("../../generators/generateOverlay"); const { handleIndividualFields } = await import("../../utils/handleIndividualFields"); const { isCustomFieldMultipleInstance } = await import("../../utils/isCustomFieldMultipleInstance"); +const { generateThread, toggleCollabPopup } = await import("../../generators/generateThread"); function makeEditableElement(): HTMLElement { const el = document.createElement("div"); @@ -210,3 +215,38 @@ describe("handleBuilderInteraction — custom field multiple instance suppressio expect(addFocusOverlay).toHaveBeenCalled(); }); }); + +describe("handleBuilderInteraction — pauseFeedback guard", () => { + let editableElement: HTMLElement; + + beforeEach(() => { + vi.clearAllMocks(); + document.body.innerHTML = ""; + editableElement = makeEditableElement(); + VisualBuilder.VisualBuilderGlobalState.value.previousSelectedEditableDOM = null; + VisualBuilder.VisualBuilderGlobalState.value.isFocussed = false; + }); + + it("should return early without processing click when collab is enabled and pauseFeedback is true", async () => { + hoistedConfigMocks.configGet.mockReturnValue({ + collab: { enable: true, pauseFeedback: true, isFeedbackMode: true }, + }); + + await handleBuilderInteraction(makeParams(editableElement)); + + expect(getCsDataOfElement).not.toHaveBeenCalled(); + expect(generateThread).not.toHaveBeenCalled(); + expect(toggleCollabPopup).not.toHaveBeenCalled(); + expect(addFocusOverlay).not.toHaveBeenCalled(); + }); + + it("should call generateThread when collab is enabled and pauseFeedback is false", async () => { + hoistedConfigMocks.configGet.mockReturnValue({ + collab: { enable: true, pauseFeedback: false, isFeedbackMode: true }, + }); + + await handleBuilderInteraction(makeParams(editableElement)); + + expect(generateThread).toHaveBeenCalled(); + }); +});