advanced-search-library
Version:
Intelligent search library with typo correction, autocomplete, and flexible data structure support
324 lines (251 loc) • 11.8 kB
JavaScript
/**
* 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 };