test: remove non-core Playwright tests to bring suite under 10 minutes

Removed 18 test files (73 tests) that don't meet core E2E criteria:

Pure cosmetic/UI-detail tests:
- clickable-editor-empty-space (cursor:text CSS)
- filter-pills-height (exact pixel height check)
- properties-panel-style (label casing, font sizes, styling)
- title-emoji-inline (flex layout, font size, alignment)
- title-field-alignment (CSS variables, separator border)
- type-icon-color-sidebar-label (icon color CSS variables)
- migrate-to-flat-vault (title field styling, H1 CSS hiding)
- image-drop-overlay-fix (drag overlay visibility)

Duplicate/redundant coverage:
- note-icon (fully duplicated by note-icon-emoji-picker)
- note-icon-emoji-picker (granular emoji picker, not core flow)
- emoji-icon-everywhere (emoji display locations)
- split-notelist-god-component (stale refactor validation)
- split-usenoteactions (stale refactor validation)
- open-in-new-window (command existence check)
- three-source-of-truth (covered by cache-invalidation-vault-open)
- show-type-instances-inspector (inspector detail)
- note-list-incomplete-relationships (complex async timing)
- note-list-preview-snippet (snippet formatting detail)

Suite: 195 → 122 tests, runtime: ~12.5m → ~9.6m (under 10m target)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Test
2026-03-31 00:19:26 +02:00
parent 289ab82ed1
commit ff1f166ca6
18 changed files with 0 additions and 1422 deletions

View File

@@ -1,66 +0,0 @@
import { test, expect } from '@playwright/test'
const EDITOR_CONTAINER = '.editor__blocknote-container'
test.describe('Clickable editor empty space — click below content focuses editor', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
// Open the first note to mount the editor
const noteList = page.locator('[data-testid="note-list-container"]')
await noteList.waitFor({ timeout: 5_000 })
await noteList.locator('.cursor-pointer').first().click()
await page.waitForTimeout(500)
await page.waitForSelector(EDITOR_CONTAINER, { timeout: 5_000 })
})
test('container onClick handler focuses the editor', async ({ page }) => {
// Dispatch a click directly on the container element (simulating empty space click)
// This is equivalent to a user clicking on the container background, which happens
// when the editor content is shorter than the container.
const focused = await page.evaluate((sel) => {
const container = document.querySelector(sel)
if (!container) return 'no-container'
container.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }))
const active = document.activeElement
if (!active) return 'no-active'
return container.contains(active) ? 'focused' : `active-is-${active.tagName}`
}, EDITOR_CONTAINER)
expect(focused).toBe('focused')
})
test('editor container has cursor:text CSS for visual affordance', async ({ page }) => {
// Verify the cursor:text rule is in the stylesheet
const hasCursorText = await page.evaluate(() => {
for (const sheet of document.styleSheets) {
try {
for (const rule of sheet.cssRules) {
if (rule instanceof CSSStyleRule &&
rule.selectorText?.includes('editor__blocknote-container') &&
rule.style.cursor === 'text') {
return true
}
}
} catch { /* cross-origin sheets throw */ }
}
return false
})
expect(hasCursorText).toBe(true)
})
test('clicking on actual editor content does not disrupt normal editing', async ({ page }) => {
const editor = page.locator('.bn-editor').first()
await editor.waitFor({ timeout: 5_000 })
await editor.click()
await page.waitForTimeout(200)
const editorHasFocus = await page.evaluate(() => {
const active = document.activeElement
if (!active) return false
const container = document.querySelector('.editor__blocknote-container')
return container?.contains(active) ?? false
})
expect(editorHasFocus).toBe(true)
})
})

View File

@@ -1,66 +0,0 @@
import { test, expect } from '@playwright/test'
test.describe('Emoji icon shown everywhere title appears', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
await page.waitForTimeout(2500)
})
test('emoji icon appears in editor and note list after setting it', async ({ page }) => {
// Open a note
const noteItem = page.locator('[data-testid="note-list-container"] .cursor-pointer').first()
await noteItem.waitFor({ timeout: 5000 })
await noteItem.click()
await page.waitForTimeout(500)
// Remove any existing icon first for a clean state
const iconDisplay = page.locator('[data-testid="note-icon-display"]')
if (await iconDisplay.isVisible()) {
await iconDisplay.click()
await page.locator('[data-testid="note-icon-remove"]').click()
await page.waitForTimeout(300)
}
// Set an emoji icon
const iconArea = page.locator('[data-testid="note-icon-area"]')
await iconArea.hover()
await page.locator('[data-testid="note-icon-add"]').click()
const firstEmoji = page.locator('[data-testid="emoji-option"]').first()
const emojiText = await firstEmoji.textContent()
expect(emojiText).toBeTruthy()
await firstEmoji.click()
await page.waitForTimeout(500)
// Verify emoji in note list item
const noteListText = await noteItem.textContent()
expect(noteListText).toContain(emojiText!)
// Verify emoji appears in the editor NoteIcon area
// Wait for frontmatter update to propagate through the single-note reload cycle
const iconAfterSet = page.locator('[data-testid="note-icon-display"]')
await expect(iconAfterSet).toBeVisible({ timeout: 8000 })
await expect(iconAfterSet).toHaveText(emojiText!, { timeout: 3000 })
})
test('note without emoji shows no emoji span in tab or note list', async ({ page }) => {
// Open a note
const noteItem = page.locator('[data-testid="note-list-container"] .cursor-pointer').first()
await noteItem.waitFor({ timeout: 5000 })
await noteItem.click()
await page.waitForTimeout(500)
// Remove icon if present
const iconDisplay = page.locator('[data-testid="note-icon-display"]')
if (await iconDisplay.isVisible()) {
await iconDisplay.click()
await page.locator('[data-testid="note-icon-remove"]').click()
await page.waitForTimeout(300)
}
// The note list item title row should not contain an emoji span (mr-1)
const titleRow = noteItem.locator('.truncate.text-foreground').first()
const emojiSpans = titleRow.locator('.mr-1')
await expect(emojiSpans).toHaveCount(0)
})
})

View File

