1
0
Fork 0
mirror of https://github.com/codex-team/codex.docs.git synced 2025-07-19 05:09:41 +02:00

Page creation basics (#7)

* Page cration basics

* remove unused code

* add client-side Header validation

* remove static method

* rm await duplication
This commit is contained in:
Peter Savchenko 2018-10-04 22:08:21 +03:00 committed by GitHub
parent 5c0560a2ed
commit 073772c047
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 476 additions and 93 deletions

View file

@ -13,6 +13,7 @@
"@babel/polyfill": "^7.0.0", "@babel/polyfill": "^7.0.0",
"body-parser": "latest", "body-parser": "latest",
"codex.editor": "^2.0.11", "codex.editor": "^2.0.11",
"codex.editor.header": "^2.0.5",
"cookie-parser": "~1.4.3", "cookie-parser": "~1.4.3",
"debug": "~2.6.9", "debug": "~2.6.9",
"express": "~4.16.0", "express": "~4.16.0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1 +1 @@
/*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */html{-webkit-text-size-adjust:100%;line-height:1.15}body{margin:0}h1{font-size:2em;margin:.67em 0}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}:root{--color-line-gray:#e8e8eb;--color-link-active:#388ae5;--color-text-main:#1d202b;--color-text-second:#7b7e89;--layout-padding-horisontal:40px;--layout-width-aside:250px;--layout-width-main-col:650px}.docs{display:-webkit-box;display:-ms-flexbox;display:flex;padding:0 40px;padding:0 var(--layout-padding-horisontal)}.docs__aside{width:250px;width:var(--layout-width-aside)}.docs__content{-webkit-box-flex:2;-ms-flex-positive:2;flex-grow:2;word-wrap:break-word}.docs__content-inner{margin:0 auto;max-width:650px;max-width:var(--layout-width-main-col)}.docs__aside,.docs__content{padding:40px 0}.docs-header{border-bottom:1px solid #e8e8eb;border-bottom:1px solid var(--color-line-gray);display:-webkit-box;display:-ms-flexbox;display:flex;font-size:15.8px;line-height:50px;padding:0 40px;padding:0 var(--layout-padding-horisontal)}.docs-header a{display:inline-block;text-decoration:none}.docs-header__logo{color:inherit;font-weight:700}.docs-header__menu{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 0 0 auto}.docs-header__menu li{list-style:none;margin-left:20px}.docs-header__menu a:not(.docs-header__button){color:inherit}.docs-header__menu a:not(.docs-header__button):hover{color:#388ae5;color:var(--color-link-active)}.docs-header__button{background:#388ae5;background:var(--color-link-active);border-radius:3px;color:#fff;display:inline-block;font-size:14px;line-height:1em;padding:9px 15px}.docs-header__button svg{margin:0 .3em 0 -.05em}.docs-header__button:hover{background:#387ecc}.docs-header__button{margin:auto 30px auto auto}.docs-aside{color:#7b7e89;color:var(--color-text-second);font-size:15px}.docs-aside a{text-decoration:none}.docs-aside__section{margin-bottom:30px}.docs-aside__section-title{color:#388ae5;color:var(--color-link-active);margin-bottom:15px}.docs-aside__section-list{list-style:none;padding-left:0}.docs-aside__section-list a{color:inherit;display:inline-block;padding:8px 0}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#1d202b;color:var(--color-text-main);font-family:system-ui,-apple-system,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,Helvetica,Arial,Verdana}svg{fill:currentColor} /*! normalize.css v8.0.0 | MIT License | github.com/necolas/normalize.css */html{-webkit-text-size-adjust:100%;line-height:1.15}body{margin:0}h1{font-size:2em;margin:.67em 0}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}[hidden],template{display:none}:root{--color-line-gray:#e8e8eb;--color-link-active:#388ae5;--color-text-main:#1d202b;--color-text-second:#7b7e89;--layout-padding-horisontal:40px;--layout-padding-vertical:40px;--layout-width-aside:250px;--layout-width-main-col:650px}.docs{display:-webkit-box;display:-ms-flexbox;display:flex;padding:0 40px;padding:0 var(--layout-padding-horisontal)}.docs__aside{width:250px;width:var(--layout-width-aside)}.docs__content{-webkit-box-flex:2;-ms-flex-positive:2;flex-grow:2;word-wrap:break-word}.docs__content-inner{margin:0 auto;max-width:650px;max-width:var(--layout-width-main-col)}.docs__aside,.docs__content{padding:40px 0;padding:var(--layout-padding-vertical) 0}.docs-header{border-bottom:1px solid #e8e8eb;border-bottom:1px solid var(--color-line-gray);display:-webkit-box;display:-ms-flexbox;display:flex;font-size:15.8px;line-height:50px;padding:0 40px;padding:0 var(--layout-padding-horisontal)}.docs-header a{display:inline-block;text-decoration:none}.docs-header__logo{color:inherit;font-weight:700}.docs-header__menu{display:-webkit-box;display:-ms-flexbox;display:flex;margin:0 0 0 auto}.docs-header__menu li{list-style:none;margin-left:20px}.docs-header__menu a:not(.docs-header__button){color:inherit}.docs-header__menu a:not(.docs-header__button):hover{color:#388ae5;color:var(--color-link-active)}.docs-header__button{background:#388ae5;background:var(--color-link-active);border-radius:3px;color:#fff;display:inline-block;font-size:14px;line-height:1em;padding:9px 15px}.docs-header__button svg{margin:0 .3em 0 -.05em}.docs-header__button:hover{background:#387ecc}.docs-header__button{margin:auto 30px auto auto}.docs-aside{color:#7b7e89;color:var(--color-text-second);font-size:15px}.docs-aside a{text-decoration:none}.docs-aside__section{margin-bottom:30px}.docs-aside__section-title{color:#388ae5;color:var(--color-link-active);margin-bottom:15px}.docs-aside__section-list{list-style:none;padding-left:0}.docs-aside__section-list a{color:inherit;display:inline-block;padding:8px 0}.writing-header{background:#fff;-webkit-box-shadow:0 3px 10px #fff;box-shadow:0 3px 10px #fff;display:-webkit-box;display:-ms-flexbox;display:flex;margin-top:-40px;margin-top:calc(-1 * var(--layout-padding-vertical));padding:15px 0;position:-webkit-sticky;position:sticky;top:0;z-index:2}.writing-header__save{background:#388ae5;background:var(--color-link-active);border-radius:3px;color:#fff;display:inline-block;font-size:14px;line-height:1em;padding:9px 15px}.writing-header__save svg{margin:0 .3em 0 -.05em}.writing-header__save:hover{background:#387ecc}.writing-header__save{margin-left:auto}.writing-header__left{color:#7b7e89;color:var(--color-text-second);margin:auto 0}body{-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;color:#1d202b;color:var(--color-text-main);font-family:system-ui,-apple-system,Segoe UI,Roboto,Noto Sans,Ubuntu,Cantarell,Helvetica Neue,Helvetica,Arial,Verdana}svg{fill:currentColor}

View file

@ -12,7 +12,7 @@ class Pages {
* @returns {['title', 'body']} * @returns {['title', 'body']}
*/ */
static get REQUIRED_FIELDS() { static get REQUIRED_FIELDS() {
return ['title', 'body']; return [ 'body' ];
} }
/** /**
@ -48,23 +48,47 @@ class Pages {
* @returns {Promise<Page>} * @returns {Promise<Page>}
*/ */
static async insert(data) { static async insert(data) {
if (!Pages.validate(data)) { try {
throw new Error('Invalid request format'); Pages.validate(data);
const page = new Model(data);
return page.save();
} catch (validationError) {
throw new Error(validationError);
} }
const page = new Model(data);
return page.save();
} }
/** /**
* Check PageData object for required fields * Check PageData object for required fields
* *
* @param {PageData} data * @param {PageData} data
* @returns {boolean} * @throws {Error} - validation error
*/ */
static validate(data) { static validate(data) {
return Pages.REQUIRED_FIELDS.every(field => typeof data[field] !== 'undefined'); const allRequiredFields = Pages.REQUIRED_FIELDS.every(field => typeof data[field] !== 'undefined');
if (!allRequiredFields) {
throw new Error('Some of required fields is missed');
}
const hasBlocks = data.body && data.body.blocks && Array.isArray(data.body.blocks) && data.body.blocks.length > 0;
if (!hasBlocks) {
throw new Error('Page body is invalid');
}
const hasHeaderAsFirstBlock = data.body.blocks[0].type === 'header';
if (!hasHeaderAsFirstBlock) {
throw new Error('First page Block must be a Header');
}
const headerIsNotEmpty = data.body.blocks[0].data.text.replace('<br>', '').trim() !== '';
if (!headerIsNotEmpty) {
throw new Error('Please, fill page Header');
}
} }
/** /**

View file

@ -1,7 +1,42 @@
import CodeXEditor from 'codex.editor'; import CodeXEditor from 'codex.editor';
import Header from 'codex.editor.header';
/**
* Class for working with Editor.js
*/
export default class Editor { export default class Editor {
/**
* Creates Editor instance
*/
constructor() { constructor() {
this.editor = new CodeXEditor(); this.editor = new CodeXEditor({
tools: {
header: {
class: Header,
config: {
placeholder: 'Enter a title'
}
}
},
data: {
blocks: [
{
type: 'header',
data: {
text: '',
level: 2
}
}
]
}
});
}
/**
* Return Editor data
* @return {Promise.<{}>}
*/
save() {
return this.editor.saver.save();
} }
} }

View file

@ -1,29 +1,115 @@
/** /**
* Module for pages create/edit * Module for pages create/edit
*/ */
/**
* @typedef {object} editorData
* @property {{type, data}[]} blocks - array of Blocks
* @property {string} version - used Editor version
* @property {number} time - saving time
*/
export default class Writing { export default class Writing {
/**
* Creates base properties
*/
constructor(){ constructor(){
this.editorWrapper = null;
this.editor = null; this.editor = null;
this.nodes = {
editorWrapper: null,
saveButton: null,
parentIdSelector: null,
}
} }
/** /**
* Called by ModuleDispatcher to initialize module from DOM * Called by ModuleDispatcher to initialize module from DOM
* @param {object} settings - module settings
* @param {HTMLElement} moduleEl - module element
*/ */
init(config, moduleEl) { init(settings, moduleEl) {
this.editorWrapper = document.createElement('div'); /**
this.editorWrapper.id = 'codex-editor'; * Create Editor
*/
this.nodes.editorWrapper = document.createElement('div');
this.nodes.editorWrapper.id = 'codex-editor';
moduleEl.appendChild(this.editorWrapper); moduleEl.appendChild(this.nodes.editorWrapper);
this.loadEditor().then((editor) => { this.loadEditor().then((editor) => {
this.editor = editor; this.editor = editor;
}) });
/**
* Activate form elements
*/
this.nodes.saveButton = moduleEl.querySelector('[name="js-submit"]');
this.nodes.saveButton.addEventListener('click', () => {
this.saveButtonClicked();
});
this.nodes.parentIdSelector = moduleEl.querySelector('[name="parent"]');
}; };
/**
* Loads class for working with Editor
* @return {Promise<Editor>}
*/
async loadEditor(){ async loadEditor(){
const {default: Editor} = await import(/* webpackChunkName: "editor" */ './../classes/editor'); const {default: Editor} = await import(/* webpackChunkName: "editor" */ './../classes/editor');
return new Editor(); return new Editor();
} }
/**
* Returns all writing form data
* @throws {Error} - validation error
* @return {Promise.<{parent: string, body: {editorData}}>}
*/
async getData(){
const editorData = await this.editor.save();
const firstBlock = editorData.blocks.length ? editorData.blocks[0] : null;
const title = firstBlock && firstBlock.type === 'header' ? firstBlock.data.text : null;
if (!title) {
throw new Error('Entry should start with Header');
}
return {
parent: this.nodes.parentIdSelector.value,
body: editorData
};
}
/**
* Handler for clicks on the Save button
*/
async saveButtonClicked(){
try {
const writingData = await this.getData();
try {
let response = await fetch('/page', {
method: 'PUT',
headers: {
"Content-Type": "application/json; charset=utf-8",
},
body: JSON.stringify(writingData),
});
response = await response.json();
if (response.success){
document.location = '/page/' + response.result._id;
} else {
alert(response.error);
console.log('Validation failed:', response.error);
}
} catch (sendingError) {
console.log('Saving request failed:', sendingError);
}
} catch (savingError){
alert(savingError);
console.log('Saving error: ', savingError);
}
}
} }

View file

@ -0,0 +1,20 @@
.writing-header {
display: flex;
padding: 15px 0;
margin-top: calc(-1 * var(--layout-padding-vertical));
position: sticky;
top: 0;
background: #fff;
z-index: 2;
box-shadow: 0 3px 10px #fff;
&__save {
@apply --button;
margin-left: auto;
}
&__left {
margin: auto 0;
color: var(--color-text-second);
}
}

View file

@ -18,6 +18,6 @@
&__aside, &__aside,
&__content { &__content {
padding: 40px 0; padding: var(--layout-padding-vertical) 0;
} }
} }

