From 5c85bc41f6476028749c063241dc4efb2188f03f Mon Sep 17 00:00:00 2001 From: Test Date: Sun, 8 Mar 2026 22:15:08 +0100 Subject: [PATCH] test: add Playwright smoke test for exact-match search ranking Verifies that searching "Writing" in Quick Open shows the exact title match first, followed by prefix matches. Also adds Refactoring test entries to mock data and demo vault. Co-Authored-By: Claude Opus 4.6 --- demo-vault-v2/area/refactoring.md | 7 ++ demo-vault-v2/note/refactoring-ideas.md | 6 ++ demo-vault-v2/note/refactoring-key-ideas.md | 6 ++ demo-vault-v2/note/refactoring-patterns.md | 6 ++ src/mock-tauri/mock-entries.ts | 113 ++++++++++++++++++++ tests/smoke/exact-match-search.spec.ts | 78 ++++++++++++++ 6 files changed, 216 insertions(+) create mode 100644 demo-vault-v2/area/refactoring.md create mode 100644 demo-vault-v2/note/refactoring-ideas.md create mode 100644 demo-vault-v2/note/refactoring-key-ideas.md create mode 100644 demo-vault-v2/note/refactoring-patterns.md create mode 100644 tests/smoke/exact-match-search.spec.ts diff --git a/demo-vault-v2/area/refactoring.md b/demo-vault-v2/area/refactoring.md new file mode 100644 index 00000000..e6a8f65f --- /dev/null +++ b/demo-vault-v2/area/refactoring.md @@ -0,0 +1,7 @@ +--- +Is A: Area +Status: Active +--- +# Refactoring + +Area note covering refactoring practices, principles, and techniques for improving code quality without changing external behavior. diff --git a/demo-vault-v2/note/refactoring-ideas.md b/demo-vault-v2/note/refactoring-ideas.md new file mode 100644 index 00000000..10e8492b --- /dev/null +++ b/demo-vault-v2/note/refactoring-ideas.md @@ -0,0 +1,6 @@ +--- +Is A: Note +--- +# Refactoring Ideas + +Collection of ideas for refactoring the codebase to improve maintainability and performance. diff --git a/demo-vault-v2/note/refactoring-key-ideas.md b/demo-vault-v2/note/refactoring-key-ideas.md new file mode 100644 index 00000000..48925948 --- /dev/null +++ b/demo-vault-v2/note/refactoring-key-ideas.md @@ -0,0 +1,6 @@ +--- +Is A: Note +--- +# Refactoring Key Ideas + +Key takeaways from Martin Fowler's Refactoring book and other refactoring resources. diff --git a/demo-vault-v2/note/refactoring-patterns.md b/demo-vault-v2/note/refactoring-patterns.md new file mode 100644 index 00000000..5841c9ab --- /dev/null +++ b/demo-vault-v2/note/refactoring-patterns.md @@ -0,0 +1,6 @@ +--- +Is A: Note +--- +# Refactoring Patterns + +Common refactoring patterns including Extract Method, Rename Variable, and Replace Conditional with Polymorphism. diff --git a/src/mock-tauri/mock-entries.ts b/src/mock-tauri/mock-entries.ts index 9779b016..a833d901 100644 --- a/src/mock-tauri/mock-entries.ts +++ b/src/mock-tauri/mock-entries.ts @@ -1099,6 +1099,119 @@ export const MOCK_ENTRIES: VaultEntry[] = [ 'Type': ['[[type/experiment]]'], }, }, + // --- Refactoring entries for exact-match search testing --- + { + path: '/Users/luca/Laputa/area/refactoring.md', + filename: 'refactoring.md', + title: 'Refactoring', + isA: 'Area', + aliases: [], + belongsTo: [], + relatedTo: [], + status: 'Active', + owner: null, + cadence: null, + archived: false, + trashed: false, + trashedAt: null, + modifiedAt: now - 86400 * 30, + createdAt: now - 86400 * 365, + fileSize: 1200, + snippet: 'Area note covering refactoring practices and principles.', + wordCount: 180, + relationships: {}, + icon: null, + color: null, + order: null, + sidebarLabel: null, + template: null, sort: null, view: null, visible: null, + outgoingLinks: [], + properties: {}, + }, + { + path: '/Users/luca/Laputa/note/refactoring-ideas.md', + filename: 'refactoring-ideas.md', + title: 'Refactoring Ideas', + isA: 'Note', + aliases: [], + belongsTo: [], + relatedTo: [], + status: null, + owner: null, + cadence: null, + archived: false, + trashed: false, + trashedAt: null, + modifiedAt: now - 86400 * 5, + createdAt: now - 86400 * 60, + fileSize: 800, + snippet: 'Ideas for refactoring the codebase.', + wordCount: 120, + relationships: {}, + icon: null, + color: null, + order: null, + sidebarLabel: null, + template: null, sort: null, view: null, visible: null, + outgoingLinks: [], + properties: {}, + }, + { + path: '/Users/luca/Laputa/note/refactoring-key-ideas.md', + filename: 'refactoring-key-ideas.md', + title: 'Refactoring Key Ideas', + isA: 'Note', + aliases: [], + belongsTo: [], + relatedTo: [], + status: null, + owner: null, + cadence: null, + archived: false, + trashed: false, + trashedAt: null, + modifiedAt: now - 86400 * 10, + createdAt: now - 86400 * 90, + fileSize: 600, + snippet: 'Key ideas from the refactoring book.', + wordCount: 95, + relationships: {}, + icon: null, + color: null, + order: null, + sidebarLabel: null, + template: null, sort: null, view: null, visible: null, + outgoingLinks: [], + properties: {}, + }, + { + path: '/Users/luca/Laputa/note/refactoring-patterns.md', + filename: 'refactoring-patterns.md', + title: 'Refactoring Patterns', + isA: 'Note', + aliases: [], + belongsTo: [], + relatedTo: [], + status: null, + owner: null, + cadence: null, + archived: false, + trashed: false, + trashedAt: null, + modifiedAt: now - 86400 * 15, + createdAt: now - 86400 * 120, + fileSize: 950, + snippet: 'Common refactoring patterns and when to apply them.', + wordCount: 150, + relationships: {}, + icon: null, + color: null, + order: null, + sidebarLabel: null, + template: null, sort: null, view: null, visible: null, + outgoingLinks: [], + properties: {}, + }, ] // --- Bulk entry generator for large vault testing --- diff --git a/tests/smoke/exact-match-search.spec.ts b/tests/smoke/exact-match-search.spec.ts new file mode 100644 index 00000000..9739324c --- /dev/null +++ b/tests/smoke/exact-match-search.spec.ts @@ -0,0 +1,78 @@ +import { test, expect } from '@playwright/test' +import { sendShortcut } from './helpers' + +const QUICK_OPEN_INPUT = 'input[placeholder="Search notes..."]' + +async function openQuickOpen(page: import('@playwright/test').Page) { + await page.locator('body').click() + await sendShortcut(page, 'p', ['Control']) + await expect(page.locator(QUICK_OPEN_INPUT)).toBeVisible() +} + +/** + * Get the title text of the first (selected) search result in the Quick Open palette. + * The selected item has `bg-accent` class, and the title is in a nested `.truncate` span. + */ +async function getFirstResultTitle(page: import('@playwright/test').Page): Promise { + // The selected result row contains the title in a span.truncate + const titleSpan = page.locator('[class*="bg-accent"] span.truncate') + await titleSpan.first().waitFor({ timeout: 3000 }) + return (await titleSpan.first().textContent()) ?? '' +} + +test.describe('Exact match search ranking', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/') + await page.waitForLoadState('networkidle') + }) + + test('exact title match appears as first result', async ({ page }) => { + await openQuickOpen(page) + // "Writing" is a Topic note in the demo vault; other notes have "Writing" as a prefix + await page.locator(QUICK_OPEN_INPUT).fill('Writing') + await page.waitForTimeout(300) + + const firstTitle = await getFirstResultTitle(page) + expect(firstTitle).toBe('Writing') + }) + + test('case-insensitive exact match appears first', async ({ page }) => { + await openQuickOpen(page) + await page.locator(QUICK_OPEN_INPUT).fill('writing') + await page.waitForTimeout(300) + + const firstTitle = await getFirstResultTitle(page) + expect(firstTitle).toBe('Writing') + }) + + test('partial matches still appear below exact match', async ({ page }) => { + await openQuickOpen(page) + await page.locator(QUICK_OPEN_INPUT).fill('Writing') + await page.waitForTimeout(300) + + // Should have multiple results (exact + prefix/fuzzy matches) + const resultRows = page.locator('[class*="cursor-pointer"][class*="items-center"]') + const count = await resultRows.count() + expect(count).toBeGreaterThan(1) + + // First result is the exact match + const firstTitle = await getFirstResultTitle(page) + expect(firstTitle).toBe('Writing') + }) + + test('arrow keys navigate search results past the exact match', async ({ page }) => { + await openQuickOpen(page) + await page.locator(QUICK_OPEN_INPUT).fill('Writing') + await page.waitForTimeout(300) + + // First result should be selected (exact match) + const firstTitle = await getFirstResultTitle(page) + expect(firstTitle).toBe('Writing') + + // ArrowDown moves to next result (a prefix or fuzzy match) + await page.keyboard.press('ArrowDown') + await page.waitForTimeout(100) + const secondTitle = await getFirstResultTitle(page) + expect(secondTitle).not.toBe('Writing') + }) +})