@@ -1,40 +0,0 @@
import { test, expect } from '@playwright/test'
test.describe('Filter pills height matches breadcrumb bar', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('filter pills row height equals breadcrumb bar height (45px)', async ({ page }) => {
// Click on the "Projects" type section in the sidebar
await page.getByText('Projects', { exact: true }).click()
// Filter pills should now be visible
const pills = page.getByTestId('filter-pills')
await expect(pills).toBeVisible()
// Verify height matches breadcrumb bar (45px)
const pillsBox = await pills.boundingBox()
expect(pillsBox).not.toBeNull()
expect(pillsBox!.height).toBe(45)
// Verify all three pills are present
await expect(page.getByTestId('filter-pill-open')).toBeVisible()
await expect(page.getByTestId('filter-pill-archived')).toBeVisible()
await expect(page.getByTestId('filter-pill-trashed')).toBeVisible()
// Open pill should be active by default
const openPill = page.getByTestId('filter-pill-open')
await expect(openPill).toHaveAttribute('aria-selected', 'true')
// Verify pills are keyboard accessible (Tab + Enter)
await openPill.focus()
await expect(openPill).toBeFocused()
await page.keyboard.press('Tab')
const archivedPill = page.getByTestId('filter-pill-archived')
await expect(archivedPill).toBeFocused()
await page.keyboard.press('Enter')
await expect(archivedPill).toHaveAttribute('aria-selected', 'true')
})
})

View File

@@ -1,114 +0,0 @@
import { test, expect } from '@playwright/test'
const DROP_OVERLAY = '.editor__drop-overlay'
const EDITOR_CONTAINER = '.editor__blocknote-container'
test.describe('Image drop overlay — internal drag does not trigger overlay', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
// Open the first note to mount the editor
const noteList = page.locator('[data-testid="note-list-container"]')
await noteList.waitFor({ timeout: 5_000 })
await noteList.locator('.cursor-pointer').first().click()
await page.waitForTimeout(300)
await page.waitForSelector(EDITOR_CONTAINER, { timeout: 5_000 })
})
test('internal drag (no image files) does not show the overlay', async ({ page }) => {
// Simulate an internal drag (e.g. block or tab) — dataTransfer has no files
await page.locator(EDITOR_CONTAINER).first().dispatchEvent('dragover', {
bubbles: true,
cancelable: true,
})
// The overlay should NOT appear for non-file drags
await expect(page.locator(DROP_OVERLAY)).not.toBeVisible()
})
test('dragover with image file shows the overlay', async ({ page }) => {
// Simulate an OS file drag with an image file in dataTransfer
// Playwright dispatchEvent can't set dataTransfer items directly,
// so we use page.evaluate to dispatch a proper DragEvent with file items
await page.evaluate((selector) => {
const el = document.querySelector(selector)
if (!el) throw new Error('Editor container not found')
const dt = new DataTransfer()
dt.items.add(new File(['fake'], 'photo.png', { type: 'image/png' }))
const event = new DragEvent('dragover', {
dataTransfer: dt,
bubbles: true,
cancelable: true,
})
el.dispatchEvent(event)
}, EDITOR_CONTAINER)
await expect(page.locator(DROP_OVERLAY)).toBeVisible()
await expect(page.locator(DROP_OVERLAY)).toContainText('Drop image here')
})
test('dragleave after image dragover hides the overlay', async ({ page }) => {
// First show the overlay via image dragover
await page.evaluate((selector) => {
const el = document.querySelector(selector)
if (!el) throw new Error('Editor container not found')
const dt = new DataTransfer()
dt.items.add(new File(['fake'], 'photo.png', { type: 'image/png' }))
el.dispatchEvent(new DragEvent('dragover', {
dataTransfer: dt,
bubbles: true,
cancelable: true,
}))
}, EDITOR_CONTAINER)
await expect(page.locator(DROP_OVERLAY)).toBeVisible()
// Now simulate dragleave (cursor left the container)
await page.evaluate((selector) => {
const el = document.querySelector(selector)
if (!el) throw new Error('Editor container not found')
el.dispatchEvent(new DragEvent('dragleave', {
bubbles: true,
cancelable: true,
relatedTarget: document.body,
}))
}, EDITOR_CONTAINER)
await expect(page.locator(DROP_OVERLAY)).not.toBeVisible()
})
test('drop resets the overlay', async ({ page }) => {
// Show overlay via image dragover
await page.evaluate((selector) => {
const el = document.querySelector(selector)
if (!el) throw new Error('Editor container not found')
const dt = new DataTransfer()
dt.items.add(new File(['fake'], 'photo.png', { type: 'image/png' }))
el.dispatchEvent(new DragEvent('dragover', {
dataTransfer: dt,
bubbles: true,
cancelable: true,
}))
}, EDITOR_CONTAINER)
await expect(page.locator(DROP_OVERLAY)).toBeVisible()
// Simulate drop
await page.evaluate((selector) => {
const el = document.querySelector(selector)
if (!el) throw new Error('Editor container not found')
el.dispatchEvent(new DragEvent('drop', {
bubbles: true,
cancelable: true,
}))
}, EDITOR_CONTAINER)
await expect(page.locator(DROP_OVERLAY)).not.toBeVisible()
})
})

View File

@@ -1,62 +0,0 @@
import { test, expect } from '@playwright/test'
test.describe('Flat vault migration', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('title field appears above editor when note is open', async ({ page }) => {
// Click a note in the sidebar to open it
const noteItem = page.locator('[data-testid="note-list-item"]').first()
if (await noteItem.isVisible()) {
await noteItem.click()
await page.waitForTimeout(300)
// The TitleField should be visible
const titleField = page.locator('[data-testid="title-field"]')
await expect(titleField).toBeVisible()
// The input should have the note's title
const input = page.locator('[data-testid="title-field-input"]')
const value = await input.inputValue()
expect(value.length).toBeGreaterThan(0)
}
})
test('title field is editable and shows filename on change', async ({ page }) => {
// Open a note
const noteItem = page.locator('[data-testid="note-list-item"]').first()
if (await noteItem.isVisible()) {
await noteItem.click()
await page.waitForTimeout(300)
const input = page.locator('[data-testid="title-field-input"]')
await input.focus()
// Should show filename indicator when focused
const filenameIndicator = page.locator('[data-testid="title-field-filename"]')
await expect(filenameIndicator).toBeVisible()
}
})
test('no migration banner when vault is already flat (mock)', async ({ page }) => {
// In browser mode (mock), vault should be flat and no migration banner
const banner = page.locator('[data-testid="migration-banner"]')
await page.waitForTimeout(1000)
await expect(banner).not.toBeVisible()
})
test('H1 heading is hidden in editor (CSS rule active)', async ({ page }) => {
// Check that the CSS rule for hiding H1 is present in the document
const styles = await page.evaluate(() => {
for (const sheet of document.styleSheets) {
try {
for (const rule of sheet.cssRules) {
if (rule.cssText?.includes('data-content-type="heading"') && rule.cssText?.includes('display: none')) {
return true
}
}
} catch { /* cross-origin */ }
}
return false
})
expect(styles).toBe(true)
})
})

View File

