apostrophe
Version:
The Apostrophe Content Management System.
637 lines (600 loc) • 17.8 kB
JavaScript
const t = require('../test-lib/test.js');
const assert = require('assert');
const rteArea = {
_id: 'ckjyva89o000k2a67vcgl914k',
items: [
{
_id: 'ckjyvagy9000p2a67fbf8nwd3',
metaType: 'widget',
type: '@apostrophecms/rich-text',
content: '<p>Perhaps its fate that today is the 4th of July, and you will once again be fighting for our freedom, not from tyranny, oppression, or persecution -- but from annihilation.</p><p>We\'re fighting for our right to live, to exist.</p>'
}
],
metaType: 'area'
};
const mixedArea = {
_id: 'ckjyv67wt001a2a67wibekp0c',
items: [
{
_id: 'ckjyv6ezu001f2a67s572cvn2',
metaType: 'widget',
type: '@apostrophecms/rich-text',
content: '<p>Good morning. In less than an hour, aircraft from here will join others from around the world. And you will be launching the largest aerial battle in this history of mankind.</p>'
},
{
_id: 'ckk32go3e00152a67tbbgzcf9',
code: '<marquee>The HTML <code><marquee></code> element is used to insert a scrolling area of text. You can control what happens when the text reaches the edges of its content area using its attributes.</marquee>',
metaType: 'widget',
type: '@apostrophecms/html'
}
],
metaType: 'area'
};
let areaDocs = [
{
title: '1: Nested article bits',
slug: 'nested-article-bits',
type: 'article',
metaType: 'doc',
main: rteArea
},
{
title: '2: Fresh article',
slug: 'fresh-article',
type: 'article',
metaType: 'doc',
main: mixedArea,
moreAreas: [
{
_id: 'ckk4746rp004i2a67iiw024yl',
someWidgets: {
_id: 'ckk4746s7004k2a67of8ra2zn',
items: [
{
_id: 'ckk474uzf004q2a67fy06gwba',
code: '<h1>🌝</h1>',
metaType: 'widget',
type: '@apostrophecms/html'
},
{
_id: '32l474uzf004q2adddy06gwba',
code: '<h2>🌚</h2>',
metaType: 'widget',
type: '@apostrophecms/html'
}
],
metaType: 'area'
}
}, {
_id: 'ckk4763vq00642a67udf3hqbx',
someWidgets: {
_id: 'ckk477sm0000qw43r94lpmn2m',
items: [],
metaType: 'area'
}
}
]
}
];
describe('Areas', function() {
let apos;
this.timeout(t.timeout);
before(async function() {
apos = await t.create({
root: module,
modules: {
article: {
extend: '@apostrophecms/piece-type',
options: {
alias: 'article',
name: 'article',
label: 'Article'
},
fields: {
add: {
main: {
type: 'area',
label: 'Main area',
options: {
widgets: {
'@apostrophecms/rich-text': {
toolbar: [ 'bold' ],
styles: [
{
tag: 'p',
label: 'Paragraph'
}
]
},
'@apostrophecms/html': {}
}
}
},
moreAreas: {
type: 'array',
label: 'Some more areas',
fields: {
add: {
someWidgets: {
type: 'area',
label: 'Some widgets in the area',
options: {
widgets: {
'@apostrophecms/html': {}
}
}
}
}
}
},
withDefaults: {
type: 'area',
options: {
widgets: {
'@apostrophecms/rich-text': {},
'@apostrophecms/image': {},
'@apostrophecms/video': {},
'@apostrophecms/html': {}
}
},
def: [
'@apostrophecms/rich-text',
'@apostrophecms/image',
'@apostrophecms/video',
'@apostrophecms/html'
]
},
expandedWithDefaults: {
type: 'area',
options: {
groups: {
content: {
widgets: {
'@apostrophecms/rich-text': {}
}
},
media: {
widgets: {
'@apostrophecms/image': {}
}
}
}
},
def: [
'@apostrophecms/rich-text',
'@apostrophecms/image'
]
}
}
}
}
}
});
assert(apos.modules['@apostrophecms/area']);
assert(apos.area);
// In tests this will be the name of the test file,
// so override that in order to get apostrophe to
// listen normally and not try to run a task. -Tom
apos.argv._ = [];
});
after(async function() {
return t.destroy(apos);
});
beforeEach(function() {
apos.area.widgetOperations = [];
});
it('can get widgets from groups', function() {
const options = {
groups: {
content: {
widgets: {
article: {},
'selected-article': {},
topic: {},
hero: {},
'@apostrophecms/rich-text': {},
'@apostrophecms/form': {}
}
},
media: {
widgets: {
'@apostrophecms/image': {},
'@apostrophecms/video': {}
}
},
layout: {
widgets: {
'two-column': {}
}
}
}
};
const expected = {
...options.groups.content.widgets,
...options.groups.media.widgets,
...options.groups.layout.widgets
};
const widgets = apos.area.getWidgets(options);
assert.strict(expected, widgets);
});
it('returns the rich text of an area via the richText method', function() {
assert(apos.area.richText({
metaType: 'area',
items: [
{
metaType: 'widget',
type: '@apostrophecms/rich-text',
content: '<h2>So cool</h2>'
},
{
metaType: 'widget',
type: 'something-else',
content: '<h3>Do not return me</h3>'
},
{
metaType: 'widget',
type: '@apostrophecms/rich-text',
content: '<h2>Something else cool</h2>'
}
]
}) === '<h2>So cool</h2>\n<h2>Something else cool</h2>');
assert(apos.area.richText({
metaType: 'area',
items: [
{
metaType: 'widget',
type: '@apostrophecms/rich-text',
content: '<h2>So cool</h2>'
},
{
metaType: 'widget',
type: 'something-else',
content: '<h3>Do not return me</h3>'
},
{
metaType: 'widget',
type: '@apostrophecms/rich-text',
content: '<h2>Something else cool</h2>'
}
]
}, { delimiter: '' }) === '<h2>So cool</h2><h2>Something else cool</h2>');
assert(apos.area.richText({
metaType: 'area',
items: [
{
metaType: 'widget',
type: '@apostrophecms/rich-text',
content: '<h2>So cool</h2>'
},
{
metaType: 'widget',
type: 'something-else',
content: '<h3>Do not return me</h3>'
},
{
metaType: 'widget',
type: '@apostrophecms/rich-text',
content: '<h2>Something else cool</h2>'
}
]
}, { wrapper: 'div' }) === '<div><h2>So cool</h2></div><div><h2>Something else cool</h2></div>');
});
it('returns the plaintext of an area via the plaintext method', function() {
assert.strictEqual(apos.area.plaintext({
metaType: 'area',
items: [
{
metaType: 'widget',
type: '@apostrophecms/rich-text',
content: '<h2>So cool</h2>'
},
{
metaType: 'widget',
type: 'something-else',
content: '<h3>Do not return me</h3>'
},
{
metaType: 'widget',
type: '@apostrophecms/rich-text',
content: '<h2>Something else cool</h2>'
}
]
}), 'So cool\nSomething else cool');
assert.strictEqual(apos.area.plaintext({
metaType: 'area',
items: [
{
metaType: 'widget',
type: '@apostrophecms/rich-text',
content: '<h2>So cool</h2>'
},
{
metaType: 'widget',
type: 'something-else',
content: '<h3>Do not return me</h3>'
},
{
metaType: 'widget',
type: '@apostrophecms/rich-text',
content: '<h2>Something else cool</h2>'
}
]
}, { limit: 15 }), 'So cool...');
});
it('can insert area docs', async function() {
const req = apos.task.getReq({ mode: 'draft' });
const ids = [];
for (const doc of areaDocs) {
const inserted = await apos.article.insert(req, doc);
ids.push(inserted.aposDocId);
await apos.article.publish(req, inserted);
}
areaDocs = await apos.article.find(req.clone({ mode: 'published' }), {
aposDocId: { $in: ids }
}).sort({ title: 1 }).toArray();
});
it('can populate an area object with required properties using the prepForRender method', async function () {
apos.area.prepForRender(rteArea, areaDocs[0], 'main');
assert(rteArea._fieldId);
assert(rteArea._docId);
assert(rteArea._edit !== undefined);
});
let firstRendered;
let secondRendered;
it('renders an area passed to the `renderArea` method', async function () {
const req = apos.task.getReq();
firstRendered = await apos.area.renderArea(req, rteArea, areaDocs[0]);
assert(firstRendered);
assert.equal(firstRendered.trim(),
`<div
class="apos-area"
>
<div data-rich-text>
<p>Perhaps its fate that today is the 4th of July, and you will once again be fighting for our freedom, not from tyranny, oppression, or persecution -- but from annihilation.</p><p>We're fighting for our right to live, to exist.</p>
</div>
</div>
`.trim());
});
it('returns rendered HTML from the `renderArea` method for a mixed widget area', async function() {
const req = apos.task.getReq();
apos.area.prepForRender(mixedArea, areaDocs[1], 'main');
secondRendered = await apos.area.renderArea(req, mixedArea, areaDocs[1]);
assert(secondRendered.includes(`<div data-rich-text>
<p>Good morning.`));
assert(secondRendered.includes('<marquee>The HTML <code><marquee></code> element'));
});
it('populates a document object with rendered HTML areas using the renderDocsAreas method.', async function () {
const req = apos.task.getReq();
areaDocs.forEach(doc => {
// No rendered HTML yet.
assert(!doc.main._rendered);
});
await apos.area.renderDocsAreas(req, areaDocs);
areaDocs.forEach(doc => {
// Now they're there.
assert(doc.main._rendered);
assert(!doc.main.items);
// TODO the approach in this test can't cover array or object area
// rendering properly without a further overhaul (not a new problem). if
// (doc.moreAreas) { doc.moreAreas.forEach(area => {
// assert(area.someWidgets._rendered); assert(!area.someWidgets.items);
// }); }
});
assert.equal(areaDocs[0].main._rendered, firstRendered);
assert.equal(areaDocs[1].main._rendered, secondRendered);
});
it('area considered empty when it should be', function() {
const doc = {
type: 'test',
_id: 'test',
body: {
metaType: 'area',
items: []
},
emptyText: {
metaType: 'area',
items: [
{
metaType: 'widget',
_id: 'test2',
type: '@apostrophecms/rich-text',
content: ''
}
]
},
insignificantText: {
metaType: 'area',
items: [
{
metaType: 'widget',
_id: 'test2',
type: '@apostrophecms/rich-text',
content: '<h4> </h4>'
}
]
}
};
assert(apos.area.isEmpty({ area: doc.body }));
assert(apos.area.isEmpty(doc, 'body'));
assert(apos.area.isEmpty(doc, 'nonexistent'));
assert(apos.area.isEmpty(doc, 'emptyText'));
assert(apos.area.isEmpty(doc, 'insignificantText'));
});
it('area not considered empty when it should not be', function() {
const doc = {
type: 'test',
_id: 'test',
body: {
metaType: 'area',
items: [
{
metaType: 'widget',
_id: 'test2',
type: '@apostrophecms/video',
url: 'http://somewhere.com'
}
]
},
emptyText: {
metaType: 'area',
items: [
{
metaType: 'widget',
_id: 'test2',
type: '@apostrophecms/rich-text',
content: ''
}
]
},
fullText: {
metaType: 'area',
items: [
{
metaType: 'widget',
_id: 'test2',
type: '@apostrophecms/rich-text',
content: '<h4>Some text</h4>'
}
]
}
};
assert(!apos.area.isEmpty({ area: doc.body }));
assert(!apos.area.isEmpty(doc, 'body'));
assert(!apos.area.isEmpty(doc, 'fullText'));
assert(!apos.area.isEmpty({ area: doc.fullText }));
});
it('both isEmpty and legacy empty methods work on schema fields', function() {
assert(
!apos.schema.fieldTypes.boolean.isEmpty({
type: 'boolean',
name: 'test'
}, true)
);
assert(
!apos.schema.fieldTypes.boolean.isEmpty({
type: 'boolean',
name: 'test'
}, false)
);
assert(
apos.schema.fieldTypes.boolean.isEmpty({
type: 'boolean',
name: 'test'
}, null)
);
assert(
apos.schema.fieldTypes.boolean.isEmpty({
type: 'boolean',
name: 'test'
}, undefined)
);
assert(
apos.schema.fieldTypes.boolean.isEmpty({
type: 'boolean',
name: 'test'
}, 0)
);
assert(
!apos.schema.fieldTypes.boolean.empty({
type: 'boolean',
name: 'test'
}, true)
);
assert(
!apos.schema.fieldTypes.boolean.empty({
type: 'boolean',
name: 'test'
}, false)
);
assert(
apos.schema.fieldTypes.boolean.empty({
type: 'boolean',
name: 'test'
}, null)
);
assert(
apos.schema.fieldTypes.boolean.empty({
type: 'boolean',
name: 'test'
}, undefined)
);
assert(
apos.schema.fieldTypes.boolean.empty({
type: 'boolean',
name: 'test'
}, 0)
);
});
it('area with default widgets gets populated correctly', async function() {
const doc = await apos.article.newInstance();
assert(doc.withDefaults);
assert(doc.withDefaults.items);
assert.strictEqual(doc.withDefaults.items.length, 4);
const types = doc.withDefaults.items.map(item => item.type);
assert(types.includes('@apostrophecms/rich-text'));
assert(types.includes('@apostrophecms/image'));
assert(types.includes('@apostrophecms/video'));
assert(types.includes('@apostrophecms/html'));
const actual = doc.withDefaults.items.map(item => {
const {
type, metaType, _autofocus, content, aposPlaceholder
} = item;
return {
type,
metaType,
_autofocus,
content,
aposPlaceholder
};
});
const expected = [
{
type: '@apostrophecms/rich-text',
metaType: 'widget',
_autofocus: false,
content: '',
aposPlaceholder: undefined
},
{
type: '@apostrophecms/image',
metaType: 'widget',
aposPlaceholder: true,
_autofocus: undefined,
content: undefined
},
{
type: '@apostrophecms/video',
metaType: 'widget',
aposPlaceholder: true,
_autofocus: undefined,
content: undefined
},
{
type: '@apostrophecms/html',
metaType: 'widget',
_autofocus: undefined,
content: undefined,
aposPlaceholder: undefined
}
];
assert.deepEqual(actual, expected);
});
it('expanded menu area with default widgets gets populated correctly', async function() {
const doc = await apos.article.newInstance();
assert.strictEqual(doc.expandedWithDefaults.items.length, 2);
const types = doc.expandedWithDefaults.items.map(item => item.type);
assert(types.includes('@apostrophecms/rich-text'));
assert(types.includes('@apostrophecms/image'));
});
it('nonexistent widget types are rejected in defaults', async function() {
const field = apos.article.schema.find(field => field.name === 'withDefaults');
const saveDef = field.def;
field.def.push('nonexistent-widget');
const doc = await apos.article.newInstance();
assert.strictEqual(doc.expandedWithDefaults.items.length, 2);
const types = doc.expandedWithDefaults.items.map(item => item.type);
assert(types.includes('@apostrophecms/rich-text'));
assert(types.includes('@apostrophecms/image'));
field.def = saveDef;
});
});