@elementor/editor-canvas 4.2.0-862 → 4.2.0-864

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.js CHANGED
@@ -4083,6 +4083,7 @@ var validateInput = {
4083
4083
  };
4084
4084
 
4085
4085
  // src/composition-builder/composition-builder.ts
4086
+ var CREATE_ELEMENT_INVALID_CONTAINER_MESSAGE = "createElement did not return an element container with a model.";
4086
4087
  var CompositionBuilder = class _CompositionBuilder {
4087
4088
  elementConfig = {};
4088
4089
  elementStylesConfig = {};
@@ -4301,6 +4302,9 @@ ${childTypeErrors.join("\n")}`);
4301
4302
  model: modelTree,
4302
4303
  options: { useHistory: false }
4303
4304
  });
4305
+ if (!newElement?.model) {
4306
+ throw new Error(CREATE_ELEMENT_INVALID_CONTAINER_MESSAGE);
4307
+ }
4304
4308
  this.rootContainers.push(newElement);
4305
4309
  await this.awaitViewRender(newElement);
4306
4310
  } catch (e) {
package/dist/index.mjs CHANGED
@@ -4078,6 +4078,7 @@ var validateInput = {
4078
4078
  };
4079
4079
 
4080
4080
  // src/composition-builder/composition-builder.ts
4081
+ var CREATE_ELEMENT_INVALID_CONTAINER_MESSAGE = "createElement did not return an element container with a model.";
4081
4082
  var CompositionBuilder = class _CompositionBuilder {
4082
4083
  elementConfig = {};
4083
4084
  elementStylesConfig = {};
@@ -4296,6 +4297,9 @@ ${childTypeErrors.join("\n")}`);
4296
4297
  model: modelTree,
4297
4298
  options: { useHistory: false }
4298
4299
  });
4300
+ if (!newElement?.model) {
4301
+ throw new Error(CREATE_ELEMENT_INVALID_CONTAINER_MESSAGE);
4302
+ }
4299
4303
  this.rootContainers.push(newElement);
4300
4304
  await this.awaitViewRender(newElement);
4301
4305
  } catch (e) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@elementor/editor-canvas",
3
3
  "description": "Elementor Editor Canvas",
4
- "version": "4.2.0-862",
4
+ "version": "4.2.0-864",
5
5
  "private": false,
6
6
  "author": "Elementor Team",
7
7
  "homepage": "https://elementor.com/",
@@ -37,25 +37,25 @@
37
37
  "react-dom": "^18.3.1"
38
38
  },
39
39
  "dependencies": {
40
- "@elementor/editor": "4.2.0-862",
40
+ "@elementor/editor": "4.2.0-864",
41
41
  "dompurify": "^3.2.6",
42
- "@elementor/editor-controls": "4.2.0-862",
43
- "@elementor/editor-documents": "4.2.0-862",
44
- "@elementor/editor-elements": "4.2.0-862",
45
- "@elementor/editor-interactions": "4.2.0-862",
46
- "@elementor/editor-mcp": "4.2.0-862",
47
- "@elementor/editor-notifications": "4.2.0-862",
48
- "@elementor/editor-props": "4.2.0-862",
49
- "@elementor/editor-responsive": "4.2.0-862",
50
- "@elementor/editor-styles": "4.2.0-862",
51
- "@elementor/editor-styles-repository": "4.2.0-862",
52
- "@elementor/editor-ui": "4.2.0-862",
53
- "@elementor/editor-v1-adapters": "4.2.0-862",
54
- "@elementor/schema": "4.2.0-862",
55
- "@elementor/twing": "4.2.0-862",
42
+ "@elementor/editor-controls": "4.2.0-864",
43
+ "@elementor/editor-documents": "4.2.0-864",
44
+ "@elementor/editor-elements": "4.2.0-864",
45
+ "@elementor/editor-interactions": "4.2.0-864",
46
+ "@elementor/editor-mcp": "4.2.0-864",
47
+ "@elementor/editor-notifications": "4.2.0-864",
48
+ "@elementor/editor-props": "4.2.0-864",
49
+ "@elementor/editor-responsive": "4.2.0-864",
50
+ "@elementor/editor-styles": "4.2.0-864",
51
+ "@elementor/editor-styles-repository": "4.2.0-864",
52
+ "@elementor/editor-ui": "4.2.0-864",
53
+ "@elementor/editor-v1-adapters": "4.2.0-864",
54
+ "@elementor/schema": "4.2.0-864",
55
+ "@elementor/twing": "4.2.0-864",
56
56
  "@elementor/ui": "1.37.5",
57
- "@elementor/utils": "4.2.0-862",
58
- "@elementor/wp-media": "4.2.0-862",
57
+ "@elementor/utils": "4.2.0-864",
58
+ "@elementor/wp-media": "4.2.0-864",
59
59
  "@floating-ui/react": "^0.27.5",
60
60
  "@wordpress/i18n": "^5.13.0"
61
61
  },
