apostrophe 4.7.0 → 4.8.0

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.
Files changed (53) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/index.js +1 -1
  3. package/lib/moog.js +1 -1
  4. package/modules/@apostrophecms/admin-bar/index.js +61 -2
  5. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBreakpointPreviewMode.vue +166 -0
  6. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextTitle.vue +25 -0
  7. package/modules/@apostrophecms/area/ui/apos/components/AposAreaEditor.vue +3 -12
  8. package/modules/@apostrophecms/asset/index.js +41 -1
  9. package/modules/@apostrophecms/asset/lib/globalIcons.js +6 -0
  10. package/modules/@apostrophecms/asset/lib/webpack/apos/webpack.scss.js +16 -16
  11. package/modules/@apostrophecms/asset/lib/webpack/media-to-container-queries-loader.js +94 -0
  12. package/modules/@apostrophecms/asset/lib/webpack/src/webpack.scss.js +12 -0
  13. package/modules/@apostrophecms/attachment/index.js +8 -1
  14. package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuKey.vue +1 -1
  15. package/modules/@apostrophecms/command-menu/ui/apos/components/AposCommandMenuShortcut.vue +5 -2
  16. package/modules/@apostrophecms/doc-type/index.js +0 -1
  17. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +1 -19
  18. package/modules/@apostrophecms/i18n/i18n/en.json +12 -1
  19. package/modules/@apostrophecms/image/ui/apos/components/AposMediaUploader.vue +9 -18
  20. package/modules/@apostrophecms/migration/index.js +20 -13
  21. package/modules/@apostrophecms/migration/lib/addMissingSchemaFields.js +182 -0
  22. package/modules/@apostrophecms/page/index.js +19 -0
  23. package/modules/@apostrophecms/piece-type/ui/apos/components/AposRelationshipEditor.vue +2 -7
  24. package/modules/@apostrophecms/rich-text-widget/index.js +66 -0
  25. package/modules/@apostrophecms/schema/index.js +20 -29
  26. package/modules/@apostrophecms/schema/lib/addFieldTypes.js +2 -27
  27. package/modules/@apostrophecms/schema/lib/newInstance.js +36 -0
  28. package/modules/@apostrophecms/schema/ui/apos/components/AposInputArray.vue +238 -80
  29. package/modules/@apostrophecms/schema/ui/apos/components/AposInputWrapper.vue +34 -24
  30. package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +2 -7
  31. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputArray.js +207 -44
  32. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +3 -14
  33. package/modules/@apostrophecms/schema/ui/apos/scss/AposInputArray.scss +288 -105
  34. package/modules/@apostrophecms/template/views/outerLayoutBase.html +1 -0
  35. package/modules/@apostrophecms/ui/ui/apos/components/AposButton.vue +2 -1
  36. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +9 -6
  37. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuDialog.vue +8 -6
  38. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenuItem.vue +52 -51
  39. package/modules/@apostrophecms/ui/ui/apos/components/AposFile.vue +16 -6
  40. package/modules/@apostrophecms/ui/ui/apos/components/AposIndicator.vue +1 -0
  41. package/modules/@apostrophecms/ui/ui/apos/components/AposLocalePicker.vue +6 -4
  42. package/modules/@apostrophecms/ui/ui/apos/components/AposSlatList.vue +6 -2
  43. package/modules/@apostrophecms/ui/ui/apos/components/AposTagApply.vue +8 -6
  44. package/modules/@apostrophecms/ui/ui/apos/scss/global/_breakpoint_preview.scss +38 -0
  45. package/modules/@apostrophecms/ui/ui/apos/scss/global/_tables.scss +3 -1
  46. package/modules/@apostrophecms/ui/ui/apos/scss/global/import-all.scss +1 -0
  47. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +2 -13
  48. package/package.json +3 -2
  49. package/test/add-missing-schema-fields.js +323 -0
  50. package/test/content-i18n.js +1 -1
  51. package/test/events2.js +4 -2
  52. package/test/pages.js +10 -0
  53. package/test/parked-pages.js +75 -0
@@ -2,10 +2,12 @@
2
2
  <div>
3
3
  <label
4
4
  class="apos-input-wrapper apos-file-dropzone"
5
+ tabindex="0"
5
6
  :class="{
6
7
  'apos-file-dropzone--dragover': dragging,
7
8
  'apos-is-disabled': disabled || fileOrAttachment
8
9
  }"
