Files
searchi/apps/desktop/tests/match-display.test.mjs

125 lines
7.8 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import {literalQuery,highlightParts,renderHighlighted,displayExcerpt,matchExplanation} from '../src/match-display.ts';
const marked = (text,query) => highlightParts(text,literalQuery(query)).filter(part => part.matched).map(part => part.text);
test('literal marks use whole words, identifiers, quoted phrases and filename separators', () => {
assert.deepEqual(marked('Contract contractual subcontract CONTRACT.', 'contract'), ['Contract','CONTRACT']);
assert.deepEqual(marked('E_SEARCH_071 E_SEARCH_0712', 'E_SEARCH_071'), ['E_SEARCH_071']);
assert.deepEqual(marked('invoice_october.pdf', 'invoice october'), ['invoice','october']);
assert.deepEqual(marked('Give end of contract notice. Contract end.', '"end of contract"'), ['end of contract','Contract','end']);
assert.deepEqual(marked('Use C++ and literal a+b, not aab.', 'C++ a+b'), ['C++','a+b']);
});
test('accent and case matching preserves original combining characters and surrogate offsets', () => {
const source='🧾 Café Cafe\u0301 MÜNCHEN München. 𐐀cafe';
const parts=highlightParts(source,literalQuery('cafe munchen'));
assert.equal(parts.map(part => part.text).join(''),source);
assert.deepEqual(parts.filter(part => part.matched).map(part => part.text), ['Café','Cafe\u0301','MÜNCHEN','München']);
assert.equal(parts[0].text, '🧾 ');
});
test('common English/German stopwords and punctuation do not create noisy evidence', () => {
assert.deepEqual(marked('The and für über supplier', 'the and für'), []);
assert.deepEqual(marked('The and. Supplier.', 'the and.'), []);
assert.deepEqual(marked('The supplier partnership may end.', 'cancel the supplier partnership'), ['supplier','partnership']);
});
test('query snapshots and mark runs are bounded; later inputs cannot relabel older results', () => {
let input='supplier';
const snapshot=literalQuery(input);
input='garden';
assert.equal(snapshot.query,'supplier');
assert.deepEqual(highlightParts('supplier garden',snapshot).filter(part => part.matched).map(part => part.text),['supplier']);
assert.equal(Object.isFrozen(snapshot.patterns),true);
assert.ok(literalQuery('x'.repeat(9000)).query.length <= 4096);
assert.ok(literalQuery(Array.from({length:100},(_,i)=>`term${i}`).join(' ')).patterns.length <= 64);
assert.ok(marked('supplier '.repeat(1000),'supplier').length <= 256);
});
test('list excerpts remain original text around the first literal hit and retain ellipsis', () => {
const text='Intro '.repeat(200)+'supplier partnership notice '+ 'Closing '.repeat(200);
const excerpt=displayExcerpt(text,literalQuery('supplier partnership'));
assert.ok(excerpt.length <= 424);
assert.ok(excerpt.startsWith('… '));
assert.ok(excerpt.endsWith(' …'));
assert.ok(excerpt.includes('supplier partnership'));
assert.ok(text.includes(excerpt.slice(2,-2)));
});
test('bounded marks retain the earliest hit across terms instead of favoring longest-term repetition', () => {
const source='short '+ 'longerterm '.repeat(400);
const parts=highlightParts(source,literalQuery('short longerterm'));
assert.equal(parts[0].text,'short');
assert.equal(parts[0].matched,true);
assert.ok(parts.filter(part=>part.matched).length <= 256);
assert.equal(parts.map(part=>part.text).join(''),source);
});
test('markup and regex-looking queries create text nodes and marks, never parsed HTML', () => {
const nodes=[];
const doc={createDocumentFragment:()=>({append:node=>nodes.push(node)}),createElement:tag=>({tag,dataset:{},textContent:''}),createTextNode:text=>({text})};
const target={ownerDocument:doc,replaceChildren:()=>{}};
const source='<img src=x onerror=evil()> supplier [a-z].';
renderHighlighted(target,source,literalQuery('supplier [a-z]'));
assert.equal(nodes.map(node=>node.text ?? node.textContent).join(''),source);
assert.ok(nodes.some(node=>node.tag==='mark' && node.textContent==='supplier'));
assert.ok(nodes.every(node=>node.tag===undefined || node.tag==='mark'));
});
test('optional reasons use real source routes and never claim semantic word confidence', () => {
const text=matchExplanation({match_kind:'semantic',source:{kind:'page',number:2}});
assert.match(text.reason,/page 2/);
assert.match(text.legend,/without the same words/);
assert.equal(text.semanticPassage,true);
assert.match(matchExplanation({match_kind:'semantic_audio',source:{kind:'timestamp_ms',number:13129}}).reason,/0:13.*not an exact spoken phrase/);
const speech=matchExplanation({match_kind:'semantic_speech',source:{kind:'timestamp_ms',number:13129}});
assert.match(speech.reason,/0:13.*estimated token position/);
assert.equal(speech.semanticPassage,true);
assert.match(matchExplanation({match_kind:'semantic_video',source:{kind:'timestamp_ms',number:24167}}).reason,/frame.*0:24/);
assert.match(matchExplanation({match_kind:'semantic_image'}).reason,/whole/);
assert.match(matchExplanation({match_kind:'name_terms'}).reason,/filename, path or content/);
assert.equal(matchExplanation({match_kind:'hybrid',source:{kind:'page',number:2}}).semanticPassage,false);
assert.doesNotMatch(JSON.stringify(text),/confidence|percent|importance/);
});
test('code passage reasons retain actual indexed source line', () => {
assert.match(matchExplanation({match_kind:'semantic', source:{kind:'line',number:97}}).reason, /at line 97/);
assert.match(matchExplanation({match_kind:'hybrid', source:{kind:'line',number:97}}).reason, /showing the passage at line 97/);
});
import {baseMatchKind,partitionResults} from '../src/match-display.ts';
import {ResultSelection} from '../src/selection-state.ts';
test('related candidates remain retained but excluded from collapsed keyboard selection', () => {
const weak={path:'mountain',match_kind:'semantic_image_related'}, strong={path:'supplier',match_kind:'semantic'}, exact={path:'name',match_kind:'exact_filename'};
const found=[weak,strong,exact];
assert.deepEqual(partitionResults(found),{primary:[strong,exact],related:[weak],visible:[strong,exact]});
assert.deepEqual(partitionResults(found,true).visible,[strong,exact,weak]);
assert.deepEqual(found,[weak,strong,exact]);
});
test('only related has no implicit Enter target; empty stays distinct', () => {
const weak={path:'weak',match_kind:'semantic_related'};
const selection=new ResultSelection(); selection.begin('missing');
assert.equal(selection.resolve(partitionResults([weak]).visible.map(r=>r.path)),-1);
assert.equal(selection.resolve(partitionResults([weak],true).visible.map(r=>r.path)),0);
assert.deepEqual(partitionResults([]),{primary:[],related:[],visible:[]});
});
test('progressive primary results preserve user intent across related disclosure', () => {
const a={path:'a',match_kind:'content'},b={path:'b',match_kind:'semantic_related'};
const selection=new ResultSelection(); selection.begin('query');
selection.resolve(partitionResults([a,b]).visible.map(r=>r.path)); selection.choose('b');
assert.equal(selection.resolve(partitionResults([a,b],true).visible.map(r=>r.path)),1);
assert.equal(selection.resolve(partitionResults([a,b]).visible.map(r=>r.path)),0);
assert.equal(selection.resolve(partitionResults([a,b],true).visible.map(r=>r.path)),1);
});
test('related media and speech retain source routes and explain unverified similarity', () => {
for(const kind of ['semantic','semantic_image','semantic_audio','semantic_video','semantic_speech']) {
assert.equal(baseMatchKind(kind+'_related'),kind);
const primary=matchExplanation({match_kind:kind,source:{kind:'timestamp_ms',number:1500}});
const related=matchExplanation({match_kind:kind+'_related',source:{kind:'timestamp_ms',number:1500}});
assert.equal(related.semanticPassage,primary.semanticPassage); assert.equal(related.legend,primary.legend);
assert.ok(related.reason.includes('similarity alone does not verify')); assert.ok(related.reason.endsWith(primary.reason));
}
});