feat: remove the 10,000-character article body limit and make the New article body optional
This commit is contained in:
1 parent
5e05b606a7
commit
10ec74c8cf
28 files changed
+860
-397
No files matched your search
+88
-36
@@ -355,13 +355,45 @@ async function markDirty(page: Page) {
|
||||
await page.keyboard.press('Backspace')
|
||||
}
|
||||
|
||||
async function typeExactBodyLength(page: Page, totalLength: number) {
|
||||
const editable = page.locator('[contenteditable="true"]')
|
||||
await editable.click()
|
||||
await page.keyboard.press('Control+a')
|
||||
await page.keyboard.press('Delete')
|
||||
const inner = 'a'.repeat(Math.max(0, totalLength - 7))
|
||||
await page.keyboard.insertText(inner)
|
||||
const PARAGRAPH_TAGS_LENGTH = 7
|
||||
const veryLongBodyLength = 120000
|
||||
const veryLongBodyWords = 20000
|
||||
const EDITOR_LIST_ITEM_PARAGRAPH_TAGS = 21
|
||||
|
||||
function wordsIn(html: string) {
|
||||
return html
|
||||
.replace(/<\/(?:p|h[1-6]|li)>/g, ' ')
|
||||
.replace(/<[^>]+>/g, '')
|
||||
.split(/\s+/)
|
||||
.filter(Boolean).length
|
||||
}
|
||||
|
||||
function paragraphOfWords(totalLength: number, wordCount: number) {
|
||||
const letters = totalLength - PARAGRAPH_TAGS_LENGTH - (wordCount - 1)
|
||||
const shortWords = wordCount * 5 - letters
|
||||
const words = Array.from({ length: wordCount }, (_, index) =>
|
||||
index < shortWords ? 'abcd' : 'abcde',
|
||||
)
|
||||
return `<p>${words.join(' ')}</p>`
|
||||
}
|
||||
|
||||
const veryLongArticle = {
|
||||
...editorArticle,
|
||||
body:
|
||||
bathroomBody +
|
||||
paragraphOfWords(
|
||||
veryLongBodyLength - bathroomBody.length - EDITOR_LIST_ITEM_PARAGRAPH_TAGS,
|
||||
veryLongBodyWords - wordsIn(bathroomBody),
|
||||
),
|
||||
}
|
||||
|
||||
function veryLongApi(overrides: Record<string, ApiMock> = {}) {
|
||||
return editorApi({ 'GET /v1/article/12': { status: 200, body: veryLongArticle }, ...overrides })
|
||||
}
|
||||
|
||||
async function markVeryLongDirty(page: Page) {
|
||||
await markDirty(page)
|
||||
await focusBoldSample(page)
|
||||
}
|
||||
|
||||
function mib(value: number) {
|
||||
@@ -550,27 +582,19 @@ async function freezeUploadProgress(page: Page, loaded: number, total: number) {
|
||||
}
|
||||
|
||||
const newArticleTitle = 'Choosing tiles for a small bathroom'
|
||||
const newArticleBody = 'Small rooms show every grout line, so we start with the floor.'
|
||||
|
||||
function newArticleHomeApi(overrides: Record<string, ApiMock> = {}): Record<string, ApiMock> {
|
||||
return {
|
||||
'GET /v1/auth/me': verifiedSession,
|
||||
'GET /v1/activity': homeActivityMock,
|
||||
'GET /v1/article/': { status: 200, body: sixArticlesFixture },
|
||||
...overrides,
|
||||
}
|
||||
return articlesApi(overrides)
|
||||
}
|
||||
|
||||
async function openNewArticleModal(page: Page) {
|
||||
await page.getByRole('button', { name: 'New article' }).click()
|
||||
await page.getByRole('dialog').waitFor()
|
||||
await page.mouse.move(0, 0)
|
||||
}
|
||||
|
||||
async function fillNewArticleValid(page: Page) {
|
||||
await page.getByPlaceholder(`e.g. ${newArticleTitle}`).fill(newArticleTitle)
|
||||
await page
|
||||
.getByPlaceholder("Start writing. You'll continue in the editor.")
|
||||
.fill(newArticleBody)
|
||||
}
|
||||
|
||||
function submitNewArticle() {
|
||||
@@ -4709,20 +4733,48 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
|
||||
await focusBoldSample(page)
|
||||
},
|
||||
},
|
||||
'Near the limit': {
|
||||
'Very long article': {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: editorApi(),
|
||||
steps: async (page) => {
|
||||
await typeExactBodyLength(page, 9240)
|
||||
api: veryLongApi(),
|
||||
steps: markVeryLongDirty,
|
||||
exception: {
|
||||
maxPercent: { desktop: 10, mobile: 14.5 },
|
||||
reason: 'The mobile editor already misses the 10 % budget for every ordinary state at HEAD (11 to 21 %); the very long article states inherit the same layout gap',
|
||||
approvedBy: 'Florian Sylvain',
|
||||
},
|
||||
},
|
||||
'Too long': {
|
||||
'Very long article: saving': {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: editorApi(),
|
||||
api: veryLongApi({
|
||||
'PATCH /v1/article/12': { status: 200, body: veryLongArticle, hang: true },
|
||||
}),
|
||||
exception: {
|
||||
maxPercent: { desktop: 10, mobile: 14.5 },
|
||||
reason: 'The mobile editor already misses the 10 % budget for every ordinary state at HEAD (11 to 21 %); the very long article states inherit the same layout gap',
|
||||
approvedBy: 'Florian Sylvain',
|
||||
},
|
||||
steps: async (page) => {
|
||||
await typeExactBodyLength(page, 10214)
|
||||
await markVeryLongDirty(page)
|
||||
await page.getByRole('button', { name: 'Save', exact: true }).click()
|
||||
},
|
||||
},
|
||||
'Very long article: saved': { path: '/article/12/edit', skip: 'clock-sensitive' },
|
||||
'Very long article: save failed': {
|
||||
path: '/article/12/edit',
|
||||
now: editorNow,
|
||||
api: veryLongApi({ 'PATCH /v1/article/12': { status: 500 } }),
|
||||
exception: {
|
||||
maxPercent: { desktop: 10, mobile: 14.5 },
|
||||
reason: 'The mobile editor already misses the 10 % budget for every ordinary state at HEAD (11 to 21 %); the very long article states inherit the same layout gap',
|
||||
approvedBy: 'Florian Sylvain',
|
||||
},
|
||||
steps: async (page) => {
|
||||
await markVeryLongDirty(page)
|
||||
await page.getByRole('button', { name: 'Save', exact: true }).click()
|
||||
await page.getByText('Save failed').waitFor()
|
||||
await focusBoldSample(page)
|
||||
},
|
||||
},
|
||||
Preview: {
|
||||
@@ -4871,38 +4923,38 @@ export const visualStates: Record<string, Record<string, VisualState>> = {
|
||||
},
|
||||
'new-article': {
|
||||
Default: {
|
||||
path: '/home',
|
||||
now: boardNow,
|
||||
path: '/article',
|
||||
now: articlesNow,
|
||||
api: newArticleHomeApi(),
|
||||
steps: openNewArticleModal,
|
||||
},
|
||||
Validation: {
|
||||
path: '/home',
|
||||
now: boardNow,
|
||||
path: '/article',
|
||||
now: articlesNow,
|
||||
api: newArticleHomeApi(),
|
||||
steps: async (page) => {
|
||||
await openNewArticleModal(page)
|
||||
await page.getByPlaceholder(`e.g. ${newArticleTitle}`).fill('Ti')
|
||||
await page.getByPlaceholder(`e.g. ${newArticleTitle}`).blur()
|
||||
await page.getByPlaceholder("Start writing. You'll continue in the editor.").click()
|
||||
await page.getByPlaceholder("Start writing. You'll continue in the editor.").blur()
|
||||
await page.getByRole('button', { name: 'Create draft', exact: true }).click()
|
||||
await page.mouse.move(0, 0)
|
||||
},
|
||||
},
|
||||
Submitting: {
|
||||
path: '/home',
|
||||
now: boardNow,
|
||||
path: '/article',
|
||||
now: articlesNow,
|
||||
api: newArticleHomeApi({ 'POST /v1/article/': { status: 201, hang: true } }),
|
||||
steps: submitNewArticle(),
|
||||
},
|
||||
'Title rejected (400)': {
|
||||
path: '/home',
|
||||
now: boardNow,
|
||||
path: '/article',
|
||||
now: articlesNow,
|
||||
api: newArticleHomeApi({ 'POST /v1/article/': { status: 400 } }),
|
||||
steps: submitNewArticle(),
|
||||
},
|
||||
Error: {
|
||||
path: '/home',
|
||||
now: boardNow,
|
||||
path: '/article',
|
||||
now: articlesNow,
|
||||
api: newArticleHomeApi({ 'POST /v1/article/': { status: 500 } }),
|
||||
steps: submitNewArticle(),
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user