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