@@ -1,136 +0,0 @@
import { test, expect } from '@playwright/test'
import { openCommandPalette, executeCommand } from './helpers'
test.describe('Note icon emoji picker — full emoji set, search, continuous scroll', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
// Open the first note to mount the editor
const noteList = page.locator('[data-testid="note-list-container"]')
await noteList.waitFor({ timeout: 5_000 })
await noteList.locator('.cursor-pointer').first().click()
await page.waitForTimeout(500)
await page.waitForSelector('[data-testid="note-icon-area"]', { timeout: 5_000 })
})
test('add-icon button opens emoji picker', async ({ page }) => {
// Hover the note-icon-area to reveal the Add icon button
await page.locator('[data-testid="note-icon-area"]').hover()
const addBtn = page.locator('[data-testid="note-icon-add"]')
await addBtn.waitFor({ timeout: 3_000 })
await addBtn.click()
await expect(page.locator('[data-testid="emoji-picker"]')).toBeVisible()
})
test('emoji picker contains full emoji set (1800+)', async ({ page }) => {
await page.locator('[data-testid="note-icon-area"]').hover()
await page.locator('[data-testid="note-icon-add"]').click()
const emojiCount = await page.locator('[data-testid="emoji-option"]').count()
expect(emojiCount).toBeGreaterThan(1800)
})
test('emoji search by English name works (rocket → 🚀)', async ({ page }) => {
await page.locator('[data-testid="note-icon-area"]').hover()
await page.locator('[data-testid="note-icon-add"]').click()
const searchInput = page.locator('[data-testid="emoji-picker-search"]')
await searchInput.fill('rocket')
await page.waitForTimeout(100)
const results = page.locator('[data-testid="emoji-option"]')
const count = await results.count()
expect(count).toBeGreaterThan(0)
// Verify 🚀 is in the results
const texts = await results.allTextContents()
expect(texts).toContain('🚀')
})
test('emoji search for "heart" returns results', async ({ page }) => {
await page.locator('[data-testid="note-icon-area"]').hover()
await page.locator('[data-testid="note-icon-add"]').click()
await page.locator('[data-testid="emoji-picker-search"]').fill('heart')
await page.waitForTimeout(100)
const results = page.locator('[data-testid="emoji-option"]')
const count = await results.count()
expect(count).toBeGreaterThan(5)
})
test('emoji search for "fire" finds 🔥', async ({ page }) => {
await page.locator('[data-testid="note-icon-area"]').hover()
await page.locator('[data-testid="note-icon-add"]').click()
await page.locator('[data-testid="emoji-picker-search"]').fill('fire')
await page.waitForTimeout(100)
const texts = await page.locator('[data-testid="emoji-option"]').allTextContents()
expect(texts).toContain('🔥')
})
test('all emojis visible in continuous scroll (no category lock)', async ({ page }) => {
await page.locator('[data-testid="note-icon-area"]').hover()
await page.locator('[data-testid="note-icon-add"]').click()
const grid = page.locator('[data-testid="emoji-picker-grid"]')
// Verify the grid is scrollable (content height > container height)
const isScrollable = await grid.evaluate(el => el.scrollHeight > el.clientHeight)
expect(isScrollable).toBe(true)
// All emojis should be in the DOM (continuous scroll, not category-locked)
const emojiCount = await page.locator('[data-testid="emoji-option"]').count()
expect(emojiCount).toBeGreaterThan(1800)
})
test('selecting an emoji saves it and shows it in editor', async ({ page }) => {
await page.locator('[data-testid="note-icon-area"]').hover()
await page.locator('[data-testid="note-icon-add"]').click()
// Pick the first emoji
const firstEmoji = page.locator('[data-testid="emoji-option"]').first()
const emojiText = await firstEmoji.textContent()
await firstEmoji.click()
// Picker should close and emoji should be displayed
await expect(page.locator('[data-testid="emoji-picker"]')).not.toBeVisible()
const display = page.locator('[data-testid="note-icon-display"]')
await expect(display).toBeVisible()
await expect(display).toHaveText(emojiText!)
})
test('clicking set emoji shows change/remove menu', async ({ page }) => {
// First set an emoji
await page.locator('[data-testid="note-icon-area"]').hover()
await page.locator('[data-testid="note-icon-add"]').click()
await page.locator('[data-testid="emoji-option"]').first().click()
// Now click the displayed emoji to open menu
await page.locator('[data-testid="note-icon-display"]').click()
await expect(page.locator('[data-testid="note-icon-menu"]')).toBeVisible()
await expect(page.locator('[data-testid="note-icon-change"]')).toBeVisible()
await expect(page.locator('[data-testid="note-icon-remove"]')).toBeVisible()
})
test('remove emoji removes it from display', async ({ page }) => {
// Set an emoji first
await page.locator('[data-testid="note-icon-area"]').hover()
await page.locator('[data-testid="note-icon-add"]').click()
await page.locator('[data-testid="emoji-option"]').first().click()
await expect(page.locator('[data-testid="note-icon-display"]')).toBeVisible()
// Remove it
await page.locator('[data-testid="note-icon-display"]').click()
await page.locator('[data-testid="note-icon-remove"]').click()
await expect(page.locator('[data-testid="note-icon-display"]')).not.toBeVisible()
})
test('command palette "Set Note Icon" opens picker', async ({ page }) => {
await openCommandPalette(page)
await executeCommand(page, 'Set Note Icon')
await expect(page.locator('[data-testid="emoji-picker"]')).toBeVisible()
})
test('Escape closes the emoji picker', async ({ page }) => {
await page.locator('[data-testid="note-icon-area"]').hover()
await page.locator('[data-testid="note-icon-add"]').click()
await expect(page.locator('[data-testid="emoji-picker"]')).toBeVisible()
await page.keyboard.press('Escape')
await expect(page.locator('[data-testid="emoji-picker"]')).not.toBeVisible()
})
test('no results shows empty state message', async ({ page }) => {
await page.locator('[data-testid="note-icon-area"]').hover()
await page.locator('[data-testid="note-icon-add"]').click()
await page.locator('[data-testid="emoji-picker-search"]').fill('xyzzyplugh')
await page.waitForTimeout(100)
await expect(page.getByText('No emojis found')).toBeVisible()
})
})

View File

