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 <noreply@anthropic.com>
This commit is contained in:
7
demo-vault-v2/area/refactoring.md
Normal file
7
demo-vault-v2/area/refactoring.md
Normal file
@@ -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.
|
||||
6
demo-vault-v2/note/refactoring-ideas.md
Normal file
6
demo-vault-v2/note/refactoring-ideas.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
Is A: Note
|
||||
---
|
||||
# Refactoring Ideas
|
||||
|
||||
Collection of ideas for refactoring the codebase to improve maintainability and performance.
|
||||
6
demo-vault-v2/note/refactoring-key-ideas.md
Normal file
6
demo-vault-v2/note/refactoring-key-ideas.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
Is A: Note
|
||||
---
|
||||
# Refactoring Key Ideas
|
||||
|
||||
Key takeaways from Martin Fowler's Refactoring book and other refactoring resources.
|
||||
6
demo-vault-v2/note/refactoring-patterns.md
Normal file
6
demo-vault-v2/note/refactoring-patterns.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
Is A: Note
|
||||
---
|
||||
# Refactoring Patterns
|
||||
|
||||
Common refactoring patterns including Extract Method, Rename Variable, and Replace Conditional with Polymorphism.
|
||||
@@ -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 ---
|
||||
|
||||
78
tests/smoke/exact-match-search.spec.ts
Normal file
78
tests/smoke/exact-match-search.spec.ts
Normal file
@@ -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<string> {
|
||||
// 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')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user