When toggling from raw mode back to BlockNote, the editor now correctly re-parses content from tab.content instead of using stale cached blocks. Key changes: - useEditorTabSwap: detect rawMode true→false transition, invalidate block cache, and re-parse from tab.content. Added rawSwapPendingRef guard to prevent a second effect run from re-caching stale blocks before the deferred doSwap microtask runs. - useRawMode: added onBeforeRawEnd callback to flush debounced raw editor content synchronously before toggling off. - Editor.tsx: wired rawLatestContentRef and handleBeforeRawEnd to ensure the latest raw content reaches tab.content before the swap. - RawEditorView: exposed latestContentRef so parent can read the latest keystroke content without waiting for the 500ms debounce. - EditorContent: threaded rawLatestContentRef through to RawEditorView. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
152 lines
5.6 KiB
TypeScript
152 lines
5.6 KiB
TypeScript
import { test, expect, type Page } from '@playwright/test'
|
|
import { openCommandPalette, executeCommand } from './helpers'
|
|
|
|
/**
|
|
* Smoke test: editing in raw (CodeMirror) mode and switching back to
|
|
* BlockNote must show the updated content — the two editors stay in sync.
|
|
*/
|
|
|
|
async function openFirstNote(page: Page) {
|
|
const noteList = page.locator('[data-testid="note-list-container"]')
|
|
await noteList.waitFor({ timeout: 5000 })
|
|
await noteList.locator('.cursor-pointer').first().click()
|
|
await page.waitForTimeout(500)
|
|
await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 5000 })
|
|
}
|
|
|
|
async function toggleRawMode(page: Page) {
|
|
await openCommandPalette(page)
|
|
await executeCommand(page, 'Toggle Raw')
|
|
await page.waitForTimeout(500)
|
|
}
|
|
|
|
/** Get the full text content from the CodeMirror raw editor. */
|
|
async function getRawEditorContent(page: Page): Promise<string> {
|
|
return page.evaluate(() => {
|
|
const el = document.querySelector('.cm-content')
|
|
if (!el) return ''
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const view = (el as any).cmTile?.view
|
|
if (view) return view.state.doc.toString() as string
|
|
return el.textContent ?? ''
|
|
})
|
|
}
|
|
|
|
/** Replace the entire raw editor content via CodeMirror dispatch (reliable). */
|
|
async function setRawEditorContent(page: Page, content: string) {
|
|
await page.evaluate((newContent) => {
|
|
const el = document.querySelector('.cm-content')
|
|
if (!el) return
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
const view = (el as any).cmTile?.view
|
|
if (!view) return
|
|
view.dispatch({
|
|
changes: { from: 0, to: view.state.doc.length, insert: newContent },
|
|
})
|
|
}, content)
|
|
}
|
|
|
|
test.describe('Raw editor ↔ BlockNote sync', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.setViewportSize({ width: 1600, height: 900 })
|
|
await page.goto('/')
|
|
await page.waitForLoadState('networkidle')
|
|
})
|
|
|
|
test('editing in raw mode and switching to BlockNote shows updated content', async ({ page }) => {
|
|
await openFirstNote(page)
|
|
|
|
// Read the original H1 from the BlockNote editor
|
|
const h1Locator = page.locator('.bn-editor h1.bn-inline-content').first()
|
|
await expect(h1Locator).toBeVisible({ timeout: 5000 })
|
|
const originalH1 = await h1Locator.textContent()
|
|
expect(originalH1).toBeTruthy()
|
|
|
|
// Toggle to raw mode
|
|
await toggleRawMode(page)
|
|
await expect(page.locator('.cm-content')).toBeVisible()
|
|
|
|
// Read raw content and verify it contains the original title
|
|
const rawContent = await getRawEditorContent(page)
|
|
expect(rawContent).toContain(originalH1!)
|
|
|
|
// Replace the H1 line with a new heading via CodeMirror dispatch
|
|
const newTitle = 'Updated By Raw Editor'
|
|
const updatedContent = rawContent.replace(
|
|
new RegExp(`# ${originalH1!.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`),
|
|
`# ${newTitle}`,
|
|
)
|
|
await setRawEditorContent(page, updatedContent)
|
|
await page.waitForTimeout(600) // Wait for debounce (500ms)
|
|
|
|
// Toggle back to BlockNote
|
|
await toggleRawMode(page)
|
|
await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 5000 })
|
|
await page.waitForTimeout(500)
|
|
|
|
// Verify the BlockNote editor shows the updated heading
|
|
const updatedH1 = page.locator('.bn-editor h1.bn-inline-content').first()
|
|
await expect(updatedH1).toContainText(newTitle, { timeout: 5000 })
|
|
})
|
|
|
|
test('switching BlockNote → raw → BlockNote multiple times preserves content', async ({ page }) => {
|
|
await openFirstNote(page)
|
|
|
|
// Cycle 1: toggle to raw, edit, toggle back
|
|
await toggleRawMode(page)
|
|
await expect(page.locator('.cm-content')).toBeVisible()
|
|
|
|
const rawContent1 = await getRawEditorContent(page)
|
|
const edit1 = rawContent1.replace(/# .+/, '# Cycle One Title')
|
|
await setRawEditorContent(page, edit1)
|
|
await page.waitForTimeout(600)
|
|
|
|
await toggleRawMode(page)
|
|
await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 5000 })
|
|
await page.waitForTimeout(500)
|
|
|
|
await expect(
|
|
page.locator('.bn-editor h1.bn-inline-content').first(),
|
|
).toContainText('Cycle One Title', { timeout: 5000 })
|
|
|
|
// Cycle 2: toggle to raw again, verify content persisted, edit again
|
|
await toggleRawMode(page)
|
|
await expect(page.locator('.cm-content')).toBeVisible()
|
|
|
|
const rawContent2 = await getRawEditorContent(page)
|
|
expect(rawContent2).toContain('Cycle One Title')
|
|
|
|
const edit2 = rawContent2.replace(/# .+/, '# Cycle Two Title')
|
|
await setRawEditorContent(page, edit2)
|
|
await page.waitForTimeout(600)
|
|
|
|
await toggleRawMode(page)
|
|
await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 5000 })
|
|
await page.waitForTimeout(500)
|
|
|
|
await expect(
|
|
page.locator('.bn-editor h1.bn-inline-content').first(),
|
|
).toContainText('Cycle Two Title', { timeout: 5000 })
|
|
})
|
|
|
|
test('appended text in raw mode appears in BlockNote after switch', async ({ page }) => {
|
|
await openFirstNote(page)
|
|
|
|
// Toggle to raw and append text via CodeMirror dispatch
|
|
await toggleRawMode(page)
|
|
await expect(page.locator('.cm-content')).toBeVisible()
|
|
|
|
const content = await getRawEditorContent(page)
|
|
await setRawEditorContent(page, content + '\n\nAppended by raw editor test')
|
|
await page.waitForTimeout(600) // Wait for debounce
|
|
|
|
// Toggle back to BlockNote
|
|
await toggleRawMode(page)
|
|
await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 5000 })
|
|
await page.waitForTimeout(500)
|
|
|
|
// Verify the appended text shows up in BlockNote
|
|
await expect(page.locator('.bn-editor')).toContainText('Appended by raw editor test', { timeout: 5000 })
|
|
})
|
|
})
|