diff --git a/tests/smoke/clickable-editor-empty-space.spec.ts b/tests/smoke/clickable-editor-empty-space.spec.ts deleted file mode 100644 index 7ae257c9..00000000 --- a/tests/smoke/clickable-editor-empty-space.spec.ts +++ /dev/null @@ -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) - }) -}) diff --git a/tests/smoke/emoji-icon-everywhere.spec.ts b/tests/smoke/emoji-icon-everywhere.spec.ts deleted file mode 100644 index ca554aaf..00000000 --- a/tests/smoke/emoji-icon-everywhere.spec.ts +++ /dev/null @@ -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) - }) -}) diff --git a/tests/smoke/filter-pills-height.spec.ts b/tests/smoke/filter-pills-height.spec.ts deleted file mode 100644 index becfa966..00000000 --- a/tests/smoke/filter-pills-height.spec.ts +++ /dev/null @@ -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') - }) -}) diff --git a/tests/smoke/image-drop-overlay-fix.spec.ts b/tests/smoke/image-drop-overlay-fix.spec.ts deleted file mode 100644 index 5e6e1eb2..00000000 --- a/tests/smoke/image-drop-overlay-fix.spec.ts +++ /dev/null @@ -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() - }) -}) diff --git a/tests/smoke/migrate-to-flat-vault.spec.ts b/tests/smoke/migrate-to-flat-vault.spec.ts deleted file mode 100644 index a43571e7..00000000 --- a/tests/smoke/migrate-to-flat-vault.spec.ts +++ /dev/null @@ -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) - }) -}) diff --git a/tests/smoke/note-icon-emoji-picker.spec.ts b/tests/smoke/note-icon-emoji-picker.spec.ts deleted file mode 100644 index e84ccd65..00000000 --- a/tests/smoke/note-icon-emoji-picker.spec.ts +++ /dev/null @@ -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() - }) -}) diff --git a/tests/smoke/note-icon.spec.ts b/tests/smoke/note-icon.spec.ts deleted file mode 100644 index d0b209a9..00000000 --- a/tests/smoke/note-icon.spec.ts +++ /dev/null @@ -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) - } - }) -}) diff --git a/tests/smoke/note-list-incomplete-relationships.spec.ts b/tests/smoke/note-list-incomplete-relationships.spec.ts deleted file mode 100644 index 9423090e..00000000 --- a/tests/smoke/note-list-incomplete-relationships.spec.ts +++ /dev/null @@ -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 }) - }) -}) diff --git a/tests/smoke/note-list-preview-snippet.spec.ts b/tests/smoke/note-list-preview-snippet.spec.ts deleted file mode 100644 index 6e1ba609..00000000 --- a/tests/smoke/note-list-preview-snippet.spec.ts +++ /dev/null @@ -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+\. /) - } - } - }) -}) diff --git a/tests/smoke/open-in-new-window.spec.ts b/tests/smoke/open-in-new-window.spec.ts deleted file mode 100644 index 882902c6..00000000 --- a/tests/smoke/open-in-new-window.spec.ts +++ /dev/null @@ -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 }) - }) -}) diff --git a/tests/smoke/properties-panel-style.spec.ts b/tests/smoke/properties-panel-style.spec.ts deleted file mode 100644 index 3f96e244..00000000 --- a/tests/smoke/properties-panel-style.spec.ts +++ /dev/null @@ -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') - }) -}) diff --git a/tests/smoke/show-type-instances-inspector.spec.ts b/tests/smoke/show-type-instances-inspector.spec.ts deleted file mode 100644 index 6b6cc214..00000000 --- a/tests/smoke/show-type-instances-inspector.spec.ts +++ /dev/null @@ -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() - }) -}) diff --git a/tests/smoke/split-notelist-god-component.spec.ts b/tests/smoke/split-notelist-god-component.spec.ts deleted file mode 100644 index 1bd14298..00000000 --- a/tests/smoke/split-notelist-god-component.spec.ts +++ /dev/null @@ -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) - }) -}) diff --git a/tests/smoke/split-usenoteactions.spec.ts b/tests/smoke/split-usenoteactions.spec.ts deleted file mode 100644 index a178bfe2..00000000 --- a/tests/smoke/split-usenoteactions.spec.ts +++ /dev/null @@ -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() - } - }) -}) diff --git a/tests/smoke/three-source-of-truth.spec.ts b/tests/smoke/three-source-of-truth.spec.ts deleted file mode 100644 index 2b5f8003..00000000 --- a/tests/smoke/three-source-of-truth.spec.ts +++ /dev/null @@ -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') - }) -}) diff --git a/tests/smoke/title-emoji-inline.spec.ts b/tests/smoke/title-emoji-inline.spec.ts deleted file mode 100644 index 4f0f19ff..00000000 --- a/tests/smoke/title-emoji-inline.spec.ts +++ /dev/null @@ -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() - }) -}) diff --git a/tests/smoke/title-field-alignment.spec.ts b/tests/smoke/title-field-alignment.spec.ts deleted file mode 100644 index 5035155c..00000000 --- a/tests/smoke/title-field-alignment.spec.ts +++ /dev/null @@ -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) - }) -}) diff --git a/tests/smoke/type-icon-color-sidebar-label.spec.ts b/tests/smoke/type-icon-color-sidebar-label.spec.ts deleted file mode 100644 index da97705b..00000000 --- a/tests/smoke/type-icon-color-sidebar-label.spec.ts +++ /dev/null @@ -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)') - }) -})