10
+ @keydown="handleKeydown"
9
11
  @drop.prevent="uploadFile"
10
12
  @dragover="dragHandler"
11
13
  @dragleave="dragging = false"
@@ -29,6 +31,7 @@
29
31
  class="apos-sr-only"
30
32
  :disabled="disabled || fileOrAttachment"
31
33
  :accept="allowedExtensions"
34
+ tabindex="-1"
32
35
  @input="uploadFile"
33
36
  >
34
37
  </label>
@@ -101,6 +104,15 @@ export default {
101
104
  }
102
105
  },
103
106
  methods: {
107
+ handleKeydown(event) {
108
+ switch (event.key) {
109
+ case ' ':
110
+ case 'Enter':
111
+ event.preventDefault();
112
+ this.$refs.uploadField.click();
113
+ break;
114
+ }
115
+ },
104
116
  async uploadFile ({ target, dataTransfer }) {
105
117
  this.dragging = false;
106
118
  const [ file ] = target.files ? target.files : (dataTransfer.files || []);
@@ -174,14 +186,12 @@ export default {
174
186
  transition: all 200ms ease;
175
187
  }
176
188
 
177
- &:hover {
178
- border-color: var(--a-primary);
179
- background-color: var(--a-base-10);
180
- }
181
-
189
+ &:hover,
182
190
  &:active,
183
191
  &:focus {
184
- border: 2px solid var(--a-primary);
192
+ border-color: var(--a-primary);
193
+ background-color: var(--a-base-10);
194
+ outline: none;
185
195
  }
186
196
 
187
197
  &.apos-is-disabled {
@@ -4,6 +4,7 @@
4
4
  v-apos-tooltip="tooltip"
5
5
  class="apos-indicator"
6
6
  :aria-hidden="decorative"
7
+ :tabindex="decorative ? '-1' : '0'"
7
8
  >
8
9
  <component
9
10
  :is="icon"
@@ -151,10 +151,12 @@ function getForbiddenTooltip(locale) {
151
151
  .apos-locale-picker__locale-display {
152
152
  @include apos-button-reset();
153
153
 
154
- display: block;
155
- box-sizing: border-box;
156
- width: 100%;
157
- padding: 12px 35px;
154
+ & {
155
+ display: block;
156
+ box-sizing: border-box;
157
+ width: 100%;
158
+ padding: 12px 35px;
159
+ }
158
160
 
159
161
  &:focus, &:active {
160
162
  outline: none;
@@ -132,6 +132,8 @@ export default {
132
132
  oldIndex, newIndex
133
133
  }) {
134
134
  this.next.splice(newIndex, 0, this.next.splice(oldIndex, 1)[0]);
135
+ // FIX: swapping the items does not trigger the watcher
136
+ this.next = this.next.slice();
135
137
  },
136
138
  engage(id) {
137
139
  this.engaged = id;
@@ -199,8 +201,10 @@ export default {
199
201
  .apos-slat-list {
200
202
  @include apos-list-reset();
201
203
 
202
- min-height: 20px;
203
- max-width: $input-max-width;
204
+ & {
205
+ min-height: 20px;
206
+ max-width: $input-max-width;
207
+ }
204
208
  }
205
209
 
206
210
  .apos-slat-status {
@@ -317,12 +317,14 @@ export default {
317
317
  .apos-apply-tag-menu__tags {
318
318
  @include apos-list-reset();
319
319
 
320
- max-height: 160px;
321
- overflow-y: auto;
322
- margin-top: 15px;
323
- // Negative margin/padding below is for the checkbox focus state.
324
- margin-left: -10px;
325
- padding-left: 10px;
320
+ & {
321
+ max-height: 160px;
322
+ overflow-y: auto;
323
+ margin-top: 15px;
324
+ // Negative margin/padding below is for the checkbox focus state.
325
+ margin-left: -10px;
326
+ padding-left: 10px;
327
+ }
326
328
  }
327
329
 
328
330
  .apos-apply-tag-menu__tag {
@@ -0,0 +1,38 @@
1
+ [data-apos-context-label] {
2
+ @include type-help;
3
+
4
+ & {
5
+ position: relative;
6
+ top: $spacing-base * 5;
7
+ display: none;
8
+ text-align: center;
9
+ }
10
+ }
11
+
12
+ body[data-breakpoint-preview-mode] {
13
+ background: var(--a-base-10);
14
+
15
+ [data-apos-context-label] {
16
+ display: block;
17
+ }
18
+
19
+ [data-apos-refreshable] {
20
+ container-type: size;
21
+ overflow: clip scroll;
22
+ flex-grow: unset;
23
+ margin: $spacing-base * 6 auto auto;
24
+ border: 1px solid var(--a-base-6);
25
+ box-shadow: 0 0 12px 0 var(--a-base-7);
26
+ background-color: var(--a-background-primary);
27
+
28
+ &[data-resizable="false"] {
29
+ transition: width 400ms ease, height 400ms ease;
30
+ }
31
+
32
+ &[data-resizable="true"] {
33
+ resize: both;
34
+ overflow: scroll;
35
+ }
36
+ }
37
+
38
+ }
@@ -19,7 +19,9 @@
19
19
  .apos-table__header-icon {
20
20
  @include apos-align-icon();
21
21
 
22
- margin-right: 5px;
22
+ & {
23
+ margin-right: 5px;
24
+ }
23
25
  }
24
26
 
25
27
  .apos-table__header-label,
@@ -11,3 +11,4 @@
11
11
  @import './_tables';
12
12
  @import './_tooltips';
13
13
  @import './_widgets';
14
+ @import './_breakpoint_preview';
@@ -67,6 +67,7 @@ import AposModalTabsMixin from 'Modules/@apostrophecms/modal/mixins/AposModalTab
67
67
  import { detectDocChange } from 'Modules/@apostrophecms/schema/lib/detectChange';
68
68
  import { createId } from '@paralleldrive/cuid2';
69
69
  import { klona } from 'klona';
70
+ import newInstance from 'apostrophe/modules/@apostrophecms/schema/lib/newInstance.js';
70
71
 
71
72
  export default {
72
73
  name: 'AposWidgetEditor',
@@ -234,19 +235,7 @@ export default {
234
235
  });
235
236
  },
236
237
  getDefault() {
237
- const widget = {};
238
- this.schema.forEach(field => {
239
- if (field.name.startsWith('_')) {
240
- return;
241
- }
242
- // Using `hasOwn` here, not simply checking if `field.def` is truthy
243
- // so that `false`, `null`, `''` or `0` are taken into account:
244
- const hasDefaultValue = Object.hasOwn(field, 'def');
245
- widget[field.name] = hasDefaultValue
246
- ? klona(field.def)
247
- : null;
248
- });
249
- return widget;
238
+ return newInstance(this.schema);
250
239
  },
251
240
  getAposSchema(field) {
252
241
  return this.$refs[field.group.name][0];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "apostrophe",
3
- "version": "4.7.0",
3
+ "version": "4.8.0",
4
4
  "description": "The Apostrophe Content Management System.",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -100,6 +100,7 @@
100
100
  "path-to-regexp": "^1.8.0",
101
101
  "performance-now": "^2.1.0",
102
102
  "pinia": "^2.1.7",
103
+ "postcss": "^8.4.47",
103
104
  "postcss-html": "^1.3.0",
104
105
  "postcss-loader": "^5.0.0",
105
106
  "postcss-scss": "^4.0.3",
@@ -140,7 +141,7 @@
140
141
  "nyc": "^15.1.0",
141
142
  "replace-in-file": "^6.1.0",
142
143
  "stylelint": "^16.5.0",
143
- "stylelint-config-apostrophe": "4.1.0",
144
+ "stylelint-config-apostrophe": "^4.2.0",
144
145
  "vue-eslint-parser": "^7.1.1",
145
146
  "vue-template-compiler": "^2.7.14"
146
147
  },
@@ -0,0 +1,323 @@
1
+ const t = require('../test-lib/test.js');
2
+ const assert = require('assert');
3
+ const _ = require('lodash');
4
+
5
+ const cleanup = [];
6
+
7
+ after(async () => {
8
+ for (const apos of cleanup) {
9
+ await t.destroy(apos);
10
+ }
11
+ });
12
+
13
+ describe('add missing schema fields', function() {
14
+
15
+ this.timeout(t.timeout);
16
+
17
+ it('first generation & sanity checks', async function() {
18
+ const apos = await t.create({
19
+ root: module,
20
+ shortName: 'test-amsf',
21
+ modules: {
22
+ product: {
23
+ extend: '@apostrophecms/piece-type',
24
+ options: {
25
+ alias: 'product'
26
+ },
27
+ fields: {
28
+ add: {
29
+ price: {
30
+ type: 'integer',
31
+ required: true,
32
+ def: 10
33
+ },
34
+ body: {
35
+ type: 'area',
36
+ widgets: {
37
+ hero: {},
38
+ '@apostrophecms/rich-text': {}
39
+ }
40
+ }
41
+ }
42
+ }
43
+ },
44
+ 'hero-widget': {
45
+ extend: '@apostrophecms/widget-type',
46
+ fields: {
47
+ add: {
48
+ label: {
49
+ type: 'string'
50
+ }
51
+ }
52
+ }
53
+ }
54
+ }
55
+ });
56
+ cleanup.push(apos);
57
+ const product = apos.product.newInstance();
58
+ product.title = 'Product 1';
59
+ // Intentionally not equal to the default
60
+ product.price = 15;
61
+ product.body = {
62
+ metaType: 'area',
63
+ items: [
64
+ {
65
+ type: 'hero',
66
+ label: 'Sample Label'
67
+ }
68
+ ]
69
+ };
70
+ const req = apos.task.getReq();
71
+ await apos.product.insert(req, product);
72
+ const products = await apos.doc.db.find({
73
+ type: 'product'
74
+ }).sort({
75
+ aposMode: 1
76
+ }).toArray();
77
+ assert.strictEqual(products.length, 2);
78
+ for (const product of products) {
79
+ // Not a thing yet
80
+ assert.strictEqual(product.code, undefined);
81
+ assert.strictEqual(product.title, 'Product 1');
82
+ assert.strictEqual(product.price, 15);
83
+ assert.strictEqual(product.body.items[0].label, 'Sample Label');
84
+ }
85
+ });
86
+
87
+ it('second generation schema (objects, arrays, new widget fields)', async function() {
88
+ const apos = await t.create({
89
+ root: module,
90
+ // Same on purpose so we reuse the database
91
+ shortName: 'test-amsf',
92
+ modules: {
93
+ product: {
94
+ extend: '@apostrophecms/piece-type',
95
+ options: {
96
+ alias: 'product'
97
+ },
98
+ fields: {
99
+ add: {
100
+ price: {
101
+ type: 'float',
102
+ required: true
103
+ },
104
+ body: {
105
+ type: 'area',
106
+ widgets: {
107
+ hero: {},
108
+ '@apostrophecms/rich-text': {}
109
+ }
110
+ },
111
+ code: {
112
+ type: 'integer',
113
+ required: true,
114
+ def: 20
115
+ },
116
+ attitude: {
117
+ // Test the fallback default of the type
118
+ type: 'string'
119
+ },
120
+ addresses: {
121
+ type: 'array',
122
+ fields: {
123
+ add: {
124
+ street: {
125
+ type: 'string'
126
+ }
127
+ }
128
+ }
129
+ },
130
+ flavors: {
131
+ type: 'object',
132
+ fields: {
133
+ add: {
134
+ chocolate: {
135
+ type: 'boolean',
136
+ def: true
137
+ },
138
+ vanilla: {
139
+ type: 'boolean',
140
+ def: false
141
+ },
142
+ strawberry: {
143
+ type: 'boolean'
144
+ // should get fallback default
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+ }
151
+ },
152
+ 'hero-widget': {
153
+ extend: '@apostrophecms/widget-type',
154
+ fields: {
155
+ add: {
156
+ label: {
157
+ type: 'string'
158
+ },
159
+ // New properties, with defaults
160
+ sublabel: {
161
+ type: 'string',
162
+ def: 'Default Sublabel'
163
+ },
164
+ specialized: {
165
+ type: 'boolean',
166
+ def: false
167
+ }
168
+ }
169
+ }
170
+ }
171
+ }
172
+ });
173
+ cleanup.push(apos);
174
+ const products = await apos.doc.db.find({
175
+ type: 'product'
176
+ }).sort({
177
+ aposMode: 1
178
+ }).toArray();
179
+ assert.strictEqual(products.length, 2);
180
+ for (const product of products) {
181
+ assert.strictEqual(product.code, 20);
182
+ assert.strictEqual(product.title, 'Product 1');
183
+ // Still unchanged from existing non-default value
184
+ assert.strictEqual(product.price, 15);
185
+ assert.strictEqual(product.attitude, '');
186
+ assert(_.isEqual(product.addresses, []));
187
+ assert.strictEqual(product.flavors.chocolate, true);
188
+ assert.strictEqual(product.flavors.vanilla, false);
189
+ // Type has no fallback default (update test if this changes)
190
+ assert.strictEqual(product.flavors.strawberry, undefined);
191
+ assert.strictEqual(product.body.items[0].label, 'Sample Label');
192
+ assert.strictEqual(product.body.items[0].sublabel, 'Default Sublabel');
193
+ assert.strictEqual(product.body.items[0].specialized, false);
194
+ }
195
+ const product = products.find(product => product.aposMode === 'draft');
196
+ const req = apos.task.getReq({
197
+ mode: 'draft'
198
+ });
199
+ product.addresses.push({
200
+ street: '1168 E Passyunk Ave'
201
+ });
202
+ const result = await apos.product.update(req, product);
203
+ await apos.product.publish(req, result);
204
+ assert.strictEqual(result.addresses.length, 1);
205
+ assert.strictEqual(result.addresses[0].street, '1168 E Passyunk Ave');
206
+ });
207
+
208
+ it('third generation schema (new array fields & verify new defaults do not crush existing values)', async function() {
209
+ const apos = await t.create({
210
+ root: module,
211
+ // Same on purpose so we reuse the database
212
+ shortName: 'test-amsf',
213
+ modules: {
214
+ product: {
215
+ extend: '@apostrophecms/piece-type',
216
+ options: {
217
+ alias: 'product'
218
+ },
219
+ fields: {
220
+ add: {
221
+ price: {
222
+ type: 'float',
223
+ required: true
224
+ },
225
+ body: {
226
+ type: 'area',
227
+ widgets: {
228
+ hero: {},
229
+ '@apostrophecms/rich-text': {}
230
+ }
231
+ },
232
+ code: {
233
+ type: 'integer',
234
+ required: true,
235
+ def: 20
236
+ },
237
+ addresses: {
238
+ type: 'array',
239
+ fields: {
240
+ add: {
241
+ street: {
242
+ type: 'string'
243
+ },
244
+ city: {
245
+ type: 'string',
246
+ def: 'Philadelphia'
247
+ }
248
+ }
249
+ }
250
+ },
251
+ flavors: {
252
+ type: 'object',
253
+ fields: {
254
+ add: {
255
+ chocolate: {
256
+ type: 'boolean',
257
+ def: true
258
+ },
259
+ vanilla: {
260
+ type: 'boolean',
261
+ // Verify change of default does NOT mess up an existing property
262
+ def: true
263
+ },
264
+ strawberry: {
265
+ type: 'boolean'
266
+ // should get fallback default
267
+ }
268
+ }
269
+ }
270
+ }
271
+ }
272
+ }
273
+ },
274
+ 'hero-widget': {
275
+ extend: '@apostrophecms/widget-type',
276
+ fields: {
277
+ add: {
278
+ label: {
279
+ type: 'string'
280
+ },
281
+ // New properties, with defaults
282
+ sublabel: {
283
+ type: 'string',
284
+ def: 'Default Sublabel'
285
+ },
286
+ specialized: {
287
+ type: 'boolean',
288
+ def: false
289
+ }
290
+ }
291
+ }
292
+ }
293
+ }
294
+ });
295
+ cleanup.push(apos);
296
+ const products = await apos.doc.db.find({
297
+ type: 'product',
298
+ aposMode: {
299
+ $in: [ 'draft', 'published' ]
300
+ }
301
+ }).sort({
302
+ aposMode: 1
303
+ }).toArray();
304
+ assert.strictEqual(products.length, 2);
305
+ for (const product of products) {
306
+ assert.strictEqual(product.code, 20);
307
+ assert.strictEqual(product.title, 'Product 1');
308
+ assert.strictEqual(product.addresses.length, 1);
309
+ // Should NOT change because it already has a value
310
+ assert.strictEqual(product.flavors.vanilla, false);
311
+ assert.strictEqual(product.addresses[0].street, '1168 E Passyunk Ave');
312
+ assert.strictEqual(product.addresses[0].city, 'Philadelphia');
313
+ }
314
+
315
+ // Direct invocation to make sure an additional invocation with the same schemas
316
+ // does no new work
317
+
318
+ const { scans, updates } = await apos.migration.addMissingSchemaFields();
319
+ assert.strictEqual(scans, 0);
320
+ assert.strictEqual(updates, 0);
321
+ });
322
+
323
+ });
@@ -96,7 +96,7 @@ describe('content-i18n', function() {
96
96
  peoplePageFrCA = await apos.page.find(frCAReq, {
97
97
  parkedId: 'people'
98
98
  }).toObject();
99
- assert(peoplePageFrCA.title === 'Altered');
99
+ assert(peoplePageFrCA.title === 'People');
100
100
  });
101
101
 
102
102
  let home;
package/test/events2.js CHANGED
@@ -45,10 +45,12 @@ describe('Promisified Events: @apostrophecms/doc-type:beforeInsert', function()
45
45
  {
46
46
  type: 'default-page',
47
47
  findMeAgain: true,
48
- title: 'Test',
49
48
  slug: '/test',
50
49
  visibility: 'public',
51
- parkedId: 'test'
50
+ parkedId: 'test',
51
+ _defaults: {
52
+ title: 'Test'
53
+ }
52
54
  }
53
55
  ]
54
56
  }
package/test/pages.js CHANGED
@@ -421,6 +421,16 @@ describe('Pages', function() {
421
421
  assert.strictEqual(page.rank, 1);
422
422
  });
423
423
 
424
+ it('is not able to move a page under itself', async function() {
425
+ await assert.rejects(
426
+ apos.page.move(apos.task.getReq(), 'cousin:en:published', 'cousin:en:published', 'lastChild'),
427
+ {
428
+ name: 'forbidden',
429
+ message: 'Cannot move a page under itself'
430
+ }
431
+ );
432
+ });
433
+
424
434
  it('is able to move root/cousin before root/parent/child', async function() {
425
435
  // 'Cousin' _id === 4312
426
436
  // 'Child' _id === 2341
@@ -271,6 +271,81 @@ describe('Parked Pages', function() {
271
271
  });
272
272
  await validate(apos6, [ '/', '/archive', '/default1', '/default2', '/default3', '/default3/child1' ]);
273
273
  });
274
+
275
+ it('field override on save is possible only when it is configured as default', async function () {
276
+ this.timeout(20000);
277
+ await t.destroy(apos6);
278
+ apos6 = await t.create({
279
+ root: module,
280
+ modules: {
281
+ '@apostrophecms/page': {
282
+ options: {
283
+ park: [
284
+ ...park2,
285
+ {
286
+ parkedId: 'default3',
287
+ type: 'default-page',
288
+ title: 'Default 3',
289
+ slug: '/default3'
290
+ },
291
+ {
292
+ parkedId: 'default4',
293
+ type: 'default-page',
294
+ title: 'Default 4',
295
+ _defaults: {
296
+ slug: '/default4'
297
+ }
298
+ }
299
+ ]
300
+ }
301
+ },
302
+ 'default-page': {}
303
+ }
304
+ });
305
+ const manager = apos6.doc.getManager('default-page');
306
+ const req = apos.task.getReq({ mode: 'draft' });
307
+
308
+ // slug override not possible, slug is NOT configured in defaults
309
+ {
310
+ const page = await manager.find(req, {
311
+ parkedId: 'default3',
312
+ aposMode: 'draft'
313
+ }).toObject();
314
+ assert.strictEqual(page.slug, '/default3');
315
+ assert.deepStrictEqual(page.parked, [ 'parkedId', 'type', 'title', 'slug' ]);
316
+
317
+ page.slug = '/default3-overridden';
318
+ await manager.update(req, page);
319
+ const updated = await manager.find(req, {
320
+ parkedId: 'default3',
321
+ aposMode: 'draft'
322
+ }).toObject();
323
+
324
+ assert.strictEqual(updated.slug, '/default3');
325
+ }
326
+
327
+ // slug override is possible because slug is configured in defaults
328
+ {
329
+ const page = await manager.find(req, {
330
+ parkedId: 'default4',
331
+ aposMode: 'draft'
332
+ }).toObject();
333
+ assert.strictEqual(page.slug, '/default4');
334
+ assert.deepStrictEqual(page.parked, [ 'parkedId', 'type', 'title' ]);
335
+
336
+ page.slug = '/default4-overridden';
337
+ await manager.update(req, page);
338
+ const updated = await manager.find(req, {
339
+ parkedId: 'default4',
340
+ aposMode: 'draft'
341
+ }).toObject();
342
+
343
+ assert.strictEqual(updated.slug, '/default4-overridden');
344
+ }
345
+
346
+ await t.destroy(apos6);
347
+ apos6 = null;
348
+ });
274
349
  });
275
350
 
276
351
  async function validate(apos, expected) {