apostrophe 4.28.1 → 4.29.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 (89) hide show
  1. package/CHANGELOG.md +29 -4
  2. package/README.md +2 -2
  3. package/defaults.js +1 -0
  4. package/lib/safe-json-script.js +27 -0
  5. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposAdminBarLocale.vue +1 -1
  6. package/modules/@apostrophecms/admin-bar/ui/apos/components/TheAposContextBar.vue +1 -0
  7. package/modules/@apostrophecms/area/ui/apos/components/AposAreaWidget.vue +3 -5
  8. package/modules/@apostrophecms/area/ui/apos/components/AposBreadcrumbOperations.vue +13 -1
  9. package/modules/@apostrophecms/asset/lib/globalIcons.js +3 -0
  10. package/modules/@apostrophecms/attachment/index.js +43 -1
  11. package/modules/@apostrophecms/color-field/index.js +7 -1
  12. package/modules/@apostrophecms/doc/index.js +11 -1
  13. package/modules/@apostrophecms/doc-type/index.js +165 -32
  14. package/modules/@apostrophecms/doc-type/ui/apos/components/AposDocEditor.vue +1 -1
  15. package/modules/@apostrophecms/doc-type/ui/apos/logic/AposDocContextMenu.js +104 -59
  16. package/modules/@apostrophecms/file/index.js +109 -8
  17. package/modules/@apostrophecms/i18n/i18n/de.json +0 -2
  18. package/modules/@apostrophecms/i18n/i18n/en.json +40 -1
  19. package/modules/@apostrophecms/i18n/i18n/es.json +0 -1
  20. package/modules/@apostrophecms/i18n/i18n/fr.json +0 -1
  21. package/modules/@apostrophecms/i18n/i18n/it.json +0 -1
  22. package/modules/@apostrophecms/i18n/i18n/pt-BR.json +0 -1
  23. package/modules/@apostrophecms/i18n/i18n/sk.json +0 -1
  24. package/modules/@apostrophecms/i18n/ui/apos/apps/AposI18nBatchReporting.js +18 -1
  25. package/modules/@apostrophecms/i18n/ui/apos/apps/AposI18nLocalizeActions.js +50 -0
  26. package/modules/@apostrophecms/i18n/ui/apos/components/AposI18nLocalize.vue +56 -13
  27. package/modules/@apostrophecms/image/ui/apos/components/AposImageRelationshipEditor.vue +8 -2
  28. package/modules/@apostrophecms/layout-column-widget/index.js +156 -163
  29. package/modules/@apostrophecms/layout-widget/index.js +7 -2
  30. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposAreaLayoutEditor.vue +6 -11
  31. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridColumn.vue +3 -5
  32. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridLayout.vue +4 -4
  33. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposGridManager.vue +0 -16
  34. package/modules/@apostrophecms/layout-widget/ui/apos/lib/grid-state.mjs +7 -27
  35. package/modules/@apostrophecms/layout-widget/views/column.html +7 -9
  36. package/modules/@apostrophecms/login/index.js +39 -40
  37. package/modules/@apostrophecms/modal/ui/apos/components/AposDocsManagerToolbar.vue +17 -2
  38. package/modules/@apostrophecms/modal/ui/apos/components/AposModal.vue +3 -2
  39. package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +1 -0
  40. package/modules/@apostrophecms/page/index.js +2 -0
  41. package/modules/@apostrophecms/piece-type/index.js +3 -1
  42. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManager.vue +1 -0
  43. package/modules/@apostrophecms/piece-type/ui/apos/components/AposDocsManagerDisplay.vue +5 -0
  44. package/modules/@apostrophecms/recently-edited/index.js +831 -0
  45. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposCellTitle.vue +54 -0
  46. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedCombo.vue +454 -0
  47. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedFilterTag.vue +75 -0
  48. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedFilters.vue +287 -0
  49. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedIcon.vue +16 -0
  50. package/modules/@apostrophecms/recently-edited/ui/apos/components/AposRecentlyEditedManager.vue +346 -0
  51. package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedBatch.js +193 -0
  52. package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedData.js +276 -0
  53. package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedFetch.js +199 -0
  54. package/modules/@apostrophecms/recently-edited/ui/apos/composables/useRecentlyEditedFilters.js +100 -0
  55. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputRelationship.js +8 -4
  56. package/modules/@apostrophecms/schema/ui/apos/logic/AposInputWrapper.js +1 -1
  57. package/modules/@apostrophecms/styles/index.js +10 -0
  58. package/modules/@apostrophecms/styles/lib/apiRoutes.js +6 -0
  59. package/modules/@apostrophecms/styles/lib/handlers.js +5 -0
  60. package/modules/@apostrophecms/styles/lib/methods.js +9 -3
  61. package/modules/@apostrophecms/styles/lib/presets.js +119 -0
  62. package/modules/@apostrophecms/styles/ui/apos/components/TheAposStyles.vue +3 -8
  63. package/modules/@apostrophecms/styles/ui/apos/composables/AposStyles.js +1 -3
  64. package/modules/@apostrophecms/styles/ui/apos/render-factory.js +29 -0
  65. package/modules/@apostrophecms/styles/ui/apos/universal/backgroundHelpers.mjs +140 -0
  66. package/modules/@apostrophecms/styles/ui/apos/universal/customRules.mjs +105 -0
  67. package/modules/@apostrophecms/styles/ui/apos/universal/render.mjs +195 -15
  68. package/modules/@apostrophecms/template/index.js +22 -6
  69. package/modules/@apostrophecms/ui/ui/apos/components/AposCellContextMenu.vue +2 -0
  70. package/modules/@apostrophecms/ui/ui/apos/components/AposContextMenu.vue +18 -4
  71. package/modules/@apostrophecms/ui/ui/apos/composables/useInfiniteScroll.js +91 -0
  72. package/modules/@apostrophecms/ui/ui/apos/scss/global/_theme.scss +1 -0
  73. package/modules/@apostrophecms/ui/ui/apos/stores/modal.js +5 -2
  74. package/modules/@apostrophecms/ui/ui/apos/utils/index.js +9 -0
  75. package/modules/@apostrophecms/url/index.js +38 -4
  76. package/modules/@apostrophecms/widget-type/index.js +22 -6
  77. package/modules/@apostrophecms/widget-type/ui/apos/components/AposWidgetEditor.vue +8 -4
  78. package/package.json +17 -17
  79. package/test/add-missing-schema-fields-project/node_modules/.package-lock.json +2 -2
  80. package/test/layout-widget-migration.js +719 -0
  81. package/test/login-requirements.js +1 -1
  82. package/test/pieces-public-api.js +80 -0
  83. package/test/pieces.js +25 -0
  84. package/test/recently-edited.js +2311 -0
  85. package/test/schemas.js +39 -3
  86. package/test/static-build.js +642 -0
  87. package/test/styles.js +2569 -0
  88. package/.claude/settings.local.json +0 -15
  89. package/modules/@apostrophecms/layout-widget/ui/apos/components/AposLayoutColControlDialog.vue +0 -171
