feat(search): Improve search functionality: update documentation, refine Lucene query transformation, and add advanced search tests
This commit is contained in:
187
test/test.search.advanced.ts
Normal file
187
test/test.search.advanced.ts
Normal file
@ -0,0 +1,187 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import * as smartmongo from '@push.rocks/smartmongo';
|
||||
import * as smartdata from '../ts/index.js';
|
||||
import { searchable, getSearchableFields } from '../ts/classes.doc.js';
|
||||
import { smartunique } from '../ts/plugins.js';
|
||||
|
||||
// Set up database connection
|
||||
let smartmongoInstance: smartmongo.SmartMongo;
|
||||
let testDb: smartdata.SmartdataDb;
|
||||
|
||||
// Define a test class for advanced search scenarios
|
||||
@smartdata.Collection(() => testDb)
|
||||
class Product extends smartdata.SmartDataDbDoc<Product, Product> {
|
||||
@smartdata.unI()
|
||||
public id: string = smartunique.shortId();
|
||||
|
||||
@smartdata.svDb()
|
||||
@searchable()
|
||||
public name: string;
|
||||
|
||||
@smartdata.svDb()
|
||||
@searchable()
|
||||
public description: string;
|
||||
|
||||
@smartdata.svDb()
|
||||
@searchable()
|
||||
public category: string;
|
||||
|
||||
@smartdata.svDb()
|
||||
public price: number;
|
||||
|
||||
constructor(
|
||||
nameArg: string,
|
||||
descriptionArg: string,
|
||||
categoryArg: string,
|
||||
priceArg: number,
|
||||
) {
|
||||
super();
|
||||
this.name = nameArg;
|
||||
this.description = descriptionArg;
|
||||
this.category = categoryArg;
|
||||
this.price = priceArg;
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize DB and insert sample products
|
||||
tap.test('setup advanced search database', async () => {
|
||||
smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
|
||||
testDb = new smartdata.SmartdataDb(
|
||||
await smartmongoInstance.getMongoDescriptor(),
|
||||
);
|
||||
await testDb.init();
|
||||
});
|
||||
|
||||
tap.test('insert products for advanced search', async () => {
|
||||
const products = [
|
||||
new Product(
|
||||
'Night Owl Lamp',
|
||||
'Bright lamp for night reading',
|
||||
'Lighting',
|
||||
29,
|
||||
),
|
||||
new Product(
|
||||
'Day Light Lamp',
|
||||
'Daytime lamp with adjustable brightness',
|
||||
'Lighting',
|
||||
39,
|
||||
),
|
||||
new Product(
|
||||
'Office Chair',
|
||||
'Ergonomic chair for office',
|
||||
'Furniture',
|
||||
199,
|
||||
),
|
||||
new Product(
|
||||
'Gaming Chair',
|
||||
'Comfortable for long gaming sessions',
|
||||
'Furniture',
|
||||
299,
|
||||
),
|
||||
new Product(
|
||||
'iPhone 12',
|
||||
'Latest iPhone with A14 Bionic chip',
|
||||
'Electronics',
|
||||
999,
|
||||
),
|
||||
new Product(
|
||||
'AirPods',
|
||||
'Wireless earbuds with noise cancellation',
|
||||
'Electronics',
|
||||
249,
|
||||
),
|
||||
];
|
||||
for (const p of products) {
|
||||
await p.save();
|
||||
}
|
||||
const all = await Product.getInstances({});
|
||||
expect(all.length).toEqual(products.length);
|
||||
});
|
||||
|
||||
// Simple exact field:value matching
|
||||
tap.test('simpleExact: category:Furniture returns chairs', async () => {
|
||||
const res = await Product.search('category:Furniture');
|
||||
expect(res.length).toEqual(2);
|
||||
const names = res.map((r) => r.name).sort();
|
||||
expect(names).toEqual(['Gaming Chair', 'Office Chair']);
|
||||
});
|
||||
|
||||
// simpleExact invalid field should throw
|
||||
tap.test('simpleExact invalid field errors', async () => {
|
||||
let error: Error;
|
||||
try {
|
||||
await Product.search('price:29');
|
||||
} catch (e) {
|
||||
error = e as Error;
|
||||
}
|
||||
expect(error).toBeTruthy();
|
||||
expect(error.message).toMatch(/not searchable/);
|
||||
});
|
||||
|
||||
// Quoted phrase search
|
||||
tap.test('quoted phrase "Bright lamp" matches Night Owl Lamp', async () => {
|
||||
const res = await Product.search('"Bright lamp"');
|
||||
expect(res.length).toEqual(1);
|
||||
expect(res[0].name).toEqual('Night Owl Lamp');
|
||||
});
|
||||
|
||||
tap.test("quoted phrase 'night reading' matches Night Owl Lamp", async () => {
|
||||
const res = await Product.search("'night reading'");
|
||||
expect(res.length).toEqual(1);
|
||||
expect(res[0].name).toEqual('Night Owl Lamp');
|
||||
});
|
||||
|
||||
|
||||
tap.test('wildcard description:*gaming* matches Gaming Chair', async () => {
|
||||
const res = await Product.search('description:*gaming*');
|
||||
expect(res.length).toEqual(1);
|
||||
expect(res[0].name).toEqual('Gaming Chair');
|
||||
});
|
||||
|
||||
// Boolean AND and OR
|
||||
tap.test('boolean AND: category:Lighting AND lamp', async () => {
|
||||
const res = await Product.search('category:Lighting AND lamp');
|
||||
expect(res.length).toEqual(2);
|
||||
});
|
||||
|
||||
tap.test('boolean OR: Furniture OR Electronics', async () => {
|
||||
const res = await Product.search('Furniture OR Electronics');
|
||||
expect(res.length).toEqual(4);
|
||||
});
|
||||
|
||||
// Multi-term unquoted -> AND across terms
|
||||
tap.test('multi-term unquoted adjustable brightness', async () => {
|
||||
const res = await Product.search('adjustable brightness');
|
||||
expect(res.length).toEqual(1);
|
||||
expect(res[0].name).toEqual('Day Light Lamp');
|
||||
});
|
||||
|
||||
tap.test('multi-term unquoted Night Lamp', async () => {
|
||||
const res = await Product.search('Night Lamp');
|
||||
expect(res.length).toEqual(1);
|
||||
expect(res[0].name).toEqual('Night Owl Lamp');
|
||||
});
|
||||
|
||||
// Grouping with parentheses
|
||||
tap.test('grouping: (Furniture OR Electronics) AND Chair', async () => {
|
||||
const res = await Product.search(
|
||||
'(Furniture OR Electronics) AND Chair',
|
||||
);
|
||||
expect(res.length).toEqual(2);
|
||||
const names = res.map((r) => r.name).sort();
|
||||
expect(names).toEqual(['Gaming Chair', 'Office Chair']);
|
||||
});
|
||||
|
||||
// Teardown
|
||||
tap.test('cleanup advanced search database', async () => {
|
||||
await testDb.mongoDb.dropDatabase();
|
||||
await testDb.close();
|
||||
if (smartmongoInstance) {
|
||||
await smartmongoInstance.stopAndDumpToDir(
|
||||
`.nogit/dbdump/test.search.advanced.ts`,
|
||||
);
|
||||
}
|
||||
setTimeout(() => process.exit(), 2000);
|
||||
});
|
||||
|
||||
tap.start({ throwOnError: true });
|
@ -221,6 +221,42 @@ tap.test('should search multi-word term across fields', async () => {
|
||||
expect(termResults[0].name).toEqual('iPhone 12');
|
||||
});
|
||||
|
||||
// Additional search scenarios
|
||||
tap.test('should return zero results for non-existent terms', async () => {
|
||||
const noResults = await Product.search('NonexistentTerm');
|
||||
expect(noResults.length).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('should search products by description term "noise"', async () => {
|
||||
const noiseResults = await Product.search('noise');
|
||||
expect(noiseResults.length).toEqual(1);
|
||||
expect(noiseResults[0].name).toEqual('AirPods');
|
||||
});
|
||||
|
||||
tap.test('should search products by description term "flagship"', async () => {
|
||||
const flagshipResults = await Product.search('flagship');
|
||||
expect(flagshipResults.length).toEqual(1);
|
||||
expect(flagshipResults[0].name).toEqual('Galaxy S21');
|
||||
});
|
||||
|
||||
tap.test('should search numeric strings "12"', async () => {
|
||||
const twelveResults = await Product.search('12');
|
||||
expect(twelveResults.length).toEqual(1);
|
||||
expect(twelveResults[0].name).toEqual('iPhone 12');
|
||||
});
|
||||
|
||||
tap.test('should search hyphenated terms "high-speed"', async () => {
|
||||
const hyphenResults = await Product.search('high-speed');
|
||||
expect(hyphenResults.length).toEqual(1);
|
||||
expect(hyphenResults[0].name).toEqual('Blender');
|
||||
});
|
||||
|
||||
tap.test('should search hyphenated terms "E-reader"', async () => {
|
||||
const ereaderResults = await Product.search('E-reader');
|
||||
expect(ereaderResults.length).toEqual(1);
|
||||
expect(ereaderResults[0].name).toEqual('Kindle Paperwhite');
|
||||
});
|
||||
|
||||
tap.test('close database connection', async () => {
|
||||
await testDb.mongoDb.dropDatabase();
|
||||
await testDb.close();
|
||||
|
Reference in New Issue
Block a user