209 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			209 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| import { test, expect } from '@playwright/test';
 | |
| 
 | |
| test.describe('Meilisearch Search Integration', () => {
 | |
|   test.beforeEach(async ({ page }) => {
 | |
|     // Navigate to the app
 | |
|     await page.goto('http://localhost:4000');
 | |
|     
 | |
|     // Wait for the app to load
 | |
|     await page.waitForLoadState('networkidle');
 | |
|   });
 | |
| 
 | |
|   test('should perform search via Meilisearch backend', async ({ page }) => {
 | |
|     // Open search panel (assuming there's a search button or input)
 | |
|     const searchInput = page.locator('input[type="text"]').first();
 | |
|     await searchInput.click();
 | |
|     
 | |
|     // Type search query
 | |
|     await searchInput.fill('test');
 | |
|     
 | |
|     // Wait for debounce (300ms) + network request
 | |
|     await page.waitForTimeout(500);
 | |
|     
 | |
|     // Check that search results are displayed
 | |
|     const results = page.locator('[class*="search-result"]').first();
 | |
|     await expect(results).toBeVisible({ timeout: 5000 });
 | |
|   });
 | |
| 
 | |
|   test('should not freeze UI during search typing', async ({ page }) => {
 | |
|     const searchInput = page.locator('input[type="text"]').first();
 | |
|     await searchInput.click();
 | |
|     
 | |
|     // Type quickly to test debounce
 | |
|     await searchInput.type('angular', { delay: 50 });
 | |
|     
 | |
|     // UI should remain responsive
 | |
|     const isEnabled = await searchInput.isEnabled();
 | |
|     expect(isEnabled).toBe(true);
 | |
|     
 | |
|     // Wait for debounced search
 | |
|     await page.waitForTimeout(400);
 | |
|   });
 | |
| 
 | |
|   test('should use Meilisearch API endpoint', async ({ page }) => {
 | |
|     // Listen for API calls
 | |
|     const apiCalls: string[] = [];
 | |
|     page.on('request', request => {
 | |
|       if (request.url().includes('/api/search')) {
 | |
|         apiCalls.push(request.url());
 | |
|       }
 | |
|     });
 | |
|     
 | |
|     // Perform search
 | |
|     const searchInput = page.locator('input[type="text"]').first();
 | |
|     await searchInput.click();
 | |
|     await searchInput.fill('note');
 | |
|     await searchInput.press('Enter');
 | |
|     
 | |
|     // Wait for API call
 | |
|     await page.waitForTimeout(1000);
 | |
|     
 | |
|     // Verify API was called
 | |
|     expect(apiCalls.length).toBeGreaterThan(0);
 | |
|     expect(apiCalls[0]).toContain('/api/search?q=note');
 | |
|   });
 | |
| 
 | |
|   test('should display search results quickly', async ({ page }) => {
 | |
|     const searchInput = page.locator('input[type="text"]').first();
 | |
|     await searchInput.click();
 | |
|     
 | |
|     const startTime = Date.now();
 | |
|     
 | |
|     // Perform search
 | |
|     await searchInput.fill('test');
 | |
|     await searchInput.press('Enter');
 | |
|     
 | |
|     // Wait for results
 | |
|     await page.waitForSelector('[class*="search-result"]', { timeout: 2000 });
 | |
|     
 | |
|     const endTime = Date.now();
 | |
|     const duration = endTime - startTime;
 | |
|     
 | |
|     // Search should complete in less than 2 seconds
 | |
|     expect(duration).toBeLessThan(2000);
 | |
|     console.log(`Search completed in ${duration}ms`);
 | |
|   });
 | |
| 
 | |
|   test('should handle empty search gracefully', async ({ page }) => {
 | |
|     const searchInput = page.locator('input[type="text"]').first();
 | |
|     await searchInput.click();
 | |
|     await searchInput.fill('xyzabc123notfound');
 | |
|     await searchInput.press('Enter');
 | |
|     
 | |
|     // Wait for response
 | |
|     await page.waitForTimeout(1000);
 | |
|     
 | |
|     // Should show "no results" message
 | |
|     const noResults = page.locator('text=/No results|Aucun résultat/i').first();
 | |
|     await expect(noResults).toBeVisible({ timeout: 3000 });
 | |
|   });
 | |
| 
 | |
|   test('should support Obsidian search operators', async ({ page }) => {
 | |
|     const searchInput = page.locator('input[type="text"]').first();
 | |
|     await searchInput.click();
 | |
|     
 | |
|     // Test path: operator
 | |
|     await searchInput.fill('path:docs');
 | |
|     await searchInput.press('Enter');
 | |
|     await page.waitForTimeout(500);
 | |
|     
 | |
|     // Clear and test tag: operator
 | |
|     await searchInput.clear();
 | |
|     await searchInput.fill('tag:important');
 | |
|     await searchInput.press('Enter');
 | |
|     await page.waitForTimeout(500);
 | |
|     
 | |
|     // Should not crash
 | |
|     const isEnabled = await searchInput.isEnabled();
 | |
|     expect(isEnabled).toBe(true);
 | |
|   });
 | |
| 
 | |
|   test('should debounce live search during typing', async ({ page }) => {
 | |
|     // Listen for API calls
 | |
|     let apiCallCount = 0;
 | |
|     page.on('request', request => {
 | |
|       if (request.url().includes('/api/search')) {
 | |
|         apiCallCount++;
 | |
|       }
 | |
|     });
 | |
|     
 | |
|     const searchInput = page.locator('input[type="text"]').first();
 | |
|     await searchInput.click();
 | |
|     
 | |
|     // Type slowly (each char triggers potential search)
 | |
|     await searchInput.type('angular', { delay: 100 });
 | |
|     
 | |
|     // Wait for debounce to settle
 | |
|     await page.waitForTimeout(500);
 | |
|     
 | |
|     // Should have made fewer API calls than characters typed (due to debounce)
 | |
|     // "angular" = 7 chars, but debounce should reduce calls
 | |
|     expect(apiCallCount).toBeLessThan(7);
 | |
|     console.log(`API calls made: ${apiCallCount} (debounced from 7 chars)`);
 | |
|   });
 | |
| });
 | |
