test: add Playwright + Vitest tests for raw editor type propagation

Verifies that editing the type field in the raw editor (Cmd+\) immediately
updates the Properties panel type selector without navigation or reload.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Test
2026-03-31 13:51:48 +02:00
parent 39db25a39a
commit 1199840fdc
2 changed files with 129 additions and 0 deletions

View File

@@ -125,6 +125,42 @@ describe('useEditorSaveWithLinks', () => {
})
})
it('handleContentChange calls updateEntry with frontmatter patch when type changes', () => {
const { result } = renderHookWithLinks()
act(() => {
result.current.handleContentChange('/note.md', '---\ntype: Project\nstatus: Active\n---\nBody')
})
expect(updateEntry).toHaveBeenCalledWith('/note.md', { isA: 'Project', status: 'Active' })
})
it('handleContentChange does NOT call updateEntry for frontmatter when unchanged', () => {
const { result } = renderHookWithLinks()
const content = '---\ntype: Essay\n---\nBody text'
act(() => { result.current.handleContentChange('/note.md', content) })
const callCount = updateEntry.mock.calls.length
act(() => { result.current.handleContentChange('/note.md', content + ' more') })
// Same frontmatter, only body changed — no extra updateEntry for frontmatter
expect(updateEntry).toHaveBeenCalledTimes(callCount)
})
it('handleContentChange updates entry when type changes in frontmatter', () => {
const { result } = renderHookWithLinks()
act(() => {
result.current.handleContentChange('/note.md', '---\ntype: Essay\n---\nBody')
})
expect(updateEntry).toHaveBeenCalledWith('/note.md', { isA: 'Essay' })
act(() => {
result.current.handleContentChange('/note.md', '---\ntype: Note\n---\nBody')
})
expect(updateEntry).toHaveBeenCalledWith('/note.md', { isA: 'Note' })
})
it('spreads all properties from useEditorSave onto the return value', () => {
const { result } = renderHookWithLinks()

View File

@@ -0,0 +1,93 @@
import { test, expect } from '@playwright/test'
test.describe('Raw editor type propagation', () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: 1600, height: 900 })
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('editing type in raw editor immediately updates Properties panel', async ({ page }) => {
const noteList = page.locator('[data-testid="note-list-container"]')
await noteList.waitFor({ timeout: 5000 })
const items = noteList.locator('.cursor-pointer')
const count = await items.count()
test.skip(count === 0, 'No notes in note list')
// Click first note and open Properties panel
await items.nth(0).click()
await page.waitForTimeout(300)
await page.keyboard.press('Control+Shift+i')
await page.waitForTimeout(500)
// Find a note with a visible type selector (skip Theme)
const typeSelector = page.locator('[data-testid="type-selector"]')
let originalType = ''
for (let i = 0; i < Math.min(count, 10); i++) {
await items.nth(i).click()
await page.waitForTimeout(400)
if (!(await typeSelector.isVisible())) continue
const trigger = typeSelector.locator('button[role="combobox"]')
const text = (await trigger.textContent())?.trim() ?? ''
if (text && !text.includes('Theme')) {
originalType = text
break
}
}
test.skip(!originalType, 'No non-Theme note with type selector found')
// Open raw editor (Ctrl+\)
await page.keyboard.press('Control+Backslash')
const rawEditor = page.locator('[data-testid="raw-editor-codemirror"]')
await expect(rawEditor).toBeVisible({ timeout: 3000 })
await page.waitForTimeout(300)
// Find the "type:" line in the editor
const typeLineIndex = await page.evaluate(() => {
const lines = document.querySelectorAll('.cm-line')
for (let i = 0; i < lines.length; i++) {
if (lines[i].textContent?.match(/^(?:type|Is A):\s/i)) return i
}
return -1
})
test.skip(typeLineIndex < 0, 'No type field found in frontmatter')
// Click the type line, select it, and retype with new type
const typeLine = page.locator('.cm-line').nth(typeLineIndex)
await typeLine.click()
await page.waitForTimeout(100)
await page.keyboard.press('Home')
await page.keyboard.press('Shift+End')
const newType = originalType.includes('Note') ? 'Project' : 'Note'
await page.keyboard.type(`type: ${newType}`)
// Wait for debounce (500ms) + state propagation
await page.waitForTimeout(800)
// Verify Properties panel shows the new type
const trigger = typeSelector.locator('button[role="combobox"]')
await expect(trigger).toContainText(newType, { timeout: 3000 })
// Restore: select the type line and retype original
const restoreLineIndex = await page.evaluate(() => {
const lines = document.querySelectorAll('.cm-line')
for (let i = 0; i < lines.length; i++) {
if (lines[i].textContent?.match(/^type:\s/)) return i
}
return -1
})
if (restoreLineIndex >= 0) {
const restoreLine = page.locator('.cm-line').nth(restoreLineIndex)
await restoreLine.click()
await page.keyboard.press('Home')
await page.keyboard.press('Shift+End')
await page.keyboard.type(`type: ${originalType.replace(/Note$/, '')}`)
await page.waitForTimeout(800)
}
// Close raw editor
await page.keyboard.press('Control+Backslash')
await page.waitForTimeout(300)
})
})