test: Playwright smoke test for serializer blank lines fix
Verifies tight lists stay tight, headings don't gain extra blank lines, and saving without editing doesn't add whitespace changes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
168
tests/smoke/blocknote-serializer-blank-lines.spec.ts
Normal file
168
tests/smoke/blocknote-serializer-blank-lines.spec.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
import { test, expect } from '@playwright/test'
|
||||
import { openCommandPalette, executeCommand } from './helpers'
|
||||
|
||||
test.describe('BlockNote serializer blank lines fix', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1600, height: 900 })
|
||||
await page.goto('/')
|
||||
await page.waitForLoadState('networkidle')
|
||||
})
|
||||
|
||||
test('tight lists are serialized without blank lines between items', async ({ page }) => {
|
||||
// 1. Open the first note in the note list (mock content has bullet lists)
|
||||
const noteListContainer = page.locator('[data-testid="note-list-container"]')
|
||||
await noteListContainer.waitFor({ timeout: 5000 })
|
||||
const firstNote = noteListContainer.locator('.cursor-pointer').first()
|
||||
await firstNote.click()
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// 2. Wait for the BlockNote editor to render
|
||||
const editorContainer = page.locator('.bn-editor')
|
||||
await expect(editorContainer).toBeVisible({ timeout: 5000 })
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// 3. Type a character in the editor to trigger serialization
|
||||
await editorContainer.click()
|
||||
// Move cursor to end of first paragraph and type a space
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.type(' ')
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// 4. Toggle to raw editor to see the serialized content
|
||||
await openCommandPalette(page)
|
||||
await executeCommand(page, 'Toggle Raw')
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// 5. Read the raw editor content (CodeMirror textarea)
|
||||
const rawEditor = page.locator('[data-testid="raw-editor-codemirror"]')
|
||||
await expect(rawEditor).toBeVisible({ timeout: 5000 })
|
||||
|
||||
// Get the raw markdown content via the CodeMirror view
|
||||
const rawContent = await page.evaluate(() => {
|
||||
const el = document.querySelector('[data-testid="raw-editor-codemirror"]')
|
||||
if (!el) return ''
|
||||
// CodeMirror stores its content in the view
|
||||
const cm = el.querySelector('.cm-content')
|
||||
return cm?.textContent ?? ''
|
||||
})
|
||||
|
||||
// 6. Verify: bullet list items should NOT have blank lines between them
|
||||
// The mock content has bullet list items like "- First level item"
|
||||
// After BlockNote serialization, they should still be tight (no blank lines)
|
||||
expect(rawContent).toBeTruthy()
|
||||
|
||||
// Check there are no patterns like "* item\n\n* item" or "- item\n\n- item"
|
||||
// in the serialized output. We look for list markers since the raw editor
|
||||
// shows the serialized content.
|
||||
const lines = rawContent.split('\n')
|
||||
for (let i = 0; i < lines.length - 2; i++) {
|
||||
const line = lines[i]
|
||||
const nextLine = lines[i + 1]
|
||||
const lineAfter = lines[i + 2]
|
||||
// If current line is a list item and line after blank is also a list item,
|
||||
// that's the bug
|
||||
if (/^[-*+]\s/.test(line.trim()) && nextLine?.trim() === '' && /^[-*+]\s/.test(lineAfter?.trim() ?? '')) {
|
||||
throw new Error(
|
||||
`Found blank line between list items at line ${i + 1}:\n` +
|
||||
` "${line}"\n (blank)\n "${lineAfter}"\n` +
|
||||
'This indicates the serializer is adding extra blank lines between list items.'
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test('saving without editing does not add whitespace changes', async ({ page }) => {
|
||||
// 1. Open the first note
|
||||
const noteListContainer = page.locator('[data-testid="note-list-container"]')
|
||||
await noteListContainer.waitFor({ timeout: 5000 })
|
||||
const firstNote = noteListContainer.locator('.cursor-pointer').first()
|
||||
await firstNote.click()
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// 2. Wait for BlockNote to load the note
|
||||
const editorContainer = page.locator('.bn-editor')
|
||||
await expect(editorContainer).toBeVisible({ timeout: 5000 })
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// 3. Toggle to raw mode to read the note content BEFORE any edits
|
||||
await openCommandPalette(page)
|
||||
await executeCommand(page, 'Toggle Raw')
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
const rawEditor = page.locator('[data-testid="raw-editor-codemirror"]')
|
||||
await expect(rawEditor).toBeVisible({ timeout: 5000 })
|
||||
|
||||
const contentBefore = await page.evaluate(() => {
|
||||
const el = document.querySelector('[data-testid="raw-editor-codemirror"]')
|
||||
const cm = el?.querySelector('.cm-content')
|
||||
return cm?.textContent ?? ''
|
||||
})
|
||||
|
||||
// 4. Toggle back to WYSIWYG, make no edits, then toggle raw again
|
||||
await openCommandPalette(page)
|
||||
await executeCommand(page, 'Toggle Raw')
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
// Just click the editor without typing
|
||||
await page.locator('.bn-editor').click()
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// Toggle raw again to compare
|
||||
await openCommandPalette(page)
|
||||
await executeCommand(page, 'Toggle Raw')
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
const contentAfter = await page.evaluate(() => {
|
||||
const el = document.querySelector('[data-testid="raw-editor-codemirror"]')
|
||||
const cm = el?.querySelector('.cm-content')
|
||||
return cm?.textContent ?? ''
|
||||
})
|
||||
|
||||
// 5. Content should be identical (no extra blank lines added)
|
||||
expect(contentAfter).toBe(contentBefore)
|
||||
})
|
||||
|
||||
test('headings do not have extra blank lines added after them', async ({ page }) => {
|
||||
// 1. Open the first note
|
||||
const noteListContainer = page.locator('[data-testid="note-list-container"]')
|
||||
await noteListContainer.waitFor({ timeout: 5000 })
|
||||
const firstNote = noteListContainer.locator('.cursor-pointer').first()
|
||||
await firstNote.click()
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
const editorContainer = page.locator('.bn-editor')
|
||||
await expect(editorContainer).toBeVisible({ timeout: 5000 })
|
||||
|
||||
// 2. Type a character to trigger serialization
|
||||
await editorContainer.click()
|
||||
await page.keyboard.press('End')
|
||||
await page.keyboard.type(' ')
|
||||
await page.waitForTimeout(300)
|
||||
|
||||
// 3. Toggle raw editor
|
||||
await openCommandPalette(page)
|
||||
await executeCommand(page, 'Toggle Raw')
|
||||
await page.waitForTimeout(500)
|
||||
|
||||
const rawContent = await page.evaluate(() => {
|
||||
const el = document.querySelector('[data-testid="raw-editor-codemirror"]')
|
||||
const cm = el?.querySelector('.cm-content')
|
||||
return cm?.textContent ?? ''
|
||||
})
|
||||
|
||||
// 4. Verify: no heading is followed by more than one blank line
|
||||
const lines = rawContent.split('\n')
|
||||
for (let i = 0; i < lines.length - 2; i++) {
|
||||
if (/^#{1,6}\s/.test(lines[i].trim())) {
|
||||
// A heading followed by two consecutive blank lines is the bug
|
||||
if (lines[i + 1]?.trim() === '' && lines[i + 2]?.trim() === '') {
|
||||
throw new Error(
|
||||
`Found multiple blank lines after heading at line ${i + 1}:\n` +
|
||||
` "${lines[i]}"\n (blank)\n (blank)\n` +
|
||||
'Headings should have at most one blank line after them.'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user