@@ -0,0 +1,190 @@
1
+ import { type V1Element } 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 createMockRootContainer = (): V1Element =>
27
+ ( {
28
+ id: 'root',
29
+ model: { get: jest.fn(), set: jest.fn(), toJSON: jest.fn() },
30
+ settings: { get: jest.fn(), set: jest.fn(), toJSON: jest.fn() },
31
+ children: [],
32
+ } ) as unknown as V1Element;
33
+
34
+ const createMockPartialContainer = ( id: string ): V1Element =>
35
+ ( {
36
+ id,
37
+ model: { get: jest.fn(), set: jest.fn(), toJSON: jest.fn() },
38
+ settings: { get: jest.fn(), set: jest.fn(), toJSON: jest.fn() },
39
+ children: [],
40
+ } ) as unknown as V1Element;
41
+
42
+ describe( 'CompositionBuilder.build createElement failure cleanup', () => {
43
+ it( 'calls deleteElement when createElement fails and getContainer returns a container', async () => {
44
+ // Arrange
45
+ const partialContainer = createMockPartialContainer( GENERATED_ELEMENT_ID );
46
+ const deleteElement = jest.fn();
47
+ const doUpdateElementProperty = jest.fn();
48
+ const createElement = jest.fn().mockImplementation( () => {
49
+ throw new Error( 'create failed' );
50
+ } );
51
+ const getContainer = jest
52
+ .fn()
53
+ .mockImplementation( ( id: string ) => ( id === GENERATED_ELEMENT_ID ? partialContainer : undefined ) );
54
+ const builder = CompositionBuilder.fromXMLString( xmlStringWithConfiguration, {
55
+ createElement,
56
+ deleteElement,
57
+ getContainer,
58
+ generateElementId: jest.fn().mockReturnValue( GENERATED_ELEMENT_ID ),
59
+ getWidgetsCache: jest.fn().mockReturnValue( createMinimalWidgetsCache() ),
60
+ doUpdateElementProperty,
61
+ } );
62
+ builder.setElementConfig( createElementConfigPayload() );
63
+
64
+ // Act
65
+ await expect( builder.build( createMockRootContainer() ) ).rejects.toThrow( 'create failed' );
66
+
67
+ // Assert
68
+ expect( getContainer ).toHaveBeenCalledWith( GENERATED_ELEMENT_ID );
69
+ expect( deleteElement ).toHaveBeenCalledTimes( 1 );
70
+ expect( deleteElement ).toHaveBeenCalledWith( { container: partialContainer } );
71
+ expect( doUpdateElementProperty ).not.toHaveBeenCalled();
72
+ } );
73
+
74
+ it( 'does not call deleteElement when createElement fails and getContainer returns undefined', async () => {
75
+ // Arrange
76
+ const deleteElement = jest.fn();
77
+ const doUpdateElementProperty = jest.fn();
78
+ const createElement = jest.fn().mockImplementation( () => {
79
+ throw new Error( 'create failed' );
80
+ } );
81
+ const getContainer = jest.fn().mockReturnValue( undefined );
82
+ const builder = CompositionBuilder.fromXMLString( xmlStringWithConfiguration, {
83
+ createElement,
84
+ deleteElement,
85
+ getContainer,
86
+ generateElementId: jest.fn().mockReturnValue( GENERATED_ELEMENT_ID ),
87
+ getWidgetsCache: jest.fn().mockReturnValue( createMinimalWidgetsCache() ),
88
+ doUpdateElementProperty,
89
+ } );
90
+ builder.setElementConfig( createElementConfigPayload() );
91
+
92
+ // Act
93
+ await expect( builder.build( createMockRootContainer() ) ).rejects.toThrow( 'create failed' );
94
+
95
+ // Assert
96
+ expect( getContainer ).toHaveBeenCalledWith( GENERATED_ELEMENT_ID );
97
+ expect( deleteElement ).not.toHaveBeenCalled();
98
+ expect( doUpdateElementProperty ).not.toHaveBeenCalled();
99
+ } );
100
+
101
+ it( 'calls deleteElement when createElement returns without a model', async () => {
102
+ // Arrange
103
+ const partialContainer = createMockPartialContainer( GENERATED_ELEMENT_ID );
104
+ const deleteElement = jest.fn();
105
+ const doUpdateElementProperty = jest.fn();
106
+ const createElement = jest.fn().mockReturnValue( {} as V1Element );
107
+ const getContainer = jest
108
+ .fn()
109
+ .mockImplementation( ( id: string ) => ( id === GENERATED_ELEMENT_ID ? partialContainer : undefined ) );
110
+ const builder = CompositionBuilder.fromXMLString( xmlStringWithConfiguration, {
111
+ createElement,
112
+ deleteElement,
113
+ getContainer,
114
+ generateElementId: jest.fn().mockReturnValue( GENERATED_ELEMENT_ID ),
115
+ getWidgetsCache: jest.fn().mockReturnValue( createMinimalWidgetsCache() ),
116
+ doUpdateElementProperty,
117
+ } );
118
+ builder.setElementConfig( createElementConfigPayload() );
119
+
120
+ // Act
121
+ await expect( builder.build( createMockRootContainer() ) ).rejects.toThrow(
122
+ 'createElement did not return an element container with a model.'
123
+ );
124
+
125
+ // Assert
126
+ expect( getContainer ).toHaveBeenCalledWith( GENERATED_ELEMENT_ID );
127
+ expect( deleteElement ).toHaveBeenCalledTimes( 1 );
128
+ expect( deleteElement ).toHaveBeenCalledWith( { container: partialContainer } );
129
+ expect( doUpdateElementProperty ).not.toHaveBeenCalled();
130
+ } );
131
+ } );
132
+
133
+ describe( 'CompositionBuilder.build applyProperties after create', () => {
134
+ it( 'calls doUpdateElementProperty when create succeeds with element config', async () => {
135
+ // Arrange
136
+ const deleteElement = jest.fn();
137
+ const doUpdateElementProperty = jest.fn();
138
+ const createdElement = createMockPartialContainer( GENERATED_ELEMENT_ID );
139
+ const createElement = jest.fn().mockReturnValue( createdElement );
140
+ const getContainer = jest
141
+ .fn()
142
+ .mockImplementation( ( id: string ) => ( id === GENERATED_ELEMENT_ID ? createdElement : undefined ) );
143
+ const builder = CompositionBuilder.fromXMLString( xmlStringWithConfiguration, {
144
+ createElement,
145
+ deleteElement,
146
+ getContainer,
147
+ generateElementId: jest.fn().mockReturnValue( GENERATED_ELEMENT_ID ),
148
+ getWidgetsCache: jest.fn().mockReturnValue( createMinimalWidgetsCache() ),
149
+ doUpdateElementProperty,
150
+ } );
151
+ builder.setElementConfig( createElementConfigPayload() );
152
+
153
+ // Act
154
+ await builder.build( createMockRootContainer() );
155
+
156
+ // Assert
157
+ expect( deleteElement ).not.toHaveBeenCalled();
158
+ expect( createElement ).toHaveBeenCalledTimes( 1 );
159
+ expect( doUpdateElementProperty ).toHaveBeenCalledTimes( 1 );
160
+ expect( doUpdateElementProperty ).toHaveBeenCalledWith( {
161
+ elementId: GENERATED_ELEMENT_ID,
162
+ propertyName: ELEMENT_CONFIG_PROPERTY,
163
+ propertyValue: ELEMENT_CONFIG_VALUE,
164
+ elementType: ROOT_CHILD_TAG,
165
+ } );
166
+ } );
167
+
168
+ it( 'does not call doUpdateElementProperty when create succeeds without element config', async () => {
169
+ // Arrange
170
+ const deleteElement = jest.fn();
171
+ const doUpdateElementProperty = jest.fn();
172
+ const createdElement = createMockPartialContainer( GENERATED_ELEMENT_ID );
173
+ const createElement = jest.fn().mockReturnValue( createdElement );
174
+ const builder = CompositionBuilder.fromXMLString( `<${ ROOT_CHILD_TAG } />`, {
175
+ createElement,
176
+ deleteElement,
177
+ getContainer: jest.fn(),
178
+ generateElementId: jest.fn().mockReturnValue( GENERATED_ELEMENT_ID ),
179
+ getWidgetsCache: jest.fn().mockReturnValue( createMinimalWidgetsCache() ),
180
+ doUpdateElementProperty,
181
+ } );
182
+
183
+ // Act
184
+ await builder.build( createMockRootContainer() );
185
+
186
+ // Assert
187
+ expect( deleteElement ).not.toHaveBeenCalled();
188
+ expect( doUpdateElementProperty ).not.toHaveBeenCalled();
189
+ } );
190
+ } );
@@ -17,6 +17,8 @@ import { validateInput } from '../mcp/utils/validate-input';
17
17
  type AnyValue = z.infer< z.ZodTypeAny >;
18
18
  type AnyConfig = Record< string, Record< string, AnyValue > >;
19
19
 
20
+ const CREATE_ELEMENT_INVALID_CONTAINER_MESSAGE = 'createElement did not return an element container with a model.';
21
+
20
22
  type API = {
21
23
  createElement: typeof createElement;
22
24
  deleteElement: typeof deleteElement;
@@ -296,6 +298,9 @@ export class CompositionBuilder {
296
298
  model: modelTree as CreateElementParams[ 'model' ],
297
299
  options: { useHistory: false },
298
300
  } );
301
+ if ( ! newElement?.model ) {
302
+ throw new Error( CREATE_ELEMENT_INVALID_CONTAINER_MESSAGE );
303
+ }
299
304
  this.rootContainers.push( newElement );
300
305
  await this.awaitViewRender( newElement );
301
306
  } catch ( e: unknown ) {