UNPKG

punchcard-content-types

Version:

Combines with input plugins to create forms with validation. Creates forms for Punchcard CMS.

368 lines (321 loc) 11.2 kB
import test from 'ava'; import cloneDeep from 'lodash/cloneDeep'; import includes from 'lodash/includes'; import types from '../lib/content-types'; import only from '../lib/content-types/only.js'; import config from './fixtures/config/default'; import barInput from './fixtures/objects/bar-input.js'; import barExpected from './fixtures/objects/bar-expected.js'; const correctCT = [{ name: 'Foo', id: 'foo', identifier: 'my-text', attributes: [ { type: 'text', name: 'My Text', id: 'my-text', }, { type: 'email', name: 'My Email', id: 'my-email', }, { type: 'quote', name: 'Quote', id: 'quote', }, { type: 'checkbox', name: 'Checkbox', id: 'checkbox', inputs: { checkbox: { options: [ { label: 'one', value: 'one', }, { label: 'two', value: 'two', }, ], }, }, }, ], }]; test('Content Types', t => { return only(barInput, { 'something-new': { text: { value: 'foo' }, }, 'my-quote': [ { author: { value: 'bar' }, quote: { value: 'foo' }, source: { value: 'baz' }, }, { author: { value: 'bar1' }, quote: { value: 'foo1' }, source: { value: 'baz1' }, }, { author: { value: 'bar2' }, quote: { value: 'foo2' }, source: { value: 'baz2' }, }, ], 'something-new-other': { text: { value: 'bar' }, }, }).then(result => { t.deepEqual(result, barExpected, 'Only method works!'); t.pass(); }); }); test('returns error on unfound content types default directory because tests have module root as process.cwd', t => { return types() .catch(err => { t.true(includes(err.message, 'ENOENT: no such file or directory, scandir'), 'Should return an error with non-object config'); }); }); test('rejects when config is not an object', t => { return types('', 'config') .catch(err => { t.is(err.message, 'Configuration parameter must be an object', 'Should return an error with non-object config'); }); }); test('returns all types from configured content type directory', t => { return types('', config) .then(result => { t.true(Array.isArray(result), 'Should return an array'); t.is(result.length, 3, 'Should only have one content type'); t.is(result[0].name, 'Content Type BAR', 'Get first content type name'); t.is(result[0].description, 'Bar Baz Foo', 'Get first content type desc'); t.is(result[0].id, 'bar', 'Get first content type id'); t.is(result[1].name, 'Content Type Baz', 'Get second content type name'); t.is(result[1].description, 'Bar Baz Foo', 'Get second content type desc'); t.is(result[1].id, 'baz', 'Get second content type id'); t.is(result[2].name, 'Content Type FOO', 'Get third content type name'); t.is(result[2].description, 'Foo Bar Baz', 'Get third content type desc'); t.is(result[2].id, 'foo', 'Get third content type id'); }); }); test('reject when something other than array is passed in', t => { return types(correctCT[0]).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Content types must be an array', 'Non-Array Rejected'); }); }); test('reject when plugin not found', t => { const type = cloneDeep(correctCT); type[0].attributes[0].type = 'input-plugin-text'; return types(type).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Input \'input-plugin-text\' not found', 'Missing Plugin'); }); }); test('reject when name not found - no id', t => { const type = cloneDeep(correctCT); delete type[0].attributes[0].name; delete type[0].attributes[0].id; return types(type).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Input \'text\' in content type \'Foo\' needs a name', 'Missing Name'); }); }); test('reject when name not found - id', t => { const type = cloneDeep(correctCT); delete type[0].attributes[0].name; type[0].attributes[0].id = 'My Text'; return types(type).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Input \'My Text\' in content type \'Foo\' needs a name', 'Missing Name'); }); }); test('reject when id not found', t => { const type = cloneDeep(correctCT); delete type[0].attributes[0].id; return types(type).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Input \'My Text\' in content type \'Foo\' needs an ID', 'Missing ID'); }); }); test('reject when id is not kebab case', t => { const type = cloneDeep(correctCT); type[0].attributes[0].id = 'myText'; return types(type).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Input ID \'myText\' needs to be written in kebab case (e.g. \'my-text\')', 'Not Kebab ID'); }); }); test('reject when id is duplicated', t => { const type = cloneDeep(correctCT); type[0].attributes[1].id = 'my-text'; return types(type).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Input ID \'my-text\' in content type \'Foo\' cannot be duplicated (in \'My Email\')', 'Duplicate id in attributes'); }); }); test('reject when identifier missing', t => { const type = cloneDeep(correctCT); delete type[0].identifier; return types(type).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Identifier missing in content type \'Foo\'', 'Identifier not in config'); }); }); test('reject when identifier not a string', t => { const type = cloneDeep(correctCT); type[0].identifier = []; return types(type).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Identifier in content type \'Foo\' must be a string', 'Identifier not string'); }); }); test('Content Type config check identifier exists', t => { const type = cloneDeep(correctCT); type[0].identifier = 'sandwich'; return types(type).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Identifier \'sandwich\' is not an attribute in content type \'Foo\'.', 'Identifier must match an attribute'); }); }); test('Content Type config check identifier input not multi', t => { const type = cloneDeep(correctCT); type[0].identifier = 'quote'; return types(type).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Identifier \'quote\' in content type \'Foo\' has more than one input. Only single-input attributes may be the identifier.', 'Identifier attr cannot have multiple inputs'); }); }); test('reject when identifier is repeatable', t => { const type = cloneDeep(correctCT); type[0].attributes[0].repeatable = true; return types(type).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Identifier \'my-text\' in content type \'Foo\' is repeatable. Only non-repeatable attributes may be the identifier.', 'Identifier cannot be repeatable'); }); }); test('reject when identifier has options', t => { const type = cloneDeep(correctCT); type[0].identifier = 'checkbox'; return types(type).then(() => { t.fail('Merged should fail'); }).catch(e => { t.is(e.message, 'Identifier \'checkbox\' in content type \'Foo\' has options. Attributes with options may not be the identifier.', 'Identifier cannot have options'); }); }); test('identifier gets required-to-save', t => { const type = cloneDeep(correctCT); return types(type).then(result => { const merged = result[0]; t.is(merged.attributes[0].required, 'save', 'Identifier is required to save'); }); }); test('identifier converted from publish to save', t => { const type = cloneDeep(correctCT); type[0].attributes[0].required = 'publish'; return types(type).then(result => { const merged = result[0]; t.is(merged.attributes[0].required, 'save', 'Should convert publish to save for identifier'); }); }); test('merged with correct param', t => { const testCT = { name: 'FooRific', description: 'A very foo content model.', id: 'foo-rific', identifier: 'username', attributes: [ { type: 'text', id: 'username', name: 'Username', description: 'Please enter a username', }, { type: 'text', id: 'full-name', name: 'Full Name', description: 'Please enter your full name', inputs: { text: { label: 'Foo Bar Baz', }, }, }, { type: 'selects-related', id: 'related-selects', name: 'Related Select Fields', description: 'Please choose wisely', inputs: { select1: { label: 'I am the first', }, select2: { label: 'I am the second', }, }, }, ], }; return types([testCT]) .then(result => { let input; const merged = result[0]; t.is(result.length, 1, 'There is one result'); t.is(merged.name, 'FooRific', 'Content type name does not change'); t.is(merged.description, 'A very foo content model.', 'Content type description does not change'); t.is(merged.id, 'foo-rific', 'Content type ID does not change'); t.true(merged.hasOwnProperty('attributes'), 'Content type has attributes'); t.is(merged.attributes.length, 3, 'Content type has three attributes'); merged.attributes.forEach((attr, i) => { const base = testCT.attributes[i]; t.is(attr.name, base.name, 'Attribute name does not change'); t.is(attr.description, base.description, 'Attribute description does not change'); t.is(attr.id, base.id, 'Attribute ID does not change'); t.is(attr.type, base.type, 'Attribute type does not change'); t.true(attr.hasOwnProperty('validation'), 'Attribute has validation'); t.true(attr.hasOwnProperty('html'), 'Attribute has HTML'); t.true(attr.hasOwnProperty('inputs'), 'Attribute has inputs'); if (Object.keys(attr.inputs).length === 1) { input = Object.keys(attr.inputs)[0]; if (base.hasOwnProperty('inputs')) { if (base.inputs.hasOwnProperty(input)) { if (base.inputs[input].hasOwnProperty('label')) { t.is(attr.inputs[input].label, base.inputs[input].label, 'Input label is used if present in base if one input'); } } } else { t.is(attr.inputs[input].label, base.name, 'Input label is set to name if one input'); } } if (attr === merged.attributes[2]) { input = Object.keys(attr.inputs); t.true(attr.inputs[input[0]].hasOwnProperty('script'), 'Attribute has scripts'); } if (attr.id === 'username') { t.is(attr.required, 'save', 'Identifier attr must be required to save'); } }); }); });