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