apostrophe 3.59.0 → 3.60.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.
- package/CHANGELOG.md +38 -0
- package/modules/@apostrophecms/area/index.js +1 -0
- package/modules/@apostrophecms/area/ui/apos/components/AposAreaExpandedMenu.vue +1 -1
- package/modules/@apostrophecms/doc/index.js +11 -2
- package/modules/@apostrophecms/job/index.js +1 -0
- package/modules/@apostrophecms/module/index.js +10 -4
- package/modules/@apostrophecms/notification/index.js +4 -1
- package/modules/@apostrophecms/notification/ui/apos/components/AposNotification.vue +9 -0
- package/modules/@apostrophecms/page/index.js +83 -3
- package/modules/@apostrophecms/rich-text-widget/index.js +5 -0
- package/modules/@apostrophecms/rich-text-widget/ui/apos/components/AposRichTextWidgetEditor.vue +1 -0
- package/modules/@apostrophecms/schema/index.js +12 -13
- package/modules/@apostrophecms/schema/ui/apos/logic/AposArrayEditor.js +3 -3
- package/modules/@apostrophecms/template/index.js +35 -28
- package/package.json +1 -1
- package/test/external-front.js +43 -0
- package/test/pages-rest.js +15 -9
- package/test/published-pages.js +144 -0
- package/test/schemas.js +243 -33
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,43 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.60.0 (2023-11-29)
|
|
4
|
+
|
|
5
|
+
### Adds
|
|
6
|
+
|
|
7
|
+
* Add the possibility to add custom classes to notifications.
|
|
8
|
+
Setting the `apos-notification--hidden` class will hide the notification, which can be useful when we only care about the event carried by it.
|
|
9
|
+
* Give the possibility to add horizontal rules from the insert menu of the rich text editor with the following widget option: `insert: [ 'horizontalRule' ]`.
|
|
10
|
+
Improve also the UX to focus back the editor after inserting a horizontal rule or a table.
|
|
11
|
+
|
|
12
|
+
### Fixes
|
|
13
|
+
|
|
14
|
+
* The `render-widget` route now provides an `options` property on the widget, so that
|
|
15
|
+
schema-level options of the widget are available to the external front end when
|
|
16
|
+
rendering a newly added or edited widget in the editor. Note that when rendering a full page,
|
|
17
|
+
this information is already available on the parent area: `area.options.widgets[widget.type]`
|
|
18
|
+
* Pages inserted directly in the published mode are now given a
|
|
19
|
+
correct `lastPublishedAt` property, correcting several bugs relating
|
|
20
|
+
to the page tree.
|
|
21
|
+
* A migration has been added to introduce `lastPublishedAt` wherever
|
|
22
|
+
it is missing for existing pages.
|
|
23
|
+
* Fixed a bug that prevented page ranks from renumbering properly during "insert after" operations.
|
|
24
|
+
* Added a one-time migration to make existing page ranks unique among peers.
|
|
25
|
+
* Fixes conditional fields not being properly updated when switching items in array editor.
|
|
26
|
+
* The `beforeSend` event for pages and the loading of deferred widgets are now
|
|
27
|
+
handled in `renderPage` with the proper timing so that areas can be annotated
|
|
28
|
+
successfully for "external front" use.
|
|
29
|
+
* The external front now receives 100% of the serialization-friendly data that Nunjucks receives,
|
|
30
|
+
including the `home` property etc. Note that the responsibility to avoid passing any nonserializable
|
|
31
|
+
or excessively large data in `req.data` falls on the developer when choosing to use the
|
|
32
|
+
`apos-external-front` feature.
|
|
33
|
+
* Wraps the group label in the expanded preview menu component in `$t()` to allow translation
|
|
34
|
+
|
|
35
|
+
## 3.59.1 (2023-11-14)
|
|
36
|
+
|
|
37
|
+
### Fixes
|
|
38
|
+
|
|
39
|
+
* Fix `if` and `requiredIf` fields inside arrays. With regard to `if`, this is a hotfix for a regression introduced in 3.59.0.
|
|
40
|
+
|
|
3
41
|
## 3.59.0 (2023-11-03)
|
|
4
42
|
|
|
5
43
|
### Changes
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
:key="groupIndex"
|
|
17
17
|
class="apos-widget-group"
|
|
18
18
|
>
|
|
19
|
-
<h2 class="apos-widget-group__label" v-if="group.label">{{ group.label }}</h2>
|
|
19
|
+
<h2 class="apos-widget-group__label" v-if="group.label">{{ $t(group.label) }}</h2>
|
|
20
20
|
<div
|
|
21
21
|
:class="[
|
|
22
22
|
`apos-widget-group--${group.columns}-column${
|
|
@@ -239,13 +239,22 @@ module.exports = {
|
|
|
239
239
|
})) {
|
|
240
240
|
return;
|
|
241
241
|
}
|
|
242
|
+
const lastPublishedAt = doc.createdAt || new Date();
|
|
242
243
|
const draft = {
|
|
243
244
|
...doc,
|
|
244
245
|
_id: draftId,
|
|
245
246
|
aposLocale: draftLocale,
|
|
246
|
-
lastPublishedAt
|
|
247
|
+
lastPublishedAt
|
|
247
248
|
};
|
|
248
|
-
|
|
249
|
+
await manager.insertDraftOf(req, doc, draft, options);
|
|
250
|
+
// Published doc must know it is published, otherwise various bugs ensue
|
|
251
|
+
return self.apos.doc.db.updateOne({
|
|
252
|
+
_id: doc._id
|
|
253
|
+
}, {
|
|
254
|
+
$set: {
|
|
255
|
+
lastPublishedAt
|
|
256
|
+
}
|
|
257
|
+
});
|
|
249
258
|
}
|
|
250
259
|
},
|
|
251
260
|
fixUniqueError: {
|
|
@@ -379,8 +379,8 @@ module.exports = {
|
|
|
379
379
|
return self.apos.template.renderStringForModule(req, s, data, self);
|
|
380
380
|
},
|
|
381
381
|
|
|
382
|
-
// TIP: you
|
|
383
|
-
//
|
|
382
|
+
// TIP: more often you will want `self.sendPage`, which also sends the response
|
|
383
|
+
// to the browser.
|
|
384
384
|
//
|
|
385
385
|
// This method generates a complete HTML page for transmission to the
|
|
386
386
|
// browser. Returns HTML markup ready to send (but `self.sendPage` is
|
|
@@ -419,11 +419,19 @@ module.exports = {
|
|
|
419
419
|
//
|
|
420
420
|
// This method is async in 3.x and must be awaited.
|
|
421
421
|
//
|
|
422
|
+
// If the external front feature is in use for the request, then
|
|
423
|
+
// self.apos.template.annotateDataForExternalFront and
|
|
424
|
+
// self.apos.template.pruneDataForExternalFront are called
|
|
425
|
+
// and the data is returned, in place of normal Nunjucks rendering.
|
|
426
|
+
//
|
|
422
427
|
// No longer deprecated because it is a useful override point
|
|
423
428
|
// for this part of the behavior of sendPage.
|
|
424
429
|
|
|
425
430
|
async renderPage(req, template, data) {
|
|
431
|
+
await self.apos.page.emit('beforeSend', req);
|
|
432
|
+
await self.apos.area.loadDeferredWidgets(req);
|
|
426
433
|
if (req.aposExternalFront) {
|
|
434
|
+
data = self.apos.template.getRenderDataArgs(req, data, self);
|
|
427
435
|
await self.apos.template.annotateDataForExternalFront(req, template, data);
|
|
428
436
|
self.apos.template.pruneDataForExternalFront(req, template, data);
|
|
429
437
|
// Reply with JSON
|
|
@@ -484,8 +492,6 @@ module.exports = {
|
|
|
484
492
|
span.setAttribute(telemetry.Attributes.TEMPLATE, template);
|
|
485
493
|
|
|
486
494
|
try {
|
|
487
|
-
await self.apos.page.emit('beforeSend', req);
|
|
488
|
-
await self.apos.area.loadDeferredWidgets(req);
|
|
489
495
|
const result = await self.renderPage(req, template, data);
|
|
490
496
|
req.res.send(result);
|
|
491
497
|
span.setStatus({ code: telemetry.api.SpanStatusCode.OK });
|
|
@@ -99,6 +99,7 @@ module.exports = {
|
|
|
99
99
|
], 'info');
|
|
100
100
|
const icon = self.apos.launder.string(req.body.icon);
|
|
101
101
|
const message = self.apos.launder.string(req.body.message);
|
|
102
|
+
const classes = self.apos.launder.strings(req.body.classes);
|
|
102
103
|
const interpolate = launderInterpolate(req.body.interpolate);
|
|
103
104
|
const dismiss = self.apos.launder.integer(req.body.dismiss);
|
|
104
105
|
let buttons = req.body.buttons;
|
|
@@ -118,6 +119,7 @@ module.exports = {
|
|
|
118
119
|
}));
|
|
119
120
|
}
|
|
120
121
|
return self.trigger(req, message, {
|
|
122
|
+
classes,
|
|
121
123
|
interpolate,
|
|
122
124
|
dismiss,
|
|
123
125
|
icon,
|
|
@@ -280,7 +282,8 @@ module.exports = {
|
|
|
280
282
|
localize: has(req.body, 'localize')
|
|
281
283
|
? self.apos.launder.boolean(req.body.localize) : true,
|
|
282
284
|
job: options.job || null,
|
|
283
|
-
event: options.event
|
|
285
|
+
event: options.event,
|
|
286
|
+
classes: options.classes || null
|
|
284
287
|
};
|
|
285
288
|
|
|
286
289
|
if (copiedOptions.dismiss === true) {
|
|
@@ -79,6 +79,11 @@ export default {
|
|
|
79
79
|
computed: {
|
|
80
80
|
classList() {
|
|
81
81
|
const classes = [ 'apos-notification' ];
|
|
82
|
+
|
|
83
|
+
if (Array.isArray(this.notification.classes) && this.notification.classes.length) {
|
|
84
|
+
classes.push(...this.notification.classes);
|
|
85
|
+
}
|
|
86
|
+
|
|
82
87
|
if (this.notification.type && this.notification.type !== 'none') {
|
|
83
88
|
classes.push(`apos-notification--${this.notification.type}`);
|
|
84
89
|
}
|
|
@@ -223,6 +228,10 @@ export default {
|
|
|
223
228
|
}
|
|
224
229
|
}
|
|
225
230
|
|
|
231
|
+
.apos-notification--hidden {
|
|
232
|
+
display: none;
|
|
233
|
+
}
|
|
234
|
+
|
|
226
235
|
.apos-notification--long {
|
|
227
236
|
border-radius: 10px;
|
|
228
237
|
}
|
|
@@ -101,6 +101,8 @@ module.exports = {
|
|
|
101
101
|
self.addLegacyMigrations();
|
|
102
102
|
self.addMisreplicatedParkedPagesMigration();
|
|
103
103
|
self.addDuplicateParkedPagesMigration();
|
|
104
|
+
self.apos.migration.add('deduplicateRanks2', self.deduplicateRanks2Migration);
|
|
105
|
+
self.apos.migration.add('missingLastPublishedAt', self.missingLastPublishedAtMigration);
|
|
104
106
|
await self.createIndexes();
|
|
105
107
|
},
|
|
106
108
|
restApiRoutes(self) {
|
|
@@ -850,8 +852,8 @@ database.`);
|
|
|
850
852
|
const query = self.find(req, criteria, options).permission('edit').archived(null);
|
|
851
853
|
return query;
|
|
852
854
|
},
|
|
853
|
-
// Insert a page. `targetId` must be an existing page id,
|
|
854
|
-
// `position` may be `before`, `inside` or `after`. Alternatively
|
|
855
|
+
// Insert a page. `targetId` must be an existing page id, `_archive` or
|
|
856
|
+
// `_home`, and `position` may be `before`, `inside` or `after`. Alternatively
|
|
855
857
|
// `position` may be a zero-based offset for the new child
|
|
856
858
|
// of `targetId` (note that the `rank` property of sibling pages
|
|
857
859
|
// is not strictly ascending, so use an array index into `_children` to
|
|
@@ -930,7 +932,7 @@ database.`);
|
|
|
930
932
|
return self.insert(req, target._id, 'before', page, options);
|
|
931
933
|
}
|
|
932
934
|
page.rank = target.rank + 1;
|
|
933
|
-
const index = peers.findIndex(peer => peer.
|
|
935
|
+
const index = peers.findIndex(peer => peer._id === target._id);
|
|
934
936
|
if (index !== -1) {
|
|
935
937
|
pushed = peers.slice(index + 1).map(peer => peer._id);
|
|
936
938
|
}
|
|
@@ -2503,6 +2505,84 @@ database.`);
|
|
|
2503
2505
|
}
|
|
2504
2506
|
});
|
|
2505
2507
|
},
|
|
2508
|
+
async deduplicateRanks2Migration() {
|
|
2509
|
+
for (const locale of Object.keys(self.apos.i18n.locales)) {
|
|
2510
|
+
for (const mode of [ 'previous', 'draft', 'published' ]) {
|
|
2511
|
+
const pages = await self.apos.doc.db.find({
|
|
2512
|
+
slug: /^\//,
|
|
2513
|
+
aposLocale: `${locale}:${mode}`
|
|
2514
|
+
}, {
|
|
2515
|
+
path: 1,
|
|
2516
|
+
rank: 1,
|
|
2517
|
+
slug: 1
|
|
2518
|
+
}).toArray();
|
|
2519
|
+
const pagesByPath = new Map();
|
|
2520
|
+
for (const page of pages) {
|
|
2521
|
+
page._children = [];
|
|
2522
|
+
pagesByPath.set(page.path, page);
|
|
2523
|
+
}
|
|
2524
|
+
for (const page of pages) {
|
|
2525
|
+
if (page.level === 0) {
|
|
2526
|
+
// Home page has no parent
|
|
2527
|
+
continue;
|
|
2528
|
+
}
|
|
2529
|
+
const parentPath = self.getParentPath(page);
|
|
2530
|
+
const parent = pagesByPath.get(parentPath);
|
|
2531
|
+
if (!parent) {
|
|
2532
|
+
self.apos.util.error(`Warning: page ${page._id} has no parent in the tree`);
|
|
2533
|
+
continue;
|
|
2534
|
+
}
|
|
2535
|
+
parent._children.push(page);
|
|
2536
|
+
}
|
|
2537
|
+
for (const page of pages) {
|
|
2538
|
+
const children = page._children;
|
|
2539
|
+
children.sort((a, b) => a.rank - b.rank);
|
|
2540
|
+
let lastRank = null;
|
|
2541
|
+
let bad = false;
|
|
2542
|
+
for (const child of children) {
|
|
2543
|
+
if (child.rank === lastRank) {
|
|
2544
|
+
bad = true;
|
|
2545
|
+
break;
|
|
2546
|
+
}
|
|
2547
|
+
lastRank = child.rank;
|
|
2548
|
+
}
|
|
2549
|
+
if (bad) {
|
|
2550
|
+
self.apos.util.warn(`Fixing ranks for children of ${page.slug} in ${page.aposLocale}`);
|
|
2551
|
+
for (let i = 0; (i < children.length); i++) {
|
|
2552
|
+
await self.apos.doc.db.updateOne({
|
|
2553
|
+
_id: children[i]._id
|
|
2554
|
+
}, {
|
|
2555
|
+
$set: {
|
|
2556
|
+
rank: i
|
|
2557
|
+
}
|
|
2558
|
+
});
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
}
|
|
2564
|
+
},
|
|
2565
|
+
missingLastPublishedAtMigration() {
|
|
2566
|
+
return self.apos.migration.eachDoc({
|
|
2567
|
+
aposMode: 'published',
|
|
2568
|
+
lastPublishedAt: null
|
|
2569
|
+
}, async doc => {
|
|
2570
|
+
const draft = await self.apos.doc.db.findOne({
|
|
2571
|
+
_id: doc._id.replace(':published', ':draft')
|
|
2572
|
+
});
|
|
2573
|
+
if (!draft) {
|
|
2574
|
+
self.apos.util.error(`Warning: published document has no matching draft: ${doc._id}`);
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
await self.apos.doc.db.updateOne({
|
|
2578
|
+
_id: doc._id
|
|
2579
|
+
}, {
|
|
2580
|
+
$set: {
|
|
2581
|
+
lastPublishedAt: draft.lastPublishedAt
|
|
2582
|
+
}
|
|
2583
|
+
});
|
|
2584
|
+
});
|
|
2585
|
+
},
|
|
2506
2586
|
async inferLastTargetIdAndPosition(doc) {
|
|
2507
2587
|
const parentPath = self.getParentPath(doc);
|
|
2508
2588
|
const parentAposDocId = parentPath.split('/').pop();
|
|
@@ -220,6 +220,11 @@ module.exports = {
|
|
|
220
220
|
label: 'apostrophe:image',
|
|
221
221
|
description: 'apostrophe:imageDescription',
|
|
222
222
|
component: 'AposImageControlDialog'
|
|
223
|
+
},
|
|
224
|
+
horizontalRule: {
|
|
225
|
+
icon: 'minus-icon',
|
|
226
|
+
label: 'apostrophe:richTextHorizontalRule',
|
|
227
|
+
action: 'setHorizontalRule'
|
|
223
228
|
}
|
|
224
229
|
},
|
|
225
230
|
// Additional properties used in executing tiptap commands
|
|
@@ -498,10 +498,10 @@ module.exports = {
|
|
|
498
498
|
}
|
|
499
499
|
continue;
|
|
500
500
|
} else if (val.$ne) {
|
|
501
|
-
|
|
502
|
-
if (val.$ne == destinationKey) {
|
|
501
|
+
if (val.$ne === destinationKey) {
|
|
503
502
|
return false;
|
|
504
503
|
}
|
|
504
|
+
continue;
|
|
505
505
|
}
|
|
506
506
|
|
|
507
507
|
// Handle external conditions:
|
|
@@ -526,23 +526,22 @@ module.exports = {
|
|
|
526
526
|
continue;
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
529
|
+
// test with Object.prototype for the case val.min === 0
|
|
530
|
+
if (Object.hasOwn(val, 'min') || Object.hasOwn(val, 'max')) {
|
|
531
|
+
if (destinationKey < val.min) {
|
|
532
|
+
return false;
|
|
533
|
+
}
|
|
534
|
+
if (destinationKey > val.max) {
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
continue;
|
|
534
538
|
}
|
|
535
539
|
|
|
536
540
|
if (conditionalFields?.[key] === false) {
|
|
537
541
|
return false;
|
|
538
542
|
}
|
|
539
543
|
|
|
540
|
-
if (
|
|
541
|
-
return false;
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
// eslint-disable-next-line eqeqeq
|
|
545
|
-
if ((typeof val === 'string' || typeof val === 'number') && destinationKey != val) {
|
|
544
|
+
if (destinationKey !== val) {
|
|
546
545
|
return false;
|
|
547
546
|
}
|
|
548
547
|
}
|
|
@@ -138,9 +138,8 @@ export default {
|
|
|
138
138
|
async mounted() {
|
|
139
139
|
this.modal.active = true;
|
|
140
140
|
await this.evaluateExternalConditions();
|
|
141
|
-
this.evaluateConditions();
|
|
142
141
|
if (this.next.length) {
|
|
143
|
-
this.select(this.next[0]._id);
|
|
142
|
+
await this.select(this.next[0]._id);
|
|
144
143
|
}
|
|
145
144
|
if (this.serverError && this.serverError.data && this.serverError.data.errors) {
|
|
146
145
|
const first = this.serverError.data.errors[0];
|
|
@@ -168,6 +167,7 @@ export default {
|
|
|
168
167
|
hasErrors: false,
|
|
169
168
|
data: this.next.find(item => item._id === _id)
|
|
170
169
|
};
|
|
170
|
+
this.evaluateConditions();
|
|
171
171
|
this.triggerValidation = false;
|
|
172
172
|
}
|
|
173
173
|
},
|
|
@@ -193,7 +193,7 @@ export default {
|
|
|
193
193
|
const item = this.newInstance();
|
|
194
194
|
item._id = cuid();
|
|
195
195
|
this.next.push(item);
|
|
196
|
-
this.select(item._id);
|
|
196
|
+
await this.select(item._id);
|
|
197
197
|
this.updateMinMax();
|
|
198
198
|
}
|
|
199
199
|
},
|
|
@@ -276,35 +276,15 @@ module.exports = {
|
|
|
276
276
|
},
|
|
277
277
|
|
|
278
278
|
// Implementation detail of `renderBody` responsible for
|
|
279
|
-
// creating the input object passed to
|
|
280
|
-
//
|
|
281
|
-
// `apos`
|
|
279
|
+
// creating the input object passed to the template engine e.g. Nunjucks.
|
|
280
|
+
// Includes both serializable data like `user` and non-JSON-friendly
|
|
281
|
+
// properties like `apos`, `getOptions()` and `__req`. If you are only
|
|
282
|
+
// interested in serializable data use `getRenderDataArgs`
|
|
282
283
|
|
|
283
284
|
getRenderArgs(req, data, module) {
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
_.defaults(merged, data);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const args = {};
|
|
291
|
-
|
|
292
|
-
args.data = merged;
|
|
293
|
-
|
|
294
|
-
if (req.data) {
|
|
295
|
-
_.defaults(merged, req.data);
|
|
296
|
-
}
|
|
297
|
-
_.defaults(merged, {
|
|
298
|
-
user: req.user,
|
|
299
|
-
permissions: (req.user && req.user._permissions) || {}
|
|
300
|
-
});
|
|
301
|
-
|
|
302
|
-
if (module.templateData) {
|
|
303
|
-
_.defaults(merged, module.templateData);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
args.data.locale = args.data.locale || req.locale;
|
|
307
|
-
|
|
285
|
+
const args = {
|
|
286
|
+
data: self.getRenderDataArgs(req, data, module)
|
|
287
|
+
};
|
|
308
288
|
args.apos = self.templateApos;
|
|
309
289
|
args.__t = req.t;
|
|
310
290
|
args.__ = key => {
|
|
@@ -328,6 +308,33 @@ module.exports = {
|
|
|
328
308
|
return args;
|
|
329
309
|
},
|
|
330
310
|
|
|
311
|
+
// Just the external front-compatible parts of `getRenderArgs` that
|
|
312
|
+
// go into `args.data` for Nunjucks, e.g. merging `req.data` and `data`, adding
|
|
313
|
+
// `req.user` as `user`, etc.
|
|
314
|
+
|
|
315
|
+
getRenderDataArgs(req, data, module) {
|
|
316
|
+
const merged = {};
|
|
317
|
+
|
|
318
|
+
if (data) {
|
|
319
|
+
_.defaults(merged, data);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (req.data) {
|
|
323
|
+
_.defaults(merged, req.data);
|
|
324
|
+
}
|
|
325
|
+
_.defaults(merged, {
|
|
326
|
+
user: req.user,
|
|
327
|
+
permissions: (req.user && req.user._permissions) || {}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
if (module.templateData) {
|
|
331
|
+
_.defaults(merged, module.templateData);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
merged.locale = merged.locale || req.locale;
|
|
335
|
+
return merged;
|
|
336
|
+
},
|
|
337
|
+
|
|
331
338
|
// Fetch a nunjucks environment in which `include`, `extends`, etc. search
|
|
332
339
|
// the views directories of the specified module and its ancestors.
|
|
333
340
|
// Typically you will call `self.render` on your module
|
|
@@ -897,7 +904,7 @@ module.exports = {
|
|
|
897
904
|
},
|
|
898
905
|
|
|
899
906
|
getDocsForExternalFront(req, template, data) {
|
|
900
|
-
return [ data.page, data.piece, ...(data.pieces || []) ].filter(doc => !!doc);
|
|
907
|
+
return [ data.home, ...(data.page?._ancestors || []), ...(data.page?._children || []), data.page, data.piece, ...(data.pieces || []) ].filter(doc => !!doc);
|
|
901
908
|
},
|
|
902
909
|
|
|
903
910
|
annotateDocForExternalFront(doc) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
let apos;
|
|
5
|
+
// Set env var so these tests work even if you have a dev key in your bashrc etc.
|
|
6
|
+
process.env.APOS_EXTERNAL_FRONT_KEY = 'this is a test external front key';
|
|
7
|
+
|
|
8
|
+
describe('External Front', function() {
|
|
9
|
+
|
|
10
|
+
this.timeout(t.timeout);
|
|
11
|
+
|
|
12
|
+
after(function() {
|
|
13
|
+
return t.destroy(apos);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('apostrophe should initialize normally', async function() {
|
|
17
|
+
apos = await t.create({
|
|
18
|
+
root: module
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
assert(apos.page.__meta.name === '@apostrophecms/page');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('fetch home with external front', async function() {
|
|
25
|
+
const data = await await apos.http.get('/', {
|
|
26
|
+
headers: {
|
|
27
|
+
'x-requested-with': 'AposExternalFront',
|
|
28
|
+
'apos-external-front-key': process.env.APOS_EXTERNAL_FRONT_KEY
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
assert.strictEqual(typeof data, 'object');
|
|
32
|
+
assert(data.page);
|
|
33
|
+
assert(data.home);
|
|
34
|
+
assert(data.page.slug === data.home.slug);
|
|
35
|
+
assert(data.page.slug === '/');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('fetch home normally', async function() {
|
|
39
|
+
const data = await await apos.http.get('/', {});
|
|
40
|
+
assert.strictEqual(typeof data, 'string');
|
|
41
|
+
assert(data.includes('Home Page Template'));
|
|
42
|
+
});
|
|
43
|
+
});
|
package/test/pages-rest.js
CHANGED
|
@@ -192,6 +192,7 @@ describe('Pages REST', function() {
|
|
|
192
192
|
});
|
|
193
193
|
|
|
194
194
|
it('should be able to use db to insert documents', async function() {
|
|
195
|
+
const lastPublishedAt = new Date();
|
|
195
196
|
const testItems = [
|
|
196
197
|
{
|
|
197
198
|
_id: 'parent:en:published',
|
|
@@ -202,7 +203,8 @@ describe('Pages REST', function() {
|
|
|
202
203
|
visibility: 'public',
|
|
203
204
|
path: `${homeId.replace(':en:published', '')}/parent`,
|
|
204
205
|
level: 1,
|
|
205
|
-
rank: 0
|
|
206
|
+
rank: 0,
|
|
207
|
+
lastPublishedAt
|
|
206
208
|
},
|
|
207
209
|
{
|
|
208
210
|
_id: 'child:en:published',
|
|
@@ -213,7 +215,8 @@ describe('Pages REST', function() {
|
|
|
213
215
|
visibility: 'public',
|
|
214
216
|
path: `${homeId.replace(':en:published', '')}/parent/child`,
|
|
215
217
|
level: 2,
|
|
216
|
-
rank: 0
|
|
218
|
+
rank: 0,
|
|
219
|
+
lastPublishedAt
|
|
217
220
|
},
|
|
218
221
|
{
|
|
219
222
|
_id: 'grandchild:en:published',
|
|
@@ -224,7 +227,8 @@ describe('Pages REST', function() {
|
|
|
224
227
|
visibility: 'public',
|
|
225
228
|
path: `${homeId.replace(':en:published', '')}/parent/child/grandchild`,
|
|
226
229
|
level: 3,
|
|
227
|
-
rank: 0
|
|
230
|
+
rank: 0,
|
|
231
|
+
lastPublishedAt
|
|
228
232
|
},
|
|
229
233
|
{
|
|
230
234
|
_id: 'sibling:en:published',
|
|
@@ -235,8 +239,8 @@ describe('Pages REST', function() {
|
|
|
235
239
|
visibility: 'public',
|
|
236
240
|
path: `${homeId.replace(':en:published', '')}/parent/sibling`,
|
|
237
241
|
level: 2,
|
|
238
|
-
rank: 1
|
|
239
|
-
|
|
242
|
+
rank: 1,
|
|
243
|
+
lastPublishedAt
|
|
240
244
|
},
|
|
241
245
|
{
|
|
242
246
|
_id: 'cousin:en:published',
|
|
@@ -247,7 +251,8 @@ describe('Pages REST', function() {
|
|
|
247
251
|
visibility: 'public',
|
|
248
252
|
path: `${homeId.replace(':en:published', '')}/parent/sibling/cousin`,
|
|
249
253
|
level: 3,
|
|
250
|
-
rank: 0
|
|
254
|
+
rank: 0,
|
|
255
|
+
lastPublishedAt
|
|
251
256
|
},
|
|
252
257
|
{
|
|
253
258
|
_id: 'another-parent:en:published',
|
|
@@ -258,7 +263,8 @@ describe('Pages REST', function() {
|
|
|
258
263
|
visibility: 'public',
|
|
259
264
|
path: `${homeId.replace(':en:published', '')}/another-parent`,
|
|
260
265
|
level: 1,
|
|
261
|
-
rank: 1
|
|
266
|
+
rank: 1,
|
|
267
|
+
lastPublishedAt
|
|
262
268
|
},
|
|
263
269
|
{
|
|
264
270
|
_id: 'neighbor:en:published',
|
|
@@ -269,7 +275,8 @@ describe('Pages REST', function() {
|
|
|
269
275
|
visibility: 'public',
|
|
270
276
|
path: `${homeId.replace(':en:published', '')}/neighbor`,
|
|
271
277
|
level: 1,
|
|
272
|
-
rank: 2
|
|
278
|
+
rank: 2,
|
|
279
|
+
lastPublishedAt
|
|
273
280
|
}
|
|
274
281
|
];
|
|
275
282
|
|
|
@@ -482,7 +489,6 @@ describe('Pages REST', function() {
|
|
|
482
489
|
},
|
|
483
490
|
jar
|
|
484
491
|
});
|
|
485
|
-
|
|
486
492
|
const cousin = await apos.http.get('/api/v1/@apostrophecms/page/cousin:en:published', { jar });
|
|
487
493
|
const sibling = await apos.http.get('/api/v1/@apostrophecms/page/sibling:en:published', { jar });
|
|
488
494
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
const t = require('../test-lib/test.js');
|
|
2
|
+
const assert = require('assert');
|
|
3
|
+
|
|
4
|
+
let apos;
|
|
5
|
+
|
|
6
|
+
describe('Pages', function() {
|
|
7
|
+
|
|
8
|
+
this.timeout(t.timeout);
|
|
9
|
+
|
|
10
|
+
after(function() {
|
|
11
|
+
return t.destroy(apos);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// EXISTENCE
|
|
15
|
+
|
|
16
|
+
it('should be a property of the apos object', async function() {
|
|
17
|
+
apos = await t.create({
|
|
18
|
+
root: module,
|
|
19
|
+
modules: {
|
|
20
|
+
'@apostrophecms/page': {
|
|
21
|
+
options: {
|
|
22
|
+
park: [],
|
|
23
|
+
types: [
|
|
24
|
+
{
|
|
25
|
+
name: '@apostrophecms/home-page',
|
|
26
|
+
label: 'Home'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'test-page',
|
|
30
|
+
label: 'Test Page'
|
|
31
|
+
}
|
|
32
|
+
],
|
|
33
|
+
publicApiProjection: {
|
|
34
|
+
title: 1,
|
|
35
|
+
_url: 1
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
'test-page': {
|
|
40
|
+
extend: '@apostrophecms/page-type'
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
assert(apos.page.__meta.name === '@apostrophecms/page');
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('inserting child pages with a published req should produce the correct draft/published pairs', async function() {
|
|
49
|
+
const req = apos.task.getAdminReq();
|
|
50
|
+
const manager = apos.doc.getManager('test-page');
|
|
51
|
+
|
|
52
|
+
for (let i = 1; (i <= 10); i++) {
|
|
53
|
+
const page = manager.newInstance();
|
|
54
|
+
page.title = `test-child-${i}`;
|
|
55
|
+
page.type = 'test-page';
|
|
56
|
+
const { _id } = await apos.page.insert(req, '_home', 'lastChild', page, {});
|
|
57
|
+
const fetchedPage = await apos.page.find(req, { _id }).toObject();
|
|
58
|
+
assert.strictEqual(fetchedPage.aposMode, 'published');
|
|
59
|
+
assert(fetchedPage);
|
|
60
|
+
const draftReq = req.clone({
|
|
61
|
+
mode: 'draft'
|
|
62
|
+
});
|
|
63
|
+
const draft = await apos.page.find(draftReq, {
|
|
64
|
+
aposDocId: fetchedPage.aposDocId
|
|
65
|
+
}).toObject();
|
|
66
|
+
assert(draft);
|
|
67
|
+
assert.strictEqual(draft.aposMode, 'draft');
|
|
68
|
+
assert(draft.level === fetchedPage.level);
|
|
69
|
+
assert(draft.lastPublishedAt);
|
|
70
|
+
assert(fetchedPage.lastPublishedAt);
|
|
71
|
+
assert(draft.lastPublishedAt.getTime() === fetchedPage.lastPublishedAt.getTime());
|
|
72
|
+
}
|
|
73
|
+
assert(checkRanks('en:published'));
|
|
74
|
+
assert(checkRanks('en:draft'));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('Can fix the ranks after intentionally messing them up', async function() {
|
|
78
|
+
for (let i = 5; (i <= 10); i++) {
|
|
79
|
+
await apos.doc.db.updateMany({
|
|
80
|
+
title: `test-child-${i}`
|
|
81
|
+
}, {
|
|
82
|
+
$set: {
|
|
83
|
+
rank: i - 2
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
await checkRanks('en:published');
|
|
89
|
+
assert(false);
|
|
90
|
+
} catch (e) {
|
|
91
|
+
// Good, supposed to fail
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
await checkRanks('en:draft');
|
|
95
|
+
assert(false);
|
|
96
|
+
} catch (e) {
|
|
97
|
+
// Good, supposed to fail
|
|
98
|
+
}
|
|
99
|
+
await apos.page.deduplicateRanks2Migration();
|
|
100
|
+
await checkRanks('en:published');
|
|
101
|
+
await checkRanks('en:draft');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('Can fix lastPublishedAt after intentionally messing it up', async function() {
|
|
105
|
+
let published = await apos.doc.db.findOne({
|
|
106
|
+
aposLocale: 'en:published',
|
|
107
|
+
slug: '/test-child-1'
|
|
108
|
+
});
|
|
109
|
+
assert(published.lastPublishedAt);
|
|
110
|
+
await apos.doc.db.updateOne({
|
|
111
|
+
_id: published._id
|
|
112
|
+
}, {
|
|
113
|
+
$unset: {
|
|
114
|
+
lastPublishedAt: 1
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
published = await apos.doc.db.findOne({
|
|
118
|
+
aposLocale: 'en:published',
|
|
119
|
+
slug: '/test-child-1'
|
|
120
|
+
});
|
|
121
|
+
assert(!published.lastPublishedAt);
|
|
122
|
+
await apos.page.missingLastPublishedAtMigration();
|
|
123
|
+
published = await apos.doc.db.findOne({
|
|
124
|
+
aposLocale: 'en:published',
|
|
125
|
+
slug: '/test-child-1'
|
|
126
|
+
});
|
|
127
|
+
assert(published.lastPublishedAt);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
async function checkRanks(aposLocale) {
|
|
132
|
+
const pages = await apos.doc.db.find({
|
|
133
|
+
level: 1,
|
|
134
|
+
aposLocale
|
|
135
|
+
}).project({
|
|
136
|
+
slug: 1,
|
|
137
|
+
rank: 1,
|
|
138
|
+
title: 1
|
|
139
|
+
}).toArray();
|
|
140
|
+
for (let i = 1; (i <= 10); i++) {
|
|
141
|
+
assert(pages.find(page => (page.rank === i - 1) && page.title === `test-child-${i}`));
|
|
142
|
+
}
|
|
143
|
+
assert(pages.find(page => (page.slug === '/archive') && (page.rank === 10)));
|
|
144
|
+
}
|
package/test/schemas.js
CHANGED
|
@@ -2381,7 +2381,9 @@ describe('Schemas', function() {
|
|
|
2381
2381
|
|
|
2382
2382
|
assert.deepEqual(actual, expected);
|
|
2383
2383
|
});
|
|
2384
|
+
});
|
|
2384
2385
|
|
|
2386
|
+
describe('field if|ifRequired', function () {
|
|
2385
2387
|
it('should enforce required property not equal match', async function() {
|
|
2386
2388
|
const req = apos.task.getReq();
|
|
2387
2389
|
const schema = apos.schema.compose({
|
|
@@ -2468,10 +2470,7 @@ describe('Schemas', function() {
|
|
|
2468
2470
|
subfield: false
|
|
2469
2471
|
}
|
|
2470
2472
|
}, output);
|
|
2471
|
-
|
|
2472
|
-
colors: true,
|
|
2473
|
-
depth: 1
|
|
2474
|
-
}));
|
|
2473
|
+
|
|
2475
2474
|
assert(!output.requiredProp);
|
|
2476
2475
|
});
|
|
2477
2476
|
|
|
@@ -2676,6 +2675,120 @@ describe('Schemas', function() {
|
|
|
2676
2675
|
}, 'requiredProp', 'required');
|
|
2677
2676
|
});
|
|
2678
2677
|
|
|
2678
|
+
it('should enforce required property number min', async function() {
|
|
2679
|
+
const req = apos.task.getReq();
|
|
2680
|
+
const schema = apos.schema.compose({
|
|
2681
|
+
addFields: [
|
|
2682
|
+
{
|
|
2683
|
+
name: 'prop1',
|
|
2684
|
+
type: 'integer',
|
|
2685
|
+
required: false
|
|
2686
|
+
},
|
|
2687
|
+
{
|
|
2688
|
+
name: 'prop2',
|
|
2689
|
+
type: 'string',
|
|
2690
|
+
required: true,
|
|
2691
|
+
if: {
|
|
2692
|
+
prop1: {
|
|
2693
|
+
min: 0,
|
|
2694
|
+
max: 10
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2697
|
+
}
|
|
2698
|
+
]
|
|
2699
|
+
});
|
|
2700
|
+
const output = {};
|
|
2701
|
+
await apos.schema.convert(req, schema, {
|
|
2702
|
+
prop1: -1,
|
|
2703
|
+
prop2: ''
|
|
2704
|
+
}, output);
|
|
2705
|
+
assert(output.prop2 === '');
|
|
2706
|
+
});
|
|
2707
|
+
|
|
2708
|
+
it('should error required property number min', async function() {
|
|
2709
|
+
const schema = apos.schema.compose({
|
|
2710
|
+
addFields: [
|
|
2711
|
+
{
|
|
2712
|
+
name: 'prop1',
|
|
2713
|
+
type: 'integer',
|
|
2714
|
+
required: false
|
|
2715
|
+
},
|
|
2716
|
+
{
|
|
2717
|
+
name: 'prop2',
|
|
2718
|
+
type: 'string',
|
|
2719
|
+
required: true,
|
|
2720
|
+
if: {
|
|
2721
|
+
prop1: {
|
|
2722
|
+
min: 0,
|
|
2723
|
+
max: 10
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
}
|
|
2727
|
+
]
|
|
2728
|
+
});
|
|
2729
|
+
await testSchemaError(schema, {
|
|
2730
|
+
prop1: 0,
|
|
2731
|
+
prop2: ''
|
|
2732
|
+
}, 'prop2', 'required');
|
|
2733
|
+
});
|
|
2734
|
+
|
|
2735
|
+
it('should enforce required property number max', async function() {
|
|
2736
|
+
const req = apos.task.getReq();
|
|
2737
|
+
const schema = apos.schema.compose({
|
|
2738
|
+
addFields: [
|
|
2739
|
+
{
|
|
2740
|
+
name: 'prop1',
|
|
2741
|
+
type: 'integer',
|
|
2742
|
+
required: false
|
|
2743
|
+
},
|
|
2744
|
+
{
|
|
2745
|
+
name: 'prop2',
|
|
2746
|
+
type: 'string',
|
|
2747
|
+
required: true,
|
|
2748
|
+
if: {
|
|
2749
|
+
prop1: {
|
|
2750
|
+
min: -10,
|
|
2751
|
+
max: 0
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
]
|
|
2756
|
+
});
|
|
2757
|
+
const output = {};
|
|
2758
|
+
await apos.schema.convert(req, schema, {
|
|
2759
|
+
prop1: 1,
|
|
2760
|
+
prop2: ''
|
|
2761
|
+
}, output);
|
|
2762
|
+
assert(output.prop2 === '');
|
|
2763
|
+
});
|
|
2764
|
+
|
|
2765
|
+
it('should error required property number max', async function() {
|
|
2766
|
+
const schema = apos.schema.compose({
|
|
2767
|
+
addFields: [
|
|
2768
|
+
{
|
|
2769
|
+
name: 'prop1',
|
|
2770
|
+
type: 'integer',
|
|
2771
|
+
required: false
|
|
2772
|
+
},
|
|
2773
|
+
{
|
|
2774
|
+
name: 'prop2',
|
|
2775
|
+
type: 'string',
|
|
2776
|
+
required: true,
|
|
2777
|
+
if: {
|
|
2778
|
+
prop1: {
|
|
2779
|
+
min: -10,
|
|
2780
|
+
max: 0
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
]
|
|
2785
|
+
});
|
|
2786
|
+
await testSchemaError(schema, {
|
|
2787
|
+
prop1: 0,
|
|
2788
|
+
prop2: ''
|
|
2789
|
+
}, 'prop2', 'required');
|
|
2790
|
+
});
|
|
2791
|
+
|
|
2679
2792
|
it('should enforce required property nested logical AND', async function() {
|
|
2680
2793
|
const req = apos.task.getReq();
|
|
2681
2794
|
const schema = apos.schema.compose({
|
|
@@ -2956,48 +3069,49 @@ describe('Schemas', function() {
|
|
|
2956
3069
|
const schema = apos.schema.compose({
|
|
2957
3070
|
addFields: [
|
|
2958
3071
|
{
|
|
2959
|
-
name: '
|
|
2960
|
-
type: '
|
|
3072
|
+
name: 'prop1',
|
|
3073
|
+
type: 'boolean',
|
|
2961
3074
|
required: false
|
|
2962
3075
|
},
|
|
2963
3076
|
{
|
|
2964
|
-
name: '
|
|
2965
|
-
type: '
|
|
3077
|
+
name: 'prop2',
|
|
3078
|
+
type: 'string',
|
|
2966
3079
|
requiredIf: {
|
|
2967
|
-
|
|
3080
|
+
prop1: true
|
|
2968
3081
|
}
|
|
2969
3082
|
}
|
|
2970
3083
|
]
|
|
2971
3084
|
});
|
|
2972
3085
|
const output = {};
|
|
2973
3086
|
await apos.schema.convert(req, schema, {
|
|
2974
|
-
|
|
2975
|
-
|
|
3087
|
+
prop1: false,
|
|
3088
|
+
prop2: ''
|
|
2976
3089
|
}, output);
|
|
2977
|
-
|
|
3090
|
+
|
|
3091
|
+
assert(output.prop2 === '');
|
|
2978
3092
|
});
|
|
2979
3093
|
|
|
2980
3094
|
it('should error required property with ifRequired boolean', async function() {
|
|
2981
3095
|
const schema = apos.schema.compose({
|
|
2982
3096
|
addFields: [
|
|
2983
3097
|
{
|
|
2984
|
-
name: '
|
|
2985
|
-
type: '
|
|
3098
|
+
name: 'prop1',
|
|
3099
|
+
type: 'boolean',
|
|
2986
3100
|
required: false
|
|
2987
3101
|
},
|
|
2988
3102
|
{
|
|
2989
|
-
name: '
|
|
2990
|
-
type: '
|
|
3103
|
+
name: 'prop2',
|
|
3104
|
+
type: 'string',
|
|
2991
3105
|
requiredIf: {
|
|
2992
|
-
|
|
3106
|
+
prop1: true
|
|
2993
3107
|
}
|
|
2994
3108
|
}
|
|
2995
3109
|
]
|
|
2996
3110
|
});
|
|
2997
3111
|
await testSchemaError(schema, {
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
}, '
|
|
3112
|
+
prop1: true,
|
|
3113
|
+
prop2: ''
|
|
3114
|
+
}, 'prop2', 'required');
|
|
3001
3115
|
});
|
|
3002
3116
|
|
|
3003
3117
|
it('should enforce required property with ifRequired string', async function() {
|
|
@@ -3006,7 +3120,7 @@ describe('Schemas', function() {
|
|
|
3006
3120
|
addFields: [
|
|
3007
3121
|
{
|
|
3008
3122
|
name: 'age',
|
|
3009
|
-
type: '
|
|
3123
|
+
type: 'string',
|
|
3010
3124
|
required: false
|
|
3011
3125
|
},
|
|
3012
3126
|
{
|
|
@@ -3031,7 +3145,7 @@ describe('Schemas', function() {
|
|
|
3031
3145
|
addFields: [
|
|
3032
3146
|
{
|
|
3033
3147
|
name: 'age',
|
|
3034
|
-
type: '
|
|
3148
|
+
type: 'string',
|
|
3035
3149
|
required: false
|
|
3036
3150
|
},
|
|
3037
3151
|
{
|
|
@@ -3135,8 +3249,29 @@ describe('Schemas', function() {
|
|
|
3135
3249
|
required: false
|
|
3136
3250
|
},
|
|
3137
3251
|
{
|
|
3138
|
-
name: '
|
|
3139
|
-
type: '
|
|
3252
|
+
name: 'shoeSize',
|
|
3253
|
+
type: 'integer',
|
|
3254
|
+
requiredIf: {
|
|
3255
|
+
age: {
|
|
3256
|
+
min: 18
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
}
|
|
3260
|
+
]
|
|
3261
|
+
});
|
|
3262
|
+
await testSchemaError(schema, {
|
|
3263
|
+
shoeSize: '',
|
|
3264
|
+
age: 19
|
|
3265
|
+
}, 'shoeSize', 'required');
|
|
3266
|
+
});
|
|
3267
|
+
|
|
3268
|
+
it('should enforce required property with ifRequired number min 0', async function() {
|
|
3269
|
+
const req = apos.task.getReq();
|
|
3270
|
+
const schema = apos.schema.compose({
|
|
3271
|
+
addFields: [
|
|
3272
|
+
{
|
|
3273
|
+
name: 'age',
|
|
3274
|
+
type: 'integer',
|
|
3140
3275
|
required: false
|
|
3141
3276
|
},
|
|
3142
3277
|
{
|
|
@@ -3144,7 +3279,34 @@ describe('Schemas', function() {
|
|
|
3144
3279
|
type: 'integer',
|
|
3145
3280
|
requiredIf: {
|
|
3146
3281
|
age: {
|
|
3147
|
-
min:
|
|
3282
|
+
min: 1
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
}
|
|
3286
|
+
]
|
|
3287
|
+
});
|
|
3288
|
+
const output = {};
|
|
3289
|
+
await apos.schema.convert(req, schema, {
|
|
3290
|
+
shoeSize: '',
|
|
3291
|
+
age: 0
|
|
3292
|
+
}, output);
|
|
3293
|
+
assert(output.shoeSize === null);
|
|
3294
|
+
});
|
|
3295
|
+
|
|
3296
|
+
it('should error required property with ifRequired number min 0', async function() {
|
|
3297
|
+
const schema = apos.schema.compose({
|
|
3298
|
+
addFields: [
|
|
3299
|
+
{
|
|
3300
|
+
name: 'age',
|
|
3301
|
+
type: 'integer',
|
|
3302
|
+
required: false
|
|
3303
|
+
},
|
|
3304
|
+
{
|
|
3305
|
+
name: 'shoeSize',
|
|
3306
|
+
type: 'integer',
|
|
3307
|
+
requiredIf: {
|
|
3308
|
+
age: {
|
|
3309
|
+
min: 0
|
|
3148
3310
|
}
|
|
3149
3311
|
}
|
|
3150
3312
|
}
|
|
@@ -3152,8 +3314,7 @@ describe('Schemas', function() {
|
|
|
3152
3314
|
});
|
|
3153
3315
|
await testSchemaError(schema, {
|
|
3154
3316
|
shoeSize: '',
|
|
3155
|
-
age:
|
|
3156
|
-
prop2: false
|
|
3317
|
+
age: 0
|
|
3157
3318
|
}, 'shoeSize', 'required');
|
|
3158
3319
|
});
|
|
3159
3320
|
|
|
@@ -3194,11 +3355,6 @@ describe('Schemas', function() {
|
|
|
3194
3355
|
type: 'integer',
|
|
3195
3356
|
required: false
|
|
3196
3357
|
},
|
|
3197
|
-
{
|
|
3198
|
-
name: 'prop2',
|
|
3199
|
-
type: 'boolean',
|
|
3200
|
-
required: false
|
|
3201
|
-
},
|
|
3202
3358
|
{
|
|
3203
3359
|
name: 'shoeSize',
|
|
3204
3360
|
type: 'integer',
|
|
@@ -3217,6 +3373,61 @@ describe('Schemas', function() {
|
|
|
3217
3373
|
}, 'shoeSize', 'required');
|
|
3218
3374
|
});
|
|
3219
3375
|
|
|
3376
|
+
it('should enforce required property with ifRequired number max 0', async function() {
|
|
3377
|
+
const req = apos.task.getReq();
|
|
3378
|
+
const schema = apos.schema.compose({
|
|
3379
|
+
addFields: [
|
|
3380
|
+
{
|
|
3381
|
+
name: 'prop1',
|
|
3382
|
+
type: 'integer',
|
|
3383
|
+
required: false
|
|
3384
|
+
},
|
|
3385
|
+
{
|
|
3386
|
+
name: 'prop2',
|
|
3387
|
+
type: 'string',
|
|
3388
|
+
requiredIf: {
|
|
3389
|
+
prop1: {
|
|
3390
|
+
min: -10,
|
|
3391
|
+
max: 0
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
}
|
|
3395
|
+
]
|
|
3396
|
+
});
|
|
3397
|
+
const output = {};
|
|
3398
|
+
await apos.schema.convert(req, schema, {
|
|
3399
|
+
prop1: 1,
|
|
3400
|
+
prop2: ''
|
|
3401
|
+
}, output);
|
|
3402
|
+
assert(output.prop2 === '');
|
|
3403
|
+
});
|
|
3404
|
+
|
|
3405
|
+
it('should error required property with ifRequired number max 0', async function() {
|
|
3406
|
+
const schema = apos.schema.compose({
|
|
3407
|
+
addFields: [
|
|
3408
|
+
{
|
|
3409
|
+
name: 'prop1',
|
|
3410
|
+
type: 'integer',
|
|
3411
|
+
required: false
|
|
3412
|
+
},
|
|
3413
|
+
{
|
|
3414
|
+
name: 'prop2',
|
|
3415
|
+
type: 'string',
|
|
3416
|
+
requiredIf: {
|
|
3417
|
+
prop1: {
|
|
3418
|
+
min: -10,
|
|
3419
|
+
max: 0
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
}
|
|
3423
|
+
]
|
|
3424
|
+
});
|
|
3425
|
+
await testSchemaError(schema, {
|
|
3426
|
+
prop1: 0,
|
|
3427
|
+
prop2: ''
|
|
3428
|
+
}, 'prop2', 'required');
|
|
3429
|
+
});
|
|
3430
|
+
|
|
3220
3431
|
it('should enforce required property with ifRequired logical AND', async function() {
|
|
3221
3432
|
const req = apos.task.getReq();
|
|
3222
3433
|
const schema = apos.schema.compose({
|
|
@@ -4080,7 +4291,6 @@ describe('Schemas', function() {
|
|
|
4080
4291
|
|
|
4081
4292
|
await testSchemaError(schema, {}, 'age', 'required');
|
|
4082
4293
|
});
|
|
4083
|
-
|
|
4084
4294
|
});
|
|
4085
4295
|
});
|
|
4086
4296
|
|