@@ -0,0 +1,719 @@
1
+ const assert = require('node:assert/strict');
2
+ const t = require('../test-lib/test.js');
3
+
4
+ describe('Layout Widget Migration', function () {
5
+ this.timeout(t.timeout);
6
+
7
+ describe('migrateColumnWidget (unit)', function () {
8
+ let apos;
9
+ let migrate;
10
+
11
+ before(async function () {
12
+ apos = await t.create({ root: module });
13
+ migrate = apos.modules['@apostrophecms/layout-column-widget'].migrateColumnWidget;
14
+ });
15
+
16
+ after(async function () {
17
+ await t.destroy(apos);
18
+ });
19
+
20
+ it('returns $set for old-schema widget', function () {
21
+ const widget = {
22
+ _id: 'w1',
23
+ type: '@apostrophecms/layout-column',
24
+ metaType: 'widget',
25
+ desktop: {
26
+ colstart: 1,
27
+ colspan: 6,
28
+ rowstart: 1,
29
+ rowspan: 1,
30
+ order: 0,
31
+ justify: 'center',
32
+ align: 'start'
33
+ },
34
+ tablet: {
35
+ show: false,
36
+ order: 2
37
+ },
38
+ mobile: {
39
+ show: true,
40
+ order: 3
41
+ }
42
+ };
43
+
44
+ const result = migrate(widget, 'main.items.0');
45
+
46
+ assert.deepEqual(result, {
47
+ $set: {
48
+ 'main.items.0.colstart': 1,
49
+ 'main.items.0.colspan': 6,
50
+ 'main.items.0.rowstart': 1,
51
+ 'main.items.0.rowspan': 1,
52
+ 'main.items.0.order': 0,
53
+ 'main.items.0.justify': 'center',
54
+ 'main.items.0.align': 'start',
55
+ 'main.items.0.showTablet': false,
56
+ 'main.items.0.showMobile': true
57
+ }
58
+ });
59
+ });
60
+
61
+ it('applies defaults when optional fields are missing', function () {
62
+ const widget = {
63
+ _id: 'w1',
64
+ type: '@apostrophecms/layout-column',
65
+ metaType: 'widget',
66
+ desktop: {
67
+ colstart: 2,
68
+ colspan: 4
69
+ },
70
+ tablet: {},
71
+ mobile: {}
72
+ };
73
+
74
+ const result = migrate(widget, 'main.items.0');
75
+
76
+ assert.deepEqual(result, {
77
+ $set: {
78
+ 'main.items.0.colstart': 2,
79
+ 'main.items.0.colspan': 4,
80
+ 'main.items.0.rowstart': 1,
81
+ 'main.items.0.rowspan': 1,
82
+ 'main.items.0.order': null,
83
+ 'main.items.0.justify': null,
84
+ 'main.items.0.align': null,
85
+ 'main.items.0.showTablet': true,
86
+ 'main.items.0.showMobile': true
87
+ }
88
+ });
89
+ });
90
+
91
+ it('uses correct dot path for deeply nested widget', function () {
92
+ const widget = {
93
+ _id: 'w-inner',
94
+ type: '@apostrophecms/layout-column',
95
+ metaType: 'widget',
96
+ desktop: {
97
+ colstart: 3,
98
+ colspan: 2
99
+ },
100
+ tablet: { show: false },
101
+ mobile: { show: true }
102
+ };
103
+
104
+ const result = migrate(widget, 'main.items.0.content.items.0');
105
+
106
+ assert.equal(result.$set['main.items.0.content.items.0.colstart'], 3);
107
+ assert.equal(result.$set['main.items.0.content.items.0.colspan'], 2);
108
+ assert.equal(result.$set['main.items.0.content.items.0.showTablet'], false);
109
+ assert.equal(result.$set['main.items.0.content.items.0.showMobile'], true);
110
+ });
111
+
112
+ it('returns null when desktop property is absent', function () {
113
+ const widget = {
114
+ _id: 'w1',
115
+ type: '@apostrophecms/layout-column',
116
+ metaType: 'widget',
117
+ colstart: 1,
118
+ colspan: 6,
119
+ rowstart: 1,
120
+ rowspan: 1,
121
+ order: 0,
122
+ showTablet: true,
123
+ showMobile: true
124
+ };
125
+
126
+ const result = migrate(widget, 'main.items.0');
127
+ assert.equal(result, null);
128
+ });
129
+
130
+ it('returns update when desktop property is present', function () {
131
+ const widget = {
132
+ _id: 'w1',
133
+ type: '@apostrophecms/layout-column',
134
+ metaType: 'widget',
135
+ colstart: null,
136
+ desktop: {
137
+ colstart: 5,
138
+ colspan: 3
139
+ },
140
+ tablet: { show: true },
141
+ mobile: { show: false }
142
+ };
143
+
144
+ const result = migrate(widget, 'main.items.0');
145
+
146
+ assert.equal(result.$set['main.items.0.colstart'], 5);
147
+ assert.equal(result.$set['main.items.0.colspan'], 3);
148
+ assert.equal(result.$set['main.items.0.showMobile'], false);
149
+ });
150
+ });
151
+
152
+ describe('migration registration (integration)', function () {
153
+ let apos;
154
+
155
+ before(async function () {
156
+ apos = await t.create({
157
+ root: module,
158
+ modules: {
159
+ 'test-column-widget': {
160
+ extend: '@apostrophecms/layout-column-widget',
161
+ options: {
162
+ label: 'Test Column'
163
+ }
164
+ },
165
+ 'test-layout-widget': {
166
+ extend: '@apostrophecms/layout-widget',
167
+ options: {
168
+ label: 'Test Layout'
169
+ },
170
+ fields: {
171
+ add: {
172
+ columns: {
173
+ type: 'area',
174
+ options: {
175
+ widgets: {
176
+ 'test-column': {}
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
182
+ },
183
+ 'default-page': {
184
+ extend: '@apostrophecms/page-type',
185
+ options: {
186
+ label: 'Default'
187
+ },
188
+ fields: {
189
+ add: {
190
+ main: {
191
+ type: 'area',
192
+ options: {
193
+ widgets: {
194
+ '@apostrophecms/layout': {},
195
+ 'test-layout': {}
196
+ }
197
+ }
198
+ }
199
+ }
200
+ }
201
+ },
202
+ '@apostrophecms/page': {
203
+ options: {
204
+ types: [
205
+ {
206
+ name: 'default-page',
207
+ label: 'Default'
208
+ }
209
+ ]
210
+ }
211
+ }
212
+ }
213
+ });
214
+ });
215
+
216
+ after(async function () {
217
+ await t.destroy(apos);
218
+ });
219
+
220
+ it('registers migration for base column widget', async function () {
221
+ const record = await apos.migration.db.findOne({
222
+ _id: '@apostrophecms/layout-column-widget:flatten-column-schema'
223
+ });
224
+ assert(record, 'Base column migration should be registered');
225
+ });
226
+
227
+ it('registers migration for extended column widget', async function () {
228
+ const record = await apos.migration.db.findOne({
229
+ _id: 'test-column-widget:flatten-column-schema'
230
+ });
231
+ assert(record, 'Extended column migration should be registered');
232
+ });
233
+
234
+ it('both migrations are independent records', async function () {
235
+ const base = await apos.migration.db.findOne({
236
+ _id: '@apostrophecms/layout-column-widget:flatten-column-schema'
237
+ });
238
+ const extended = await apos.migration.db.findOne({
239
+ _id: 'test-column-widget:flatten-column-schema'
240
+ });
241
+ assert.notEqual(base._id, extended._id);
242
+ });
243
+
244
+ it('returns $set for extended column type', function () {
245
+ const migrate = apos.modules['test-column-widget'].migrateColumnWidget;
246
+ const widget = {
247
+ _id: 'w1',
248
+ type: 'test-column',
249
+ metaType: 'widget',
250
+ desktop: {
251
+ colstart: 2,
252
+ colspan: 4,
253
+ rowstart: 1,
254
+ rowspan: 2,
255
+ order: 1,
256
+ justify: 'start',
257
+ align: 'center'
258
+ },
259
+ tablet: {
260
+ show: true,
261
+ order: 3
262
+ },
263
+ mobile: {
264
+ show: false,
265
+ order: 4
266
+ }
267
+ };
268
+
269
+ const result = migrate(widget, 'main.items.0');
270
+
271
+ assert.deepEqual(result, {
272
+ $set: {
273
+ 'main.items.0.colstart': 2,
274
+ 'main.items.0.colspan': 4,
275
+ 'main.items.0.rowstart': 1,
276
+ 'main.items.0.rowspan': 2,
277
+ 'main.items.0.order': 1,
278
+ 'main.items.0.justify': 'start',
279
+ 'main.items.0.align': 'center',
280
+ 'main.items.0.showTablet': true,
281
+ 'main.items.0.showMobile': false
282
+ }
283
+ });
284
+ });
285
+ });
286
+
287
+ describe('migration execution (database)', function () {
288
+ let apos;
289
+
290
+ const modules = {
291
+ 'test-column-widget': {
292
+ extend: '@apostrophecms/layout-column-widget',
293
+ options: {
294
+ label: 'Test Column'
295
+ }
296
+ },
297
+ 'test-layout-widget': {
298
+ extend: '@apostrophecms/layout-widget',
299
+ options: {
300
+ label: 'Test Layout'
301
+ },
302
+ fields: {
303
+ add: {
304
+ columns: {
305
+ type: 'area',
306
+ options: {
307
+ widgets: {
308
+ 'test-column': {}
309
+ }
310
+ }
311
+ }
312
+ }
313
+ }
314
+ },
315
+ 'default-page': {
316
+ extend: '@apostrophecms/page-type',
317
+ options: {
318
+ label: 'Default'
319
+ },
320
+ fields: {
321
+ add: {
322
+ main: {
323
+ type: 'area',
324
+ options: {
325
+ widgets: {
326
+ '@apostrophecms/layout': {},
327
+ 'test-layout': {}
328
+ }
329
+ }
330
+ }
331
+ }
332
+ }
333
+ },
334
+ '@apostrophecms/page': {
335
+ options: {
336
+ types: [
337
+ {
338
+ name: 'default-page',
339
+ label: 'Default'
340
+ }
341
+ ]
342
+ }
343
+ }
344
+ };
345
+
346
+ afterEach(async function () {
347
+ await t.destroy(apos);
348
+ });
349
+
350
+ async function setupAndRunMigration(oldDocs) {
351
+ // Boot the instance (migrations run automatically on boot)
352
+ apos = await t.create({
353
+ root: module,
354
+ modules
355
+ });
356
+
357
+ // Remove our migration records so they will run again
358
+ await apos.migration.db.deleteMany({
359
+ _id: {
360
+ $in: [
361
+ '@apostrophecms/layout-column-widget:flatten-column-schema',
362
+ 'test-column-widget:flatten-column-schema'
363
+ ]
364
+ }
365
+ });
366
+
367
+ // Insert old-shaped data directly into the DB (bypass managers)
368
+ for (const doc of oldDocs) {
369
+ await apos.doc.db.insertOne(doc);
370
+ }
371
+
372
+ // Verify the old data is in the DB as expected
373
+ for (const doc of oldDocs) {
374
+ const found = await apos.doc.db.findOne({ _id: doc._id });
375
+ assert(found, `Doc ${doc._id} should exist in DB`);
376
+ }
377
+
378
+ // Run only the flatten-column-schema migrations directly,
379
+ // avoiding the full migrate() which skips on a new site
380
+ const migrations = apos.migration.migrations.filter(
381
+ m => m.name.endsWith(':flatten-column-schema')
382
+ );
383
+ for (const migration of migrations) {
384
+ await apos.migration.runOne(migration);
385
+ }
386
+
387
+ // Return fetched docs for assertions
388
+ const results = {};
389
+ for (const doc of oldDocs) {
390
+ results[doc._id] = await apos.doc.db.findOne({ _id: doc._id });
391
+ }
392
+ return results;
393
+ }
394
+
395
+ it('migrates base column widget in DB', async function () {
396
+ const results = await setupAndRunMigration([
397
+ {
398
+ _id: 'test-base-col:en:published',
399
+ aposLocale: 'en:published',
400
+ type: 'default-page',
401
+ slug: '/test-base-col',
402
+ main: {
403
+ _id: 'area1',
404
+ metaType: 'area',
405
+ items: [
406
+ {
407
+ _id: 'w1',
408
+ type: '@apostrophecms/layout-column',
409
+ metaType: 'widget',
410
+ desktop: {
411
+ colstart: 1,
412
+ colspan: 6,
413
+ rowstart: 1,
414
+ rowspan: 1,
415
+ order: 0,
416
+ justify: 'center',
417
+ align: 'start'
418
+ },
419
+ tablet: {
420
+ show: false,
421
+ order: 2
422
+ },
423
+ mobile: {
424
+ show: true,
425
+ order: 3
426
+ }
427
+ }
428
+ ]
429
+ }
430
+ }
431
+ ]);
432
+
433
+ const widget = results['test-base-col:en:published'].main.items[0];
434
+ assert.equal(widget.colstart, 1);
435
+ assert.equal(widget.colspan, 6);
436
+ assert.equal(widget.rowstart, 1);
437
+ assert.equal(widget.rowspan, 1);
438
+ assert.equal(widget.order, 0);
439
+ assert.equal(widget.justify, 'center');
440
+ assert.equal(widget.align, 'start');
441
+ assert.equal(widget.showTablet, false);
442
+ assert.equal(widget.showMobile, true);
443
+ assert(widget.desktop);
444
+ });
445
+
446
+ it('migrates extended column widget in DB', async function () {
447
+ const results = await setupAndRunMigration([
448
+ {
449
+ _id: 'test-ext-col:en:published',
450
+ aposLocale: 'en:published',
451
+ type: 'default-page',
452
+ slug: '/test-ext-col',
453
+ main: {
454
+ _id: 'area1',
455
+ metaType: 'area',
456
+ items: [
457
+ {
458
+ _id: 'w1',
459
+ type: 'test-column',
460
+ metaType: 'widget',
461
+ desktop: {
462
+ colstart: 2,
463
+ colspan: 4,
464
+ rowstart: 1,
465
+ rowspan: 2,
466
+ order: 1,
467
+ justify: 'start',
468
+ align: 'center'
469
+ },
470
+ tablet: {
471
+ show: true,
472
+ order: 3
473
+ },
474
+ mobile: {
475
+ show: false,
476
+ order: 4
477
+ }
478
+ }
479
+ ]
480
+ }
481
+ }
482
+ ]);
483
+
484
+ const widget = results['test-ext-col:en:published'].main.items[0];
485
+ assert.equal(widget.colstart, 2);
486
+ assert.equal(widget.colspan, 4);
487
+ assert.equal(widget.rowstart, 1);
488
+ assert.equal(widget.rowspan, 2);
489
+ assert.equal(widget.order, 1);
490
+ assert.equal(widget.justify, 'start');
491
+ assert.equal(widget.align, 'center');
492
+ assert.equal(widget.showTablet, true);
493
+ assert.equal(widget.showMobile, false);
494
+ assert(widget.desktop);
495
+ });
496
+
497
+ it('applies defaults for missing optional fields in DB', async function () {
498
+ const results = await setupAndRunMigration([
499
+ {
500
+ _id: 'test-defaults:en:published',
501
+ aposLocale: 'en:published',
502
+ type: 'default-page',
503
+ slug: '/test-defaults',
504
+ main: {
505
+ _id: 'area1',
506
+ metaType: 'area',
507
+ items: [
508
+ {
509
+ _id: 'w1',
510
+ type: '@apostrophecms/layout-column',
511
+ metaType: 'widget',
512
+ desktop: {
513
+ colstart: 2,
514
+ colspan: 4
515
+ },
516
+ tablet: {},
517
+ mobile: {}
518
+ }
519
+ ]
520
+ }
521
+ }
522
+ ]);
523
+
524
+ const widget = results['test-defaults:en:published'].main.items[0];
525
+ assert.equal(widget.colstart, 2);
526
+ assert.equal(widget.colspan, 4);
527
+ assert.equal(widget.rowstart, 1);
528
+ assert.equal(widget.rowspan, 1);
529
+ assert.equal(widget.order, null);
530
+ assert.equal(widget.justify, null);
531
+ assert.equal(widget.align, null);
532
+ assert.equal(widget.showTablet, true);
533
+ assert.equal(widget.showMobile, true);
534
+ assert(widget.desktop);
535
+ });
536
+
537
+ it('does not touch non-column widgets in DB', async function () {
538
+ const results = await setupAndRunMigration([
539
+ {
540
+ _id: 'test-non-col:en:published',
541
+ aposLocale: 'en:published',
542
+ type: 'default-page',
543
+ slug: '/test-non-col',
544
+ main: {
545
+ _id: 'area1',
546
+ metaType: 'area',
547
+ items: [
548
+ {
549
+ _id: 'w1',
550
+ type: '@apostrophecms/layout-column',
551
+ metaType: 'widget',
552
+ desktop: {
553
+ colstart: 1,
554
+ colspan: 6
555
+ },
556
+ tablet: {},
557
+ mobile: {}
558
+ },
559
+ {
560
+ _id: 'w2',
561
+ type: '@apostrophecms/rich-text',
562
+ metaType: 'widget',
563
+ desktop: { something: 'untouched' }
564
+ }
565
+ ]
566
+ }
567
+ }
568
+ ]);
569
+
570
+ const richText = results['test-non-col:en:published'].main.items[1];
571
+ assert.deepEqual(richText.desktop, { something: 'untouched' });
572
+ });
573
+
574
+ it('migrates deeply nested areas in DB', async function () {
575
+ const results = await setupAndRunMigration([
576
+ {
577
+ _id: 'test-nested:en:published',
578
+ aposLocale: 'en:published',
579
+ type: 'default-page',
580
+ slug: '/test-nested',
581
+ main: {
582
+ _id: 'area1',
583
+ metaType: 'area',
584
+ items: [
585
+ {
586
+ _id: 'w-outer',
587
+ type: '@apostrophecms/some-widget',
588
+ metaType: 'widget',
589
+ content: {
590
+ _id: 'area2',
591
+ metaType: 'area',
592
+ items: [
593
+ {
594
+ _id: 'w-inner',
595
+ type: '@apostrophecms/layout-column',
596
+ metaType: 'widget',
597
+ desktop: {
598
+ colstart: 3,
599
+ colspan: 2
600
+ },
601
+ tablet: { show: false },
602
+ mobile: { show: true }
603
+ }
604
+ ]
605
+ }
606
+ }
607
+ ]
608
+ }
609
+ }
610
+ ]);
611
+
612
+ const inner = results['test-nested:en:published'].main.items[0].content.items[0];
613
+ assert.equal(inner.colstart, 3);
614
+ assert.equal(inner.colspan, 2);
615
+ assert.equal(inner.showTablet, false);
616
+ assert.equal(inner.showMobile, true);
617
+ assert(inner.desktop);
618
+ });
619
+
620
+ it('migrates both base and extended column widgets in the same doc', async function () {
621
+ const results = await setupAndRunMigration([
622
+ {
623
+ _id: 'test-both:en:published',
624
+ aposLocale: 'en:published',
625
+ type: 'default-page',
626
+ slug: '/test-both',
627
+ main: {
628
+ _id: 'area1',
629
+ metaType: 'area',
630
+ items: [
631
+ {
632
+ _id: 'w-base',
633
+ type: '@apostrophecms/layout-column',
634
+ metaType: 'widget',
635
+ desktop: {
636
+ colstart: 1,
637
+ colspan: 6
638
+ },
639
+ tablet: { show: false },
640
+ mobile: { show: true }
641
+ },
642
+ {
643
+ _id: 'w-ext',
644
+ type: 'test-column',
645
+ metaType: 'widget',
646
+ desktop: {
647
+ colstart: 7,
648
+ colspan: 6
649
+ },
650
+ tablet: { show: true },
651
+ mobile: { show: false }
652
+ }
653
+ ]
654
+ }
655
+ }
656
+ ]);
657
+
658
+ const baseWidget = results['test-both:en:published'].main.items[0];
659
+ assert.equal(baseWidget.colstart, 1);
660
+ assert.equal(baseWidget.colspan, 6);
661
+ assert.equal(baseWidget.showTablet, false);
662
+ assert.equal(baseWidget.showMobile, true);
663
+ assert(baseWidget.desktop);
664
+
665
+ const extWidget = results['test-both:en:published'].main.items[1];
666
+ assert.equal(extWidget.colstart, 7);
667
+ assert.equal(extWidget.colspan, 6);
668
+ assert.equal(extWidget.showTablet, true);
669
+ assert.equal(extWidget.showMobile, false);
670
+ assert(extWidget.desktop);
671
+ });
672
+
673
+ it('migrates deeply nested extended column in DB', async function () {
674
+ const results = await setupAndRunMigration([
675
+ {
676
+ _id: 'test-ext-nested:en:published',
677
+ aposLocale: 'en:published',
678
+ type: 'default-page',
679
+ slug: '/test-ext-nested',
680
+ main: {
681
+ _id: 'area1',
682
+ metaType: 'area',
683
+ items: [
684
+ {
685
+ _id: 'w-outer',
686
+ type: '@apostrophecms/some-widget',
687
+ metaType: 'widget',
688
+ content: {
689
+ _id: 'area2',
690
+ metaType: 'area',
691
+ items: [
692
+ {
693
+ _id: 'w-inner',
694
+ type: 'test-column',
695
+ metaType: 'widget',
696
+ desktop: {
697
+ colstart: 5,
698
+ colspan: 3
699
+ },
700
+ tablet: { show: false },
701
+ mobile: { show: true }
702
+ }
703
+ ]
704
+ }
705
+ }
706
+ ]
707
+ }
708
+ }
709
+ ]);
710
+
711
+ const inner = results['test-ext-nested:en:published'].main.items[0].content.items[0];
712
+ assert.equal(inner.colstart, 5);
713
+ assert.equal(inner.colspan, 3);
714
+ assert.equal(inner.showTablet, false);
715
+ assert.equal(inner.showMobile, true);
716
+ assert(inner.desktop);
717
+ });
718
+ });
719
+ });