UNPKG

advanced-search-library

Version:

Intelligent search library with typo correction, autocomplete, and flexible data structure support

324 lines (251 loc) 11.8 kB
/** * Advanced Search Test Suite * * @author Rıdvan Sevindik <sevindikbusiness@gmail.com> * @github https://github.com/Ridvan0 * @linkedin https://www.linkedin.com/in/ridvansevindik/ */ const AdvancedSearch = require('../src/AdvancedSearch'); class TestRunner { constructor() { this.tests = []; this.passedTests = 0; this.failedTests = 0; } test(name, testFunc) { this.tests.push({ name, testFunc }); } async run() { console.log('🔍 Advanced Search Test Suite Starting...\n'); for (const test of this.tests) { try { await test.testFunc(); console.log(`✅ PASSED: ${test.name}\n`); this.passedTests++; } catch (error) { console.log(`❌ FAILED: ${test.name}`); console.log(` Error: ${error.message}\n`); this.failedTests++; } } console.log('📊 TEST RESULTS:'); console.log(`✅ Passed: ${this.passedTests}`); console.log(`❌ Failed: ${this.failedTests}`); console.log(`📈 Success Rate: ${Math.round((this.passedTests / this.tests.length) * 100)}%`); if (this.failedTests === 0) { console.log('\n🎉 All tests passed successfully!'); } } assertEqual(actual, expected, message = '') { if (actual !== expected) { throw new Error(`${message} - Expected: ${expected}, Got: ${actual}`); } } assertTrue(condition, message = '') { if (!condition) { throw new Error(`${message} - Condition returned false`); } } assertContains(array, item, message = '') { if (!array.includes(item)) { throw new Error(`${message} - Array does not contain "${item}"`); } } } // Test data const testData = [ { id: 1, name: "Turkish Coffee", category: "Beverage", price: 45, rating: 4.8 }, { id: 2, name: "Tea Glass Set", category: "Kitchenware", price: 85, rating: 4.5 }, { id: 3, name: "Drum Set", category: "Music", price: 2500, rating: 4.9 }, { id: 4, name: "Classical Guitar", category: "Music", price: 850, rating: 4.7 }, { id: 5, name: "Smartphone", category: "Electronics", price: 15000, rating: 4.6 } ]; // Test runner'ı başlat const runner = new TestRunner(); // Test 1: Constructor runner.test('Constructor', () => { const search = new AdvancedSearch(); runner.assertTrue(search instanceof AdvancedSearch, 'Should create AdvancedSearch instance'); runner.assertTrue(Array.isArray(search.data), 'Data should be an array'); runner.assertEqual(search.data.length, 0, 'Initial data should be empty'); console.log(' ℹ️ Constructor test completed successfully'); }); // Test 2: Add Data runner.test('Add Data', () => { const search = new AdvancedSearch(); search.addData(testData); runner.assertEqual(search.data.length, 5, 'Should add 5 items'); runner.assertTrue(search.data[0]._searchText, 'Should create search text'); runner.assertTrue(search.data[0]._searchTextNormalized, 'Should create normalized search text'); console.log(` ℹ️ Added ${search.data.length} items successfully`); }); // Test 3: Basic Search runner.test('Basic Search', () => { const search = new AdvancedSearch(); search.addData(testData); const results = search.search('coffee'); runner.assertTrue(results.length > 0, 'Coffee search should return results'); runner.assertTrue(results[0].name.toLowerCase().includes('coffee'), 'First result should contain coffee'); console.log(` ℹ️ "coffee" search returned ${results.length} results`); }); // Test 4: Typo Correction runner.test('Typo Correction', () => { const search = new AdvancedSearch(); search.addData(testData); const results = search.search('cofee'); // Missing 'f' runner.assertTrue(results.length > 0, 'Typo correction should work'); console.log(` ℹ️ "cofee" typo search returned ${results.length} results`); }); // Test 5: Autocomplete runner.test('Autocomplete', () => { const search = new AdvancedSearch(); search.addData(testData); const suggestions = search.autocomplete('tea'); runner.assertTrue(Array.isArray(suggestions), 'Suggestions should be array'); console.log(` ℹ️ "tea" autocomplete returned ${suggestions.length} suggestions: [${suggestions.join(', ')}]`); }); // Test 6: Price Range Filter runner.test('Price Range Filter', () => { const search = new AdvancedSearch(); search.addData(testData); const results = search.search('', { priceRange: [40, 100] }); runner.assertTrue(results.length > 0, 'Price filter should return results'); results.forEach(item => { runner.assertTrue(item.price >= 40 && item.price <= 100, 'All results should be within price range'); }); console.log(` ℹ️ Price range [40-100] returned ${results.length} results`); }); // Test 7: Category Filter runner.test('Category Filter', () => { const search = new AdvancedSearch(); search.addData(testData); const results = search.search('', { category: 'Music' }); runner.assertTrue(results.length > 0, 'Category filter should return results'); results.forEach(item => { runner.assertTrue(item.category.toLowerCase().includes('music'), 'All results should be in Music category'); }); console.log(` ℹ️ Music category filter returned ${results.length} results`); }); // Test 8: Sorting runner.test('Sorting', () => { const search = new AdvancedSearch(); search.addData(testData); const results = search.search('', { sortBy: 'price_asc' }); runner.assertTrue(results.length > 0, 'Sorting should return results'); for (let i = 1; i < results.length; i++) { runner.assertTrue(results[i-1].price <= results[i].price, 'Results should be sorted by price ascending'); } console.log(` ℹ️ Price ascending sort test passed with ${results.length} results`); }); // Test 9: Search History runner.test('Search History', () => { const search = new AdvancedSearch(); search.addData(testData); search.search('coffee'); search.search('guitar'); const history = search.getSearchHistory(); runner.assertTrue(Array.isArray(history), 'History should be array'); runner.assertTrue(history.length >= 2, 'History should contain searches'); console.log(` ℹ️ Search history contains ${history.length} entries`); }); // Test 10: Statistics runner.test('Statistics', () => { const search = new AdvancedSearch(); search.addData(testData); search.search('coffee'); search.search('guitar'); const stats = search.getStats(); runner.assertEqual(stats.totalItems, 5, 'Total items should be 5'); runner.assertTrue(stats.totalSearches >= 2, 'Total searches should be at least 2'); console.log(` ℹ️ Statistics: ${stats.totalItems} items, ${stats.totalSearches} searches`); }); // Test 11: Empty Query runner.test('Empty Query', () => { const search = new AdvancedSearch(); search.addData(testData); const results = search.search(''); runner.assertEqual(results.length, testData.length, 'Empty query should return all results'); console.log(` ℹ️ Empty query returned ${results.length} results (all items)`); }); // Test 12: Case Insensitive Search runner.test('Case Insensitive Search', () => { const search = new AdvancedSearch(); search.addData(testData); const lowerResults = search.search('coffee'); const upperResults = search.search('COFFEE'); const mixedResults = search.search('Coffee'); runner.assertEqual(lowerResults.length, upperResults.length, 'Case should not affect results'); runner.assertEqual(lowerResults.length, mixedResults.length, 'Mixed case should work same'); console.log(` ℹ️ Case insensitive test passed - all variants returned ${lowerResults.length} results`); }); // Test 13: Custom Fields runner.test('Custom Fields', () => { const search = new AdvancedSearch({ customFields: [ { field: 'name', priority: 15, exact: 25 }, { field: 'description', priority: 8, exact: 12 } ] }); const customData = [ { id: 1, name: 'Test Product', description: 'This is a test item for searching' }, { id: 2, name: 'Another Item', description: 'Different product description' } ]; search.addData(customData); const results = search.search('test'); runner.assertTrue(results.length > 0, 'Custom fields search should work'); runner.assertTrue(results[0].name.toLowerCase().includes('test'), 'Should find test in name field'); console.log(` ℹ️ Custom fields search returned ${results.length} results`); }); // Test 14: Auto Field Detection runner.test('Auto Field Detection', () => { const search = new AdvancedSearch(); const customData = [ { id: 1, productName: 'Phone', specifications: 'Smart device', manufacturer: 'Apple' }, { id: 2, productName: 'Tablet', specifications: 'Portable', manufacturer: 'Samsung' } ]; search.addData(customData); const results = search.search('phone'); runner.assertTrue(results.length > 0, 'Auto-detected fields search should work'); runner.assertEqual(results[0].productName, 'Phone', 'Custom field search should be successful'); console.log(` ℹ️ Detected fields: ${search.searchableFields.map(f => f.field).join(', ')}`); }); // Test 15: Multi-word Search runner.test('Multi-word Search', () => { const search = new AdvancedSearch(); search.addData(testData); const results = search.search('drum set'); runner.assertTrue(results.length > 0, 'Multi-word search should work'); const topResult = results[0]; runner.assertTrue( topResult.name.toLowerCase().includes('drum') && topResult.name.toLowerCase().includes('set'), 'Top result should contain both words' ); console.log(` ℹ️ "drum set" multi-word search returned ${results.length} results`); }); // Test 16: Performance Test runner.test('Performance Test', () => { const search = new AdvancedSearch(); // Generate larger dataset const largeDataset = []; for (let i = 0; i < 1000; i++) { largeDataset.push({ id: i, name: `Product ${i}`, category: `Category ${i % 10}`, description: `Description for product ${i} with various keywords`, price: Math.floor(Math.random() * 1000) + 10, rating: Math.round((Math.random() * 5) * 10) / 10 }); } search.addData(largeDataset); const startTime = performance.now(); const results = search.search('product'); const endTime = performance.now(); const searchTime = endTime - startTime; runner.assertTrue(searchTime < 100, 'Search should complete within 100ms'); runner.assertTrue(results.length > 0, 'Should find results in large dataset'); console.log(` ℹ️ Performance: ${searchTime.toFixed(2)}ms for 1000 items, found ${results.length} results`); }); // Run all tests runner.run().catch(console.error); module.exports = { TestRunner, testData };