Skip to content

Commit 9398aa0

Browse files
committed
field wrapper separate
1 parent a75562a commit 9398aa0

10 files changed

Lines changed: 2285 additions & 511 deletions
Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
import { render, waitFor, act, findByTestId } from "@testing-library/preact";
2+
import FieldLabelWrapperComponent from "../../fieldLabelWrapper";
3+
import { VisualBuilderCslpEventDetails } from "../../../types/visualBuilder.types";
4+
import { singleLineFieldSchema } from "../../../../__test__/data/fields";
5+
import { isFieldDisabled } from "../../../utils/isFieldDisabled";
6+
import { FieldSchemaMap } from "../../../utils/fieldSchemaMap";
7+
import visualBuilderPostMessage from "../../../utils/visualBuilderPostMessage";
8+
import { VisualBuilderPostMessageEvents } from "../../../utils/types/postMessage.types";
9+
import React from "preact/compat";
10+
// Import shared mocks and constants
11+
import {
12+
mockStyles,
13+
DISPLAY_NAMES,
14+
PARENT_PATHS,
15+
mockFieldMetadata,
16+
mockEntryPermissionsResponse,
17+
} from "./fieldLabelWrapper.mocks";
18+
19+
// Local cache for this test file (can't use imported cache in vi.mock due to hoisting)
20+
const testFieldSchemaCache: Record<string, Record<string, any>> = {};
21+
22+
// All mocks - inline implementations (can't use imported functions in vi.mock due to hoisting)
23+
vi.mock("../Tooltip", () => ({
24+
ToolbarTooltip: ({ children, data, disabled }: any) => (
25+
<div
26+
data-testid="toolbar-tooltip"
27+
data-disabled={disabled}
28+
data-content-type-name={data.contentTypeName}
29+
data-reference-field-name={data.referenceFieldName}
30+
>
31+
{children}
32+
</div>
33+
),
34+
}));
35+
36+
vi.mock("../../../utils/fieldSchemaMap", async (importOriginal) => {
37+
const actual =
38+
await importOriginal<typeof import("../../../utils/fieldSchemaMap")>();
39+
// Inline implementation - can't use imported function due to vi.mock hoisting
40+
// Using local cache variable defined above
41+
return {
42+
FieldSchemaMap: {
43+
...actual.FieldSchemaMap,
44+
getFieldSchema: vi
45+
.fn()
46+
.mockImplementation(
47+
(contentTypeUid: string, fieldPath: string) => {
48+
if (testFieldSchemaCache[contentTypeUid]?.[fieldPath]) {
49+
return Promise.resolve(
50+
testFieldSchemaCache[contentTypeUid][fieldPath]
51+
);
52+
}
53+
const defaultSchema = {
54+
display_name: "Field 0",
55+
data_type: "text",
56+
field_metadata: {
57+
description: "",
58+
default_value: "",
59+
version: 3,
60+
},
61+
uid: "test_field",
62+
};
63+
if (!testFieldSchemaCache[contentTypeUid]) {
64+
testFieldSchemaCache[contentTypeUid] = {};
65+
}
66+
testFieldSchemaCache[contentTypeUid][fieldPath] =
67+
defaultSchema;
68+
return Promise.resolve(defaultSchema);
69+
}
70+
),
71+
setFieldSchema: vi
72+
.fn()
73+
.mockImplementation(
74+
(
75+
contentTypeUid: string,
76+
schemaMap: Record<string, any>
77+
) => {
78+
if (!testFieldSchemaCache[contentTypeUid]) {
79+
testFieldSchemaCache[contentTypeUid] = {};
80+
}
81+
Object.assign(
82+
testFieldSchemaCache[contentTypeUid],
83+
schemaMap
84+
);
85+
}
86+
),
87+
hasFieldSchema: vi
88+
.fn()
89+
.mockImplementation(
90+
(contentTypeUid: string, fieldPath: string) => {
91+
return !!testFieldSchemaCache[contentTypeUid]?.[
92+
fieldPath
93+
];
94+
}
95+
),
96+
clear: vi.fn().mockImplementation(() => {
97+
Object.keys(testFieldSchemaCache).forEach(
98+
(key) => delete testFieldSchemaCache[key]
99+
);
100+
}),
101+
},
102+
};
103+
});
104+
105+
vi.mock("../../../utils/visualBuilderPostMessage", () => ({
106+
default: {
107+
send: vi.fn().mockImplementation((eventName: string, fields: any) => {
108+
// Inline implementation - can't use imported function due to vi.mock hoisting
109+
if (
110+
eventName ===
111+
VisualBuilderPostMessageEvents.GET_FIELD_DISPLAY_NAMES
112+
) {
113+
const result: Record<string, string> = {};
114+
if (Array.isArray(fields)) {
115+
fields.forEach((field: any) => {
116+
const cslpValue = field?.cslpValue || field?.cslp || "";
117+
if (!cslpValue) return;
118+
if (cslpValue === "mockFieldCslp") {
119+
result[cslpValue] = "Field 0";
120+
} else if (
121+
cslpValue ===
122+
"contentTypeUid.entryUid.locale.parentPath1"
123+
) {
124+
result[cslpValue] = "Field 1";
125+
} else if (
126+
cslpValue ===
127+
"contentTypeUid.entryUid.locale.parentPath2"
128+
) {
129+
result[cslpValue] = "Field 2";
130+
} else if (
131+
cslpValue ===
132+
"contentTypeUid.entryUid.locale.parentPath3"
133+
) {
134+
result[cslpValue] = "Field 3";
135+
} else {
136+
result[cslpValue] = cslpValue;
137+
}
138+
});
139+
}
140+
return Promise.resolve(result);
141+
} else if (
142+
eventName ===
143+
VisualBuilderPostMessageEvents.GET_CONTENT_TYPE_NAME ||
144+
eventName === "get-content-type-name"
145+
) {
146+
return Promise.resolve({
147+
contentTypeName: "Page CT",
148+
});
149+
} else if (
150+
eventName === VisualBuilderPostMessageEvents.REFERENCE_MAP ||
151+
eventName === "get-reference-map"
152+
) {
153+
return Promise.resolve({});
154+
}
155+
return Promise.resolve({});
156+
}),
157+
},
158+
}));
159+
160+
vi.mock("../../../utils/isFieldDisabled", async (importOriginal) => {
161+
const actual =
162+
await importOriginal<typeof import("../../../utils/isFieldDisabled")>();
163+
return {
164+
...actual,
165+
isFieldDisabled: vi
166+
.fn()
167+
.mockReturnValue({ isDisabled: false, reason: "" }),
168+
};
169+
});
170+
171+
vi.mock("../../../cslp", () => ({
172+
extractDetailsFromCslp: vi.fn().mockImplementation((path) => {
173+
return {
174+
content_type_uid: "mockContentTypeUid",
175+
fieldPath: path,
176+
cslpValue: path,
177+
};
178+
}),
179+
}));
180+
181+
vi.mock("../../../utils/fetchEntryPermissionsAndStageDetails", () => ({
182+
fetchEntryPermissionsAndStageDetails: vi.fn().mockImplementation(() => {
183+
return Promise.resolve(mockEntryPermissionsResponse);
184+
}),
185+
}));
186+
187+
vi.mock("../generators/generateCustomCursor", () => ({
188+
getFieldIcon: vi.fn().mockReturnValue("<svg>mock-icon</svg>"),
189+
FieldTypeIconsMap: {
190+
reference: "<svg>reference-icon</svg>",
191+
},
192+
}));
193+
194+
vi.mock("../visualBuilder.style", () => ({
195+
visualBuilderStyles: vi.fn(() => mockStyles),
196+
}));
197+
198+
vi.mock("../../VariantIndicator", () => ({
199+
VariantIndicator: () => <div data-testid="variant-indicator">Variant</div>,
200+
}));
201+
202+
vi.mock("../../../utils/errorHandling", () => ({
203+
hasPostMessageError: vi.fn().mockReturnValue(false),
204+
}));
205+
206+
describe("FieldLabelWrapperComponent - Disabled Class", () => {
207+
beforeEach(() => {
208+
// Reset all mocks to their default state before each test
209+
vi.clearAllMocks();
210+
211+
// Reset isFieldDisabled to default
212+
(isFieldDisabled as any).mockReturnValue({
213+
isDisabled: false,
214+
reason: "",
215+
});
216+
217+
// Reset visualBuilderPostMessage mock - inline implementation
218+
vi.mocked(visualBuilderPostMessage!.send).mockImplementation(
219+
(eventName: string, fields: any) => {
220+
if (
221+
eventName ===
222+
VisualBuilderPostMessageEvents.GET_FIELD_DISPLAY_NAMES
223+
) {
224+
const result: Record<string, string> = {};
225+
if (Array.isArray(fields)) {
226+
fields.forEach((field: any) => {
227+
const cslpValue =
228+
field?.cslpValue || field?.cslp || "";
229+
if (!cslpValue) return;
230+
if (cslpValue === "mockFieldCslp") {
231+
result[cslpValue] = "Field 0";
232+
} else if (
233+
cslpValue ===
234+
"contentTypeUid.entryUid.locale.parentPath1"
235+
) {
236+
result[cslpValue] = "Field 1";
237+
} else if (
238+
cslpValue ===
239+
"contentTypeUid.entryUid.locale.parentPath2"
240+
) {
241+
result[cslpValue] = "Field 2";
242+
} else if (
243+
cslpValue ===
244+
"contentTypeUid.entryUid.locale.parentPath3"
245+
) {
246+
result[cslpValue] = "Field 3";
247+
} else {
248+
result[cslpValue] = cslpValue;
249+
}
250+
});
251+
}
252+
return Promise.resolve(result);
253+
} else if (
254+
eventName ===
255+
VisualBuilderPostMessageEvents.GET_CONTENT_TYPE_NAME ||
256+
eventName === "get-content-type-name"
257+
) {
258+
return Promise.resolve({
259+
contentTypeName: "Page CT",
260+
});
261+
} else if (
262+
eventName ===
263+
VisualBuilderPostMessageEvents.REFERENCE_MAP ||
264+
eventName === "get-reference-map"
265+
) {
266+
return Promise.resolve({});
267+
}
268+
return Promise.resolve({});
269+
}
270+
);
271+
272+
// Pre-set field schema in cache to avoid async fetch delay
273+
// This makes FieldSchemaMap.getFieldSchema resolve immediately from cache
274+
FieldSchemaMap.setFieldSchema(mockFieldMetadata.content_type_uid, {
275+
[mockFieldMetadata.fieldPath]: singleLineFieldSchema,
276+
});
277+
});
278+
279+
afterEach(() => {
280+
// Clean up field schema cache after each test
281+
FieldSchemaMap.clear();
282+
// Clean up DOM after each test to prevent state pollution
283+
document.body.innerHTML = "";
284+
});
285+
286+
afterAll(() => {
287+
vi.clearAllMocks();
288+
});
289+
290+
// mockFieldMetadata is now defined above the describe block
291+
292+
const mockEventDetails: VisualBuilderCslpEventDetails = {
293+
editableElement: document.createElement("div"),
294+
cslpData: "",
295+
fieldMetadata: mockFieldMetadata,
296+
};
297+
298+
const mockGetParentEditable = () => document.createElement("div");
299+
300+
test("renders with correct class when field is disabled", async () => {
301+
(isFieldDisabled as any).mockReturnValue({
302+
isDisabled: true,
303+
reason: "You have only read access to this field",
304+
});
305+
const { container } = render(
306+
<FieldLabelWrapperComponent
307+
fieldMetadata={mockFieldMetadata}
308+
eventDetails={mockEventDetails}
309+
parentPaths={[]}
310+
getParentEditableElement={mockGetParentEditable}
311+
/>
312+
);
313+
314+
// Use act() with queueMicrotask for faster resolution
315+
await act(async () => {
316+
await new Promise<void>((resolve) =>
317+
queueMicrotask(() => resolve())
318+
);
319+
});
320+
321+
// Use findByTestId which is optimized for async queries
322+
const fieldLabel = (await findByTestId(
323+
container as HTMLElement,
324+
"visual-builder__focused-toolbar__field-label-wrapper",
325+
{},
326+
{ timeout: 1000 }
327+
)) as HTMLElement;
328+
expect(fieldLabel).toHaveClass(
329+
"visual-builder__focused-toolbar--field-disabled"
330+
);
331+
});
332+
});

0 commit comments

Comments
 (0)