@@ -1,165 +0,0 @@
import { test, expect } from '@playwright/test'
import { openCommandPalette, findCommand, executeCommand, sendShortcut } from './helpers'
test.describe('Note icon emoji picker', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
await page.waitForTimeout(2500)
})
test('emoji picker opens from Add Icon button and selects emoji', async ({ page }) => {
// Open a note by clicking the first item in the note list
const noteItem = page.locator('[data-testid="note-list-container"] .cursor-pointer').first()
await noteItem.waitFor({ timeout: 5000 })
await noteItem.click()
await page.waitForTimeout(500)
// The note icon area should be visible
const iconArea = page.locator('[data-testid="note-icon-area"]')
await expect(iconArea).toBeVisible({ timeout: 3000 })
// Hover to reveal the "Add icon" button and click it
await iconArea.hover()
const addButton = page.locator('[data-testid="note-icon-add"]')
await expect(addButton).toBeVisible()
await addButton.click()
// Emoji picker should appear
const picker = page.locator('[data-testid="emoji-picker"]')
await expect(picker).toBeVisible({ timeout: 2000 })
// Click the first emoji
const emojiOption = picker.locator('[data-testid="emoji-option"]').first()
await emojiOption.click()
// Picker should close
await expect(picker).not.toBeVisible()
// The icon should now be displayed
const iconDisplay = page.locator('[data-testid="note-icon-display"]')
await expect(iconDisplay).toBeVisible({ timeout: 2000 })
})
test('emoji icon can be removed via menu', async ({ page }) => {
// Open a note
const noteItem = page.locator('[data-testid="note-list-container"] .cursor-pointer').first()
await noteItem.waitFor({ timeout: 5000 })
await noteItem.click()
await page.waitForTimeout(500)
// Add an icon first
const iconArea = page.locator('[data-testid="note-icon-area"]')
await iconArea.hover()
await page.locator('[data-testid="note-icon-add"]').click()
await page.locator('[data-testid="emoji-option"]').first().click()
await page.waitForTimeout(300)
// Click the displayed icon to show the menu
const iconDisplay = page.locator('[data-testid="note-icon-display"]')
await iconDisplay.click()
// Menu should show Remove option
const removeButton = page.locator('[data-testid="note-icon-remove"]')
await expect(removeButton).toBeVisible()
await removeButton.click()
// Icon should be removed, add button returns on hover
await expect(iconDisplay).not.toBeVisible()
await iconArea.hover()
await expect(page.locator('[data-testid="note-icon-add"]')).toBeVisible()
})
test('Cmd+K shows Set Note Icon command', async ({ page }) => {
// Open a note first
const noteItem = page.locator('[data-testid="note-list-container"] .cursor-pointer').first()
await noteItem.waitFor({ timeout: 5000 })
await noteItem.click()
await page.waitForTimeout(500)
// Open command palette and search for the icon command
await openCommandPalette(page)
const found = await findCommand(page, 'Set Note Icon')
expect(found).toBe(true)
})
test('Set Note Icon command opens emoji picker', async ({ page }) => {
// Open a note first
const noteItem = page.locator('[data-testid="note-list-container"] .cursor-pointer').first()
await noteItem.waitFor({ timeout: 5000 })
await noteItem.click()
await page.waitForTimeout(500)
// Use command palette to open emoji picker
await openCommandPalette(page)
await executeCommand(page, 'Set Note Icon')
await page.waitForTimeout(300)
// Emoji picker should be visible
const picker = page.locator('[data-testid="emoji-picker"]')
await expect(picker).toBeVisible({ timeout: 2000 })
// Select an emoji
await picker.locator('[data-testid="emoji-option"]').first().click()
// Icon should be displayed
const iconDisplay = page.locator('[data-testid="note-icon-display"]')
await expect(iconDisplay).toBeVisible({ timeout: 2000 })
})
test('Escape closes the emoji picker', async ({ page }) => {
// Open a note
const noteItem = page.locator('[data-testid="note-list-container"] .cursor-pointer').first()
await noteItem.waitFor({ timeout: 5000 })
await noteItem.click()
await page.waitForTimeout(500)
// Open picker
const iconArea = page.locator('[data-testid="note-icon-area"]')
await iconArea.hover()
await page.locator('[data-testid="note-icon-add"]').click()
const picker = page.locator('[data-testid="emoji-picker"]')
await expect(picker).toBeVisible()
// Press Escape to close
await page.keyboard.press('Escape')
await expect(picker).not.toBeVisible()
})
test('emoji icon is shown in Quick Open (Cmd+P) results', async ({ page }) => {
// Open a note and set an icon
const noteItem = page.locator('[data-testid="note-list-container"] .cursor-pointer').first()
await noteItem.waitFor({ timeout: 5000 })
// Get the note title for later search
const noteTitle = await noteItem.locator('.font-medium, .text-sm').first().textContent()
await noteItem.click()
await page.waitForTimeout(500)
// Set an icon
const iconArea = page.locator('[data-testid="note-icon-area"]')
await iconArea.hover()
await page.locator('[data-testid="note-icon-add"]').click()
const firstEmoji = page.locator('[data-testid="emoji-option"]').first()
const emojiText = await firstEmoji.textContent()
await firstEmoji.click()
await page.waitForTimeout(500)
// Open Quick Open and search for the note
await page.locator('body').click()
await sendShortcut(page, 'p', ['Control'])
const searchInput = page.locator('input[placeholder="Search notes..."]')
await expect(searchInput).toBeVisible()
if (noteTitle && emojiText) {
await searchInput.fill(noteTitle.trim().substring(0, 10))
await page.waitForTimeout(300)
// Verify the emoji appears in the search results
const results = page.locator('.py-1 .cursor-pointer .truncate')
const resultTexts = await results.allTextContents()
const hasEmoji = resultTexts.some(t => t.includes(emojiText))
expect(hasEmoji).toBe(true)
}
})
})

View File

