- Rename directory keep-notes -> memento-note with all code references - Prisma: SQLite -> PostgreSQL (both app and MCP server schemas) - Sync MCP schema with main app (add missing fields, relations, indexes) - Delete 17 SQLite migrations (clean slate for PostgreSQL) - Remove SQLite dependencies (@libsql/client, better-sqlite3, etc.) - Fix MCP server: hardcoded Windows DB paths -> DATABASE_URL env var - Fix MCP server: .dockerignore excluded index-sse.js (SSE mode broken) - MCP Dockerfile: node:20 -> node:22 - Docker Compose: add postgres service, remove SQLite volume - Generate favicon.ico, icon-192.png, icon-512.png, apple-icon.png - Update layout.tsx icons and manifest.json for PNG icons - Update all .env files for PostgreSQL - Rewrite README.md with updated sections - Remove mcp-server/node_modules and prisma/client-generated from git tracking Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
144 lines
4.7 KiB
TypeScript
144 lines
4.7 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
/**
|
|
* Non-regression tests for semantic search
|
|
* These tests ensure that increasing the threshold from 0.40 to 0.65
|
|
* doesn't lose valid positive matches
|
|
*/
|
|
test.describe('Search Non-Regression Tests', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('http://localhost:3000');
|
|
await page.waitForLoadState('networkidle');
|
|
});
|
|
|
|
test('should find notes with exact keyword matches', async ({ page }) => {
|
|
const searchInput = page.locator('input[placeholder*="Search"]');
|
|
await searchInput.fill('test');
|
|
await page.waitForTimeout(500);
|
|
|
|
const results = await page.locator('.note-card').count();
|
|
|
|
// Even with higher semantic threshold, exact keyword matches should always appear
|
|
if (results > 0) {
|
|
const titles = await page.locator('.note-card .note-title').allTextContents();
|
|
const hasExactMatch = titles.some(title =>
|
|
title.toLowerCase().includes('test')
|
|
);
|
|
expect(hasExactMatch).toBe(true);
|
|
}
|
|
});
|
|
|
|
test('should find notes with title matches', async ({ page }) => {
|
|
// Search for something that should be in titles
|
|
const searchInput = page.locator('input[placeholder*="Search"]');
|
|
|
|
// Try a generic search term
|
|
await searchInput.fill('note');
|
|
await page.waitForTimeout(500);
|
|
|
|
const results = await page.locator('.note-card');
|
|
const count = await results.count();
|
|
|
|
// Should find results with "note" in them
|
|
if (count > 0) {
|
|
const allText = await results.allTextContents();
|
|
const hasNote = allText.some(text =>
|
|
text.toLowerCase().includes('note')
|
|
);
|
|
expect(hasNote).toBe(true);
|
|
}
|
|
});
|
|
|
|
test('should handle multi-word queries correctly', async ({ page }) => {
|
|
const searchInput = page.locator('input[placeholder*="Search"]');
|
|
|
|
// Multi-word search should work
|
|
await searchInput.fill('test note');
|
|
await page.waitForTimeout(500);
|
|
|
|
// Should not error and should return results
|
|
const results = await page.locator('.note-card').count();
|
|
expect(results).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
test('should be case-insensitive', async ({ page }) => {
|
|
const searchInput = page.locator('input[placeholder*="Search"]');
|
|
|
|
// Search with uppercase
|
|
await searchInput.fill('TEST');
|
|
await page.waitForTimeout(500);
|
|
const upperCount = await page.locator('.note-card').count();
|
|
|
|
// Search with lowercase
|
|
await searchInput.fill('test');
|
|
await page.waitForTimeout(500);
|
|
const lowerCount = await page.locator('.note-card').count();
|
|
|
|
// Should return same results
|
|
expect(upperCount).toBe(lowerCount);
|
|
});
|
|
|
|
test('should handle empty search gracefully', async ({ page }) => {
|
|
const searchInput = page.locator('input[placeholder*="Search"]');
|
|
|
|
// Empty search should show all notes or handle gracefully
|
|
await searchInput.fill('');
|
|
await page.waitForTimeout(500);
|
|
|
|
// Should not error
|
|
const results = await page.locator('.note-card').count();
|
|
expect(results).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
test('should clear search results', async ({ page }) => {
|
|
const searchInput = page.locator('input[placeholder*="Search"]');
|
|
|
|
// Search for something
|
|
await searchInput.fill('temporary search');
|
|
await page.waitForTimeout(500);
|
|
|
|
// Clear search
|
|
await searchInput.fill('');
|
|
await page.waitForTimeout(500);
|
|
|
|
// Should return to showing all notes (or initial state)
|
|
const results = await page.locator('.note-card').count();
|
|
expect(results).toBeGreaterThanOrEqual(0);
|
|
});
|
|
});
|
|
|
|
/**
|
|
* Unit tests for threshold behavior
|
|
*/
|
|
test.describe('Threshold Behavior Unit Tests', () => {
|
|
test('threshold 0.65 should filter more than 0.40', () => {
|
|
// Simulate semantic scores
|
|
const scores = [0.80, 0.65, 0.50, 0.45, 0.40, 0.30, 0.20];
|
|
|
|
// Old threshold (0.40)
|
|
const oldThresholdMatches = scores.filter(s => s > 0.40).length;
|
|
|
|
// New threshold (0.65)
|
|
const newThresholdMatches = scores.filter(s => s > 0.65).length;
|
|
|
|
// New threshold should filter more strictly
|
|
expect(newThresholdMatches).toBeLessThan(oldThresholdMatches);
|
|
});
|
|
|
|
test('threshold 0.65 should keep high-quality matches', () => {
|
|
const highQualityScores = [0.95, 0.85, 0.75, 0.70, 0.68];
|
|
|
|
// All high-quality scores should pass the threshold
|
|
const matches = highQualityScores.filter(s => s > 0.65);
|
|
expect(matches.length).toBe(highQualityScores.length);
|
|
});
|
|
|
|
test('threshold 0.65 should filter low-quality matches', () => {
|
|
const lowQualityScores = [0.30, 0.40, 0.50, 0.60, 0.64];
|
|
|
|
// None should pass the 0.65 threshold
|
|
const matches = lowQualityScores.filter(s => s > 0.65);
|
|
expect(matches.length).toBe(0);
|
|
});
|
|
});
|