@plone/volto 16.0.0-rc.1 → 16.0.0-rc.2

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.
Binary file
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # Change Log
2
2
 
3
+ ## 16.0.0-rc.2 (2022-11-20)
4
+
5
+ ### Bugfix
6
+
7
+ - Overhaul how block defaults are computed. See https://github.com/plone/volto/pull/3925 for more details @tiberiuichim
8
+ - Cover an additional edge case for defaults @tiberiuichim
9
+
10
+ ### Internal
11
+
12
+ - Update to Plone 6 RC1 @sneridagh
13
+
14
+ ### Documentation
15
+
16
+ - Document `Sentry` integration move from Volto core to add-on `@plone-collective/volto-sentry` in configuration, upgrade and deployment. @ksuess
17
+ - Remove `sentryOptions` from settings reference. Clean up `deploying/sentry.md`. @stevepiercy
18
+ - Tidy up `upgrade-guide/index.md`. @stevepiercy
19
+
3
20
  ## 16.0.0-rc.1 (2022-11-18)
4
21
 
5
22
  ### Feature
@@ -195,6 +195,30 @@ Cypress.Commands.add('removeContent', ({ path = '' }) => {
195
195
  });
196
196
  });
197
197
 
198
+ // Get content
199
+ Cypress.Commands.add('getContent', ({ path = '' }) => {
200
+ let api_url, auth;
201
+ if (Cypress.env('API') === 'guillotina') {
202
+ api_url = GUILLOTINA_API_URL;
203
+ auth = {
204
+ user: 'root',
205
+ pass: 'root',
206
+ };
207
+ } else {
208
+ api_url = PLONE_API_URL;
209
+ auth = ploneAuthObj;
210
+ }
211
+
212
+ return cy.request({
213
+ method: 'get',
214
+ url: `${api_url}/${path}`,
215
+ headers: {
216
+ Accept: 'application/json',
217
+ },
218
+ auth: auth,
219
+ });
220
+ });
221
+
198
222
  // --- Add DX Content-Type ----------------------------------------------------------
199
223
  Cypress.Commands.add('addContentType', (name) => {
200
224
  let api_url, auth;
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  }
10
10
  ],
11
11
  "license": "MIT",