@@ -1,103 +0,0 @@
import { test, expect } from '@playwright/test'
import { sendShortcut } from './helpers'
test.describe('Note list shows complete relationships when opening from sidebar', () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: 1600, height: 900 })
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('entity view shows relationship groups with correct counts after sidebar click', async ({ page }) => {
// Navigate to Responsibilities type to find entity notes in the sidebar
// First, click on a sidebar type that contains entity notes
// We'll use the sidebar search to filter to "Sponsorships"
const searchToggle = page.locator('[data-testid="search-toggle"]')
if (await searchToggle.isVisible()) {
await searchToggle.click()
}
// Type in sidebar search to find Sponsorships
const sidebarSearch = page.locator('[data-testid="note-list-search"]')
if (await sidebarSearch.isVisible()) {
await sidebarSearch.fill('Sponsorships')
await page.waitForTimeout(500)
}
// Click the Sponsorships note in the note list to trigger entity view
const sponsorshipsItem = page.locator('[data-entry-path*="sponsorships"]').first()
if (await sponsorshipsItem.isVisible({ timeout: 3000 }).catch(() => false)) {
await sponsorshipsItem.click()
await page.waitForTimeout(1000)
} else {
// Fallback: open via quick open — then click in sidebar
await page.locator('body').click()
await sendShortcut(page, 'p', ['Control'])
const searchInput = page.locator('input[placeholder="Search notes..."]')
await expect(searchInput).toBeVisible()
await searchInput.fill('Sponsorships')
await page.waitForTimeout(500)
await page.keyboard.press('Enter')
await page.waitForTimeout(1000)
}
// The inspector panel (right side) should show relationship labels with font-mono-overline
// This verifies the note loaded and relationships were parsed correctly
const measuresLabel = page.locator('span.font-mono-overline').filter({ hasText: 'Has Measures' })
await expect(measuresLabel).toBeVisible({ timeout: 5000 })
const proceduresLabel = page.locator('span.font-mono-overline').filter({ hasText: 'Has Procedures' })
await expect(proceduresLabel).toBeVisible({ timeout: 5000 })
// Verify the relationships panel shows the correct number of items
// Has Measures should have 2 entries (measure-sponsorship-mrr, measure-close-rate)
const measuresSection = measuresLabel.locator('xpath=ancestor::div[1]')
const measureItems = measuresSection.locator('a, button').filter({ hasText: /measure|Measure/ })
// At least 1 resolved relationship entry
const hasItems = await measureItems.count()
expect(hasItems).toBeGreaterThanOrEqual(1)
})
test('relationship labels persist after navigating away and back', async ({ page }) => {
// Open Sponsorships via quick open
await page.locator('body').click()
await sendShortcut(page, 'p', ['Control'])
const searchInput = page.locator('input[placeholder="Search notes..."]')
await expect(searchInput).toBeVisible()
await searchInput.fill('Sponsorships')
await page.waitForTimeout(500)
await page.keyboard.press('Enter')
await page.waitForTimeout(1000)
// Verify Has Measures relationship appears in inspector
const measuresLabel = page.locator('span.font-mono-overline').filter({ hasText: 'Has Measures' })
await expect(measuresLabel).toBeVisible({ timeout: 5000 })
// Navigate to different note
await page.locator('body').click()
await sendShortcut(page, 'p', ['Control'])
const searchInput2 = page.locator('input[placeholder="Search notes..."]')
await expect(searchInput2).toBeVisible()
await searchInput2.fill('Start Laputa App')
await page.waitForTimeout(500)
await page.keyboard.press('Enter')
await page.waitForTimeout(1000)
// Navigate back to Sponsorships
await page.locator('body').click()
await sendShortcut(page, 'p', ['Control'])
const searchInput3 = page.locator('input[placeholder="Search notes..."]')
await expect(searchInput3).toBeVisible()
await searchInput3.fill('Sponsorships')
await page.waitForTimeout(500)
await page.keyboard.press('Enter')
await page.waitForTimeout(1000)
// Relationships should still be visible — not stale or incomplete
const measuresLabelAgain = page.locator('span.font-mono-overline').filter({ hasText: 'Has Measures' })
await expect(measuresLabelAgain).toBeVisible({ timeout: 5000 })
const proceduresLabelAgain = page.locator('span.font-mono-overline').filter({ hasText: 'Has Procedures' })
await expect(proceduresLabelAgain).toBeVisible({ timeout: 5000 })
})
})

View File

@@ -1,40 +0,0 @@
import { test, expect } from '@playwright/test'
test.describe('Note list preview snippet', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('snippet does not contain raw markdown formatting', async ({ page }) => {
const noteListContainer = page.locator('[data-testid="note-list-container"]')
await expect(noteListContainer).toBeVisible()
const snippets = noteListContainer.locator('[data-testid="note-snippet"]')
const count = await snippets.count()
for (let i = 0; i < Math.min(count, 8); i++) {
const text = await snippets.nth(i).textContent()
if (text && text.length > 10) {
expect(text).not.toMatch(/\*\*[^*]+\*\*/)
expect(text).not.toContain('```')
}
}
})
test('snippet does not start with list markers', async ({ page }) => {
const noteListContainer = page.locator('[data-testid="note-list-container"]')
await expect(noteListContainer).toBeVisible()
const snippets = noteListContainer.locator('[data-testid="note-snippet"]')
const count = await snippets.count()
for (let i = 0; i < Math.min(count, 10); i++) {
const text = await snippets.nth(i).textContent()
if (text && text.length > 15) {
expect(text.trimStart()).not.toMatch(/^[*\-+] /)
expect(text.trimStart()).not.toMatch(/^\d+\. /)
}
}
})
})

View File

@@ -1,41 +0,0 @@
import { test, expect } from '@playwright/test'
import { openCommandPalette, findCommand } from './helpers'
test.describe('Open in New Window', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('"Open in New Window" command appears in palette when note is active', async ({ page }) => {
// Open a note first (click first item in note list)
const firstNote = page.locator('.cursor-pointer.border-b').first()
await expect(firstNote).toBeVisible({ timeout: 3_000 })
await firstNote.click()
// Wait for editor to load
await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 3_000 })
// Open command palette and search
await openCommandPalette(page)
const found = await findCommand(page, 'Open in New Window')
expect(found).toBe(true)
})
test('"Open in New Window" shows shortcut hint', async ({ page }) => {
// Open a note
const firstNote = page.locator('.cursor-pointer.border-b').first()
await expect(firstNote).toBeVisible({ timeout: 3_000 })
await firstNote.click()
await expect(page.locator('.bn-editor')).toBeVisible({ timeout: 3_000 })
// Open command palette and search for the command
await openCommandPalette(page)
const input = page.locator('input[placeholder="Type a command..."]')
await input.fill('Open in New Window')
// The shortcut hint should be visible
const commandRow = page.locator('text=Open in New Window').first()
await expect(commandRow).toBeVisible({ timeout: 2_000 })
})
})

View File

