Skip to content

Commit 5b8c3f8

Browse files
authored
Merge branch 'develop_v4' into vitest-3-optimizing
2 parents 3bc25f8 + 9000f02 commit 5b8c3f8

15 files changed

+354
-123
lines changed

CHANGELOG.md

Lines changed: 109 additions & 82 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Alternatively, if you want to include the package directly in your website HTML
1616

1717
```html
1818
<script type='module' crossorigin="anonymous">
19-
import ContentstackLivePreview from 'https://esm.sh/@contentstack/[email protected].1';
19+
import ContentstackLivePreview from 'https://esm.sh/@contentstack/[email protected].2';
2020
2121
ContentstackLivePreview.init({
2222
stackDetails: {

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/live-preview-utils",
3-
"version": "4.1.1",
3+
"version": "4.1.2",
44
"description": "Contentstack provides the Live Preview SDK to establish a communication channel between the various Contentstack SDKs and your website, transmitting live changes to the preview pane.",
55
"type": "module",
66
"types": "dist/legacy/index.d.ts",

src/visualBuilder/components/fieldLabelWrapper.tsx

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,11 @@ function FieldLabelWrapperComponent(
113113
const allPaths = uniqBy(
114114
[
115115
props.fieldMetadata,
116-
...props.parentPaths.map((path) => {
117-
return extractDetailsFromCslp(path);
118-
}),
116+
...props.parentPaths
117+
.filter((path) => path)
118+
.map((path) => {
119+
return extractDetailsFromCslp(path);
120+
}),
119121
],
120122
"cslpValue"
121123
);
@@ -142,12 +144,14 @@ function FieldLabelWrapperComponent(
142144
const domAncestor = eventDetails.editableElement.closest(`[data-cslp]:not([data-cslp^="${props.fieldMetadata.content_type_uid}"])`);
143145
if(domAncestor) {
144146
const domAncestorCslp = domAncestor.getAttribute("data-cslp");
145-
const domAncestorDetails = extractDetailsFromCslp(domAncestorCslp!);
146-
const domAncestorContentTypeUid = domAncestorDetails.content_type_uid;
147-
const domAncestorContentParent = referenceData?.find(data => data.contentTypeUid === domAncestorContentTypeUid);
148-
if(domAncestorContentParent) {
149-
referenceFieldName = domAncestorContentParent.referenceFieldName;
150-
parentContentTypeName = domAncestorContentParent.contentTypeTitle;
147+
if (domAncestorCslp) {
148+
const domAncestorDetails = extractDetailsFromCslp(domAncestorCslp);
149+
const domAncestorContentTypeUid = domAncestorDetails.content_type_uid;
150+
const domAncestorContentParent = referenceData?.find(data => data.contentTypeUid === domAncestorContentTypeUid);
151+
if(domAncestorContentParent) {
152+
referenceFieldName = domAncestorContentParent.referenceFieldName;
153+
parentContentTypeName = domAncestorContentParent.contentTypeTitle;
154+
}
151155
}
152156
}
153157
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2+
import { sendFieldEvent } from "../generateOverlay";
3+
import { VisualBuilder } from "../..";
4+
import { VisualBuilderPostMessageEvents } from "../../utils/types/postMessage.types";
5+
import visualBuilderPostMessage from "../../utils/visualBuilderPostMessage";
6+
import { FieldSchemaMap } from "../../utils/fieldSchemaMap";
7+
import { extractDetailsFromCslp } from "../../../cslp/cslpdata";
8+
9+
vi.mock("../../utils/visualBuilderPostMessage", () => ({
10+
default: {
11+
send: vi.fn(),
12+
},
13+
}));
14+
15+
vi.mock("../../utils/fieldSchemaMap", () => ({
16+
FieldSchemaMap: {
17+
getFieldSchema: vi.fn().mockResolvedValue({
18+
display_name: "Test Field",
19+
data_type: "text",
20+
}),
21+
},
22+
}));
23+
24+
vi.mock("../../../cslp/cslpdata", () => ({
25+
extractDetailsFromCslp: vi.fn(),
26+
}));
27+
28+
describe("sendFieldEvent", () => {
29+
let previousSelectedEditableDOM: HTMLElement;
30+
let visualBuilderContainer: HTMLElement;
31+
32+
beforeEach(() => {
33+
previousSelectedEditableDOM = document.createElement("div");
34+
previousSelectedEditableDOM.setAttribute("contenteditable", "true");
35+
previousSelectedEditableDOM.innerText = "Test content";
36+
document.body.appendChild(previousSelectedEditableDOM);
37+
38+
visualBuilderContainer = document.createElement("div");
39+
document.body.appendChild(visualBuilderContainer);
40+
41+
VisualBuilder.VisualBuilderGlobalState.value.previousSelectedEditableDOM =
42+
previousSelectedEditableDOM;
43+
});
44+
45+
afterEach(() => {
46+
document.body.innerHTML = "";
47+
vi.clearAllMocks();
48+
});
49+
50+
it("should return early and not send event when data-cslp attribute is invalid", () => {
51+
previousSelectedEditableDOM.setAttribute("data-cslp", "");
52+
53+
sendFieldEvent({
54+
visualBuilderContainer,
55+
eventType: VisualBuilderPostMessageEvents.UPDATE_FIELD,
56+
});
57+
58+
expect(extractDetailsFromCslp).not.toHaveBeenCalled();
59+
expect(FieldSchemaMap.getFieldSchema).not.toHaveBeenCalled();
60+
expect(visualBuilderPostMessage?.send).not.toHaveBeenCalled();
61+
});
62+
});
63+

src/visualBuilder/generators/generateOverlay.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,12 @@ export function sendFieldEvent(options: ISendFieldEventParams): void {
177177
? actualEditedElement.innerText
178178
: actualEditedElement.textContent;
179179

180-
const fieldMetadata = extractDetailsFromCslp(
181-
previousSelectedEditableDOM.getAttribute("data-cslp") as string
182-
);
180+
const cslpData = previousSelectedEditableDOM.getAttribute("data-cslp");
181+
if (!cslpData) {
182+
return;
183+
}
184+
185+
const fieldMetadata = extractDetailsFromCslp(cslpData);
183186

184187
FieldSchemaMap.getFieldSchema(
185188
fieldMetadata.content_type_uid,

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,4 +217,46 @@ describe("getDOMEditStack", () => {
217217
const editStack = getDOMEditStack(leafEl);
218218
expect(editStack.length).toBe(2);
219219
});
220+
221+
test("get dom edit stack should filter out elements with same prefix", () => {
222+
const dom = new JSDOM(`
223+
<div data-cslp="page.pageuid.en-us.group">
224+
<div data-cslp="page.pageuid.en-us.group.field1">
225+
<div data-cslp="page.pageuid.en-us.group.field1.nested">
226+
<span data-cslp="page.pageuid.en-us.group.field1.nested.leaf">leaf</span>
227+
</div>
228+
</div>
229+
</div>
230+
`).window.document;
231+
const leafEl = dom.querySelector(
232+
'[data-cslp="page.pageuid.en-us.group.field1.nested.leaf"]'
233+
) as HTMLElement;
234+
const editStack = getDOMEditStack(leafEl);
235+
// Should only have one entry since all have same prefix (page.pageuid.en-us)
236+
expect(editStack.length).toBe(1);
237+
expect(editStack[0].content_type_uid).toBe("page");
238+
expect(editStack[0].entry_uid).toBe("pageuid");
239+
});
240+
241+
test("get dom edit stack should filter out elements with invalid data-cslp attribute", () => {
242+
const dom = new JSDOM(`
243+
<div data-cslp="page.pageuid.en-us.group">
244+
<div data-cslp>
245+
<div data-cslp="">
246+
<div data-cslp="blog.bloguid.fr-fr.title">
247+
<span data-cslp="blog.bloguid.fr-fr.title.name">name</span>
248+
</div>
249+
</div>
250+
</div>
251+
</div>
252+
`).window.document;
253+
const leafEl = dom.querySelector(
254+
'[data-cslp="blog.bloguid.fr-fr.title.name"]'
255+
) as HTMLElement;
256+
const editStack = getDOMEditStack(leafEl);
257+
// Should have two entries (page, blog) - invalid cslp attributes (empty or no value) should be filtered out
258+
expect(editStack.length).toBe(2);
259+
expect(editStack[0].content_type_uid).toBe("page");
260+
expect(editStack[1].content_type_uid).toBe("blog");
261+
});
220262
});

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,4 +47,17 @@ describe("getEntryIdentifiersInCurrentPage", () => {
4747
expect(entriesInCurrentPage.length).toBe(0);
4848
expect(entriesInCurrentPage).toEqual([]);
4949
});
50+
51+
test("should filter out elements with empty data-cslp attribute and not break", () => {
52+
document.body.innerHTML = `
53+
<div>
54+
<h1 data-cslp="">Empty CSLP</h1>
55+
<h2 data-cslp>Empty CSLP</h2>
56+
<h1 data-cslp="page.bltf5bb5f8fb088a332.en-us.page_components.0.hero_banner.banner_title">Valid CSLP</h1>
57+
</div>
58+
`;
59+
const { entriesInCurrentPage } = getEntryIdentifiersInCurrentPage();
60+
expect(entriesInCurrentPage.length).toBe(1);
61+
expect(entriesInCurrentPage[0].entryUid).toBe('bltf5bb5f8fb088a332');
62+
});
5063
});

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,25 @@ describe('getVisualBuilderRedirectionUrl', () => {
8484
const result = getVisualBuilderRedirectionUrl();
8585
expect(result.toString()).toBe('https://app.example.com/#!/stack/12345/visual-builder?branch=main&environment=production&target-url=https%3A%2F%2Fexample.com%2F');
8686
});
87+
88+
it('should ignore invalid data-cslp attribute and use locale from config', () => {
89+
document.body.innerHTML = '<div data-cslp></div>';
90+
Config.get.mockReturnValue({
91+
stackDetails: {
92+
branch: 'main',
93+
apiKey: '12345',
94+
environment: 'production',
95+
locale: 'en-US'
96+
},
97+
clientUrlParams: {
98+
url: 'https://app.example.com'
99+
}
100+
});
101+
102+
const result = getVisualBuilderRedirectionUrl();
103+
// Should use locale from config when data-cslp attribute is invalid (empty or no value)
104+
expect(result.toString()).toBe('https://app.example.com/#!/stack/12345/visual-builder?branch=main&environment=production&target-url=https%3A%2F%2Fexample.com%2F&locale=en-US');
105+
// Should not call extractDetailsFromCslp for invalid cslp
106+
expect(extractDetailsFromCslp).not.toHaveBeenCalled();
107+
});
87108
});

0 commit comments

Comments
 (0)