12
- "version": "16.0.0-rc.1",
12
+ "version": "16.0.0-rc.2",
13
13
  "repository": {
14
14
  "type": "git",
15
15
  "url": "git@github.com:plone/volto.git"
@@ -36,6 +36,7 @@
36
36
  "postinstall": "make patches",
37
37
  "analyze": "BUNDLE_ANALYZE=true razzle build",
38
38
  "start": "razzle start",
39
+ "start:coresandbox": "ADDONS=coresandbox razzle start",
39
40
  "build": "razzle build --noninteractive",
40
41
  "prepare": "husky install",
41
42
  "test": "razzle test --maxWorkers=50%",
@@ -279,6 +279,7 @@ export const DefaultTextBlockEditor = (props) => {
279
279
  block={block}
280
280
  schema={schema}
281
281
  title={schema.title}
282
+ onChangeBlock={onChangeBlock}
282
283
  onChangeField={(id, value) => {
283
284
  onChangeBlock(block, {
284
285
  ...data,
@@ -82,6 +82,7 @@ const PluginEditor = (props) => {
82
82
  }
83
83
  return onChangeValues(id, value, formData, setFormData);
84
84
  }}
85
+ onChangeFormData={setFormData}
85
86
  formData={formData}
86
87
  headerActions={
87
88
  <>
package/pyvenv.cfg ADDED
@@ -0,0 +1,3 @@
1
+ home = /opt/homebrew/opt/python@3.9/bin
2
+ include-system-site-packages = false
3
+ version = 3.9.12
@@ -1,7 +1,12 @@
1
1
  import React from 'react';
2
+ import { useIntl } from 'react-intl';
2
3
  import EditBlock from './Edit';
3
4
  import { DragDropList } from '@plone/volto/components';
4
- import { getBlocks } from '@plone/volto/helpers';
5
+ import {
6
+ getBlocks,
7
+ getBlocksFieldname,
8
+ applyBlockDefaults,
9
+ } from '@plone/volto/helpers';
5
10
  import {
6
11
  addBlock,
7
12
  insertBlock,
@@ -42,6 +47,7 @@ const BlocksForm = (props) => {
42
47
  const blockList = getBlocks(properties);
43
48
 
44
49
  const dispatch = useDispatch();
50
+ const intl = useIntl();
45
51
 
46
52
  const ClickOutsideListener = () => {
47
53
  onSelectBlock(null);
@@ -117,6 +123,16 @@ const BlocksForm = (props) => {
117
123
  current,
118
124
  config.experimental.addBlockButton.enabled ? 1 : 0,
119
125
  );
126
+
127
+ const blocksFieldname = getBlocksFieldname(newFormData);
128
+ const blockData = newFormData[blocksFieldname][newId];
129
+ newFormData[blocksFieldname][newId] = applyBlockDefaults({
130
+ data: blockData,
131
+ intl,
132
+ metadata,
133
+ properties,
134
+ });
135
+
120
136
  onChangeFormData(newFormData);
121
137
  return newId;
122
138
  };
@@ -124,6 +140,14 @@ const BlocksForm = (props) => {
124
140
  const onAddBlock = (type, index) => {
125
141
  if (editable) {
126
142
  const [id, newFormData] = addBlock(properties, type, index);
143
+ const blocksFieldname = getBlocksFieldname(newFormData);
144
+ const blockData = newFormData[blocksFieldname][id];
145
+ newFormData[blocksFieldname][id] = applyBlockDefaults({
146
+ data: blockData,
147
+ intl,
148
+ metadata,
149
+ properties,
150
+ });
127
151
  onChangeFormData(newFormData);
128
152
  return id;
129
153
  }
@@ -1,6 +1,28 @@
1
+ import React from 'react';
1
2
  import { InlineForm } from '@plone/volto/components';
2
3
  import { withVariationSchemaEnhancer } from '@plone/volto/helpers';
3
4
 
4
- const BlockDataForm = withVariationSchemaEnhancer(InlineForm);
5
+ const EnhancedBlockDataForm = withVariationSchemaEnhancer(InlineForm);
5
6
 
6
- export default BlockDataForm;
7
+ export default function BlockDataForm(props) {
8
+ const { onChangeBlock, block } = props;
9
+
10
+ if (!onChangeBlock) {
11
+ // eslint-disable-next-line no-console
12
+ console.warn(
13
+ 'BlockDataForm component is used without passing down onChangeBlock as props',
14
+ );
15
+ }
16
+
17
+ const onChangeFormData = React.useCallback(
18
+ (data) => onChangeBlock(block, data),
19
+ [block, onChangeBlock],
20
+ );
21
+
22
+ return (
23
+ <EnhancedBlockDataForm
24
+ {...props}
25
+ onChangeFormData={onChangeBlock ? onChangeFormData : undefined}
26
+ />
27
+ );
28
+ }
@@ -8,6 +8,24 @@ import { Provider } from 'react-intl-redux';
8
8
 
9
9
  const mockStore = configureStore();
10
10
 
11
+ const withStateManagement = (Component) => ({ ...props }) => {
12
+ const [formData, onChangeFormData] = React.useState(props.formData || {});
13
+ const onChangeField = (id, value) => {
14
+ onChangeFormData({ ...formData, [id]: value });
15
+ };
16
+
17
+ // NOTE: onChangeBlock here is not "really" implemented
18
+
19
+ return (
20
+ <Component
21
+ {...props}
22
+ onChangeField={onChangeField}
23
+ onChangeBlock={(block, data) => onChangeFormData(data)}
24
+ formData={formData}
25
+ />
26
+ );
27
+ };
28
+
11
29
  beforeAll(() => {
12
30
  config.widgets = {
13
31
  id: {},
@@ -55,6 +73,7 @@ beforeAll(() => {
55
73
 
56
74
  describe('BlockDataForm', () => {
57
75
  it('should does not add variations to schema when unneeded', () => {
76
+ const WrappedBlockDataForm = withStateManagement(BlockDataForm);
58
77
  const store = mockStore({
59
78
  intl: {
60
79
  locale: 'en',
@@ -71,7 +90,7 @@ describe('BlockDataForm', () => {
71
90
  };
72
91
  const { container } = render(
73
92
  <Provider store={store}>
74
- <BlockDataForm
93
+ <WrappedBlockDataForm
75
94
  formData={formData}
76
95
  schema={testSchema}
77
96
  onChangeField={(id, value) => {}}
@@ -85,6 +104,7 @@ describe('BlockDataForm', () => {
85
104
  });
86
105
 
87
106
  it('should does not add variations when only one variation', () => {
107
+ const WrappedBlockDataForm = withStateManagement(BlockDataForm);
88
108
  const store = mockStore({
89
109
  intl: {
90
110
  locale: 'en',
@@ -101,7 +121,7 @@ describe('BlockDataForm', () => {
101
121
  };
102
122
  const { container } = render(
103
123
  <Provider store={store}>
104
- <BlockDataForm
124
+ <WrappedBlockDataForm
105
125
  formData={formData}
106
126
  schema={testSchema}
107
127
  onChangeField={(id, value) => {}}
@@ -115,6 +135,7 @@ describe('BlockDataForm', () => {
115
135
  });
116
136
 
117
137
  it('should add variations to schema', () => {
138
+ const WrappedBlockDataForm = withStateManagement(BlockDataForm);
118
139
  const store = mockStore({
119
140
  intl: {
120
141
  locale: 'en',
@@ -131,7 +152,7 @@ describe('BlockDataForm', () => {
131
152
  };
132
153
  const { container } = render(
133
154
  <Provider store={store}>
134
- <BlockDataForm
155
+ <WrappedBlockDataForm
135
156
  formData={formData}
136
157
  schema={testSchema}
137
158
  onChangeField={(id, value) => {}}
@@ -232,17 +232,9 @@ const DndConnectedField = injectLazyLibs(['reactDnd'])(UnconnectedField);
232
232
 
233
233
  const Field = (props) =>
234
234
  props.onOrder ? (
235
- <DndConnectedField
236
- {...props}
237
- defaultValue={undefined}
238
- value={props.value ?? props.default ?? props.defaultValue}
239
- />
235
+ <DndConnectedField {...props} />
240
236
  ) : (
241
- <UnconnectedField
242
- {...props}
243
- defaultValue={undefined}
244
- value={props.value ?? props.default ?? props.defaultValue}
245
- />
237
+ <UnconnectedField {...props} />
246
238
  );
247
239
 
248
240
  /**
@@ -6,6 +6,7 @@ import AnimateHeight from 'react-animate-height';
6
6
  import { keys, map, isEqual } from 'lodash';
7
7
 
8
8
  import { Field, Icon } from '@plone/volto/components';
9
+ import { applySchemaDefaults } from '@plone/volto/helpers';
9
10
 
10
11
  import upSVG from '@plone/volto/icons/up-key.svg';
11
12
  import downSVG from '@plone/volto/icons/down-key.svg';
@@ -32,6 +33,7 @@ const InlineForm = (props) => {
32
33
  error, // Such as {message: "It's not good"}
33
34
  errors = {},
34
35
  formData,
36
+ onChangeFormData,
35
37
  onChangeField,
36
38
  schema,
37
39
  title,
@@ -49,26 +51,22 @@ const InlineForm = (props) => {
49
51
  // Will set field values from schema, by matching the default values
50
52
 
51
53
  const objectSchema = typeof schema === 'function' ? schema(props) : schema;
52
- const initialData = {
53
- ...Object.keys(objectSchema.properties).reduce(
54
- (accumulator, currentField) => {
55
- return objectSchema.properties[currentField].default
56
- ? {
57
- ...accumulator,
58
- [currentField]: objectSchema.properties[currentField].default,
59
- }
60
- : accumulator;
61
- },
62
- {},
63
- ),
64
- ...formData,
65
- };
66
-
67
- Object.keys(initialData).forEach((k) => {
68
- if (!isEqual(initialData[k], formData?.[k])) {
69
- onChangeField(k, initialData[k]);
70
- }
54
+
55
+ const initialData = applySchemaDefaults({
56
+ data: formData,
57
+ schema: objectSchema,
58
+ intl,
71
59
  });
60
+
61
+ if (onChangeFormData) {
62
+ onChangeFormData(initialData);
63
+ } else {
64
+ Object.keys(initialData).forEach((k) => {
65
+ if (!isEqual(initialData[k], formData?.[k])) {
66
+ onChangeField(k, initialData[k]);
67
+ }
68
+ });
69
+ }
72
70
  // eslint-disable-next-line react-hooks/exhaustive-deps
73
71
  }, []);
74
72
 
@@ -20,13 +20,18 @@ function NewBaseWidget(name) {
20
20
  }
21
21
 
22
22
  const withStateManagement = (Component) => ({ ...props }) => {
23
- const [formData, setFormData] = React.useState(props.formData || {});
23
+ const [formData, onChangeFormData] = React.useState(props.formData || {});
24
24
  const onChangeField = (id, value) => {
25
- setFormData({ ...formData, [id]: value });
25
+ onChangeFormData({ ...formData, [id]: value });
26
26
  };
27
27
 
28
28
  return (
29
- <Component {...props} onChangeField={onChangeField} formData={formData} />
29
+ <Component
30
+ {...props}
31
+ onChangeField={onChangeField}
32
+ onChangeFormData={onChangeFormData}
33
+ formData={formData}
34
+ />
30
35
  );
31
36
  };
32
37
 
@@ -114,6 +119,7 @@ describe('Form', () => {
114
119
 
115
120
  expect(container).toMatchSnapshot();
116
121
  });
122
+
117
123
  it('renders a form component with defaults in the schema - Checkboxes', () => {
118
124
  const store = mockStore({
119
125
  intl: {
@@ -163,7 +169,6 @@ describe('Form', () => {
163
169
  />
164
170
  </Provider>,
165
171
  );
166
-
167
172
  expect(container).toMatchSnapshot();
168
173
  });
169
174
  it('renders a form component with defaults in the schema - Number field', () => {
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import { defineMessages, useIntl } from 'react-intl';
3
3
  import { Accordion, Button, Segment } from 'semantic-ui-react';
4
4
  import { DragDropList, FormFieldWrapper, Icon } from '@plone/volto/components';
5
+ import { applySchemaDefaults } from '@plone/volto/helpers';
5
6
  import ObjectWidget from '@plone/volto/components/manage/Widgets/ObjectWidget';
6
7
 
7
8
  import upSVG from '@plone/volto/icons/up-key.svg';
@@ -70,14 +71,14 @@ const ObjectListWidget = (props) => {
70
71
  onChange,
71
72
  schemaExtender,
72
73
  } = props;
73
- const [activeColumn, setActiveColumn] = React.useState(value.length - 1);
74
+ const [activeObject, setActiveObject] = React.useState(value.length - 1);
74
75
  const intl = useIntl();
75
76
 
76
- function handleChangeColumn(e, blockProps) {
77
+ function handleChangeActiveObject(e, blockProps) {
77
78
  const { index } = blockProps;
78
- const newIndex = activeColumn === index ? -1 : index;
79
+ const newIndex = activeObject === index ? -1 : index;
79
80
 
80
- setActiveColumn(newIndex);
81
+ setActiveObject(newIndex);
81
82
  }
82
83
  const objectSchema = typeof schema === 'function' ? schema(props) : schema;
83
84
 
@@ -98,18 +99,25 @@ const ObjectListWidget = (props) => {
98
99
  }
99
100
  onClick={(e) => {
100
101
  e.preventDefault();
101
- onChange(id, [
102
- ...value,
103
- {
104
- '@id': uuid(),
105
- },
106
- ]);
107
- setActiveColumn(value.length);
102
+ const data = {
103
+ '@id': uuid(),
104
+ };
105
+ const objSchema = schemaExtender
106
+ ? schemaExtender(schema, data, intl)
107
+ : objectSchema;
108
+ const dataWithDefaults = applySchemaDefaults({
109
+ data,
110
+ schema: objSchema,
111
+ intl,
112
+ });
113
+
114
+ onChange(id, [...value, dataWithDefaults]);
115
+ setActiveObject(value.length);
108
116
  }}
109
117
  >
110
118
  <Icon name={addSVG} size="18px" />
111
119
  &nbsp;
112
- {/* Custom addMessage in schema, else default to english */}
120
+ {/* Custom addMessage in schema, else default to English */}
113
121
  {objectSchema.addMessage ||
114
122
  `${intl.formatMessage(messages.add)} ${objectSchema.title}`}
115
123
  </Button>
@@ -158,11 +166,11 @@ const ObjectListWidget = (props) => {
158
166
  >
159
167
  <Accordion key={index} fluid styled>
160
168
  <Accordion.Title
161
- active={activeColumn === index}
169
+ active={activeObject === index}
162
170
  index={index}
163
- onClick={handleChangeColumn}
171
+ onClick={handleChangeActiveObject}
164
172
  aria-label={`${
165
- activeColumn === index
173
+ activeObject === index
166
174
  ? intl.formatMessage(messages.labelCollapseItem)
167
175
  : intl.formatMessage(messages.labelShowItem)
168
176
  } #${index + 1}`}
@@ -195,14 +203,14 @@ const ObjectListWidget = (props) => {
195
203
  >
196
204
  <Icon name={deleteSVG} size="20px" color="#e40166" />
197
205
  </button>
198
- {activeColumn === index ? (
206
+ {activeObject === index ? (
199
207
  <Icon name={upSVG} size="20px" />
200
208
  ) : (
201
209
  <Icon name={downSVG} size="20px" />
202
210
  )}
203
211
  </div>
204
212
  </Accordion.Title>
205
- <Accordion.Content active={activeColumn === index}>
213
+ <Accordion.Content active={activeObject === index}>
206
214
  <Segment>
207
215
  <ObjectWidget
208
216
  id={`${id}-${index}`}
@@ -3,7 +3,16 @@
3
3
  * @module helpers/Blocks
4
4
  */
5
5
 
6
- import { omit, without, endsWith, find, isObject, keys, toPairs } from 'lodash';
6
+ import {
7
+ omit,
8
+ without,
9
+ endsWith,
10
+ find,
11
+ isObject,
12
+ keys,
13
+ toPairs,
14
+ merge,
15
+ } from 'lodash';
7
16
  import move from 'lodash-move';
8
17
  import { v4 as uuid } from 'uuid';
9
18
  import config from '@plone/volto/registry';
@@ -366,21 +375,55 @@ export function visitBlocks(content, callback) {
366
375
  }
367
376
  }
368
377
 
378
+ let _logged = false;
379
+
369
380
  /**
370
381
  * Initializes data with the default values coming from schema
371
382
  */
372
- export function applySchemaDefaults({ data = {}, schema }) {
373
- const derivedData = {
374
- ...Object.keys(schema.properties).reduce((accumulator, currentField) => {
383
+ export function applySchemaDefaults({ data = {}, schema, intl }) {
384
+ if (!intl && !_logged) {
385
+ // Old code that doesn't pass intl doesn't get ObjectWidget defaults
386
+ // eslint-disable-next-line no-console
387
+ console.warn(
388
+ `You should pass intl to any applySchemaDefaults call. By failing to pass
389
+ the intl object, your ObjectWidget fields will not get default values
390
+ extracted from their schema.`,
391
+ );
392
+ _logged = true;
393
+ }
394
+
395
+ const derivedData = merge(
396
+ Object.keys(schema.properties).reduce((accumulator, currentField) => {
375
397
  return typeof schema.properties[currentField].default !== 'undefined'
376
398
  ? {
377
399
  ...accumulator,
378
400
  [currentField]: schema.properties[currentField].default,
379
401
  }
402
+ : intl &&
403
+ schema.properties[currentField].schema &&
404
+ !(schema.properties[currentField].widget === 'object_list') // TODO: this should be renamed as itemSchema
405
+ ? {
406
+ ...accumulator,
407
+ [currentField]: {
408
+ ...applySchemaDefaults({
409
+ data: { ...data[currentField], ...accumulator[currentField] },
410
+ schema:
411
+ typeof schema.properties[currentField].schema === 'function'
412
+ ? schema.properties[currentField].schema({
413
+ data: accumulator[currentField],
414
+ formData: accumulator[currentField],
415
+ intl,
416
+ })
417
+ : schema.properties[currentField].schema,
418
+ intl,
419
+ }),
420
+ },
421
+ }
380
422
  : accumulator;
381
423
  }, {}),
382
- ...data,
383
- };
424
+ data,
425
+ );
426
+
384
427
  return derivedData;
385
428
  }
386
429
 
@@ -404,7 +447,7 @@ export function applyBlockDefaults({ data, intl, ...rest }, blocksConfig) {
404
447
  : blockSchema;
405
448
  schema = applySchemaEnhancer({ schema, formData: data, intl });
406
449
 
407
- return applySchemaDefaults({ data, schema });
450
+ return applySchemaDefaults({ data, schema, intl });
408
451
  }
409
452
 
410
453
  export const buildStyleClassNamesFromData = (styles) => {
@@ -165,6 +165,151 @@ config.blocks.blocksConfig.enhancedBlockCase2 = {
165
165
  }),
166
166
  };
167
167
 
168
+ const itemSchema = (props) => {
169
+ return {
170
+ title: 'Item',
171
+ addMessage: 'Add',
172
+ fieldsets: [
173
+ {
174
+ id: 'default',
175
+ title: 'Default',
176
+ fields: [
177
+ 'href',
178
+ 'title',
179
+ 'description',
180
+ 'preview_image',
181
+ 'extraDefault',
182
+ ],
183
+ },
184
+ ],
185
+
186
+ properties: {
187
+ href: {
188
+ title: 'Source',
189
+ widget: 'object_browser',
190
+ mode: 'link',
191
+ selectedItemAttrs: [
192
+ 'Title',
193
+ 'Description',
194
+ 'hasPreviewImage',
195
+ 'headtitle',
196
+ ],
197
+ allowExternals: true,
198
+ },
199
+ title: {
200
+ title: 'title',
201
+ },
202
+ description: {
203
+ title: 'description',
204
+ },
205
+ preview_image: {
206
+ title: 'Image Override',
207
+ widget: 'object_browser',
208
+ mode: 'image',
209
+ allowExternals: true,
210
+ },
211
+ extraDefault: {
212
+ title: 'Extra',
213
+ default: 'Extra default',
214
+ },
215
+ },
216
+ required: [],
217
+ };
218
+ };
219
+
220
+ config.blocks.blocksConfig.slider = {
221
+ id: 'slider',
222
+ title: 'Slider',
223
+ group: 'Special',
224
+ restricted: false,
225
+ mostUsed: false,
226
+ blockHasOwnFocusManagement: true,
227
+ blockSchema: (props) => ({
228
+ title: 'slider',
229
+ fieldsets: [
230
+ {
231
+ id: 'default',
232
+ title: 'Default',
233
+ fields: [
234
+ 'slides',
235
+ 'fieldAfterObjectList',
236
+ 'href',
237
+ 'firstWithDefault',
238
+ 'style',
239
+ 'anotherWithDefault',
240
+ 'yetAnotherWithDefault',
241
+ ],
242
+ },
243
+ ],
244
+ properties: {
245
+ slides: {
246
+ widget: 'object_list',
247
+ schema: itemSchema,
248
+ default: [
249
+ {
250
+ '@id': 'asdasdasd-qweqwe-zxczxc',
251
+ extraDefault:
252
+ 'Extra default (Manual in parent slider widget default)',
253
+ },
254
+ ],
255
+ },
256
+ fieldAfterObjectList: {
257
+ title: 'Field after OL',
258
+ },
259
+ href: {
260
+ widget: 'object_browser',
261
+ mode: 'link',
262
+ selectedItemAttrs: [
263
+ 'Title',
264
+ 'Description',
265
+ 'hasPreviewImage',
266
+ 'headtitle',
267
+ ],
268
+ allowExternals: true,
269
+ },
270
+ firstWithDefault: {
271
+ title: 'Field with default',
272
+ default: 'Some default value',
273
+ },
274
+ style: {
275
+ widget: 'object',
276
+ schema: {
277
+ title: 'Style',
278
+ fieldsets: [
279
+ {
280
+ id: 'default',
281
+ fields: ['color', 'theme'],
282
+ title: 'Default',
283
+ },
284
+ ],
285
+ properties: {
286
+ color: {
287
+ title: 'Color',
288
+ default: 'red',
289
+ },
290
+ theme: {
291
+ title: 'Theme',
292
+ default: 'primary',
293
+ },
294
+ },
295
+ required: [],
296
+ },
297
+ },
298
+ anotherWithDefault: {
299
+ title: 'Field with default 2',
300
+ default: 2,
301
+ type: 'number',
302
+ },
303
+ yetAnotherWithDefault: {
304
+ title: 'Field with default 3',
305
+ default: ['one', 'two'],
306
+ type: 'array',
307
+ },
308
+ },
309
+ required: [],
310
+ }),
311
+ };
312
+
168
313
  config.settings.defaultBlockType = 'text';
169
314
 
170
315
  describe('Blocks', () => {
@@ -508,6 +653,95 @@ describe('Blocks', () => {
508
653
  description: 'already filled',
509
654
  });
510
655
  });
656
+ it('Sets data according to schema default values, top level and styling wrapper object field', () => {
657
+ const data = {
658
+ '@type': 'slider',
659
+ };
660
+ const schema = config.blocks.blocksConfig.slider.blockSchema({ data });
661
+
662
+ // if you don't pass down intl, the ObjectWidget defaults are not applied
663
+ expect(applySchemaDefaults({ schema, data })).toEqual({
664
+ '@type': 'slider',
665
+ anotherWithDefault: 2,
666
+ slides: [
667
+ {
668
+ '@id': 'asdasdasd-qweqwe-zxczxc',
669
+ extraDefault:
670
+ 'Extra default (Manual in parent slider widget default)',
671
+ },
672
+ ],
673
+ firstWithDefault: 'Some default value',
674
+ yetAnotherWithDefault: ['one', 'two'],
675
+ });
676
+
677
+ expect(applySchemaDefaults({ schema, data, intl: {} })).toEqual({
678
+ '@type': 'slider',
679
+ anotherWithDefault: 2,
680
+ slides: [
681
+ {
682
+ '@id': 'asdasdasd-qweqwe-zxczxc',
683
+ extraDefault:
684
+ 'Extra default (Manual in parent slider widget default)',
685
+ },
686
+ ],
687
+ firstWithDefault: 'Some default value',
688
+ style: {
689
+ color: 'red',
690
+ theme: 'primary',
691
+ },
692
+ yetAnotherWithDefault: ['one', 'two'],
693
+ });
694
+ });
695
+
696
+ it('Sets data according to schema default values, keeps existing data', () => {
697
+ const schema = {
698
+ properties: {
699
+ style: {
700
+ widget: 'object',
701
+ schema: {
702
+ title: 'Style',
703
+ fieldsets: [
704
+ {
705
+ id: 'default',
706
+ fields: ['color', 'theme'],
707
+ title: 'Default',
708
+ },
709
+ ],
710
+ properties: {
711
+ color: {
712
+ title: 'Color',
713
+ default: 'red',
714
+ },
715
+ theme: {
716
+ title: 'Theme',
717
+ default: 'primary',
718
+ },
719
+ },
720
+ required: [],
721
+ },
722
+ },
723
+ },
724
+ };
725
+
726
+ expect(
727
+ applySchemaDefaults({
728
+ schema,
729
+ data: {
730
+ '@type': 'slider',
731
+ style: {
732
+ theme: 'secondary',
733
+ },
734
+ },
735
+ intl: {},
736
+ }),
737
+ ).toEqual({
738
+ '@type': 'slider',
739
+ style: {
740
+ color: 'red',
741
+ theme: 'secondary',
742
+ },
743
+ });
744
+ });
511
745
  });
512
746
 
513
747
  describe('applyBlockDefaults', () => {