View file

@ -3,6 +3,7 @@
@import url('layout.pcss'); @import url('layout.pcss');
@import url('components/header.pcss'); @import url('components/header.pcss');
@import url('components/aside.pcss'); @import url('components/aside.pcss');
@import url('components/writing.pcss');
body { body {
font-family: system-ui, Helvetica, Arial, Verdana; font-family: system-ui, Helvetica, Arial, Verdana;

View file

@ -8,6 +8,7 @@
* Site layout sizes * Site layout sizes
*/ */
--layout-padding-horisontal: 40px; --layout-padding-horisontal: 40px;
--layout-padding-vertical: 40px;
--layout-width-aside: 250px; --layout-width-aside: 250px;
--layout-width-main-col: 650px; --layout-width-main-col: 650px;

View file

@ -67,10 +67,10 @@ class Page {
* @param {PageData} pageData * @param {PageData} pageData
*/ */
set data(pageData) { set data(pageData) {
const {title, body, parent} = pageData; const {body, parent} = pageData;
this.title = title || this.title;
this.body = body || this.body; this.body = body || this.body;
this.title = this.extractTitleFromBody();
this._parent = parent || this._parent; this._parent = parent || this._parent;
} }
@ -88,6 +88,16 @@ class Page {
}; };
} }
/**
* Extract first header from editor data
* @return {string}
*/
extractTitleFromBody() {
const headerBlock = this.body ? this.body.blocks.find(block => block.type === 'header') : '';
return headerBlock ? headerBlock.data.text : '';
}
/** /**
* Link given page as parent * Link given page as parent
* *

View file

@ -7,7 +7,11 @@ const Pages = require('../controllers/pages');
* Create new page form * Create new page form
*/ */
router.get('/page/new', async (req, res) => { router.get('/page/new', async (req, res) => {
res.render('pages/form'); let pagesAvailable = await Pages.getAll();
res.render('pages/form', {
pagesAvailable
});
}); });
/** /**

View file

@ -1,6 +1,25 @@
{% extends 'layout.twig' %} {% extends 'layout.twig' %}
{% block body %} {% block body %}
<style>
.docs-header__button {
visibility: hidden;
}
</style>
<section data-module="writing"> <section data-module="writing">
<header class="writing-header">
<span class="writing-header__left">
New Page at the
<select name="parent">
<option value="0">Root</option>
{% for page in pagesAvailable %}
<option value="{{ page._id }}">{{ page.title }}</option>
{% endfor %}
</select>
</span>
<span class="writing-header__save" name="js-submit">
Save
</span>
</header>
</section> </section>
{% endblock %} {% endblock %}

View file

@ -11,7 +11,7 @@ describe('Page model', () => {
let {data} = page; let {data} = page;
expect(data._id).to.be.undefined; expect(data._id).to.be.undefined;
expect(data.title).to.be.undefined; expect(data.title).to.be.empty;
expect(data.body).to.be.undefined; expect(data.body).to.be.undefined;
expect(data.parent).to.be.undefined; expect(data.parent).to.be.undefined;
@ -20,11 +20,23 @@ describe('Page model', () => {
data = page.data; data = page.data;
expect(data._id).to.be.undefined; expect(data._id).to.be.undefined;
expect(data.title).to.be.undefined; expect(data.title).to.be.empty;
expect(data.body).to.be.undefined; expect(data.body).to.be.undefined;
expect(data.parent).to.be.undefined; expect(data.parent).to.be.undefined;
const initialData = {_id: 'page_id', title: 'Test page', body: 'Test page body'}; const initialData = {
_id: 'page_id',
body: {
blocks: [
{
type: 'header',
data: {
text: 'Page header'
}
}
]
}
};
page = new Page(initialData); page = new Page(initialData);
@ -33,19 +45,27 @@ describe('Page model', () => {
data = page.data; data = page.data;
expect(data._id).to.equal(initialData._id); expect(data._id).to.equal(initialData._id);
expect(data.title).to.equal(initialData.title); expect(data.title).to.equal(initialData.body.blocks[0].data.text);
expect(data.body).to.equal(initialData.body); expect(data.body).to.deep.equal(initialData.body);
expect(data.parent).to.be.undefined; expect(data.parent).to.be.undefined;
expect(json._id).to.equal(initialData._id); expect(json._id).to.equal(initialData._id);
expect(json.title).to.equal(initialData.title); expect(json.title).to.equal(initialData.body.blocks[0].data.text);
expect(json.body).to.equal(initialData.body); expect(json.body).to.deep.equal(initialData.body);
expect(json.parent).to.be.undefined; expect(json.parent).to.be.undefined;
const update = { const update = {
_id: 12345, _id: 12345,
title: 'Test page', body: {
body: 'Test page body' blocks: [
{
type: 'header',
data: {
text: 'Updated page header'
}
}
]
}
}; };
page.data = update; page.data = update;
@ -53,19 +73,30 @@ describe('Page model', () => {
data = page.data; data = page.data;
expect(data._id).to.equal(initialData._id); expect(data._id).to.equal(initialData._id);
expect(data.title).to.equal(update.title); expect(data.title).to.equal(update.body.blocks[0].data.text);
expect(data.body).to.equal(update.body); expect(data.body).to.equal(update.body);
expect(data.parent).to.be.undefined; expect(data.parent).to.be.undefined;
}); });
it('Saving, updating and deleting model in the database', async () => { it('Saving, updating and deleting model in the database', async () => {
const initialData = {title: 'Test page', body: 'Test page body'}; const initialData = {
body: {
blocks: [
{
type: 'header',
data: {
text: 'Page header'
}
}
]
}
};
const page = new Page(initialData); const page = new Page(initialData);
let savedPage = await page.save(); let savedPage = await page.save();
expect(savedPage._id).not.be.undefined; expect(savedPage._id).not.be.undefined;
expect(savedPage.title).to.equal(initialData.title); expect(savedPage.title).to.equal(initialData.body.blocks[0].data.text);
expect(savedPage.body).to.equal(initialData.body); expect(savedPage.body).to.equal(initialData.body);
expect(page._id).not.be.undefined; expect(page._id).not.be.undefined;
@ -73,9 +104,20 @@ describe('Page model', () => {
expect(insertedPage._id).to.equal(page._id); expect(insertedPage._id).to.equal(page._id);
expect(insertedPage.title).to.equal(page.title); expect(insertedPage.title).to.equal(page.title);
expect(insertedPage.body).to.equal(page.body); expect(insertedPage.body).to.deep.equal(page.body);
const updateData = {title: 'Updated test page', body: 'Updated test page body'}; const updateData = {
body: {
blocks: [
{
type: 'header',
data: {
text: 'Updated page header'
}
}
]
}
};
page.data = updateData; page.data = updateData;
await page.save(); await page.save();
@ -85,8 +127,8 @@ describe('Page model', () => {
const updatedPage = await pages.findOne({_id: page._id}); const updatedPage = await pages.findOne({_id: page._id});
expect(updatedPage._id).to.equal(savedPage._id); expect(updatedPage._id).to.equal(savedPage._id);
expect(updatedPage.title).to.equal(updateData.title); expect(updatedPage.title).to.equal(updateData.body.blocks[0].data.text);
expect(updatedPage.body).to.equal(updateData.body); expect(updatedPage.body).to.deep.equal(updateData.body);
await page.destroy(); await page.destroy();
@ -98,7 +140,18 @@ describe('Page model', () => {
}); });
it('Static get method', async () => { it('Static get method', async () => {
const initialData = {title: 'Test page', body: 'Test page body'}; const initialData = {
body: {
blocks: [
{
type: 'header',
data: {
text: 'Test Page header'
}
}
]
}
};
const page = new Page(initialData); const page = new Page(initialData);
const savedPage = await page.save(); const savedPage = await page.save();
@ -108,16 +161,38 @@ describe('Page model', () => {
const {data} = foundPage; const {data} = foundPage;
expect(data._id).to.equal(savedPage._id); expect(data._id).to.equal(savedPage._id);
expect(data.title).to.equal(initialData.title); expect(data.title).to.equal(initialData.body.blocks[0].data.text);
expect(data.body).to.equal(initialData.body); expect(data.body).to.deep.equal(initialData.body);
await page.destroy(); await page.destroy();
}); });
it('Static getAll method', async () => { it('Static getAll method', async () => {
const pagesToSave = [ const pagesToSave = [
new Page({title: 'Page 1', body: 'Page 1 body'}), new Page({
new Page({title: 'Page 2', body: 'Page 2 body'}) body: {
blocks: [
{
type: 'header',
data: {
text: 'Page 1 header'
}
}
]
}
}),
new Page({
body: {
blocks: [
{
type: 'header',
data: {
text: 'Page 2 header'
}
}
]
}
})
]; ];
const savedPages = await Promise.all(pagesToSave.map(page => page.save())); const savedPages = await Promise.all(pagesToSave.map(page => page.save()));
@ -126,16 +201,42 @@ describe('Page model', () => {
expect(foundPages.length).to.equal(2); expect(foundPages.length).to.equal(2);
foundPages.forEach((page, i) => { foundPages.forEach((page, i) => {
expect(page.title).to.equal(pagesToSave[i].title); expect(page.title).to.equal(pagesToSave[i].body.blocks[0].data.text);
expect(page.body).to.equal(pagesToSave[i].body); expect(page.body).to.deep.equal(pagesToSave[i].body);
}); });
}); });
it('Parent pages', async () => { it('Parent pages', async () => {
const parent = new Page({title: 'Parent page', body: 'Parent page body'}); const parent = new Page(
{
body: {
blocks: [
{
type: 'header',
data: {
text: 'Parent page header'
}
}
]
}
}
);
const {_id: parentId} = await parent.save(); const {_id: parentId} = await parent.save();
const child = new Page({title: 'Child page', body: 'Child page body'}); const child = new Page(
{
body: {
blocks: [
{
type: 'header',
data: {
text: 'Child page header'
}
}
]
}
}
);
child.parent = parent; child.parent = parent;
@ -144,8 +245,8 @@ describe('Page model', () => {
const testedParent = await child.parent; const testedParent = await child.parent;
expect(testedParent._id).to.equal(parentId); expect(testedParent._id).to.equal(parentId);
expect(testedParent.title).to.equal(parent.title); expect(testedParent.title).to.equal(parent.body.blocks[0].data.text);
expect(testedParent.body).to.equal(parent.body); expect(testedParent.body).to.deep.equal(parent.body);
const children = await parent.children; const children = await parent.children;
@ -154,12 +255,31 @@ describe('Page model', () => {
const testedChild = children.pop(); const testedChild = children.pop();
expect(testedChild._id).to.equal(childId); expect(testedChild._id).to.equal(childId);
expect(testedChild.title).to.equal(child.title); expect(testedChild.title).to.equal(child.body.blocks[0].data.text);
expect(testedChild.body).to.equal(child.body); expect(testedChild.body).to.deep.equal(child.body);
expect(testedChild._parent).to.equal(child._parent); expect(testedChild._parent).to.equal(child._parent);
expect(testedChild._parent).to.equal(parent._id); expect(testedChild._parent).to.equal(parent._id);
parent.destroy(); parent.destroy();
child.destroy(); child.destroy();
}); });
it('Extracting title from page body', async () => {
const pageData = {
body: {
blocks: [
{
type: 'header',
data: {
text: 'Page header'
}
}
]
}
};
const page = new Page(pageData);
expect(page.title).to.equal(pageData.body.blocks[0].data.text);
});
}); });

View file

@ -15,12 +15,20 @@ describe('Pages REST: ', () => {
}); });
it('Creating page', async () => { it('Creating page', async () => {
const title = 'Test page'; const body = {
const body = 'Test page body'; blocks: [
{
type: 'header',
data: {
text: 'Page header'
}
}
]
};
const res = await agent const res = await agent
.put('/page') .put('/page')
.send({title, body}); .send({body});
expect(res).to.have.status(200); expect(res).to.have.status(200);
expect(res).to.be.json; expect(res).to.be.json;
@ -29,15 +37,15 @@ describe('Pages REST: ', () => {
expect(success).to.be.true; expect(success).to.be.true;
expect(result._id).to.be.a('string'); expect(result._id).to.be.a('string');
expect(result.title).to.equal(title); expect(result.title).to.equal(body.blocks[0].data.text);
expect(result.body).to.equal(body); expect(result.body).to.deep.equal(body);
const createdPage = await model.get(result._id); const createdPage = await model.get(result._id);
expect(createdPage).not.be.null; expect(createdPage).not.be.null;
expect(createdPage._id).to.equal(result._id); expect(createdPage._id).to.equal(result._id);
expect(createdPage.title).to.equal(title); expect(createdPage.title).to.equal(body.blocks[0].data.text);
expect(createdPage.body).to.equal(body); expect(createdPage.body).to.deep.equal(body);
createdPage.destroy(); createdPage.destroy();
}); });
@ -53,16 +61,24 @@ describe('Pages REST: ', () => {
const {success, error} = res.body; const {success, error} = res.body;
expect(success).to.be.false; expect(success).to.be.false;
expect(error).to.equal('Invalid request format'); expect(error).to.equal('Error: Some of required fields is missed');
}); });
it('Finding page', async () => { it('Finding page', async () => {
const title = 'Test page'; const body = {
const body = 'Test page body'; blocks: [
{
type: 'header',
data: {
text: 'Page header'
}
}
]
};
const put = await agent const put = await agent
.put('/page') .put('/page')
.send({title, body}); .send({body});
expect(put).to.have.status(200); expect(put).to.have.status(200);
expect(put).to.be.json; expect(put).to.be.json;
@ -81,8 +97,8 @@ describe('Pages REST: ', () => {
const foundPage = await model.get(_id); const foundPage = await model.get(_id);
expect(foundPage._id).to.equal(_id); expect(foundPage._id).to.equal(_id);
expect(foundPage.title).to.equal(title); expect(foundPage.title).to.equal(body.blocks[0].data.text);
expect(foundPage.body).to.equal(body); expect(foundPage.body).to.deep.equal(body);
foundPage.destroy(); foundPage.destroy();
}); });
@ -100,24 +116,40 @@ describe('Pages REST: ', () => {
}); });
it('Updating page', async () => { it('Updating page', async () => {
const title = 'Test page'; const body = {
const body = 'Test page body'; blocks: [
{
type: 'header',
data: {
text: 'Page header'
}
}
]
};
let res = await agent let res = await agent
.put('/page') .put('/page')
.send({title, body}); .send({body});
expect(res).to.have.status(200); expect(res).to.have.status(200);
expect(res).to.be.json; expect(res).to.be.json;
const {result: {_id}} = res.body; const {result: {_id}} = res.body;
const updatedTitle = 'Updated test page'; const updatedBody = {
const updatedBody = 'Updated test page body'; blocks: [
{
type: 'header',
data: {
text: 'Updated page header'
}
}
]
};
res = await agent res = await agent
.post(`/page/${_id}`) .post(`/page/${_id}`)
.send({title: updatedTitle, body: updatedBody}); .send({body: updatedBody});
expect(res).to.have.status(200); expect(res).to.have.status(200);
expect(res).to.be.json; expect(res).to.be.json;
@ -126,26 +158,35 @@ describe('Pages REST: ', () => {
expect(success).to.be.true; expect(success).to.be.true;
expect(result._id).to.equal(_id); expect(result._id).to.equal(_id);
expect(result.title).not.equal(title); expect(result.title).not.equal(body.blocks[0].data.text);
expect(result.title).to.equal(updatedTitle); expect(result.title).to.equal(updatedBody.blocks[0].data.text);
expect(result.body).not.equal(body); expect(result.body).not.equal(body);
expect(result.body).to.equal(updatedBody); expect(result.body).to.deep.equal(updatedBody);
const updatedPage = await model.get(_id); const updatedPage = await model.get(_id);
expect(updatedPage._id).to.equal(_id); expect(updatedPage._id).to.equal(_id);
expect(updatedPage.title).not.equal(title); expect(updatedPage.title).not.equal(body.blocks[0].data.text);
expect(updatedPage.title).to.equal(updatedTitle); expect(updatedPage.title).to.equal(updatedBody.blocks[0].data.text);
expect(updatedPage.body).not.equal(body); expect(updatedPage.body).not.equal(body);
expect(updatedPage.body).to.equal(updatedBody); expect(updatedPage.body).to.deep.equal(updatedBody);
updatedPage.destroy(); updatedPage.destroy();
}); });
it('Updating page with not existing id', async () => { it('Updating page with not existing id', async () => {
const res = await agent const res = await agent
. post('/page/not-existing-id') .post('/page/not-existing-id')
.send({title: 'Updated title', body: 'Updated body'}); .send({body: {
blocks: [
{
type: 'header',
data: {
text: 'Page header'
}
}
]
}});
expect(res).to.have.status(400); expect(res).to.have.status(400);
expect(res).to.be.json; expect(res).to.be.json;
@ -157,12 +198,20 @@ describe('Pages REST: ', () => {
}); });
it('Removing page', async () => { it('Removing page', async () => {
const title = 'Test page'; const body = {
const body = 'Test page body'; blocks: [
{
type: 'header',
data: {
text: 'Page header'
}
}
]
};
let res = await agent let res = await agent
.put('/page') .put('/page')
.send({title, body}); .send({body});
expect(res).to.have.status(200); expect(res).to.have.status(200);
expect(res).to.be.json; expect(res).to.be.json;
@ -179,8 +228,8 @@ describe('Pages REST: ', () => {
expect(success).to.be.true; expect(success).to.be.true;
expect(result._id).to.be.undefined; expect(result._id).to.be.undefined;
expect(result.title).to.equal(title); expect(result.title).to.equal(body.blocks[0].data.text);
expect(result.body).to.equal(body); expect(result.body).to.deep.equal(body);
const deletedPage = await model.get(_id); const deletedPage = await model.get(_id);

View file

@ -1453,6 +1453,10 @@ code-point-at@^1.0.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
codex.editor.header@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/codex.editor.header/-/codex.editor.header-2.0.5.tgz#fe0cc7d710b090e877499b7d9951ea36fddfbdc9"
codex.editor@^2.0.11: codex.editor@^2.0.11:
version "2.0.11" version "2.0.11"
resolved "https://registry.yarnpkg.com/codex.editor/-/codex.editor-2.0.11.tgz#c45bb2c85888f96c129a6bfb01b0268f06768e15" resolved "https://registry.yarnpkg.com/codex.editor/-/codex.editor-2.0.11.tgz#c45bb2c85888f96c129a6bfb01b0268f06768e15"