Ghost/ghost/core/test/e2e-api/admin/pages.test.js
Kevin Ansfield 8c91662a47
Added conversion to beta editor format when creating content via ?source=html (#18000)
closes https://github.com/TryGhost/Product/issues/3803

Previously when the beta editor was enabled, using `?source=html` to create posts via the API would create posts in the old editor rather than the beta. This change switches conversion over to the new editor format when the beta is enabled so the full flow can be tested.

- added `htmlToLexicalConverter` method to our lexical library
- updated post and page input serializers to add html-to-lexical conversion when the beta editor is enabled
- updated post model to handle the mobiledoc+lexical co-existing state
  - this is a special case that is only valid for `?source=html` because providing both directly via the API is prohibited
  - we need the extra check here because at the input serializer layer we don't have access to the model to check if we're updating a mobiledoc post or a lexical post so the serializer sets both formats on a `?source=html` request when the beta is enabled and lets the model handle choosing the correct one
2023-09-06 21:16:40 +00:00

238 lines
8.1 KiB
JavaScript

const {agentProvider, fixtureManager, mockManager, matchers} = require('../../utils/e2e-framework');
const {anyArray, anyBoolean, anyContentVersion, anyEtag, anyLocationFor, anyObject, anyObjectId, anyISODateTime, anyString, anyUuid} = matchers;
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,
published_at: anyISODateTime,
post_revisions: anyArray,
show_title_and_feature_image: anyBoolean
};
describe('Pages API', function () {
let agent;
before(async function () {
agent = await agentProvider.getAdminAPIAgent();
await fixtureManager.init('posts');
await agent.loginAsOwner();
});
afterEach(function () {
mockManager.restore();
});
describe('Create', function () {
it('Can create a page with html', async function () {
mockManager.mockLabsDisabled('lexicalEditor');
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')
});
});
it('Can create a page with html (labs.lexicalEditor)', async function () {
mockManager.mockLabsEnabled('lexicalEditor');
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')
});
});
});
describe('Update', function () {
it('Can modify show_title_and_feature_image property', async function () {
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,
show_title_and_feature_image: false, // default is true
updated_at: pageResponse.updated_at // satisfy collision detection
}]
})
.expectStatus(200)
.matchBodySnapshot({
pages: [Object.assign({}, matchPageShallowIncludes, {
published_at: null,
show_title_and_feature_image: false
})]
})
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag,
'x-cache-invalidate': anyString
});
});
});
describe('Copy', function () {
it('Can copy a page', async function () {
const page = {
title: 'Test Page',
status: 'published'
};
const {body: pageBody} = await agent
.post('/pages/?formats=mobiledoc,lexical,html', {
headers: {
'content-type': 'application/json'
}
})
.body({pages: [page]})
.expectStatus(201)
.matchBodySnapshot({
pages: [Object.assign({}, matchPageShallowIncludes)]
});
const [pageResponse] = pageBody.pages;
await agent
.post(`/pages/${pageResponse.id}/copy?formats=mobiledoc,lexical`)
.expectStatus(201)
.matchBodySnapshot({
pages: [Object.assign({}, matchPageShallowIncludes, {published_at: null})]
})
.matchHeaderSnapshot({
'content-version': anyContentVersion,
etag: anyEtag,
location: anyLocationFor('pages')
});
});
});
describe('Convert', function () {
it('can convert a mobiledoc page to lexical', async function () {
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;
await agent
.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
});
});
});
});