Keep/keep-notes/tests/unit/adaptive-weighting.test.ts
sepehr 640fcb26f7 fix: improve note interactions and markdown LaTeX support
## 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
2026-01-09 22:13:49 +01:00

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);
});
});