- 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>
193 lines
7.1 KiB
TypeScript
193 lines
7.1 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { detectQueryType, getSearchWeights } from '../../lib/utils';
|
|
import { QueryType } from '../../lib/types';
|
|
|
|
test.describe('Query Type Detection Tests', () => {
|
|
test('should detect exact queries with quotes', () => {
|
|
expect(detectQueryType('"Error 404"')).toBe('exact');
|
|
expect(detectQueryType("'exact phrase'")).toBe('exact');
|
|
expect(detectQueryType('"multiple words"')).toBe('exact');
|
|
});
|
|
|
|
test('should detect conceptual queries', () => {
|
|
// Question words
|
|
expect(detectQueryType('how to cook pasta')).toBe('conceptual');
|
|
expect(detectQueryType('what is python')).toBe('conceptual');
|
|
expect(detectQueryType('where to find')).toBe('conceptual');
|
|
expect(detectQueryType('why does this happen')).toBe('conceptual');
|
|
expect(detectQueryType('who invented')).toBe('conceptual');
|
|
|
|
// Conceptual phrases
|
|
expect(detectQueryType('ways to improve')).toBe('conceptual');
|
|
expect(detectQueryType('best way to learn')).toBe('conceptual');
|
|
expect(detectQueryType('guide for beginners')).toBe('conceptual');
|
|
expect(detectQueryType('tips for cooking')).toBe('conceptual');
|
|
expect(detectQueryType('learn about javascript')).toBe('conceptual');
|
|
expect(detectQueryType('understand recursion')).toBe('conceptual');
|
|
|
|
// Learning patterns
|
|
expect(detectQueryType('tutorial on react')).toBe('conceptual');
|
|
expect(detectQueryType('guide to typescript')).toBe('conceptual');
|
|
expect(detectQueryType('introduction to python')).toBe('conceptual');
|
|
expect(detectQueryType('overview of microservices')).toBe('conceptual');
|
|
expect(detectQueryType('explanation of quantum computing')).toBe('conceptual');
|
|
expect(detectQueryType('examples of callbacks')).toBe('conceptual');
|
|
});
|
|
|
|
test('should detect mixed queries', () => {
|
|
// Simple terms
|
|
expect(detectQueryType('javascript')).toBe('mixed');
|
|
expect(detectQueryType('programming')).toBe('mixed');
|
|
expect(detectQueryType('cooking')).toBe('mixed');
|
|
|
|
// Multiple words without patterns
|
|
expect(detectQueryType('javascript programming')).toBe('mixed');
|
|
expect(detectQueryType('react components')).toBe('mixed');
|
|
expect(detectQueryType('database design')).toBe('mixed');
|
|
});
|
|
|
|
test('should be case-insensitive', () => {
|
|
expect(detectQueryType('How To Cook')).toBe('conceptual');
|
|
expect(detectQueryType('WHAT IS PYTHON')).toBe('conceptual');
|
|
expect(detectQueryType('"Error 404"')).toBe('exact');
|
|
});
|
|
|
|
test('should handle empty and whitespace queries', () => {
|
|
expect(detectQueryType('')).toBe('mixed');
|
|
expect(detectQueryType(' ')).toBe('mixed');
|
|
});
|
|
});
|
|
|
|
test.describe('Search Weight Calculation Tests', () => {
|
|
test('should return correct weights for exact queries', () => {
|
|
const weights = getSearchWeights('exact');
|
|
|
|
expect(weights.keywordWeight).toBe(2.0);
|
|
expect(weights.semanticWeight).toBe(0.7);
|
|
});
|
|
|
|
test('should return correct weights for conceptual queries', () => {
|
|
const weights = getSearchWeights('conceptual');
|
|
|
|
expect(weights.keywordWeight).toBe(0.7);
|
|
expect(weights.semanticWeight).toBe(1.5);
|
|
});
|
|
|
|
test('should return correct weights for mixed queries', () => {
|
|
const weights = getSearchWeights('mixed');
|
|
|
|
expect(weights.keywordWeight).toBe(1.0);
|
|
expect(weights.semanticWeight).toBe(1.0);
|
|
});
|
|
|
|
test('should handle unknown query type as mixed', () => {
|
|
const weights = getSearchWeights('unknown' as QueryType);
|
|
|
|
expect(weights.keywordWeight).toBe(1.0);
|
|
expect(weights.semanticWeight).toBe(1.0);
|
|
});
|
|
});
|
|
|
|
test.describe('Adaptive Weighting Integration Tests', () => {
|
|
test('exact query boosts keyword matches', () => {
|
|
const queryType = detectQueryType('"exact phrase"');
|
|
const weights = getSearchWeights(queryType);
|
|
|
|
// Keyword matches should be 2x more important
|
|
expect(weights.keywordWeight).toBeGreaterThan(weights.semanticWeight);
|
|
expect(weights.keywordWeight / weights.semanticWeight).toBeCloseTo(2.0 / 0.7, 1);
|
|
});
|
|
|
|
test('conceptual query boosts semantic matches', () => {
|
|
const queryType = detectQueryType('how to cook');
|
|
const weights = getSearchWeights(queryType);
|
|
|
|
// Semantic matches should be more important
|
|
expect(weights.semanticWeight).toBeGreaterThan(weights.keywordWeight);
|
|
expect(weights.semanticWeight / weights.keywordWeight).toBeCloseTo(1.5 / 0.7, 1);
|
|
});
|
|
|
|
test('mixed query treats both equally', () => {
|
|
const queryType = detectQueryType('javascript programming');
|
|
const weights = getSearchWeights(queryType);
|
|
|
|
// Both should have equal weight
|
|
expect(weights.keywordWeight).toBe(weights.semanticWeight);
|
|
expect(weights.keywordWeight).toBe(1.0);
|
|
});
|
|
|
|
test('weights significantly affect ranking scores', () => {
|
|
const k = 20;
|
|
const rank = 5;
|
|
|
|
// Same rank with different weights
|
|
const exactQueryScore = (1 / (k + rank)) * 2.0; // keyword
|
|
const conceptualQueryScore = (1 / (k + rank)) * 1.5; // semantic
|
|
|
|
// Exact keyword match should get highest score
|
|
expect(exactQueryScore).toBeGreaterThan(conceptualQueryScore);
|
|
});
|
|
});
|
|
|
|
test.describe('Weight Impact on Scenarios', () => {
|
|
test('scenario 1: User searches for "Error 404"', () => {
|
|
const query = '"Error 404"';
|
|
const queryType = detectQueryType(query);
|
|
const weights = getSearchWeights(queryType);
|
|
|
|
// Should be exact match type
|
|
expect(queryType).toBe('exact');
|
|
|
|
// Keyword matches should be heavily prioritized
|
|
expect(weights.keywordWeight).toBe(2.0);
|
|
|
|
// Semantic matches should be deprioritized
|
|
expect(weights.semanticWeight).toBe(0.7);
|
|
|
|
// This ensures that notes with "Error 404" appear first,
|
|
// even if semantic search might suggest other error types
|
|
});
|
|
|
|
test('scenario 2: User searches for "how to cook pasta"', () => {
|
|
const query = 'how to cook pasta';
|
|
const queryType = detectQueryType(query);
|
|
const weights = getSearchWeights(queryType);
|
|
|
|
// Should be conceptual type
|
|
expect(queryType).toBe('conceptual');
|
|
|
|
// Semantic matches should be boosted
|
|
expect(weights.semanticWeight).toBe(1.5);
|
|
|
|
// Keyword matches should be reduced
|
|
expect(weights.keywordWeight).toBe(0.7);
|
|
|
|
// This ensures that notes about cooking, pasta, recipes appear,
|
|
// even if they don't contain the exact words "how to cook pasta"
|
|
});
|
|
|
|
test('scenario 3: User searches for "tutorial javascript"', () => {
|
|
const query = 'tutorial javascript';
|
|
const queryType = detectQueryType(query);
|
|
const weights = getSearchWeights(queryType);
|
|
|
|
// Should be conceptual type (starts with "tutorial")
|
|
expect(queryType).toBe('conceptual');
|
|
|
|
// Semantic search should be prioritized
|
|
expect(weights.semanticWeight).toBeGreaterThan(weights.keywordWeight);
|
|
});
|
|
|
|
test('scenario 4: User searches for "react hooks"', () => {
|
|
const query = 'react hooks';
|
|
const queryType = detectQueryType(query);
|
|
const weights = getSearchWeights(queryType);
|
|
|
|
// Should be mixed type (no specific pattern)
|
|
expect(queryType).toBe('mixed');
|
|
|
|
// Both should have equal weight
|
|
expect(weights.keywordWeight).toBe(weights.semanticWeight);
|
|
});
|
|
});
|