-
Notifications
You must be signed in to change notification settings - Fork 1
Live preview outside iframe #492
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
contentstackMridul
merged 10 commits into
21st-august-2025-release
from
live-preview-outside-iframe
Aug 20, 2025
Merged
Changes from 6 commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
dda0f4d
feat: live preview: added outside iframe code
contentstackMridul ecfc580
Merge pull request #468 from contentstack/VE_6811
contentstackMridul 8371212
feat: live preview: added outside iframe code
contentstackMridul 8e100d9
feat: live preview: added outside iframe code
contentstackMridul dffe855
test: added new test cases for livePreviewEventManager and postMessag…
contentstackMridul a3a14f6
Merge pull request #477 from contentstack/VE_6811
contentstackMridul ab113eb
Merge branch 'main' of github.com:contentstack/live-preview-sdk into …
contentstackMridul b50c43e
fix: resolve new tab bugs
contentstackMridul ede97b9
chore: updated version
contentstackMridul 42ee5c1
fix: removed unused imports and increased the version
contentstackMridul File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
252 changes: 252 additions & 0 deletions
252
src/livePreview/eventManager/__test__/livePreviewEventManager.test.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,252 @@ | ||
| /** | ||
| * @vitest-environment jsdom | ||
| */ | ||
|
|
||
| import { vi } from "vitest"; | ||
| import { EventManager } from "@contentstack/advanced-post-message"; | ||
| import { LIVE_PREVIEW_CHANNEL_ID } from "../livePreviewEventManager.constant"; | ||
|
|
||
| // Mock dependencies | ||
| vi.mock("@contentstack/advanced-post-message", () => ({ | ||
| EventManager: vi.fn(), | ||
| })); | ||
|
|
||
| vi.mock("../../../common/inIframe", () => ({ | ||
| inNewTab: vi.fn(), | ||
| })); | ||
|
|
||
| // Import after mocking | ||
| import { inNewTab } from "../../../common/inIframe"; | ||
|
|
||
| describe("livePreviewEventManager", () => { | ||
| let mockEventManager: any; | ||
| let originalWindow: any; | ||
|
|
||
| beforeEach(() => { | ||
| // Reset all mocks | ||
| vi.clearAllMocks(); | ||
|
|
||
| // Create mock EventManager | ||
| mockEventManager = { | ||
| on: vi.fn(), | ||
| send: vi.fn(), | ||
| }; | ||
| (EventManager as any).mockImplementation(() => mockEventManager); | ||
|
|
||
| // Store original window | ||
| originalWindow = global.window; | ||
|
|
||
| // Reset inNewTab mock | ||
| (inNewTab as any).mockReturnValue(false); | ||
| }); | ||
|
|
||
| afterEach(() => { | ||
| // Restore original window | ||
| global.window = originalWindow; | ||
|
|
||
| // Clear module cache to reset the module state | ||
| vi.resetModules(); | ||
| }); | ||
|
|
||
| describe("when window is undefined", () => { | ||
| beforeEach(() => { | ||
| // Mock window as undefined | ||
| Object.defineProperty(global, "window", { | ||
| value: undefined, | ||
| writable: true, | ||
| }); | ||
| }); | ||
|
|
||
| it("should not initialize EventManager when window is undefined", async () => { | ||
| // Re-import the module to trigger initialization | ||
| const module = await import("../livePreviewEventManager"); | ||
|
|
||
| expect(EventManager).not.toHaveBeenCalled(); | ||
| expect(module.default).toBeUndefined(); | ||
| }); | ||
| }); | ||
|
|
||
| describe("when window is defined", () => { | ||
| let mockWindow: any; | ||
|
|
||
| beforeEach(() => { | ||
| // Create mock window object | ||
| mockWindow = { | ||
| parent: { postMessage: vi.fn() }, | ||
| opener: { postMessage: vi.fn() }, | ||
| }; | ||
|
|
||
| Object.defineProperty(global, "window", { | ||
| value: mockWindow, | ||
| writable: true, | ||
| }); | ||
| }); | ||
|
|
||
| it("should initialize EventManager with window.parent as target when not in new tab", async () => { | ||
| (inNewTab as any).mockReturnValue(false); | ||
|
|
||
| // Re-import the module to trigger initialization | ||
| const module = await import("../livePreviewEventManager"); | ||
|
|
||
| expect(EventManager).toHaveBeenCalledWith(LIVE_PREVIEW_CHANNEL_ID, { | ||
| target: mockWindow.parent, | ||
| debug: false, | ||
| suppressErrors: true, | ||
| }); | ||
| expect(module.default).toBe(mockEventManager); | ||
| }); | ||
|
|
||
| it("should initialize EventManager with window.opener as target when in new tab", async () => { | ||
| (inNewTab as any).mockReturnValue(true); | ||
|
|
||
| // Re-import the module to trigger initialization | ||
| const module = await import("../livePreviewEventManager"); | ||
|
|
||
| expect(EventManager).toHaveBeenCalledWith(LIVE_PREVIEW_CHANNEL_ID, { | ||
| target: mockWindow.opener, | ||
| debug: false, | ||
| suppressErrors: true, | ||
| }); | ||
| expect(module.default).toBe(mockEventManager); | ||
| }); | ||
|
|
||
| it("should call inNewTab to determine the target", async () => { | ||
| // Re-import the module to trigger initialization | ||
| await import("../livePreviewEventManager"); | ||
|
|
||
| expect(inNewTab).toHaveBeenCalled(); | ||
| }); | ||
|
|
||
| it("should use correct channel ID", async () => { | ||
| // Re-import the module to trigger initialization | ||
| await import("../livePreviewEventManager"); | ||
|
|
||
| expect(EventManager).toHaveBeenCalledWith( | ||
| LIVE_PREVIEW_CHANNEL_ID, | ||
| expect.any(Object) | ||
| ); | ||
| }); | ||
|
|
||
| it("should set correct default event options", async () => { | ||
| (inNewTab as any).mockReturnValue(false); | ||
|
|
||
| // Re-import the module to trigger initialization | ||
| await import("../livePreviewEventManager"); | ||
|
|
||
| expect(EventManager).toHaveBeenCalledWith( | ||
| expect.any(String), | ||
| expect.objectContaining({ | ||
| debug: false, | ||
| suppressErrors: true, | ||
| }) | ||
| ); | ||
| }); | ||
|
|
||
| describe("target selection logic", () => { | ||
| it("should prioritize window.opener when inNewTab returns true", async () => { | ||
| (inNewTab as any).mockReturnValue(true); | ||
|
|
||
| // Re-import the module to trigger initialization | ||
| await import("../livePreviewEventManager"); | ||
|
|
||
| const callArgs = (EventManager as any).mock.calls[0]; | ||
| expect(callArgs[1].target).toBe(mockWindow.opener); | ||
| expect(callArgs[1].target).not.toBe(mockWindow.parent); | ||
| }); | ||
|
|
||
| it("should use window.parent when inNewTab returns false", async () => { | ||
| (inNewTab as any).mockReturnValue(false); | ||
|
|
||
| // Re-import the module to trigger initialization | ||
| await import("../livePreviewEventManager"); | ||
|
|
||
| const callArgs = (EventManager as any).mock.calls[0]; | ||
| expect(callArgs[1].target).toBe(mockWindow.parent); | ||
| expect(callArgs[1].target).not.toBe(mockWindow.opener); | ||
| }); | ||
|
|
||
| it("should throw error when inNewTab throws an error", async () => { | ||
| (inNewTab as any).mockImplementation(() => { | ||
| throw new Error("inNewTab error"); | ||
| }); | ||
|
|
||
| // Should throw because inNewTab error is not caught in the implementation | ||
| await expect(async () => { | ||
| await import("../livePreviewEventManager"); | ||
| }).rejects.toThrow("inNewTab error"); | ||
| }); | ||
| }); | ||
|
|
||
| describe("edge cases", () => { | ||
| it("should handle missing window.parent gracefully", async () => { | ||
| mockWindow.parent = undefined; | ||
| (inNewTab as any).mockReturnValue(false); | ||
|
|
||
| // Re-import the module to trigger initialization | ||
| const module = await import("../livePreviewEventManager"); | ||
|
|
||
| expect(EventManager).toHaveBeenCalledWith(LIVE_PREVIEW_CHANNEL_ID, { | ||
| target: undefined, | ||
| debug: false, | ||
| suppressErrors: true, | ||
| }); | ||
| expect(module.default).toBe(mockEventManager); | ||
| }); | ||
|
|
||
| it("should handle missing window.opener gracefully", async () => { | ||
| mockWindow.opener = undefined; | ||
| (inNewTab as any).mockReturnValue(true); | ||
|
|
||
| // Re-import the module to trigger initialization | ||
| const module = await import("../livePreviewEventManager"); | ||
|
|
||
| expect(EventManager).toHaveBeenCalledWith(LIVE_PREVIEW_CHANNEL_ID, { | ||
| target: undefined, | ||
| debug: false, | ||
| suppressErrors: true, | ||
| }); | ||
| expect(module.default).toBe(mockEventManager); | ||
| }); | ||
|
|
||
| it("should handle when EventManager constructor throws", async () => { | ||
| (EventManager as any).mockImplementation(() => { | ||
| throw new Error("EventManager constructor error"); | ||
| }); | ||
|
|
||
| // Should not crash the module initialization | ||
| expect(async () => { | ||
| await import("../livePreviewEventManager"); | ||
| }).not.toThrow(); | ||
| }); | ||
| }); | ||
| }); | ||
|
|
||
| describe("module export", () => { | ||
| it("should export the EventManager instance when window is available", async () => { | ||
| const mockWindow = { | ||
| parent: { postMessage: vi.fn() }, | ||
| opener: { postMessage: vi.fn() }, | ||
| }; | ||
|
|
||
| Object.defineProperty(global, "window", { | ||
| value: mockWindow, | ||
| writable: true, | ||
| }); | ||
|
|
||
| const module = await import("../livePreviewEventManager"); | ||
|
|
||
| expect(module.default).toBe(mockEventManager); | ||
| }); | ||
|
|
||
| it("should export undefined when window is not available", async () => { | ||
| Object.defineProperty(global, "window", { | ||
| value: undefined, | ||
| writable: true, | ||
| }); | ||
|
|
||
| const module = await import("../livePreviewEventManager"); | ||
|
|
||
| expect(module.default).toBeUndefined(); | ||
| }); | ||
| }); | ||
| }); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.