2023-09-25 16:38:21 +03:00
|
|
|
const _ = require('lodash');
|
2023-09-21 17:51:08 +03:00
|
|
|
const {mobiledocToLexical} = require('@tryghost/kg-converters');
|
|
|
|
const models = require('../../../core/server/models');
|
2023-05-15 11:30:32 +03:00
|
|
|
const {agentProvider, fixtureManager, mockManager, matchers} = require('../../utils/e2e-framework');
|
2023-07-05 17:01:24 +03:00
|
|
|
const {anyArray, anyBoolean, anyContentVersion, anyEtag, anyLocationFor, anyObject, anyObjectId, anyISODateTime, anyString, anyUuid} = matchers;
|
2023-05-15 11:30:32 +03:00
|
|
|
|
|
|
|
const tierSnapshot = {
|
|
|
|
id: anyObjectId,
|
|
|
|
created_at: anyISODateTime,
|
|
|
|
updated_at: anyISODateTime
|
|
|
|
};
|
|
|
|
|
|
|
|
const matchPageShallowIncludes = {
|
|
|
|
id: anyObjectId,
|
|
|
|
uuid: anyUuid,
|
|
|
|
comment_id: anyString,
|
|
|
|
url: anyString,
|
|
|
|
authors: anyArray,
|
|
|
|
primary_author: anyObject,
|
|
|
|
tags: anyArray,
|
|
|
|
primary_tag: anyObject,
|
|
|
|
tiers: Array(2).fill(tierSnapshot),
|
|
|
|
created_at: anyISODateTime,
|
|
|
|
updated_at: anyISODateTime,
|
2023-06-16 10:49:12 +03:00
|
|
|
published_at: anyISODateTime,
|
2023-07-07 18:40:22 +03:00
|
|
|
show_title_and_feature_image: anyBoolean
|
2023-05-15 11:30:32 +03:00
|
|
|
};
|
2019-09-20 18:02:45 +03:00
|
|
|
|
2019-02-22 06:17:14 +03:00
|
|
|
describe('Pages API', function () {
|
2023-05-15 11:30:32 +03:00
|
|
|
let agent;
|
2020-11-30 17:25:22 +03:00
|
|
|
|
|
|
|
before(async function () {
|
2023-09-21 17:51:08 +03:00
|
|
|
mockManager.mockLabsEnabled('collectionsCard');
|
2023-05-15 11:30:32 +03:00
|
|
|
agent = await agentProvider.getAdminAPIAgent();
|
|
|
|
await fixtureManager.init('posts');
|
|
|
|
await agent.loginAsOwner();
|
2019-02-22 06:17:14 +03:00
|
|
|
});
|
|
|
|
|
2023-05-15 11:30:32 +03:00
|
|
|
afterEach(function () {
|
|
|
|
mockManager.restore();
|
2019-02-22 06:17:14 +03:00
|
|
|
});
|
|
|
|
|
2023-09-21 17:51:08 +03:00
|
|
|
describe('Read', function () {
|
|
|
|
it('Re-renders html when null', async function () {
|
|
|
|
// "queue" an existing page for re-render as happens when a published page is updated/destroyed
|
|
|
|
const page = await models.Post.findOne({slug: 'static-page-test'});
|
|
|
|
// NOTE: re-rendering only occurs for lexical pages
|
|
|
|
const lexical = mobiledocToLexical(page.get('mobiledoc'));
|
|
|
|
await models.Base.knex.raw('UPDATE posts set html=NULL, mobiledoc=NULL, lexical=? WHERE id=?', [lexical, page.id]);
|
|
|
|
|
|
|
|
await agent
|
|
|
|
.get(`/pages/${page.id}/?formats=mobiledoc,lexical,html`)
|
|
|
|
.expectStatus(200)
|
|
|
|
.matchBodySnapshot({
|
|
|
|
pages: [Object.assign({}, matchPageShallowIncludes)]
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
describe('Browse', function () {
|
|
|
|
it('Re-renders html when null', async function () {
|
|
|
|
// convert inserted pages to lexical and set html=null so we can test re-render
|
|
|
|
const pages = await models.Post.where('type', 'page').fetchAll();
|
|
|
|
for (const page of pages) {
|
|
|
|
if (!page.get('mobiledoc')) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
const lexical = mobiledocToLexical(page.get('mobiledoc'));
|
|
|
|
await models.Base.knex.raw('UPDATE posts set html=NULL, mobiledoc=NULL, lexical=? WHERE id=?', [lexical, page.id]);
|
|
|
|
}
|
|
|
|
|
|
|
|
await agent
|
|
|
|
.get('/pages/?formats=mobiledoc,lexical,html')
|
|
|
|
.expectStatus(200)
|
|
|
|
.matchBodySnapshot({
|
|
|
|
pages: Array(pages.length).fill(Object.assign({}, matchPageShallowIncludes))
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-09-07 00:16:40 +03:00
|
|
|
describe('Create', function () {
|
|
|
|
it('Can create a page with html', async function () {
|
|
|
|
const page = {
|
|
|
|
title: 'HTML test',
|
|
|
|
html: '<p>Testing page creation with html</p>'
|
|
|
|
};
|
|
|
|
|
|
|
|
await agent
|
|
|
|
.post('/pages/?source=html&formats=mobiledoc,lexical,html')
|
|
|
|
.body({pages: [page]})
|
|
|
|
.expectStatus(201)
|
|
|
|
.matchBodySnapshot({
|
|
|
|
pages: [Object.assign({}, matchPageShallowIncludes, {published_at: null})]
|
|
|
|
})
|
|
|
|
.matchHeaderSnapshot({
|
|
|
|
'content-version': anyContentVersion,
|
|
|
|
etag: anyEtag,
|
|
|
|
location: anyLocationFor('pages')
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
2023-07-05 17:01:24 +03:00
|
|
|
describe('Update', function () {
|
2023-07-07 18:40:22 +03:00
|
|
|
it('Can modify show_title_and_feature_image property', async function () {
|
2023-07-05 17:01:24 +03:00
|
|
|
const page = {
|
|
|
|
title: 'Test Page',
|
|
|
|
status: 'draft'
|
|
|
|
};
|
|
|
|
|
|
|
|
const {body: pageBody} = await agent
|
|
|
|
.post('/pages/?formats=mobiledoc,lexical,html', {
|
|
|
|
headers: {
|
|
|
|
'content-type': 'application/json'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.body({pages: [page]})
|
|
|
|
.expectStatus(201);
|
|
|
|
|
|
|
|
const [pageResponse] = pageBody.pages;
|
|
|
|
|
|
|
|
await agent
|
|
|
|
.put(`/pages/${pageResponse.id}/?formats=mobiledoc,lexical,html`)
|
|
|
|
.body({
|
|
|
|
pages: [{
|
|
|
|
id: pageResponse.id,
|
2023-07-07 18:40:22 +03:00
|
|
|
show_title_and_feature_image: false, // default is true
|
2023-07-05 17:01:24 +03:00
|
|
|
updated_at: pageResponse.updated_at // satisfy collision detection
|
|
|
|
}]
|
|
|
|
})
|
|
|
|
.expectStatus(200)
|
|
|
|
.matchBodySnapshot({
|
|
|
|
pages: [Object.assign({}, matchPageShallowIncludes, {
|
|
|
|
published_at: null,
|
2023-07-07 18:40:22 +03:00
|
|
|
show_title_and_feature_image: false
|
2023-07-05 17:01:24 +03:00
|
|
|
})]
|
|
|
|
})
|
|
|
|
.matchHeaderSnapshot({
|
|
|
|
'content-version': anyContentVersion,
|
2023-07-17 16:21:00 +03:00
|
|
|
etag: anyEtag,
|
|
|
|
'x-cache-invalidate': anyString
|
2023-07-05 17:01:24 +03:00
|
|
|
});
|
|
|
|
});
|
2023-09-25 16:38:21 +03:00
|
|
|
|
|
|
|
it('Works with latest collection card', async function () {
|
|
|
|
const initialLexical = {
|
|
|
|
root: {
|
|
|
|
children: [
|
|
|
|
{
|
|
|
|
type: 'collection',
|
|
|
|
version: 1,
|
|
|
|
collection: 'latest',
|
|
|
|
postCount: 3,
|
|
|
|
layout: 'grid',
|
|
|
|
columns: 3,
|
|
|
|
header: 'Latest'
|
|
|
|
}
|
|
|
|
],
|
|
|
|
direction: null,
|
|
|
|
format: '',
|
|
|
|
indent: 0,
|
|
|
|
type: 'root',
|
|
|
|
version: 1
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const updatedLexical = _.cloneDeep(initialLexical);
|
|
|
|
updatedLexical.root.children.push({
|
|
|
|
children: [
|
|
|
|
{
|
|
|
|
detail: 0,
|
|
|
|
format: 0,
|
|
|
|
mode: 'normal',
|
|
|
|
style: '',
|
|
|
|
text: 'Testing',
|
|
|
|
type: 'text',
|
|
|
|
version: 1
|
|
|
|
}
|
|
|
|
],
|
|
|
|
direction: 'ltr',
|
|
|
|
format: '',
|
|
|
|
indent: 0,
|
|
|
|
type: 'paragraph',
|
|
|
|
version: 1
|
|
|
|
});
|
|
|
|
|
|
|
|
const page = {
|
|
|
|
title: 'Latest Collection Card Test',
|
|
|
|
status: 'draft',
|
|
|
|
lexical: JSON.stringify(initialLexical)
|
|
|
|
};
|
|
|
|
|
|
|
|
const {body: createBody} = await agent
|
|
|
|
.post('/pages/?formats=mobiledoc,lexical,html', {
|
|
|
|
headers: {
|
|
|
|
'content-type': 'application/json'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.body({pages: [page]})
|
|
|
|
.expectStatus(201);
|
|
|
|
|
|
|
|
const [createResponse] = createBody.pages;
|
|
|
|
|
2023-09-26 14:14:20 +03:00
|
|
|
// does not match body snapshot as we mostly only care about the request succeeding.
|
|
|
|
// matching body snapshots is tricky because collection cards have dynamic content,
|
|
|
|
// most notably the post dates which are always changing.
|
2023-09-25 16:38:21 +03:00
|
|
|
await agent
|
|
|
|
.put(`/pages/${createResponse.id}/?formats=mobiledoc,lexical,html`)
|
|
|
|
.body({
|
|
|
|
pages: [{
|
|
|
|
id: createResponse.id,
|
|
|
|
lexical: JSON.stringify(updatedLexical),
|
|
|
|
updated_at: createResponse.updated_at // satisfy collision detection
|
|
|
|
}]
|
|
|
|
})
|
2023-09-26 14:14:20 +03:00
|
|
|
.expectStatus(200);
|
2023-09-25 16:38:21 +03:00
|
|
|
});
|
|
|
|
|
|
|
|
it('Works with featured collection card', async function () {
|
|
|
|
const initialLexical = {
|
|
|
|
root: {
|
|
|
|
children: [
|
|
|
|
{
|
|
|
|
type: 'collection',
|
|
|
|
version: 1,
|
|
|
|
collection: 'featured',
|
|
|
|
postCount: 3,
|
|
|
|
layout: 'grid',
|
|
|
|
columns: 3,
|
|
|
|
header: 'Featured'
|
|
|
|
}
|
|
|
|
],
|
|
|
|
direction: null,
|
|
|
|
format: '',
|
|
|
|
indent: 0,
|
|
|
|
type: 'root',
|
|
|
|
version: 1
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const updatedLexical = _.cloneDeep(initialLexical);
|
|
|
|
updatedLexical.root.children.push({
|
|
|
|
children: [
|
|
|
|
{
|
|
|
|
detail: 0,
|
|
|
|
format: 0,
|
|
|
|
mode: 'normal',
|
|
|
|
style: '',
|
|
|
|
text: 'Testing',
|
|
|
|
type: 'text',
|
|
|
|
version: 1
|
|
|
|
}
|
|
|
|
],
|
|
|
|
direction: 'ltr',
|
|
|
|
format: '',
|
|
|
|
indent: 0,
|
|
|
|
type: 'paragraph',
|
|
|
|
version: 1
|
|
|
|
});
|
|
|
|
|
|
|
|
const page = {
|
|
|
|
title: 'Latest Collection Card Test',
|
|
|
|
status: 'draft',
|
|
|
|
lexical: JSON.stringify(initialLexical)
|
|
|
|
};
|
|
|
|
|
|
|
|
const {body: createBody} = await agent
|
|
|
|
.post('/pages/?formats=mobiledoc,lexical,html', {
|
|
|
|
headers: {
|
|
|
|
'content-type': 'application/json'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.body({pages: [page]})
|
|
|
|
.expectStatus(201);
|
|
|
|
|
|
|
|
const [createResponse] = createBody.pages;
|
|
|
|
|
|
|
|
await agent
|
|
|
|
.put(`/pages/${createResponse.id}/?formats=mobiledoc,lexical,html`)
|
|
|
|
.body({
|
|
|
|
pages: [{
|
|
|
|
id: createResponse.id,
|
|
|
|
lexical: JSON.stringify(updatedLexical),
|
|
|
|
updated_at: createResponse.updated_at // satisfy collision detection
|
|
|
|
}]
|
|
|
|
})
|
2023-09-26 14:14:20 +03:00
|
|
|
.expectStatus(200);
|
2023-09-25 16:38:21 +03:00
|
|
|
});
|
2024-03-06 23:30:00 +03:00
|
|
|
|
|
|
|
describe('Access', function () {
|
|
|
|
describe('Visibility is set to tiers', function () {
|
|
|
|
it('Saves only paid tiers', async function () {
|
|
|
|
const page = {
|
|
|
|
title: 'Test Page',
|
|
|
|
status: 'draft'
|
|
|
|
};
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
const products = await models.Product.findAll();
|
|
|
|
|
|
|
|
const freeTier = products.models[0];
|
|
|
|
const paidTier = products.models[1];
|
|
|
|
|
|
|
|
const {body: pageBody} = await agent
|
|
|
|
.post('/pages/', {
|
|
|
|
headers: {
|
|
|
|
'content-type': 'application/json'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.body({pages: [page]})
|
|
|
|
.expectStatus(201);
|
|
|
|
|
|
|
|
const [pageResponse] = pageBody.pages;
|
|
|
|
|
|
|
|
await agent
|
|
|
|
.put(`/pages/${pageResponse.id}`)
|
|
|
|
.body({
|
|
|
|
pages: [{
|
|
|
|
id: pageResponse.id,
|
|
|
|
updated_at: pageResponse.updated_at,
|
|
|
|
visibility: 'tiers',
|
|
|
|
tiers: [
|
|
|
|
{id: freeTier.id},
|
|
|
|
{id: paidTier.id}
|
|
|
|
]
|
|
|
|
}]
|
|
|
|
})
|
|
|
|
.expectStatus(200)
|
|
|
|
.matchHeaderSnapshot({
|
|
|
|
'content-version': anyContentVersion,
|
|
|
|
etag: anyEtag,
|
|
|
|
'x-cache-invalidate': anyString
|
|
|
|
})
|
|
|
|
.matchBodySnapshot({
|
|
|
|
pages: [Object.assign({}, matchPageShallowIncludes, {
|
|
|
|
published_at: null,
|
|
|
|
tiers: [
|
|
|
|
{type: paidTier.get('type'), ...tierSnapshot}
|
|
|
|
]
|
|
|
|
})]
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
|
|
|
});
|
2023-07-05 17:01:24 +03:00
|
|
|
});
|
|
|
|
|
2023-05-15 11:30:32 +03:00
|
|
|
describe('Copy', function () {
|
|
|
|
it('Can copy a page', async function () {
|
|
|
|
const page = {
|
|
|
|
title: 'Test Page',
|
|
|
|
status: 'published'
|
|
|
|
};
|
2022-09-13 19:29:37 +03:00
|
|
|
|
2023-05-15 11:30:32 +03:00
|
|
|
const {body: pageBody} = await agent
|
|
|
|
.post('/pages/?formats=mobiledoc,lexical,html', {
|
|
|
|
headers: {
|
|
|
|
'content-type': 'application/json'
|
2022-09-13 19:29:37 +03:00
|
|
|
}
|
2023-05-15 11:30:32 +03:00
|
|
|
})
|
|
|
|
.body({pages: [page]})
|
2023-07-05 17:01:24 +03:00
|
|
|
.expectStatus(201)
|
|
|
|
.matchBodySnapshot({
|
|
|
|
pages: [Object.assign({}, matchPageShallowIncludes)]
|
|
|
|
});
|
2023-05-15 11:30:32 +03:00
|
|
|
|
|
|
|
const [pageResponse] = pageBody.pages;
|
|
|
|
|
|
|
|
await agent
|
|
|
|
.post(`/pages/${pageResponse.id}/copy?formats=mobiledoc,lexical`)
|
|
|
|
.expectStatus(201)
|
|
|
|
.matchBodySnapshot({
|
2023-07-05 17:01:24 +03:00
|
|
|
pages: [Object.assign({}, matchPageShallowIncludes, {published_at: null})]
|
2023-05-15 11:30:32 +03:00
|
|
|
})
|
|
|
|
.matchHeaderSnapshot({
|
|
|
|
'content-version': anyContentVersion,
|
|
|
|
etag: anyEtag,
|
|
|
|
location: anyLocationFor('pages')
|
|
|
|
});
|
2022-03-02 19:00:22 +03:00
|
|
|
});
|
2019-02-22 06:17:14 +03:00
|
|
|
});
|
2023-08-09 01:44:54 +03:00
|
|
|
|
|
|
|
describe('Convert', function () {
|
2023-09-07 00:16:40 +03:00
|
|
|
it('can convert a mobiledoc page to lexical', async function () {
|
2023-08-09 01:44:54 +03:00
|
|
|
const mobiledoc = JSON.stringify({
|
|
|
|
version: '0.3.1',
|
|
|
|
ghostVersion: '4.0',
|
|
|
|
markups: [],
|
|
|
|
atoms: [],
|
|
|
|
cards: [],
|
|
|
|
sections: [
|
|
|
|
[1, 'p', [
|
|
|
|
[0, [], 0, 'This is some great content.']
|
|
|
|
]]
|
|
|
|
]
|
|
|
|
});
|
|
|
|
const expectedLexical = JSON.stringify({
|
|
|
|
root: {
|
|
|
|
children: [
|
|
|
|
{
|
|
|
|
children: [
|
|
|
|
{
|
|
|
|
detail: 0,
|
|
|
|
format: 0,
|
|
|
|
mode: 'normal',
|
|
|
|
style: '',
|
|
|
|
text: 'This is some great content.',
|
|
|
|
type: 'text',
|
|
|
|
version: 1
|
|
|
|
}
|
|
|
|
],
|
|
|
|
direction: 'ltr',
|
|
|
|
format: '',
|
|
|
|
indent: 0,
|
|
|
|
type: 'paragraph',
|
|
|
|
version: 1
|
|
|
|
}
|
|
|
|
],
|
|
|
|
direction: 'ltr',
|
|
|
|
format: '',
|
|
|
|
indent: 0,
|
|
|
|
type: 'root',
|
|
|
|
version: 1
|
|
|
|
}
|
|
|
|
});
|
|
|
|
const pageData = {
|
|
|
|
title: 'Test Post',
|
|
|
|
status: 'published',
|
|
|
|
mobiledoc: mobiledoc,
|
|
|
|
lexical: null
|
|
|
|
};
|
|
|
|
|
|
|
|
const {body: pageBody} = await agent
|
|
|
|
.post('/pages/?formats=mobiledoc,lexical,html', {
|
|
|
|
headers: {
|
|
|
|
'content-type': 'application/json'
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.body({pages: [pageData]})
|
|
|
|
.expectStatus(201);
|
|
|
|
|
|
|
|
const [pageResponse] = pageBody.pages;
|
|
|
|
|
2023-10-17 23:38:51 +03:00
|
|
|
const convertedResponse = await agent
|
2023-08-09 01:44:54 +03:00
|
|
|
.put(`/pages/${pageResponse.id}/?formats=mobiledoc,lexical,html&convert_to_lexical=true`)
|
|
|
|
.body({pages: [Object.assign({}, pageResponse)]})
|
|
|
|
.expectStatus(200)
|
|
|
|
.matchBodySnapshot({
|
|
|
|
pages: [Object.assign({}, matchPageShallowIncludes, {lexical: expectedLexical, mobiledoc: null})]
|
|
|
|
})
|
|
|
|
.matchHeaderSnapshot({
|
|
|
|
'content-version': anyContentVersion,
|
|
|
|
etag: anyEtag
|
|
|
|
});
|
2023-10-17 23:38:51 +03:00
|
|
|
|
|
|
|
// rerunning the conversion against a converted post should not change it
|
|
|
|
const convertedPage = convertedResponse.body.pages[0];
|
|
|
|
const expectedConvertedLexical = convertedPage.lexical;
|
|
|
|
await agent
|
|
|
|
.put(`/pages/${pageResponse.id}/?formats=mobiledoc,lexical,html&convert_to_lexical=true`)
|
|
|
|
.body({pages: [Object.assign({}, convertedPage)]})
|
|
|
|
.expectStatus(200)
|
|
|
|
.matchBodySnapshot({
|
|
|
|
pages: [Object.assign({}, matchPageShallowIncludes, {lexical: expectedConvertedLexical, mobiledoc: null})]
|
|
|
|
})
|
|
|
|
.matchHeaderSnapshot({
|
|
|
|
'content-version': anyContentVersion,
|
|
|
|
etag: anyEtag
|
|
|
|
});
|
2023-08-09 01:44:54 +03:00
|
|
|
});
|
|
|
|
});
|
2019-02-22 06:17:14 +03:00
|
|
|
});
|