You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
92 lines
3.8 KiB
TypeScript
92 lines
3.8 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import type { Item } from '@sims/domain'
|
|
import { buildIndex, normalize, phoneticKey, querySearch } from '@sims/search-core'
|
|
|
|
function item(name: string, extra: Partial<Item> = {}): Item {
|
|
return {
|
|
id: name, tenantId: 't1', code: name.replace(/\s+/g, '').toLowerCase(), name,
|
|
hsn: '', taxClassCode: 'GST18', unit: { code: 'PCS', decimals: 0 },
|
|
salePricePaise: 1000, priceIncludesTax: true, barcodes: [], batchTracked: false,
|
|
status: 'active', ...extra,
|
|
}
|
|
}
|
|
|
|
const names = (items: Item[]): string[] => items.map((i) => i.name)
|
|
|
|
describe('normalize', () => {
|
|
it('lowercases, strips punctuation, collapses spaces, keeps digits', () => {
|
|
expect(normalize(' Aashirvaad Atta, 5kg! ')).toBe('aashirvaad atta 5kg')
|
|
expect(normalize('Coca-Cola')).toBe('coca cola')
|
|
})
|
|
})
|
|
|
|
describe('phoneticKey — Indic folding', () => {
|
|
it('folds the mandated collisions to one key', () => {
|
|
expect(phoneticKey('chawal')).toBe(phoneticKey('chaval')) // w→v (the mandatory case)
|
|
expect(phoneticKey('paneer')).toBe(phoneticKey('panir')) // ee→i
|
|
expect(phoneticKey('jeera')).toBe(phoneticKey('zeera')) // z→j + ee→i
|
|
expect(phoneticKey('phal')).toBe(phoneticKey('fal')) // ph→f
|
|
expect(phoneticKey('sharbat')).toBe(phoneticKey('sarbat')) // sh→s
|
|
expect(phoneticKey('dhaniya')).toBe(phoneticKey('daniya')) // dh→d
|
|
expect(phoneticKey('bhindi')).toBe(phoneticKey('bindi')) // bh→b
|
|
expect(phoneticKey('thums')).toBe(phoneticKey('tums')) // th→t
|
|
})
|
|
it('folds long vowels to short', () => {
|
|
expect(phoneticKey('aata')).toBe('ata') // aa→a
|
|
expect(phoneticKey('cheese')).toBe('chise') // ee→i (trailing vowel kept)
|
|
expect(phoneticKey('noodle')).toBe(phoneticKey('nudle')) // oo→u
|
|
})
|
|
it('drops doubled letters', () => {
|
|
expect(phoneticKey('kotta')).toBe(phoneticKey('kota'))
|
|
expect(phoneticKey('mummy')).toBe('mumy')
|
|
})
|
|
it('is stable for already-folded input', () => {
|
|
expect(phoneticKey('chaval')).toBe('chaval')
|
|
})
|
|
})
|
|
|
|
describe('querySearch — tiered ranking', () => {
|
|
const cat = [
|
|
item('Tomato'),
|
|
item('Tomato Ketchup'),
|
|
item('Green Tomato'),
|
|
item('Bottom Shelf Oil'),
|
|
]
|
|
const index = buildIndex(cat)
|
|
|
|
it('orders whole-prefix before token-prefix before substring', () => {
|
|
const out = names(querySearch(index, 'tom'))
|
|
// tier 1 (name prefix, shorter first), tier 2 (token prefix), tier 4 (substring)
|
|
expect(out).toEqual(['Tomato', 'Tomato Ketchup', 'Green Tomato', 'Bottom Shelf Oil'])
|
|
})
|
|
|
|
it('breaks ties on the shorter name', () => {
|
|
const out = names(querySearch(buildIndex([item('Tomato Ketchup'), item('Tomato')]), 'tomato'))
|
|
expect(out).toEqual(['Tomato', 'Tomato Ketchup'])
|
|
})
|
|
|
|
it('returns nothing for an empty query', () => {
|
|
expect(querySearch(index, ' ')).toEqual([])
|
|
})
|
|
})
|
|
|
|
describe('querySearch — phonetic tier finds transliteration variants', () => {
|
|
it('finds a Chawal-spelled item when the cashier types "chaval" and vice versa', () => {
|
|
const index = buildIndex([item('Chawal Basmati'), item('Sugar')])
|
|
expect(names(querySearch(index, 'chaval'))).toContain('Chawal Basmati')
|
|
const index2 = buildIndex([item('Chaval Premium'), item('Sugar')])
|
|
expect(names(querySearch(index2, 'chawal'))).toContain('Chaval Premium')
|
|
})
|
|
|
|
it('ranks an exact prefix above a phonetic-only match', () => {
|
|
const index = buildIndex([item('Chaval Rice'), item('Chawal Gold')])
|
|
// typing "chaw": "Chawal Gold" is a whole-prefix (tier 1), "Chaval Rice" only phonetic (tier 3)
|
|
expect(names(querySearch(index, 'chaw'))).toEqual(['Chawal Gold', 'Chaval Rice'])
|
|
})
|
|
|
|
it('matches against the regional secondary name', () => {
|
|
const index = buildIndex([item('Coriander', { nameSecondary: 'Dhaniya' })])
|
|
expect(names(querySearch(index, 'daniya'))).toContain('Coriander')
|
|
})
|
|
})
|