2026-10-12 19:57:34 +02:00
|
|
|
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/);
|
2026-10-13 20:18:44 +02:00
|
|
|
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);
|
2026-10-12 19:57:34 +02:00
|
|
|
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/);
|
|
|
|
|
});
|
2026-10-12 21:22:11 +02:00
|
|
|
|
|
|
|
|
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/);
|
|
|
|
|
});
|
2026-10-14 12:45:32 +02:00
|
|
|
|
|
|
|
|
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));
|
|
|
|
|
}
|
|
|
|
|
});
|