@@ -1,131 +0,0 @@
import { test, expect } from '@playwright/test'
import { sendShortcut } from './helpers'
async function openNoteViaQuickOpen(page: import('@playwright/test').Page, query: string) {
await page.locator('body').click()
await sendShortcut(page, 'p', ['Control'])
const searchInput = page.locator('input[placeholder="Search notes..."]')
await expect(searchInput).toBeVisible()
await searchInput.fill(query)
await page.waitForTimeout(500)
await page.keyboard.press('Enter')
await page.waitForTimeout(500)
}
test.describe('Properties panel visual style', () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: 1600, height: 900 })
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('labels are sentence case, not ALL CAPS', async ({ page }) => {
await openNoteViaQuickOpen(page, 'Untitled note 58')
const propertyRows = page.locator('[data-testid="editable-property"]')
await expect(propertyRows.first()).toBeVisible({ timeout: 5000 })
// Labels should be sentence case (e.g. "Status"), not ALL CAPS
const statusLabel = propertyRows.locator('span.truncate').filter({ hasText: 'Status' })
await expect(statusLabel).toBeVisible()
// Verify no ALL CAPS label exists
const allLabels = propertyRows.locator('span.truncate')
const count = await allLabels.count()
for (let i = 0; i < count; i++) {
const text = await allLabels.nth(i).textContent()
if (text && text.length > 1) {
expect(text).not.toEqual(text.toUpperCase())
}
}
})
test('status renders as colored badge with dot indicator', async ({ page }) => {
await openNoteViaQuickOpen(page, 'Untitled note 58')
const statusBadge = page.locator('[data-testid="status-badge"]')
await expect(statusBadge).toBeVisible({ timeout: 5000 })
await expect(statusBadge).toHaveText('Active')
// Should have a dot indicator (small circle span inside)
const dot = statusBadge.locator('span.rounded-full')
await expect(dot).toBeVisible()
})
test('date renders as chip with background', async ({ page }) => {
await openNoteViaQuickOpen(page, 'Untitled note 58')
const dateDisplay = page.locator('[data-testid="date-display"]')
// Date may or may not exist in the note — if it exists, verify chip styling
const count = await dateDisplay.count()
if (count > 0) {
await expect(dateDisplay.first()).toHaveClass(/bg-muted/)
}
})
test('boolean renders as checkbox', async ({ page }) => {
await openNoteViaQuickOpen(page, 'Untitled note 58')
const booleanToggle = page.locator('[data-testid="boolean-toggle"]')
const count = await booleanToggle.count()
if (count > 0) {
// Should contain an actual checkbox input
const checkbox = booleanToggle.first().locator('input[type="checkbox"]')
await expect(checkbox).toBeVisible()
}
})
test('no horizontal overflow from property values', async ({ page }) => {
await openNoteViaQuickOpen(page, 'Untitled note 58')
const propertyRows = page.locator('[data-testid="editable-property"]')
await expect(propertyRows.first()).toBeVisible({ timeout: 5000 })
// Each row should not overflow its container
const count = await propertyRows.count()
for (let i = 0; i < count; i++) {
const row = propertyRows.nth(i)
const box = await row.boundingBox()
if (!box) continue
// Inspector panel is ~300px wide; rows shouldn't exceed parent
expect(box.width).toBeLessThan(500)
}
})
test('Type label uses same font-size as other property labels', async ({ page }) => {
await openNoteViaQuickOpen(page, 'Untitled note 58')
const typeSelector = page.locator('[data-testid="type-selector"]')
const typeCount = await typeSelector.count()
if (typeCount > 0) {
const typeLabel = typeSelector.locator('span').first()
const typeFontSize = await typeLabel.evaluate(el => getComputedStyle(el).fontSize)
// Should be 12px, matching other property labels
expect(typeFontSize).toBe('12px')
}
})
test('tags add button is solid pill, not dashed circle', async ({ page }) => {
await openNoteViaQuickOpen(page, 'Untitled note 58')
const tagsAddBtn = page.locator('[data-testid="tags-add-button"]')
const count = await tagsAddBtn.count()
if (count > 0) {
// Should NOT have dashed border
const borderStyle = await tagsAddBtn.first().evaluate(el => getComputedStyle(el).borderStyle)
expect(borderStyle).not.toBe('dashed')
// Should have rounded-md (not rounded-full circle)
await expect(tagsAddBtn.first()).toHaveClass(/rounded-md/)
}
})
test('add property button is subtle', async ({ page }) => {
await openNoteViaQuickOpen(page, 'Untitled note 58')
const addBtn = page.getByText('Add property')
await expect(addBtn).toBeVisible({ timeout: 5000 })
// Should have reduced opacity (subtle appearance)
await expect(addBtn).toHaveCSS('opacity', '0.5')
})
})

View File

