Skip to content

Commit 7682fc4

Browse files
Merge pull request #479 from contentstack/VE_6673
fix: multiple reloads in timeline
2 parents 13ace62 + e781891 commit 7682fc4

6 files changed

Lines changed: 333 additions & 37 deletions

File tree

src/livePreview/__test__/live-preview.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,7 @@ describe("incoming postMessage", () => {
432432

433433
describe("testing window event listeners", () => {
434434
let addEventListenerMock: any;
435-
let sendInitEvent = vi.fn().mockImplementation(mockLivePreviewInitEventListener);
435+
const sendInitEvent = vi.fn().mockImplementation(mockLivePreviewInitEventListener);
436436
let livePreviewInstance: LivePreview;
437437

438438
beforeEach(() => {

src/livePreview/eventManager/postMessageEvent.hooks.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Config, { setConfigFromParams } from "../../configManager/configManager";
22
import { ILivePreviewWindowType } from "../../types/types";
3-
import { addParamsToUrl } from "../../utils";
3+
import { addParamsToUrl, isOpeningInTimeline } from "../../utils";
44
import livePreviewPostMessage from "./livePreviewEventManager";
55
import { LIVE_PREVIEW_POST_MESSAGE_EVENTS } from "./livePreviewEventManager.constant";
66
import {
@@ -95,7 +95,7 @@ export function sendInitializeLivePreviewPostMessageEvent(): void {
9595
// "init message did not contain contentTypeUid or entryUid."
9696
// );
9797
}
98-
if (Config.get().ssr) {
98+
if (Config.get().ssr || isOpeningInTimeline()) {
9999
addParamsToUrl();
100100
}
101101
Config.set("windowType", windowType);
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { describe, test, expect, vi, beforeEach } from "vitest";
2+
import { addLivePreviewQueryTags } from "../addLivePreviewQueryTags";
3+
import { PublicLogger } from "../../logger/logger";
4+
5+
// Mock the logger
6+
vi.mock("../../logger/logger", () => ({
7+
PublicLogger: {
8+
error: vi.fn(),
9+
},
10+
}));
11+
12+
describe("addLivePreviewQueryTags", () => {
13+
beforeEach(() => {
14+
vi.clearAllMocks();
15+
});
16+
17+
test("should return original URL when no live preview parameters in current location", () => {
18+
// This test works with current document.location (likely has no live preview params)
19+
const targetUrl = "http://example.com/target-page";
20+
21+
const result = addLivePreviewQueryTags(targetUrl);
22+
23+
// Should return unchanged since no live preview params in current location
24+
expect(result).toBe(targetUrl);
25+
});
26+
27+
test("should log error and return original link when target URL is invalid", () => {
28+
const targetUrl = "not-a-valid-url-at-all-invalid";
29+
30+
const result = addLivePreviewQueryTags(targetUrl);
31+
32+
expect(PublicLogger.error).toHaveBeenCalledWith("Error while adding live preview to URL");
33+
expect(result).toBe(targetUrl);
34+
});
35+
36+
test("should handle empty string input", () => {
37+
const targetUrl = "";
38+
39+
const result = addLivePreviewQueryTags(targetUrl);
40+
41+
expect(PublicLogger.error).toHaveBeenCalledWith("Error while adding live preview to URL");
42+
expect(result).toBe(targetUrl);
43+
});
44+
45+
test("should handle malformed URLs gracefully", () => {
46+
const targetUrl = "http://";
47+
48+
const result = addLivePreviewQueryTags(targetUrl);
49+
50+
expect(PublicLogger.error).toHaveBeenCalledWith("Error while adding live preview to URL");
51+
expect(result).toBe(targetUrl);
52+
});
53+
54+
test("should handle valid URLs without errors", () => {
55+
const targetUrl = "https://example.com/valid-page";
56+
57+
const result = addLivePreviewQueryTags(targetUrl);
58+
59+
// Should not throw errors and return some result
60+
expect(typeof result).toBe("string");
61+
expect(result.length).toBeGreaterThan(0);
62+
});
63+
64+
test("should add live preview query tags to URL with all required query parameters", () => {
65+
const originalUrl =
66+
"http://example.com?live_preview=hash&content_type_uid=ctuid&entry_uid=entryuid";
67+
const expectedUrl =
68+
"http://example.com/?live_preview=hash&content_type_uid=ctuid&entry_uid=entryuid";
69+
70+
global.window = Object.create(window);
71+
Object.defineProperty(window, "location", {
72+
value: {
73+
href: originalUrl,
74+
},
75+
writable: true,
76+
});
77+
const result = addLivePreviewQueryTags(originalUrl);
78+
79+
expect(result).toEqual(expectedUrl);
80+
});
81+
});

src/utils/__test__/index.test.ts

Lines changed: 231 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,254 @@
1-
import { PublicLogger } from "../../logger/logger";
2-
import { addLivePreviewQueryTags, hasWindow } from "../index";
1+
import { hasWindow, addParamsToUrl } from "../index";
32
import { vi } from "vitest";
43

5-
vi.mock("../../logger/logger", () => ({
6-
PublicLogger: {
7-
error: vi.fn(),
8-
},
4+
// Mock addLivePreviewQueryTags function
5+
vi.mock("../addLivePreviewQueryTags", () => ({
6+
addLivePreviewQueryTags: vi.fn()
97
}));
108

9+
// Import the mocked function after setting up the mock
10+
import { addLivePreviewQueryTags } from "../addLivePreviewQueryTags";
11+
1112
describe("hasWindow() function", () => {
1213
test("must check if window is available", () => {
1314
expect(hasWindow()).toBe(typeof window !== "undefined");
1415
});
1516
});
1617

17-
describe("addLivePreviewQueryTags", () => {
18-
test("should add live preview query tags to URL with all required query parameters", () => {
19-
const originalUrl =
20-
"http://example.com?live_preview=hash&content_type_uid=ctuid&entry_uid=entryuid";
21-
const expectedUrl =
22-
"http://example.com/?live_preview=hash&content_type_uid=ctuid&entry_uid=entryuid";
2318

24-
global.window = Object.create(window);
25-
Object.defineProperty(window, "location", {
26-
value: {
27-
href: originalUrl,
28-
},
29-
writable: true,
19+
describe("addParamsToUrl", () => {
20+
let mockAddEventListener: any;
21+
let mockDocument: any;
22+
let clickHandler: (event: any) => void;
23+
24+
beforeEach(() => {
25+
// Reset all mocks
26+
vi.clearAllMocks();
27+
28+
// Mock window.addEventListener to capture the click handler
29+
mockAddEventListener = vi.fn((event, handler) => {
30+
if (event === "click") {
31+
clickHandler = handler;
32+
}
3033
});
34+
35+
// Setup mock return value for addLivePreviewQueryTags
36+
vi.mocked(addLivePreviewQueryTags).mockImplementation((url) => `${url}?live_preview=test&content_type_uid=test&entry_uid=test`);
37+
38+
// Mock document and window
39+
mockDocument = {
40+
location: {
41+
origin: "https://example.com"
42+
}
43+
};
3144

32-
const result = addLivePreviewQueryTags(originalUrl);
45+
global.window = {
46+
addEventListener: mockAddEventListener,
47+
document: mockDocument
48+
} as any;
49+
50+
global.document = mockDocument as any;
51+
});
52+
53+
afterEach(() => {
54+
vi.restoreAllMocks();
55+
});
3356

34-
expect(result).toEqual(expectedUrl);
57+
test("should add event listener when function is called", () => {
58+
addParamsToUrl();
59+
60+
expect(mockAddEventListener).toHaveBeenCalledWith("click", expect.any(Function));
3561
});
3662

37-
test("should log error and return original link if an error occurs while adding live preview query tags", () => {
38-
const originalUrl =
39-
"http://example.com?live_preview=hash&content_type_uid=ctuid&entry_uid=entryuid";
40-
const expectedLoggedError = "Error while adding live preview to URL";
63+
describe("when clicking on elements", () => {
64+
beforeEach(() => {
65+
addParamsToUrl();
66+
});
67+
68+
test("should handle click directly on anchor tag", () => {
69+
// Create mock anchor element
70+
const mockAnchor = {
71+
href: "https://example.com/page",
72+
closest: vi.fn().mockReturnValue(null),
73+
contains: vi.fn().mockReturnValue(true)
74+
};
75+
mockAnchor.closest.mockReturnValue(mockAnchor); // closest('a') returns self
76+
77+
const mockEvent = {
78+
target: mockAnchor
79+
};
80+
81+
// Trigger the click event
82+
clickHandler(mockEvent);
4183

42-
vi.spyOn(global, "URL").mockImplementation(() => {
43-
throw new Error("Mock error");
84+
expect(mockAnchor.closest).toHaveBeenCalledWith('a');
85+
expect(mockAnchor.contains).toHaveBeenCalledWith(mockAnchor);
86+
expect(addLivePreviewQueryTags).toHaveBeenCalledWith("https://example.com/page");
87+
expect(mockAnchor.href).toBe("https://example.com/page?live_preview=test&content_type_uid=test&entry_uid=test");
4488
});
4589

46-
const result = addLivePreviewQueryTags(originalUrl);
90+
test("should handle click on child element of anchor tag", () => {
91+
// Create mock child element and parent anchor
92+
const mockChild = {
93+
closest: vi.fn()
94+
};
95+
96+
const mockAnchor = {
97+
href: "https://example.com/child-page",
98+
contains: vi.fn().mockReturnValue(true)
99+
};
100+
101+
mockChild.closest.mockReturnValue(mockAnchor); // closest('a') returns parent anchor
102+
103+
const mockEvent = {
104+
target: mockChild
105+
};
106+
107+
// Trigger the click event
108+
clickHandler(mockEvent);
109+
110+
expect(mockChild.closest).toHaveBeenCalledWith('a');
111+
expect(mockAnchor.contains).toHaveBeenCalledWith(mockChild);
112+
expect(addLivePreviewQueryTags).toHaveBeenCalledWith("https://example.com/child-page");
113+
expect(mockAnchor.href).toBe("https://example.com/child-page?live_preview=test&content_type_uid=test&entry_uid=test");
114+
});
115+
116+
test("should not process click when no anchor element is found", () => {
117+
const mockElement = {
118+
closest: vi.fn().mockReturnValue(null)
119+
};
120+
121+
const mockEvent = {
122+
target: mockElement
123+
};
124+
125+
clickHandler(mockEvent);
126+
127+
expect(mockElement.closest).toHaveBeenCalledWith('a');
128+
expect(addLivePreviewQueryTags).not.toHaveBeenCalled();
129+
});
130+
131+
test("should not process click when anchor doesn't contain clicked element", () => {
132+
const mockChild = {
133+
closest: vi.fn()
134+
};
135+
136+
const mockAnchor = {
137+
href: "https://example.com/page",
138+
contains: vi.fn().mockReturnValue(false) // Anchor doesn't contain the clicked element
139+
};
140+
141+
mockChild.closest.mockReturnValue(mockAnchor);
142+
143+
const mockEvent = {
144+
target: mockChild
145+
};
146+
147+
clickHandler(mockEvent);
148+
149+
expect(mockChild.closest).toHaveBeenCalledWith('a');
150+
expect(mockAnchor.contains).toHaveBeenCalledWith(mockChild);
151+
expect(addLivePreviewQueryTags).not.toHaveBeenCalled();
152+
});
47153

48-
expect(PublicLogger.error).toHaveBeenCalledWith(expectedLoggedError);
154+
test("should not process external links", () => {
155+
const mockAnchor = {
156+
href: "https://external-site.com/page",
157+
closest: vi.fn().mockReturnValue(null),
158+
contains: vi.fn().mockReturnValue(true)
159+
};
160+
mockAnchor.closest.mockReturnValue(mockAnchor);
161+
162+
const mockEvent = {
163+
target: mockAnchor
164+
};
49165

50-
expect(result).toEqual(originalUrl);
166+
clickHandler(mockEvent);
167+
168+
expect(addLivePreviewQueryTags).not.toHaveBeenCalled();
169+
expect(mockAnchor.href).toBe("https://external-site.com/page"); // Unchanged
170+
});
171+
172+
test("should not process links that already contain live_preview", () => {
173+
const mockAnchor = {
174+
href: "https://example.com/page?live_preview=existing",
175+
closest: vi.fn().mockReturnValue(null),
176+
contains: vi.fn().mockReturnValue(true)
177+
};
178+
mockAnchor.closest.mockReturnValue(mockAnchor);
179+
180+
const mockEvent = {
181+
target: mockAnchor
182+
};
183+
184+
clickHandler(mockEvent);
185+
186+
expect(addLivePreviewQueryTags).not.toHaveBeenCalled();
187+
expect(mockAnchor.href).toBe("https://example.com/page?live_preview=existing"); // Unchanged
188+
});
189+
190+
test("should not process links without href", () => {
191+
const mockAnchor = {
192+
href: "",
193+
closest: vi.fn().mockReturnValue(null),
194+
contains: vi.fn().mockReturnValue(true)
195+
};
196+
mockAnchor.closest.mockReturnValue(mockAnchor);
197+
198+
const mockEvent = {
199+
target: mockAnchor
200+
};
201+
202+
clickHandler(mockEvent);
203+
204+
expect(addLivePreviewQueryTags).not.toHaveBeenCalled();
205+
expect(mockAnchor.href).toBe(""); // Unchanged
206+
});
207+
208+
test("should handle case when addLivePreviewQueryTags returns empty string", () => {
209+
vi.mocked(addLivePreviewQueryTags).mockReturnValue("");
210+
211+
const originalHref = "https://example.com/page";
212+
const mockAnchor = {
213+
href: originalHref,
214+
closest: vi.fn().mockReturnValue(null),
215+
contains: vi.fn().mockReturnValue(true)
216+
};
217+
mockAnchor.closest.mockReturnValue(mockAnchor);
218+
219+
const mockEvent = {
220+
target: mockAnchor
221+
};
222+
223+
clickHandler(mockEvent);
224+
225+
expect(addLivePreviewQueryTags).toHaveBeenCalledWith(originalHref);
226+
expect(mockAnchor.href).toBe(originalHref); // Falls back to original href when empty string returned
227+
});
228+
229+
test("should handle nested child elements", () => {
230+
// Create deeply nested structure: span > button > a
231+
const mockDeepChild = {
232+
closest: vi.fn()
233+
};
234+
235+
const mockAnchor = {
236+
href: "https://example.com/nested-page",
237+
contains: vi.fn().mockReturnValue(true)
238+
};
239+
240+
mockDeepChild.closest.mockReturnValue(mockAnchor);
241+
242+
const mockEvent = {
243+
target: mockDeepChild
244+
};
245+
246+
clickHandler(mockEvent);
247+
248+
expect(mockDeepChild.closest).toHaveBeenCalledWith('a');
249+
expect(mockAnchor.contains).toHaveBeenCalledWith(mockDeepChild);
250+
expect(addLivePreviewQueryTags).toHaveBeenCalledWith("https://example.com/nested-page");
251+
expect(mockAnchor.href).toBe("https://example.com/nested-page?live_preview=test&content_type_uid=test&entry_uid=test");
252+
});
51253
});
52254
});

0 commit comments

Comments
 (0)