import { describe, expect, it } from 'vitest'; import { rankSearchHits, TIER, type SearchHit } from '../src/lib/search.js'; const hit = (p: Partial & { id: string; tier: SearchHit['tier'] }): SearchHit => ({ type: 'cancer', slug: p.id, name: p.id, subtitle: null, score: 0.5, ...p }); describe('rankSearchHits', () => { it('orders exact > alias > prefix > fuzzy regardless of input order', () => { const out = rankSearchHits([hit({ id: 'fz', tier: TIER.fuzzy, score: 0.99 }), hit({ id: 'pf', tier: TIER.prefix }), hit({ id: 'ex', tier: TIER.exact, score: 0.1 }), hit({ id: 'al', tier: TIER.alias })]); expect(out.map((r) => r.id)).toEqual(['ex', 'al', 'pf', 'fz']); expect(out.map((r) => r.match)).toEqual(['exact', 'alias', 'prefix', 'fuzzy']); }); it('is deterministic within a tier: score desc, entity type, shorter name, name, id', () => { const out = rankSearchHits([ hit({ id: 'b', tier: TIER.prefix, score: 0.5, name: 'Lung Cancer' }), hit({ id: 'a', tier: TIER.prefix, score: 0.5, name: 'Lung Adenocarcinoma' }), hit({ id: 'g', tier: TIER.prefix, score: 0.5, type: 'gene', name: 'LUNG' }), hit({ id: 'c', tier: TIER.prefix, score: 0.9, name: 'Lung Neoplasm' }), ]); expect(out.map((r) => r.id)).toEqual(['c', 'b', 'a', 'g']); expect(rankSearchHits([hit({ id: 'x', tier: TIER.prefix }), hit({ id: 'y', tier: TIER.prefix })]).map((r) => r.id)).toEqual(['x', 'y']); }); it('keeps one result per (type,id) using the best tier', () => { const out = rankSearchHits([hit({ id: 'x', tier: TIER.fuzzy }), hit({ id: 'x', tier: TIER.exact }), hit({ id: 'x', tier: TIER.alias })]); expect(out).toHaveLength(1); expect(out[0]!.match).toBe('exact'); }); it('caps the result count', () => { const many = Array.from({ length: 50 }, (_, i) => hit({ id: `id${String(i).padStart(2, '0')}`, tier: TIER.prefix })); expect(rankSearchHits(many, 20)).toHaveLength(20); }); });