| 
 | |
| test.describe('Meilisearch Backend API', () => {
 | |
|   test('should return search results from /api/search', async ({ request }) => {
 | |
|     const response = await request.get('http://localhost:4000/api/search?q=test&limit=5');
 | |
|     
 | |
|     expect(response.ok()).toBeTruthy();
 | |
|     
 | |
|     const data = await response.json();
 | |
|     expect(data).toHaveProperty('hits');
 | |
|     expect(data).toHaveProperty('processingTimeMs');
 | |
|     expect(data).toHaveProperty('query');
 | |
|     expect(data.query).toBe('test');
 | |
|     
 | |
|     console.log(`Meilisearch processed search in ${data.processingTimeMs}ms`);
 | |
|   });
 | |
| 
 | |
|   test('should support highlighting', async ({ request }) => {
 | |
|     const response = await request.get('http://localhost:4000/api/search?q=note&highlight=true&limit=3');
 | |
|     
 | |
|     expect(response.ok()).toBeTruthy();
 | |
|     
 | |
|     const data = await response.json();
 | |
|     
 | |
|     if (data.hits.length > 0) {
 | |
|       // Check if highlighting is present
 | |
|       const firstHit = data.hits[0];
 | |
|       expect(firstHit).toHaveProperty('_formatted');
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   test('should handle pagination', async ({ request }) => {
 | |
|     const response1 = await request.get('http://localhost:4000/api/search?q=note&limit=2&offset=0');
 | |
|     const response2 = await request.get('http://localhost:4000/api/search?q=note&limit=2&offset=2');
 | |
|     
 | |
|     expect(response1.ok()).toBeTruthy();
 | |
|     expect(response2.ok()).toBeTruthy();
 | |
|     
 | |
|     const data1 = await response1.json();
 | |
|     const data2 = await response2.json();
 | |
|     
 | |
|     // Results should be different (different pages)
 | |
|     if (data1.hits.length > 0 && data2.hits.length > 0) {
 | |
|       expect(data1.hits[0].id).not.toBe(data2.hits[0].id);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   test('should be fast (< 100ms)', async ({ request }) => {
 | |
|     const startTime = Date.now();
 | |
|     
 | |
|     const response = await request.get('http://localhost:4000/api/search?q=angular&limit=10');
 | |
|     
 | |
|     const endTime = Date.now();
 | |
|     const duration = endTime - startTime;
 | |
|     
 | |
|     expect(response.ok()).toBeTruthy();
 | |
|     
 | |
|     const data = await response.json();
 | |
|     
 | |
|     // Total time (network + processing) should be < 100ms for localhost
 | |
|     expect(duration).toBeLessThan(100);
 | |
|     
 | |
|     console.log(`Total search time: ${duration}ms (Meili: ${data.processingTimeMs}ms)`);
 | |
|   });
 | |
| });
 |