@@ -1,47 +0,0 @@
import { test, expect } from '@playwright/test'
import { sendShortcut } from './helpers'
async function openNoteViaQuickOpen(page: import('@playwright/test').Page, query: string) {
await page.locator('body').click()
await sendShortcut(page, 'p', ['Control'])
const searchInput = page.locator('input[placeholder="Search notes..."]')
await expect(searchInput).toBeVisible()
await searchInput.fill(query)
await page.waitForTimeout(500)
await page.keyboard.press('Enter')
await page.waitForTimeout(500)
}
test.describe('Type instances in inspector', () => {
test.beforeEach(async ({ page }) => {
await page.setViewportSize({ width: 1600, height: 900 })
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('shows Instances section when viewing a Type note', async ({ page }) => {
await openNoteViaQuickOpen(page, 'Project')
// The inspector starts open by default. Look for the "Properties" header.
// If it's collapsed, click the SlidersHorizontal icon to expand.
const propertiesHeader = page.getByText('Properties', { exact: true })
if (!(await propertiesHeader.isVisible())) {
// Inspector might be collapsed — click the toggle button
const toggleBtn = page.locator('button').filter({ has: page.locator('svg') }).last()
await toggleBtn.click()
await page.waitForTimeout(300)
}
// The Instances section should be visible with a count
const instancesLabel = page.getByText(/Instances \(\d+\)/)
await expect(instancesLabel).toBeVisible({ timeout: 5000 })
})
test('does not show Instances section for non-Type note', async ({ page }) => {
await openNoteViaQuickOpen(page, 'Build Laputa App')
// No Instances section should appear for a non-Type note
const instancesLabel = page.getByText(/Instances \(\d+\)/)
await expect(instancesLabel).not.toBeVisible()
})
})

View File

@@ -1,66 +0,0 @@
import { test, expect } from '@playwright/test'
test.describe('NoteList split refactor no behavior changes', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('note list container renders and shows notes', async ({ page }) => {
const container = page.locator('[data-testid="note-list-container"]')
await expect(container).toBeVisible()
// Notes should render inside the container
const noteItems = container.locator('.cursor-pointer')
await expect(noteItems.first()).toBeVisible({ timeout: 5000 })
const count = await noteItems.count()
expect(count).toBeGreaterThan(0)
})
test('header with title and action buttons renders', async ({ page }) => {
// The header area sits above the note-list-container: h3 title + action buttons
const header = page.locator('h3.truncate')
await expect(header).toBeVisible()
const title = await header.textContent()
expect(title && title.length > 0).toBe(true)
// Search button (magnifying glass) should be present
const searchBtn = page.locator('button[title="Search notes"]')
await expect(searchBtn).toBeVisible()
// Create note button should be present (when not in trash view)
const createBtn = page.locator('button[title="Create new note"]')
await expect(createBtn).toBeVisible()
})
test('search toggle shows and hides search input', async ({ page }) => {
const searchBtn = page.locator('button[title="Search notes"]')
await searchBtn.click()
const searchInput = page.locator('input[placeholder="Search notes..."]')
await expect(searchInput).toBeVisible()
// Toggle off
await searchBtn.click()
await expect(searchInput).not.toBeVisible()
})
test('clicking a note opens it in the editor', async ({ page }) => {
const container = page.locator('[data-testid="note-list-container"]')
await expect(container).toBeVisible()
const noteItems = container.locator('.cursor-pointer')
await expect(noteItems.first()).toBeVisible({ timeout: 5000 })
// Get the title of the first note
const noteTitle = await noteItems.first().locator('.font-medium, .font-semibold, .font-bold').first().textContent()
// Click the first note
await noteItems.first().click()
// After click, the editor area or tab bar should reflect the opened note
// Verify the note list container is still functional (no crash from refactor)
await expect(container).toBeVisible()
expect(noteTitle && noteTitle.length > 0).toBe(true)
})
})

View File

@@ -1,56 +0,0 @@
import { test, expect } from '@playwright/test'
test.describe('split-usenoteactions: note creation and rename still work', () => {
test.beforeEach(async ({ page }) => {
await page.route('**/api/vault/ping', route => route.fulfill({ status: 503 }))
await page.goto('/')
await page.waitForTimeout(500)
})
test('Cmd+N creates a new untitled note visible in the sidebar', async ({ page }) => {
await page.keyboard.press('Meta+n')
await page.waitForTimeout(300)
// The new note should appear in the note list sidebar
const noteList = page.locator('.app__note-list')
const untitledItem = noteList.locator('text=/untitled/i').first()
await expect(untitledItem).toBeVisible({ timeout: 3000 })
})
test('creating multiple notes generates unique names in the sidebar', async ({ page }) => {
await page.keyboard.press('Meta+n')
await page.waitForTimeout(200)
await page.keyboard.press('Meta+n')
await page.waitForTimeout(200)
// Both notes should be visible in the sidebar with different names
const noteList = page.locator('.app__note-list')
const untitledItems = noteList.locator('text=/untitled/i')
await expect(untitledItems.first()).toBeVisible({ timeout: 3000 })
const count = await untitledItems.count()
expect(count).toBeGreaterThanOrEqual(2)
})
test('selecting a note opens it without errors', async ({ page }) => {
const noteItem = page.locator('.app__note-list .cursor-pointer').first()
await noteItem.click()
await page.waitForTimeout(300)
// Editor area should be visible (no crash from the refactored hooks)
const editor = page.locator('.app__editor')
await expect(editor).toBeVisible({ timeout: 3000 })
})
test('frontmatter property update shows toast', async ({ page }) => {
// Select an existing note
const noteItem = page.locator('.app__note-list .cursor-pointer').first()
await noteItem.click()
await page.waitForTimeout(300)
// Inspector panel renders without errors from frontmatterOps extraction
const inspector = page.getByTestId('inspector')
if (await inspector.isVisible()) {
await expect(inspector).toBeVisible()
}
})
})

View File

@@ -1,34 +0,0 @@
import { test, expect } from '@playwright/test'
import { openCommandPalette, findCommand, executeCommand } from './helpers'
test.describe('Three source of truth', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('Reload Vault command appears in Cmd+K palette', async ({ page }) => {
await openCommandPalette(page)
const found = await findCommand(page, 'reload')
expect(found).toBe(true)
})
test('Reload Vault command is executable', async ({ page }) => {
await openCommandPalette(page)
await executeCommand(page, 'reload vault')
// After execution, palette should close and app should still render
await expect(page.locator('input[placeholder="Type a command..."]')).not.toBeVisible()
// Sidebar should still be visible after reload
await expect(page.locator('[data-testid="sidebar"], nav, aside').first()).toBeVisible()
})
test('Reload Vault findable by keyword "rescan"', async ({ page }) => {
await openCommandPalette(page)
// Type a keyword synonym — the command should appear as selected result
await page.locator('input[placeholder="Type a command..."]').fill('rescan')
const match = page.locator('[data-selected="true"]').first()
await match.waitFor({ timeout: 2_000 })
const text = await match.textContent()
expect(text?.toLowerCase()).toContain('reload')
})
})

View File

@@ -1,110 +0,0 @@
import { test, expect } from '@playwright/test'
test.describe('Title H1 with inline emoji', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForTimeout(500)
// Open a note so TitleField is visible
const noteItem = page.locator('.app__note-list .cursor-pointer').first()
await noteItem.click()
await page.waitForTimeout(500)
})
test('title-section__row is a horizontal flex container', async ({ page }) => {
const row = page.locator('.title-section__row')
await expect(row).toBeVisible({ timeout: 3000 })
const display = await row.evaluate(el => getComputedStyle(el).display)
const flexDir = await row.evaluate(el => getComputedStyle(el).flexDirection)
expect(display).toBe('flex')
expect(flexDir).toBe('row')
})
test('title field renders with large H1 font size', async ({ page }) => {
const input = page.locator('.title-field__input')
await expect(input).toBeVisible({ timeout: 3000 })
const fontSize = await input.evaluate(el => {
const computed = getComputedStyle(el)
return parseFloat(computed.fontSize)
})
// Title must be at least 24px (H1 style, significantly larger than body ~16px)
expect(fontSize).toBeGreaterThanOrEqual(24)
})
test('title field renders with bold font weight', async ({ page }) => {
const input = page.locator('.title-field__input')
await expect(input).toBeVisible({ timeout: 3000 })
const fontWeight = await input.evaluate(el => {
const computed = getComputedStyle(el)
return parseInt(computed.fontWeight, 10)
})
// Font weight 700+ (bold or heavier)
expect(fontWeight).toBeGreaterThanOrEqual(700)
})
test('no reserved space for emoji when note has no icon', async ({ page }) => {
const titleRow = page.locator('.title-section__row')
await expect(titleRow).toBeVisible({ timeout: 3000 })
// When no emoji is set, icon-display should not be in the row
const iconInRow = titleRow.locator('[data-testid="note-icon-display"]')
await expect(iconInRow).toHaveCount(0)
// Title should be the first (and only significant) child in the row
const titleInput = titleRow.locator('[data-testid="title-field-input"]')
await expect(titleInput).toBeVisible()
})
test('emoji icon and title are on the same horizontal line when icon present', async ({ page }) => {
// Hover over the title section to reveal the "Add icon" button
const titleSection = page.locator('.title-section')
await titleSection.hover()
await page.waitForTimeout(200)
const addBtn = page.locator('[data-testid="note-icon-add"]')
if (await addBtn.isVisible()) {
await addBtn.click()
await page.waitForTimeout(300)
// Pick the first emoji in the picker
const emojiBtn = page.locator('.emoji-picker-grid button').first()
if (await emojiBtn.isVisible()) {
await emojiBtn.click()
await page.waitForTimeout(300)
}
}
const iconDisplay = page.locator('[data-testid="note-icon-display"]')
const titleInput = page.locator('[data-testid="title-field-input"]')
if (await iconDisplay.isVisible()) {
const iconBox = await iconDisplay.boundingBox()
const titleBox = await titleInput.boundingBox()
expect(iconBox).not.toBeNull()
expect(titleBox).not.toBeNull()
// Emoji and title must be on the same row: their vertical centers overlap
const iconCenter = iconBox!.y + iconBox!.height / 2
const titleTop = titleBox!.y
const titleBottom = titleBox!.y + titleBox!.height
expect(iconCenter).toBeGreaterThanOrEqual(titleTop - 8)
expect(iconCenter).toBeLessThanOrEqual(titleBottom + 8)
// Emoji must be to the left of the title
expect(iconBox!.x).toBeLessThan(titleBox!.x)
}
})
test('clicking title still enters edit mode', async ({ page }) => {
const titleInput = page.locator('[data-testid="title-field-input"]')
await expect(titleInput).toBeVisible({ timeout: 3000 })
await titleInput.click()
await expect(titleInput).toBeFocused()
})
})

