## Bug Fixes ### Note Card Actions - Fix broken size change functionality (missing state declaration) - Implement React 19 useOptimistic for instant UI feedback - Add startTransition for non-blocking updates - Ensure smooth animations without page refresh - All note actions now work: pin, archive, color, size, checklist ### Markdown LaTeX Rendering - Add remark-math and rehype-katex plugins - Support inline equations with dollar sign syntax - Support block equations with double dollar sign syntax - Import KaTeX CSS for proper styling - Equations now render correctly instead of showing raw LaTeX ## Technical Details - Replace undefined currentNote references with optimistic state - Add optimistic updates before server actions for instant feedback - Use router.refresh() in transitions for smart cache invalidation - Install remark-math, rehype-katex, and katex packages ## Testing - Build passes successfully with no TypeScript errors - Dev server hot-reloads changes correctly
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);
|
|
});
|
|
});
|