apostrophe
Version:
The Apostrophe Content Management System.
1,956 lines (1,874 loc) • 151 kB
JavaScript
const assert = require('assert').strict;
const _ = require('lodash');
const t = require('../test-lib/test.js');
describe('Schemas', function() {
let apos;
const simpleFields = [
{
name: 'name',
label: 'Name',
type: 'string'
},
{
name: 'address',
label: 'Address',
type: 'string',
textarea: true
},
{
name: 'variety',
label: 'Variety',
type: 'select',
choices: [
{
value: 'candy',
label: 'Candy'
},
{
value: 'cheese',
label: 'Cheese'
}
],
def: 'candy'
},
{
name: 'slug',
label: 'Slug',
type: 'slug'
}
];
const realWorldCase = {
addFields: [
{
type: 'string',
name: 'title',
label: 'Title',
required: true,
sortify: true
},
{
type: 'slug',
name: 'slug',
label: 'Slug',
required: true
},
{
type: 'boolean',
name: 'archive',
label: 'apostrophe:archived',
contextual: true,
def: false
},
{
type: 'slug',
name: 'slug',
label: 'Old URL',
required: true,
page: true
},
{
name: 'title',
label: 'Description',
type: 'string',
required: true
},
{
name: 'urlType',
label: 'Link To',
type: 'select',
choices: [
{
label: 'Internal Page',
value: 'internal'
},
{
label: 'External URL',
value: 'external'
}
]
},
{
name: 'externalUrl',
label: 'URL',
type: 'url',
if: {
urlType: 'external'
}
},
{
name: '_newPage',
type: 'relationship',
limit: 1,
withType: '@apostrophecms/any-page-type',
label: 'Page Title',
idsStorage: 'pageId',
if: {
urlType: 'internal'
}
}
],
arrangeFields: [
{
name: 'basics',
label: 'Basics',
fields: [
'title',
'slug'
]
},
{
name: 'permissions',
label: 'Permissions',
fields: [
'visibility'
],
last: true
},
{
name: 'otherFields',
label: 'Other fields',
fields: [
'slug',
'urlType',
'_newPage',
'title',
'externalUrl'
]
}
]
};
const pageSlug = [
{
type: 'slug',
name: 'slug',
page: true
}
];
const regularSlug = [
{
type: 'slug',
name: 'slug'
}
];
const hasArea = {
addFields: [
{
type: 'area',
name: 'body',
label: 'Body',
options: {
widgets: {
'@apostrophecms/rich-text': {
toolbar: [ 'styles', 'bold' ],
styles: [
{
tag: 'p',
label: 'Paragraph'
},
{
tag: 'h4',
label: 'Header 4'
}
]
}
}
}
}
]
};
const hasGroupedArea = {
addFields: [
{
type: 'area',
name: 'body',
label: 'Body',
options: {
expanded: true,
groups: {
content: {
label: 'Content Widgets',
columns: 2,
widgets: {
'@apostrophecms/rich-text': {
toolbar: [ 'bold' ]
},
'@apostrophecms/form': {}
}
},
media: {
label: 'Media',
columns: 3,
widgets: {
'@apostrophecms/image': {},
'@apostrophecms/video': {}
}
},
layout: {
label: 'Layout Widgets',
columns: 4,
widgets: {
'two-column': {}
}
}
}
}
}
]
};
const hasAreaWithoutWidgets = {
addFields: [
{
type: 'area',
name: 'body',
label: 'Body',
options: {
widgets: {}
}
}
]
};
const warnMessages = [];
this.timeout(t.timeout);
beforeEach(async function () {
apos.schema.validatedSchemas = {};
});
before(async function() {
apos = await t.create({
root: module,
modules: {
'@apostrophecms/util': {
extendMethods() {
return {
warn(_super, ...args) {
warnMessages.push(...args);
return _super(...args);
}
};
}
},
'external-condition': {
methods() {
return {
async externalCondition() {
return 'yes';
},
async externalCondition2(req, { docId }) {
return `yes - ${req.someReqAttr} - ${docId}`;
}
};
}
},
choices: {
methods() {
return {
async getChoices(req, { docId }) {
return [
{
label: 'One',
value: 'one'
},
{
label: 'Two',
value: 'two'
},
{
label: 'DocId',
value: docId
}
];
}
};
}
},
article: {
extend: '@apostrophecms/piece-type',
options: {
alias: 'article'
},
fields(self) {
return {
add: {
title: {
label: '',
type: 'string',
required: true
},
area: {
label: 'Area',
type: 'area',
options: {
widgets: {
'@apostrophecms/rich-text': {}
}
}
},
array: {
label: 'Array',
type: 'array',
fields: {
add: {
arrayTitle: {
label: 'Array Title',
type: 'string'
}
}
}
}
}
};
}
},
topic: {
extend: '@apostrophecms/piece-type',
options: {
alias: 'topic'
},
fields(self) {
return {
add: {
title: {
label: 'Title',
type: 'string',
required: true
},
area: {
label: 'Area',
type: 'area',
options: {
widgets: {
'@apostrophecms/image': {}
}
}
},
_rel: {
label: 'Rel',
type: 'relationship',
withType: 'article'
},
array: {
label: 'Array',
type: 'array',
fields: {
add: {
_arrayRel: {
label: 'Array Rel',
type: 'relationship',
withType: 'article'
}
}
}
},
object: {
label: 'Object',
type: 'object',
fields: {
add: {
_objectRel: {
label: 'Object Rel',
type: 'relationship',
withType: 'article'
}
}
}
}
}
};
}
}
}
});
});
after(function() {
return t.destroy(apos);
});
/// ///
// EXISTENCE
/// ///
it('should be a property of the apos object', function() {
assert(apos.schema);
apos.argv._ = [];
});
it('should compose schemas correctly', function() {
const options = {
addFields: [
{
name: 'name',
type: 'string',
label: 'Name'
},
{
name: 'address',
type: 'string',
label: 'Address',
textarea: true
},
{
name: 'variety',
type: 'select',
label: 'Variety',
choices: [
{
value: 'candy',
label: 'Candy'
},
{
value: 'cheese',
label: 'Cheese'
}
]
}
],
removeFields: [ 'address' ],
alterFields: function(schema) {
const variety = _.find(schema, { name: 'variety' });
assert(variety);
variety.choices.push({
value: 'record',
label: 'Record'
});
}
};
const schema = apos.schema.compose(options);
assert(schema.length === 2);
assert(schema[0].name === 'name');
assert(schema[1].name === 'variety');
assert(_.keys(schema[1].choices).length === 3);
});
it('should compose a schema for a complex real world case correctly', function() {
const schema = apos.schema.compose(realWorldCase);
assert(schema);
const externalUrl = _.find(schema, { name: 'externalUrl' });
assert(externalUrl);
assert.strictEqual(externalUrl.group.name, 'otherFields');
const _newPage = _.find(schema, { name: '_newPage' });
assert(_newPage);
assert.strictEqual(_newPage.group.name, 'otherFields');
});
it('should error if a field is required and an empty value is submitted for a string field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'string',
name: 'name',
label: 'Name',
required: true
}
]
});
assert(schema.length === 1);
const input = {
name: ''
};
await testSchemaError(schema, input, 'name', 'required');
});
it('should error if the value submitted is less than min length for a string field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'string',
name: 'name',
label: 'Name',
min: 5
}
]
});
assert(schema.length === 1);
const input = {
name: 'Cow'
};
await testSchemaError(schema, input, 'name', 'min');
});
it('should convert and keep the correct value for a field which is required for a string field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'string',
name: 'name',
label: 'Name',
required: true
}
]
});
assert(schema.length === 1);
const input = {
name: 'Apostrophe^CMS'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(result.name === 'Apostrophe^CMS');
});
it('should keep an empty submitted field value null when there is a min / max configuration for an integer field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price',
min: 1,
max: 10
}
]
});
assert(schema.length === 1);
const input = {
price: ''
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === null);
});
it('should keep an empty submitted field value null when there is a min / max configuration for a float field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
min: 1,
max: 10
}
]
});
assert(schema.length === 1);
const input = {
price: ''
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === null);
});
it('should ensure a max value is being trimmed to the max length for a string field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'string',
name: 'name',
label: 'Name',
max: 5
}
]
});
assert(schema.length === 1);
const input = {
name: 'Apostrophe'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.name === 'Apost');
});
it('should allow saving a 0 value provided as a number if a field is required for an integer field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price',
required: true
}
]
});
assert(schema.length === 1);
const input = {
price: 0
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 0);
});
it('should allow saving a 0 value provided as a float if a field is required for an float field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
required: true
}
]
});
assert(schema.length === 1);
const input = {
price: 0.00
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 0.00);
});
it('should allow saving a 0 value provided as a number if a field is required for an float field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
required: true
}
]
});
assert(schema.length === 1);
const input = {
price: 0
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 0);
});
it('should allow saving a 0 value provided as a number if a field is required for an string field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'string',
name: 'price',
label: 'Price',
required: true
}
]
});
assert(schema.length === 1);
const input = {
price: 0
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === '0');
});
it('should allow saving a 0 value provided as a string if a field is required for an integer field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price',
required: true
}
]
});
assert(schema.length === 1);
const input = {
price: '0'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 0);
});
it('should allow saving a 0 value provided as a string if a field is required for an string field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'string',
name: 'price',
label: 'Price',
required: true
}
]
});
assert(schema.length === 1);
const input = {
price: '0'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === '0');
});
it('should allow saving a 0 value provided as a string if a field is required for an float field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
required: true
}
]
});
assert(schema.length === 1);
const input = {
price: '0'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 0);
});
it('should allow saving a 0 value provided as a string if there is no min value set for an integer field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price'
}
]
});
assert(schema.length === 1);
const input = {
price: '0'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 0);
});
it('should allow saving a 0 value provided as a string if there is no min value set for a float field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price'
}
]
});
assert(schema.length === 1);
const input = {
price: '0'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 0);
});
it('should allow saving a negative value provided as a number for an integer field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price'
}
]
});
assert(schema.length === 1);
const input = {
price: -1
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === -1);
});
it('should allow saving a negative value provided as a float for an float field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price'
}
]
});
assert(schema.length === 1);
const input = {
price: -1.3
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === -1.3);
});
it('should allow saving a negative value provided as a float for an string field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'string',
name: 'price',
label: 'Price'
}
]
});
assert(schema.length === 1);
const input = {
price: -1.3
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === '-1.3');
});
it('should allow saving a negative value provided as a number if a field is required for an integer field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price',
required: true
}
]
});
assert(schema.length === 1);
const input = {
price: -1
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === -1);
});
it('should allow saving a negative value provided as a number if a field is required for an float field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
required: true
}
]
});
assert(schema.length === 1);
const input = {
price: -1.3
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === -1.3);
});
it('should allow saving a negative value provided as a string if a field is required for an float field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
required: true
}
]
});
assert(schema.length === 1);
const input = {
price: '-1.3'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === -1.3);
});
it('should override the saved value if min and max value has been set and the submitted value is out of range for an integer field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price',
min: 5,
max: 6
}
]
});
assert(schema.length === 1);
const input = {
price: '3'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 5);
});
it('should override the saved value if min and max value has been set and the submitted value is out of range for a float field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
min: 5.1,
max: 6
}
]
});
assert(schema.length === 1);
const input = {
price: '3.2'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 5.1);
});
it('should ensure a min value is being set to the configured min value if a lower value is submitted for an integer field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price',
min: 5
}
]
});
assert(schema.length === 1);
const input = {
price: '1'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 5);
});
it('should ensure a min value is being set to the configured min value if a lower value is submitted for a float field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
min: 5.3
}
]
});
assert(schema.length === 1);
const input = {
price: '1.2'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 5.3);
});
it('should ensure a max value is being set to the max if a higher value is submitted for an integer field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price',
max: 5
}
]
});
assert(schema.length === 1);
const input = {
price: '8'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 5);
});
it('should ensure a max value is being set to the max if a higher value is submitted for a float field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
max: 5.9
}
]
});
assert(schema.length === 1);
const input = {
price: '8'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 5.9);
});
it('should not modify a value if the submitted value is within min and max for an integer field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price',
min: 4,
max: 6
}
]
});
assert(schema.length === 1);
const input = {
price: '5'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 5);
});
it('should not modify a value if the submitted value is within min and max for a float field type', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
min: 4,
max: 5
}
]
});
assert(schema.length === 1);
const input = {
price: '4.3'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.price === 4.3);
});
it('should not allow a text value to be submitted for a required integer field', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price',
required: true
}
]
});
assert(schema.length === 1);
const input = {
price: 'A'
};
await testSchemaError(schema, input, 'price', 'invalid');
});
it('should not allow a text value to be submitted for a required float field', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
required: true
}
]
});
assert(schema.length === 1);
const input = {
price: 'A'
};
await testSchemaError(schema, input, 'price', 'invalid');
});
it('should not allow a text value to be submitted for a non required integer field with min and max', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price',
min: 1,
max: 10
}
]
});
assert(schema.length === 1);
const input = {
price: 'A'
};
await testSchemaError(schema, input, 'price', 'invalid');
});
it('should not allow a text value to be submitted for a non required float field with min and max', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
min: 1,
max: 10
}
]
});
assert(schema.length === 1);
const input = {
price: 'A'
};
await testSchemaError(schema, input, 'price', 'invalid');
});
it('should not allow a text value to be submitted for a non required integer field with a default value set', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price',
def: 2
}
]
});
assert(schema.length === 1);
const input = {
price: 'A'
};
await testSchemaError(schema, input, 'price', 'invalid');
});
it('should not allow a text value to be submitted for a non required float field with a default value set', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price',
def: 2.10
}
]
});
assert(schema.length === 1);
const input = {
price: 'A'
};
await testSchemaError(schema, input, 'price', 'invalid');
});
it('should not allow a text value to be submitted for a non required integer field', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price'
}
]
});
assert(schema.length === 1);
const input = {
price: 'A'
};
await testSchemaError(schema, input, 'price', 'invalid');
});
it('should not allow a text value to be submitted for a non required float field', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price'
}
]
});
assert(schema.length === 1);
const input = {
price: 'A'
};
await testSchemaError(schema, input, 'price', 'invalid');
});
it('should allow a parsable string/integer value to be submitted for a non required integer field', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'integer',
name: 'price',
label: 'Price'
}
]
});
assert(schema.length === 1);
const input = {
price: '22a'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(result.price === 22);
});
it('should allow a parsable string/float value to be submitted for a non required float field', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'float',
name: 'price',
label: 'Price'
}
]
});
assert(schema.length === 1);
const input = {
price: '11.4b'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(result.price === 11.4);
});
it('should set the default range field value when undefined is given', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'range',
name: 'rating',
label: 'Rating',
def: 0,
min: 0,
max: 5
}
]
});
assert(schema.length === 1);
const input = {};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(result.rating === 0);
});
it('should set the default range field value when null is given', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'range',
name: 'rating',
label: 'Rating',
def: 0,
min: 0,
max: 5
}
]
});
assert(schema.length === 1);
const input = {
rating: null
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(result.rating === 0);
});
it('should set the range field value to null when a value below the min threshold is given', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'range',
name: 'rating',
label: 'Rating',
def: 0,
min: 0,
max: 5
}
]
});
assert(schema.length === 1);
const input = {
rating: -1
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(result.rating === null);
});
it('should set the range field value to null when a value over the max threshold is given', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'range',
name: 'rating',
label: 'Rating',
def: 0,
min: 0,
max: 5
}
]
});
assert(schema.length === 1);
const input = {
rating: 6
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(result.rating === null);
});
it('should convert simple data correctly', async function() {
const schema = apos.schema.compose({
addFields: simpleFields
});
assert(schema.length === 4);
const input = {
name: 'Bob Smith',
address: '5017 Awesome Street\nPhiladelphia, PA 19147',
irrelevant: 'Irrelevant',
slug: 'This Is Cool'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
// no irrelevant or missing fields
assert(_.keys(result).length === 4);
// expected fields came through
assert(result.name === input.name);
assert(result.address === input.address);
// default
assert(result.variety === undefined);
assert(result.slug === 'this-is-cool');
});
it('should update a password if provided', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'password',
name: 'password',
label: 'Password'
}
]
});
assert(schema.length === 1);
const input = {
password: 'silly'
};
const req = apos.task.getReq();
const result = { password: 'serious' };
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
// hashing is not the business of schemas, see the
// @apostrophecms/user module
assert(result.password === 'silly');
});
it('should leave a password alone if not provided', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'password',
name: 'password',
label: 'Password'
}
]
});
assert(schema.length === 1);
const input = {
password: ''
};
const req = apos.task.getReq();
const result = { password: 'serious' };
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
// hashing is not the business of schemas, see the
// @apostrophecms/user module
assert(result.password === 'serious');
});
it('should handle array schemas', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'array',
name: 'addresses',
label: 'Addresses',
schema: [
{
name: 'address',
type: 'string',
label: 'Address'
}
]
}
]
});
assert(schema.length === 1);
const input = {
addresses: [
{
address: '500 test lane'
},
{
address: '602 test ave'
}
]
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.addresses);
assert(result.addresses.length === 2);
assert(result.addresses[0]._id);
assert(result.addresses[1]._id);
assert(result.addresses[0].address === '500 test lane');
assert(result.addresses[1].address === '602 test ave');
});
it('should check for duplicates in arrays when relevant', async function() {
const schema = apos.schema.compose({
addFields: [
{
type: 'array',
name: 'addresses',
label: 'Addresses',
schema: [
{
name: 'address',
type: 'string',
label: 'Address',
unique: true
}
]
}
]
});
const input = {
addresses: [
{
address: '500 test lane'
},
{
address: '602 test ave'
}
]
};
const result = {};
const req = apos.task.getReq();
await apos.schema.convert(req, schema, input, result);
assert(_.keys(result).length === 1);
assert(result.addresses);
assert(result.addresses.length === 2);
assert(result.addresses[0]._id);
assert(result.addresses[1]._id);
assert(result.addresses[0].address === '500 test lane');
assert(result.addresses[1].address === '602 test ave');
input.addresses[1] = '500 test lane';
await apos.schema.convert(req, schema, input, result);
assert.throws(() => {
throw apos.error('duplicate', 'Address in Addresses must be unique');
}, {
name: 'duplicate',
message: 'Address in Addresses must be unique'
});
});
it('should convert string values to areas correctly', async function() {
const schema = apos.schema.compose(hasArea);
assert(schema.length === 1);
const input = {
irrelevant: 'Irrelevant',
// Should get escaped, not be treated as HTML
body: 'This is the greatest <h1>thing</h1>'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
// no irrelevant or missing fields
assert(_.keys(result).length === 1);
// expected fields came through
assert(result.body);
assert(result.body.metaType === 'area');
assert(result.body.items);
assert(result.body.items[0]);
assert(result.body.items[0].type === '@apostrophecms/rich-text');
assert(result.body.items[0].content === apos.util.escapeHtml(input.body));
});
it('should convert arrays of widgets to areas correctly', async function() {
const schema = apos.schema.compose(hasArea);
assert(schema.length === 1);
const input = {
irrelevant: 'Irrelevant',
body: [
{
metaType: 'widget',
_id: 'abc',
type: '@apostrophecms/rich-text',
content: '<h4>This <em>is</em> <strong>a header.</strong></h4>'
}
]
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
// no irrelevant or missing fields
assert(_.keys(result).length === 1);
// expected fields came through
assert(result.body);
assert(result.body.metaType === 'area');
assert(result.body.items);
assert(result.body.items[0]);
assert(result.body.items[0].type === '@apostrophecms/rich-text');
assert.strictEqual(result.body.items[0].content, '<h4>This is <strong>a header.</strong></h4>');
});
it('should not accept a widget not in the widgets object of the area', async function() {
const schema = apos.schema.compose(hasAreaWithoutWidgets);
assert(schema.length === 1);
const input = {
irrelevant: 'Irrelevant',
body: [
{
metaType: 'widget',
_id: 'abc',
type: '@apostrophecms/rich-text',
content: '<h4>This <em>is</em> <strong>a header.</strong></h4>'
}
]
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
// no irrelevant or missing fields
assert(!result.body.items[0]);
});
it('should convert areas gracefully when they are undefined', async function() {
const schema = apos.schema.compose(hasArea);
assert(schema.length === 1);
const input = {
irrelevant: 'Irrelevant',
// Should get escaped, not be treated as HTML
body: undefined
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
// no irrelevant or missing fields
assert(_.keys(result).length === 1);
// expected fields came through
assert(result.body);
assert(result.body.metaType === 'area');
assert(result.body.items);
assert(!result.body.items[0]);
});
it('should convert arrays of widgets when configured as groups to areas correctly', async function() {
const schema = apos.schema.compose(hasGroupedArea);
assert(schema.length === 1);
const input = {
irrelevant: 'Irrelevant',
// Should get escaped, not be treated as HTML
body: 'I have <h1>groups</h1>!'
};
const req = apos.task.getReq();
const result = {};
await apos.schema.convert(req, schema, input, result);
// no irrelevant or missing fields
assert(_.keys(result).length === 1);
// expected fields came through
assert(result.body);
assert(result.body.metaType === 'area');
assert(result.body.items);
assert(result.body.items[0]);
assert(result.body.items[0].type === '@apostrophecms/rich-text');
assert(result.body.items[0].content === apos.util.escapeHtml(input.body));
});
it('should clean up extra slashes in page slugs', async function() {
const req = apos.task.getReq();
const schema = apos.schema.compose({ addFields: pageSlug });
assert(schema.length === 1);
const input = {
slug: '/wiggy//wacky///wobbly////whizzle/////'
};
const result = {};
await apos.schema.convert(req, schema, input, result);
assert.strictEqual(result.slug, '/wiggy/wacky/wobbly/whizzle');
});
it('retains trailing / on the home page', async function() {
const req = apos.task.getReq();
const schema = apos.schema.compose({ addFields: pageSlug });
assert(schema.length === 1);
const input = {
slug: '/'
};
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(result.slug === '/');
});
it('does not keep slashes when page: true not present for slug', async function() {
const req = apos.task.getReq();
const schema = apos.schema.compose({ addFields: regularSlug });
assert(schema.length === 1);
const input = {
slug: '/wiggy//wacky///wobbly////whizzle/////'
};
const result = {};
await apos.schema.convert(req, schema, input, result);
assert(result.slug === 'wiggy-wacky-wobbly-whizzle');
});
it('enforces required property for ordinary field', async function() {
const schema = apos.schema.compose({
addFields: [
{
name: 'age',
label: 'Age',
type: 'integer',
required: true
}
]
});
await testSchemaError(schema, { age: '' }, 'age', 'required');
});
it('ignores required property for hidden field', async function() {
const req = apos.task.getReq();
const schema = apos.schema.compose({
addFields: [
{
name: 'age',
type: 'integer',
required: true,
if: {
ageOrShoeSize: 'age'
}
},
{
name: 'shoeSize',
type: 'integer',
required: false,
if: {
ageOrShoeSize: 'shoeSize'
}
},
{
name: 'ageOrShoeSize',
type: 'select',
choices: [
{
label: 'age',
value: 'age'
},
{
label: 'shoeSize',
value: 'shoeSize'
}
]
}
]
});
const output = {};
await apos.schema.convert(req, schema, {
ageOrShoeSize: 'shoeSize',
age: ''
}, output);
assert(output.ageOrShoeSize === 'shoeSize');
});
it('enforces required property for shown field', async function() {
const schema = apos.schema.compose({
addFields: [
{
name: 'age',
type: 'integer',
required: true,
if: {
ageOrShoeSize: 'age'
}
},
{
name: 'shoeSize',
type: 'integer',
required: false,
if: {
ageOrShoeSize: 'shoeSize'
}
},
{
name: 'ageOrShoeSize',
type: 'select',
choices: [
{
label: 'age',
value: 'age'
},
{
label: 'shoeSize',
value: 'shoeSize'
}
]
}
]
});
await testSchemaError(schema, {
ageOrShoeSize: 'age',
age: ''
}, 'age', 'required');
});
it('ignores required property for recursively hidden field', async function() {
const req = apos.task.getReq();
const schema = apos.schema.compose({
addFields: [
{
name: 'age',
type: 'integer',
required: true,
if: {
ageOrShoeSize: 'age'
}
},
{
name: 'shoeSize',
type: 'integer',
required: false,
if: {
ageOrShoeSize: 'shoeSize'
}
},
{
name: 'ageOrShoeSize',
type: 'select',
choices: [
{
label: 'age',
value: 'age'
},
{
label: 'shoeSize',
value: 'shoeSize'
}
],
if: {
doWeCare: '1'
}
},
{
name: 'doWeCare',
type: 'select',
choices: [
{
label: 'Yes',
value: '1'
},
{
label: 'No',
value: '0'
}
]
}
]
});
const output = {};
await apos.schema.convert(req, schema, {
ageOrShoeSize: 'age',
doWeCare: '0',
age: ''
}, output);
assert(output.ageOrShoeSize === 'age');
});
it('enforces required property for recursively shown field', async function() {
const schema = apos.schema.compose({
addFields: [
{
name: 'age',
type: 'integer',
required: true,
if: {
ageOrShoeSize: 'age'
}
},
{
name: 'shoeSize',
type: 'integer',
required: false,
if: {
ageOrShoeSize: 'shoeSize'
}
},
{
name: 'ageOrShoeSize',
type: 'select',
choices: [
{
label: 'age',
value: 'age'
},
{
label: 'shoeSize',
value: 'shoeSize'
}
],
if: {
doWeCare: '1'
}
},
{
name: 'doWeCare',
type: 'select',
choices: [
{
label: 'Yes',
value: '1'
},
{
label: 'No',
value: '0'
}
]
}
]
});
await testSchemaError(schema, {
ageOrShoeSize: 'age',
doWeCare: '1',
age: ''
}, 'age', 'required');
});
it('ignores required property for recursively hidden field with boolean', async function() {
const req = apos.task.getReq();
const schema = apos.schema.compose({
addFields: [
{
name: 'age',
type: 'integer',
required: true,
if: {
ageOrShoeSize: 'age'
}
},
{
name: 'shoeSize',
type: 'integer',
required: false,
if: {
ageOrShoeSize: 'shoeSize'
}
},
{
name: 'ageOrShoeSize',
type: 'select',
choices: [
{
label: 'age',
value: 'age'
},
{
label: 'shoeSize',
value: 'shoeSize'
}
],
if: {
doWeCare: true
}
},
{
name: 'doWeCare',
type: 'boolean',
choices: [
{
value: true
},
{
value: false
}
]
}
]
});
const output = {};
await apos.schema.convert(req, schema, {
ageOrShoeSize: 'age',
doWeCare: false,
age: ''
}, output);
assert(output.ageOr