View File

@@ -1,82 +0,0 @@
import { test, expect } from '@playwright/test'
test.describe('TitleField alignment and separator', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/')
await page.waitForTimeout(500)
// Open a note so TitleField is visible
const noteItem = page.locator('.app__note-list .cursor-pointer').first()
await noteItem.click()
await page.waitForTimeout(500)
})
test('title-section and editor body share the same scroll container', async ({ page }) => {
// The .editor-scroll-area should contain both .title-section and .editor__blocknote-container
const scrollArea = page.locator('.editor-scroll-area')
await expect(scrollArea).toBeVisible({ timeout: 3000 })
const titleSection = scrollArea.locator('.title-section')
await expect(titleSection).toBeVisible()
const editorContainer = scrollArea.locator('.editor__blocknote-container')
await expect(editorContainer).toBeVisible()
})
test('separator line is visible between title and editor', async ({ page }) => {
const separator = page.locator('.title-section__separator')
await expect(separator).toBeVisible({ timeout: 3000 })
// Separator must have a visible border-bottom
const borderBottom = await separator.evaluate(
el => getComputedStyle(el).borderBottomStyle
)
expect(borderBottom).toBe('solid')
})
test('title-section and editor content are horizontally aligned', async ({ page }) => {
// Resize viewport to a wide width to test alignment
await page.setViewportSize({ width: 1400, height: 800 })
await page.waitForTimeout(300)
const titleSection = page.locator('.title-section')
const editorBlock = page.locator('.editor__blocknote-container .bn-editor')
await expect(titleSection).toBeVisible({ timeout: 3000 })
await expect(editorBlock).toBeVisible({ timeout: 3000 })
const titleBox = await titleSection.boundingBox()
const editorBox = await editorBlock.boundingBox()
expect(titleBox).not.toBeNull()
expect(editorBox).not.toBeNull()
// Both should have the same left edge (within 2px tolerance)
expect(Math.abs(titleBox!.x - editorBox!.x)).toBeLessThanOrEqual(2)
})
test('title field uses H1 CSS variables for font styling', async ({ page }) => {
// Verify the title-field__input element references the H1 heading CSS vars
const usesH1Vars = await page.evaluate(() => {
const sheets = Array.from(document.styleSheets)
for (const sheet of sheets) {
try {
for (const rule of Array.from(sheet.cssRules)) {
if (rule instanceof CSSStyleRule && rule.selectorText === '.title-field__input') {
const fontSize = rule.style.getPropertyValue('font-size')
const fontWeight = rule.style.getPropertyValue('font-weight')
return {
fontSize: fontSize.includes('--headings-h1-font-size'),
fontWeight: fontWeight.includes('--headings-h1-font-weight'),
}
}
}
} catch { /* cross-origin sheet */ }
}
return { fontSize: false, fontWeight: false }
})
expect(usesH1Vars.fontSize).toBe(true)
expect(usesH1Vars.fontWeight).toBe(true)
})
})

View File

@@ -1,63 +0,0 @@
import { test, expect } from '@playwright/test'
test.describe('Type icon, color, and sidebar label', () => {
test.beforeEach(async ({ page }) => {
// Block vault API so the app falls back to mock data (which contains our test fixtures)
await page.route('**/api/vault/**', (route) => route.abort())
await page.goto('/')
await page.waitForLoadState('networkidle')
})
test('Config section shows correct sidebar label from type entry', async ({ page }) => {
const sidebar = page.locator('aside')
await sidebar.locator('button[aria-label*="Collapse"], button[aria-label*="Expand"]').first().waitFor({ timeout: 5000 })
// The Config type entry has sidebarLabel: "Config" — the section button should use that label
const configBtn = sidebar.locator('button[aria-label="Collapse Config"], button[aria-label="Expand Config"]')
await expect(configBtn).toBeVisible()
})
test('Config section icon is not the default FileText', async ({ page }) => {
const sidebar = page.locator('aside')
await sidebar.locator('button[aria-label*="Collapse"], button[aria-label*="Expand"]').first().waitFor({ timeout: 5000 })
const configSection = sidebar.locator('div.group\\/section').filter({
has: page.locator('button[aria-label="Collapse Config"], button[aria-label="Expand Config"]'),
})
await expect(configSection).toBeVisible()
// The icon SVG should be present (GearSix, resolved from type entry icon: 'gear-six')
const icon = configSection.locator('svg').first()
await expect(icon).toBeVisible()
})
test('Config section icon has gray color applied via style', async ({ page }) => {
const sidebar = page.locator('aside')
await sidebar.locator('button[aria-label*="Collapse"], button[aria-label*="Expand"]').first().waitFor({ timeout: 5000 })
const configSection = sidebar.locator('div.group\\/section').filter({
has: page.locator('button[aria-label="Collapse Config"], button[aria-label="Expand Config"]'),
})
await expect(configSection).toBeVisible()
// The icon should have gray color from the type entry's color: 'gray'
const icon = configSection.locator('svg').first()
const color = await icon.evaluate((el) => el.style.color)
expect(color).toContain('var(--accent-gray)')
})
test('custom type with icon/color reflects in sidebar (Recipe)', async ({ page }) => {
const sidebar = page.locator('aside')
await sidebar.locator('button[aria-label*="Collapse"], button[aria-label*="Expand"]').first().waitFor({ timeout: 5000 })
// Recipe type has icon: cooking-pot, color: orange — check section has correct color
const recipeSection = sidebar.locator('div.group\\/section').filter({
has: page.locator('button[aria-label="Collapse Recipes"], button[aria-label="Expand Recipes"]'),
})
await expect(recipeSection).toBeVisible()
const icon = recipeSection.locator('svg').first()
const color = await icon.evaluate((el) => el.style.color)
expect(color).toContain('var(--accent-orange)')
})
})