@elementor/editor-canvas 4.2.0-beta2 → 4.3.0-1000
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +448 -1318
- package/dist/index.mjs +313 -1196
- package/package.json +22 -22
- package/src/index.ts +1 -1
- package/src/legacy/__tests__/init-legacy-views.test.ts +1 -1
- package/src/mcp/canvas-mcp.ts +4 -2
- package/src/mcp/resources/__tests__/available-widgets-resource.test.ts +76 -0
- package/src/mcp/resources/__tests__/dynamic-tags-resource.test.ts +27 -37
- package/src/mcp/resources/__tests__/widgets-schema-resource.test.ts +79 -76
- package/src/mcp/resources/available-widgets-resource.ts +31 -40
- package/src/mcp/resources/best-practices-resource.ts +34 -0
- package/src/mcp/resources/dynamic-tags-resource.ts +28 -29
- package/src/mcp/resources/widgets-schema-resource.ts +34 -177
- package/src/mcp/tools/build-composition/tool.ts +107 -218
- package/src/sync/element-added-event.ts +8 -0
- package/src/utils/__tests__/grid-outline-utils.test.ts +39 -0
- package/src/utils/grid-outline-utils.ts +13 -2
- package/src/composition-builder/__tests__/composition-builder.test.ts +0 -352
- package/src/composition-builder/composition-builder.ts +0 -350
- package/src/composition-builder/utils/__tests__/required-children-enforcer.test.ts +0 -79
- package/src/composition-builder/utils/__tests__/required-default-child-tags.test.ts +0 -35
- package/src/composition-builder/utils/required-children-enforcer.ts +0 -56
- package/src/composition-builder/utils/required-default-child-tags.ts +0 -22
- package/src/mcp/resources/best-practices.ts +0 -159
- package/src/mcp/resources/build-llm-guidance.ts +0 -110
- package/src/mcp/tools/build-composition/__tests__/xml-leaf-wrapper.test.ts +0 -117
- package/src/mcp/tools/build-composition/prompt.ts +0 -167
- package/src/mcp/tools/build-composition/schema.ts +0 -42
- package/src/mcp/tools/build-composition/xml-leaf-wrapper.ts +0 -68
- package/src/mcp/utils/__tests__/get-composition-target-container.test.ts +0 -59
- package/src/mcp/utils/get-composition-target-container.ts +0 -15
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
computeCellRects,
|
|
4
4
|
computeGridLines,
|
|
5
5
|
parseTrackList,
|
|
6
|
+
resolveGapPx,
|
|
6
7
|
snapToHalfPixel,
|
|
7
8
|
toGridTracks,
|
|
8
9
|
toPx,
|
|
@@ -215,11 +216,29 @@ describe( 'toPx', () => {
|
|
|
215
216
|
} );
|
|
216
217
|
} );
|
|
217
218
|
|
|
219
|
+
describe( 'resolveGapPx', () => {
|
|
220
|
+
it( 'resolves a percentage gap against the reference size', () => {
|
|
221
|
+
expect( resolveGapPx( '20%', '1000px' ) ).toBe( 200 );
|
|
222
|
+
expect( resolveGapPx( '10%', '500px' ) ).toBe( 50 );
|
|
223
|
+
} );
|
|
224
|
+
|
|
225
|
+
it( 'falls back to px parsing for non-percentage gaps', () => {
|
|
226
|
+
expect( resolveGapPx( '16px', '1000px' ) ).toBe( 16 );
|
|
227
|
+
expect( resolveGapPx( 'normal', '1000px' ) ).toBe( 0 );
|
|
228
|
+
} );
|
|
229
|
+
|
|
230
|
+
it( 'returns 0 when the reference size is not a finite number', () => {
|
|
231
|
+
expect( resolveGapPx( '20%', 'auto' ) ).toBe( 0 );
|
|
232
|
+
} );
|
|
233
|
+
} );
|
|
234
|
+
|
|
218
235
|
type ComputedStyleParts = {
|
|
219
236
|
gridTemplateColumns: string;
|
|
220
237
|
gridTemplateRows: string;
|
|
221
238
|
columnGap: string;
|
|
222
239
|
rowGap: string;
|
|
240
|
+
width: string;
|
|
241
|
+
height: string;
|
|
223
242
|
paddingTop: string;
|
|
224
243
|
paddingRight: string;
|
|
225
244
|
paddingBottom: string;
|
|
@@ -233,6 +252,8 @@ function mockComputedStyle( parts: Partial< ComputedStyleParts > = {} ): CSSStyl
|
|
|
233
252
|
gridTemplateRows: 'none',
|
|
234
253
|
columnGap: 'normal',
|
|
235
254
|
rowGap: 'normal',
|
|
255
|
+
width: '1000px',
|
|
256
|
+
height: '500px',
|
|
236
257
|
paddingTop: '0px',
|
|
237
258
|
paddingRight: '0px',
|
|
238
259
|
paddingBottom: '0px',
|
|
@@ -246,6 +267,8 @@ function mockComputedStyle( parts: Partial< ComputedStyleParts > = {} ): CSSStyl
|
|
|
246
267
|
gridTemplateRows: resolved.gridTemplateRows,
|
|
247
268
|
columnGap: resolved.columnGap,
|
|
248
269
|
rowGap: resolved.rowGap,
|
|
270
|
+
width: resolved.width,
|
|
271
|
+
height: resolved.height,
|
|
249
272
|
paddingTop: resolved.paddingTop,
|
|
250
273
|
paddingRight: resolved.paddingRight,
|
|
251
274
|
paddingBottom: resolved.paddingBottom,
|
|
@@ -286,6 +309,22 @@ describe( 'toGridTracks', () => {
|
|
|
286
309
|
expect( tracks.rows ).toEqual( [] );
|
|
287
310
|
} );
|
|
288
311
|
|
|
312
|
+
it( 'resolves percentage gaps against the content-box width and height', () => {
|
|
313
|
+
const tracks = toGridTracks(
|
|
314
|
+
mockComputedStyle( {
|
|
315
|
+
gridTemplateColumns: '100px 100px',
|
|
316
|
+
gridTemplateRows: '80px 80px',
|
|
317
|
+
columnGap: '20%',
|
|
318
|
+
rowGap: '10%',
|
|
319
|
+
width: '1000px',
|
|
320
|
+
height: '500px',
|
|
321
|
+
} )
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
expect( tracks.columnGap ).toBe( 200 );
|
|
325
|
+
expect( tracks.rowGap ).toBe( 50 );
|
|
326
|
+
} );
|
|
327
|
+
|
|
289
328
|
it( 'reports gap as 0 when computed style returns "normal"', () => {
|
|
290
329
|
const tracks = toGridTracks(
|
|
291
330
|
mockComputedStyle( { gridTemplateColumns: '100px 100px', columnGap: 'normal', rowGap: 'normal' } )
|
|
@@ -28,8 +28,8 @@ export function toGridTracks( computedStyle: CSSStyleDeclaration ): GridTracks {
|
|
|
28
28
|
return {
|
|
29
29
|
columns: parseTrackList( computedStyle.gridTemplateColumns ),
|
|
30
30
|
rows: parseTrackList( computedStyle.gridTemplateRows ),
|
|
31
|
-
columnGap:
|
|
32
|
-
rowGap:
|
|
31
|
+
columnGap: resolveGapPx( computedStyle.columnGap, computedStyle.width ),
|
|
32
|
+
rowGap: resolveGapPx( computedStyle.rowGap, computedStyle.height ),
|
|
33
33
|
padding: {
|
|
34
34
|
top: toPx( computedStyle.paddingTop ),
|
|
35
35
|
right: toPx( computedStyle.paddingRight ),
|
|
@@ -142,3 +142,14 @@ export function toPx( value: string ): number {
|
|
|
142
142
|
|
|
143
143
|
return Number.isFinite( parsed ) ? parsed : 0;
|
|
144
144
|
}
|
|
145
|
+
|
|
146
|
+
export function resolveGapPx( value: string, referenceSize: string ): number {
|
|
147
|
+
if ( value.trim().endsWith( '%' ) ) {
|
|
148
|
+
const percent = parseFloat( value );
|
|
149
|
+
const reference = parseFloat( referenceSize );
|
|
150
|
+
|
|
151
|
+
return Number.isFinite( percent ) && Number.isFinite( reference ) ? ( percent / 100 ) * reference : 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return toPx( value );
|
|
155
|
+
}
|
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
import { type CreateElementParams, type V1Element, type V1ElementConfig } from '@elementor/editor-elements';
|
|
2
|
-
|
|
3
|
-
import { CompositionBuilder } from '../composition-builder';
|
|
4
|
-
|
|
5
|
-
const ROOT_CHILD_TAG = 'column';
|
|
6
|
-
const GENERATED_ELEMENT_ID = 'generated-element-id';
|
|
7
|
-
const CONFIG_ID = 'cfg-a';
|
|
8
|
-
const ELEMENT_CONFIG_PROPERTY = 'title';
|
|
9
|
-
const ELEMENT_CONFIG_VALUE = 'configured-value';
|
|
10
|
-
|
|
11
|
-
const xmlStringWithConfiguration = `<${ ROOT_CHILD_TAG } configuration-id="${ CONFIG_ID }" />`;
|
|
12
|
-
|
|
13
|
-
const createElementConfigPayload = () => ( {
|
|
14
|
-
[ CONFIG_ID ]: {
|
|
15
|
-
[ ELEMENT_CONFIG_PROPERTY ]: ELEMENT_CONFIG_VALUE,
|
|
16
|
-
},
|
|
17
|
-
} );
|
|
18
|
-
|
|
19
|
-
const createMinimalWidgetsCache = () =>
|
|
20
|
-
( {
|
|
21
|
-
[ ROOT_CHILD_TAG ]: {
|
|
22
|
-
elType: 'column',
|
|
23
|
-
},
|
|
24
|
-
} ) as Record< string, { elType: string } >;
|
|
25
|
-
|
|
26
|
-
const FORM_WIDGETS_CACHE_WITH_REQUIRED_CHILDREN = {
|
|
27
|
-
'e-form': {
|
|
28
|
-
title: 'Form',
|
|
29
|
-
controls: {},
|
|
30
|
-
elType: 'widget',
|
|
31
|
-
default_children: [
|
|
32
|
-
{
|
|
33
|
-
elType: 'e-form-success-message',
|
|
34
|
-
meta: { required: true },
|
|
35
|
-
elements: [],
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
elType: 'e-form-error-message',
|
|
39
|
-
meta: { required: true },
|
|
40
|
-
elements: [],
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
elType: 'widget',
|
|
44
|
-
widgetType: 'e-form-input',
|
|
45
|
-
elements: [],
|
|
46
|
-
},
|
|
47
|
-
],
|
|
48
|
-
},
|
|
49
|
-
'e-form-error-message': {
|
|
50
|
-
title: 'Error',
|
|
51
|
-
controls: {},
|
|
52
|
-
elType: 'e-form-error-message',
|
|
53
|
-
},
|
|
54
|
-
'e-form-input': { title: 'Input', controls: {}, elType: 'widget' },
|
|
55
|
-
'e-form-success-message': {
|
|
56
|
-
title: 'Success',
|
|
57
|
-
controls: {},
|
|
58
|
-
elType: 'e-form-success-message',
|
|
59
|
-
},
|
|
60
|
-
} as const satisfies Record< string, V1ElementConfig >;
|
|
61
|
-
|
|
62
|
-
const createMockRootContainer = (): V1Element =>
|
|
63
|
-
( {
|
|
64
|
-
id: 'root',
|
|
65
|
-
model: { get: jest.fn(), set: jest.fn(), toJSON: jest.fn() },
|
|
66
|
-
settings: { get: jest.fn(), set: jest.fn(), toJSON: jest.fn() },
|
|
67
|
-
children: [],
|
|
68
|
-
} ) as unknown as V1Element;
|
|
69
|
-
|
|
70
|
-
const createMockPartialContainer = ( id: string ): V1Element =>
|
|
71
|
-
( {
|
|
72
|
-
id,
|
|
73
|
-
model: { get: jest.fn(), set: jest.fn(), toJSON: jest.fn() },
|
|
74
|
-
settings: { get: jest.fn(), set: jest.fn(), toJSON: jest.fn() },
|
|
75
|
-
children: [],
|
|
76
|
-
} ) as unknown as V1Element;
|
|
77
|
-
|
|
78
|
-
describe( 'CompositionBuilder.build createElement failure cleanup', () => {
|
|
79
|
-
it( 'calls deleteElement when createElement fails and getContainer returns a container', async () => {
|
|
80
|
-
// Arrange
|
|
81
|
-
const partialContainer = createMockPartialContainer( GENERATED_ELEMENT_ID );
|
|
82
|
-
const deleteElement = jest.fn();
|
|
83
|
-
const doUpdateElementProperty = jest.fn();
|
|
84
|
-
const createElement = jest.fn().mockImplementation( () => {
|
|
85
|
-
throw new Error( 'create failed' );
|
|
86
|
-
} );
|
|
87
|
-
const getContainer = jest
|
|
88
|
-
.fn()
|
|
89
|
-
.mockImplementation( ( id: string ) => ( id === GENERATED_ELEMENT_ID ? partialContainer : undefined ) );
|
|
90
|
-
const builder = CompositionBuilder.fromXMLString( xmlStringWithConfiguration, {
|
|
91
|
-
createElement,
|
|
92
|
-
deleteElement,
|
|
93
|
-
getContainer,
|
|
94
|
-
generateElementId: jest.fn().mockReturnValue( GENERATED_ELEMENT_ID ),
|
|
95
|
-
getWidgetsCache: jest.fn().mockReturnValue( createMinimalWidgetsCache() ),
|
|
96
|
-
doUpdateElementProperty,
|
|
97
|
-
} );
|
|
98
|
-
builder.setElementConfig( createElementConfigPayload() );
|
|
99
|
-
|
|
100
|
-
// Act
|
|
101
|
-
await expect( builder.build( createMockRootContainer() ) ).rejects.toThrow( 'create failed' );
|
|
102
|
-
|
|
103
|
-
// Assert
|
|
104
|
-
expect( getContainer ).toHaveBeenCalledWith( GENERATED_ELEMENT_ID );
|
|
105
|
-
expect( deleteElement ).toHaveBeenCalledTimes( 1 );
|
|
106
|
-
expect( deleteElement ).toHaveBeenCalledWith( { container: partialContainer } );
|
|
107
|
-
expect( doUpdateElementProperty ).not.toHaveBeenCalled();
|
|
108
|
-
} );
|
|
109
|
-
|
|
110
|
-
it( 'does not call deleteElement when createElement fails and getContainer returns undefined', async () => {
|
|
111
|
-
// Arrange
|
|
112
|
-
const deleteElement = jest.fn();
|
|
113
|
-
const doUpdateElementProperty = jest.fn();
|
|
114
|
-
const createElement = jest.fn().mockImplementation( () => {
|
|
115
|
-
throw new Error( 'create failed' );
|
|
116
|
-
} );
|
|
117
|
-
const getContainer = jest.fn().mockReturnValue( undefined );
|
|
118
|
-
const builder = CompositionBuilder.fromXMLString( xmlStringWithConfiguration, {
|
|
119
|
-
createElement,
|
|
120
|
-
deleteElement,
|
|
121
|
-
getContainer,
|
|
122
|
-
generateElementId: jest.fn().mockReturnValue( GENERATED_ELEMENT_ID ),
|
|
123
|
-
getWidgetsCache: jest.fn().mockReturnValue( createMinimalWidgetsCache() ),
|
|
124
|
-
doUpdateElementProperty,
|
|
125
|
-
} );
|
|
126
|
-
builder.setElementConfig( createElementConfigPayload() );
|
|
127
|
-
|
|
128
|
-
// Act
|
|
129
|
-
await expect( builder.build( createMockRootContainer() ) ).rejects.toThrow( 'create failed' );
|
|
130
|
-
|
|
131
|
-
// Assert
|
|
132
|
-
expect( getContainer ).toHaveBeenCalledWith( GENERATED_ELEMENT_ID );
|
|
133
|
-
expect( deleteElement ).not.toHaveBeenCalled();
|
|
134
|
-
expect( doUpdateElementProperty ).not.toHaveBeenCalled();
|
|
135
|
-
} );
|
|
136
|
-
|
|
137
|
-
it( 'calls deleteElement when createElement returns without a model', async () => {
|
|
138
|
-
// Arrange
|
|
139
|
-
const partialContainer = createMockPartialContainer( GENERATED_ELEMENT_ID );
|
|
140
|
-
const deleteElement = jest.fn();
|
|
141
|
-
const doUpdateElementProperty = jest.fn();
|
|
142
|
-
const createElement = jest.fn().mockReturnValue( {} as V1Element );
|
|
143
|
-
const getContainer = jest
|
|
144
|
-
.fn()
|
|
145
|
-
.mockImplementation( ( id: string ) => ( id === GENERATED_ELEMENT_ID ? partialContainer : undefined ) );
|
|
146
|
-
const builder = CompositionBuilder.fromXMLString( xmlStringWithConfiguration, {
|
|
147
|
-
createElement,
|
|
148
|
-
deleteElement,
|
|
149
|
-
getContainer,
|
|
150
|
-
generateElementId: jest.fn().mockReturnValue( GENERATED_ELEMENT_ID ),
|
|
151
|
-
getWidgetsCache: jest.fn().mockReturnValue( createMinimalWidgetsCache() ),
|
|
152
|
-
doUpdateElementProperty,
|
|
153
|
-
} );
|
|
154
|
-
builder.setElementConfig( createElementConfigPayload() );
|
|
155
|
-
|
|
156
|
-
// Act
|
|
157
|
-
await expect( builder.build( createMockRootContainer() ) ).rejects.toThrow(
|
|
158
|
-
'createElement did not return an element container with a model.'
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
// Assert
|
|
162
|
-
expect( getContainer ).toHaveBeenCalledWith( GENERATED_ELEMENT_ID );
|
|
163
|
-
expect( deleteElement ).toHaveBeenCalledTimes( 1 );
|
|
164
|
-
expect( deleteElement ).toHaveBeenCalledWith( { container: partialContainer } );
|
|
165
|
-
expect( doUpdateElementProperty ).not.toHaveBeenCalled();
|
|
166
|
-
} );
|
|
167
|
-
} );
|
|
168
|
-
|
|
169
|
-
describe( 'CompositionBuilder.build applyProperties after create', () => {
|
|
170
|
-
it( 'calls doUpdateElementProperty when create succeeds with element config', async () => {
|
|
171
|
-
// Arrange
|
|
172
|
-
const deleteElement = jest.fn();
|
|
173
|
-
const doUpdateElementProperty = jest.fn();
|
|
174
|
-
const createdElement = createMockPartialContainer( GENERATED_ELEMENT_ID );
|
|
175
|
-
const createElement = jest.fn().mockReturnValue( createdElement );
|
|
176
|
-
const getContainer = jest
|
|
177
|
-
.fn()
|
|
178
|
-
.mockImplementation( ( id: string ) => ( id === GENERATED_ELEMENT_ID ? createdElement : undefined ) );
|
|
179
|
-
const builder = CompositionBuilder.fromXMLString( xmlStringWithConfiguration, {
|
|
180
|
-
createElement,
|
|
181
|
-
deleteElement,
|
|
182
|
-
getContainer,
|
|
183
|
-
generateElementId: jest.fn().mockReturnValue( GENERATED_ELEMENT_ID ),
|
|
184
|
-
getWidgetsCache: jest.fn().mockReturnValue( createMinimalWidgetsCache() ),
|
|
185
|
-
doUpdateElementProperty,
|
|
186
|
-
} );
|
|
187
|
-
builder.setElementConfig( createElementConfigPayload() );
|
|
188
|
-
|
|
189
|
-
// Act
|
|
190
|
-
await builder.build( createMockRootContainer() );
|
|
191
|
-
|
|
192
|
-
// Assert
|
|
193
|
-
expect( deleteElement ).not.toHaveBeenCalled();
|
|
194
|
-
expect( createElement ).toHaveBeenCalledTimes( 1 );
|
|
195
|
-
expect( doUpdateElementProperty ).toHaveBeenCalledTimes( 1 );
|
|
196
|
-
expect( doUpdateElementProperty ).toHaveBeenCalledWith( {
|
|
197
|
-
elementId: GENERATED_ELEMENT_ID,
|
|
198
|
-
propertyName: ELEMENT_CONFIG_PROPERTY,
|
|
199
|
-
propertyValue: ELEMENT_CONFIG_VALUE,
|
|
200
|
-
elementType: ROOT_CHILD_TAG,
|
|
201
|
-
} );
|
|
202
|
-
} );
|
|
203
|
-
|
|
204
|
-
it( 'does not call doUpdateElementProperty when create succeeds without element config', async () => {
|
|
205
|
-
// Arrange
|
|
206
|
-
const deleteElement = jest.fn();
|
|
207
|
-
const doUpdateElementProperty = jest.fn();
|
|
208
|
-
const createdElement = createMockPartialContainer( GENERATED_ELEMENT_ID );
|
|
209
|
-
const createElement = jest.fn().mockReturnValue( createdElement );
|
|
210
|
-
const builder = CompositionBuilder.fromXMLString( `<${ ROOT_CHILD_TAG } />`, {
|
|
211
|
-
createElement,
|
|
212
|
-
deleteElement,
|
|
213
|
-
getContainer: jest.fn(),
|
|
214
|
-
generateElementId: jest.fn().mockReturnValue( GENERATED_ELEMENT_ID ),
|
|
215
|
-
getWidgetsCache: jest.fn().mockReturnValue( createMinimalWidgetsCache() ),
|
|
216
|
-
doUpdateElementProperty,
|
|
217
|
-
} );
|
|
218
|
-
|
|
219
|
-
// Act
|
|
220
|
-
await builder.build( createMockRootContainer() );
|
|
221
|
-
|
|
222
|
-
// Assert
|
|
223
|
-
expect( deleteElement ).not.toHaveBeenCalled();
|
|
224
|
-
expect( doUpdateElementProperty ).not.toHaveBeenCalled();
|
|
225
|
-
} );
|
|
226
|
-
} );
|
|
227
|
-
|
|
228
|
-
describe( 'CompositionBuilder.build required children', () => {
|
|
229
|
-
it( 'rejects build when required direct children are absent from XML', async () => {
|
|
230
|
-
// Arrange
|
|
231
|
-
let elementIdSequence = 0;
|
|
232
|
-
const createdElement = createMockPartialContainer( GENERATED_ELEMENT_ID );
|
|
233
|
-
const createElementMock = jest.fn().mockReturnValue( createdElement );
|
|
234
|
-
const builder = CompositionBuilder.fromXMLString(
|
|
235
|
-
'<e-form configuration-id="form-1"><e-form-input /></e-form>',
|
|
236
|
-
{
|
|
237
|
-
createElement: createElementMock,
|
|
238
|
-
deleteElement: jest.fn(),
|
|
239
|
-
getContainer: jest.fn(),
|
|
240
|
-
generateElementId: jest.fn().mockImplementation( () => `form-comp-${ ++elementIdSequence }` ),
|
|
241
|
-
getWidgetsCache: jest.fn().mockReturnValue( FORM_WIDGETS_CACHE_WITH_REQUIRED_CHILDREN ),
|
|
242
|
-
doUpdateElementProperty: jest.fn(),
|
|
243
|
-
}
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
// Act & Assert
|
|
247
|
-
await expect( builder.build( createMockRootContainer() ) ).rejects.toThrow(
|
|
248
|
-
/Missing required direct child element tag\(s\): e-form-success-message, e-form-error-message/
|
|
249
|
-
);
|
|
250
|
-
expect( createElementMock ).not.toHaveBeenCalled();
|
|
251
|
-
} );
|
|
252
|
-
|
|
253
|
-
it( 'rejects build when only some required direct children exist', async () => {
|
|
254
|
-
// Arrange
|
|
255
|
-
let elementIdSequence = 0;
|
|
256
|
-
const createdElement = createMockPartialContainer( GENERATED_ELEMENT_ID );
|
|
257
|
-
const createElementMock = jest.fn().mockReturnValue( createdElement );
|
|
258
|
-
const builder = CompositionBuilder.fromXMLString(
|
|
259
|
-
'<e-form configuration-id="form-1"><e-form-success-message /><e-form-input /></e-form>',
|
|
260
|
-
{
|
|
261
|
-
createElement: createElementMock,
|
|
262
|
-
deleteElement: jest.fn(),
|
|
263
|
-
getContainer: jest.fn(),
|
|
264
|
-
generateElementId: jest.fn().mockImplementation( () => `form-comp-${ ++elementIdSequence }` ),
|
|
265
|
-
getWidgetsCache: jest.fn().mockReturnValue( FORM_WIDGETS_CACHE_WITH_REQUIRED_CHILDREN ),
|
|
266
|
-
doUpdateElementProperty: jest.fn(),
|
|
267
|
-
}
|
|
268
|
-
);
|
|
269
|
-
|
|
270
|
-
// Act & Assert
|
|
271
|
-
await expect( builder.build( createMockRootContainer() ) ).rejects.toThrow(
|
|
272
|
-
/Missing required direct child element tag\(s\): e-form-error-message/
|
|
273
|
-
);
|
|
274
|
-
expect( createElementMock ).not.toHaveBeenCalled();
|
|
275
|
-
} );
|
|
276
|
-
|
|
277
|
-
it( 'creates elements when XML includes all required direct children', async () => {
|
|
278
|
-
// Arrange
|
|
279
|
-
let elementIdSequence = 0;
|
|
280
|
-
const createdElement = createMockPartialContainer( GENERATED_ELEMENT_ID );
|
|
281
|
-
const createElementMock = jest.fn().mockReturnValue( createdElement );
|
|
282
|
-
const builder = CompositionBuilder.fromXMLString(
|
|
283
|
-
'<e-form configuration-id="form-1">' +
|
|
284
|
-
'<e-form-success-message /><e-form-error-message /><e-form-input />' +
|
|
285
|
-
'</e-form>',
|
|
286
|
-
{
|
|
287
|
-
createElement: createElementMock,
|
|
288
|
-
deleteElement: jest.fn(),
|
|
289
|
-
getContainer: jest.fn(),
|
|
290
|
-
generateElementId: jest.fn().mockImplementation( () => `form-comp-${ ++elementIdSequence }` ),
|
|
291
|
-
getWidgetsCache: jest.fn().mockReturnValue( FORM_WIDGETS_CACHE_WITH_REQUIRED_CHILDREN ),
|
|
292
|
-
doUpdateElementProperty: jest.fn(),
|
|
293
|
-
}
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
// Act
|
|
297
|
-
await builder.build( createMockRootContainer() );
|
|
298
|
-
|
|
299
|
-
// Assert
|
|
300
|
-
const createArgs = createElementMock.mock.calls[ 0 ]?.[ 0 ] as CreateElementParams;
|
|
301
|
-
const childElements = ( createArgs.model?.elements || [] ) as Array< { elType?: string; widgetType?: string } >;
|
|
302
|
-
|
|
303
|
-
expect( childElements.filter( ( child ) => child.elType === 'e-form-success-message' ).length ).toBe( 1 );
|
|
304
|
-
expect( childElements.filter( ( child ) => child.elType === 'e-form-error-message' ).length ).toBe( 1 );
|
|
305
|
-
expect( childElements.some( ( child ) => child.widgetType === 'e-form-input' ) ).toBe( true );
|
|
306
|
-
} );
|
|
307
|
-
} );
|
|
308
|
-
|
|
309
|
-
describe( 'CompositionBuilder.build final composition built event', () => {
|
|
310
|
-
let dispatchEventSpy: jest.SpyInstance;
|
|
311
|
-
|
|
312
|
-
beforeEach( () => {
|
|
313
|
-
dispatchEventSpy = jest.spyOn( window, 'dispatchEvent' );
|
|
314
|
-
} );
|
|
315
|
-
|
|
316
|
-
afterEach( () => {
|
|
317
|
-
dispatchEventSpy.mockRestore();
|
|
318
|
-
} );
|
|
319
|
-
|
|
320
|
-
it( 'dispatches elementor/composition/built event with root container IDs after applyProperties completes', async () => {
|
|
321
|
-
// Arrange
|
|
322
|
-
const createdElement = createMockPartialContainer( GENERATED_ELEMENT_ID );
|
|
323
|
-
const doUpdateElementProperty = jest.fn();
|
|
324
|
-
const createElement = jest.fn().mockReturnValue( createdElement );
|
|
325
|
-
const getContainer = jest
|
|
326
|
-
.fn()
|
|
327
|
-
.mockImplementation( ( id: string ) => ( id === GENERATED_ELEMENT_ID ? createdElement : undefined ) );
|
|
328
|
-
const builder = CompositionBuilder.fromXMLString( xmlStringWithConfiguration, {
|
|
329
|
-
createElement,
|
|
330
|
-
deleteElement: jest.fn(),
|
|
331
|
-
getContainer,
|
|
332
|
-
generateElementId: jest.fn().mockReturnValue( GENERATED_ELEMENT_ID ),
|
|
333
|
-
getWidgetsCache: jest.fn().mockReturnValue( createMinimalWidgetsCache() ),
|
|
334
|
-
doUpdateElementProperty,
|
|
335
|
-
} );
|
|
336
|
-
builder.setElementConfig( createElementConfigPayload() );
|
|
337
|
-
|
|
338
|
-
// Act
|
|
339
|
-
await builder.build( createMockRootContainer() );
|
|
340
|
-
|
|
341
|
-
// Assert
|
|
342
|
-
expect( doUpdateElementProperty ).toHaveBeenCalledTimes( 1 );
|
|
343
|
-
expect( dispatchEventSpy ).toHaveBeenCalledWith(
|
|
344
|
-
expect.objectContaining( {
|
|
345
|
-
type: 'elementor/composition/built',
|
|
346
|
-
detail: {
|
|
347
|
-
rootContainers: [ GENERATED_ELEMENT_ID ],
|
|
348
|
-
},
|
|
349
|
-
} )
|
|
350
|
-
);
|
|
351
|
-
} );
|
|
352
|
-
} );
|