@@ -2,7 +2,7 @@ import { describe, it, before, after } from 'node:test';
22import assert from 'node:assert' ;
33import { mkdtempSync , rmSync } from 'node:fs' ;
44import { tmpdir } from 'node:os' ;
5- import { join , resolve } from 'node:path' ;
5+ import { join } from 'node:path' ;
66
77import {
88 createSearchIndex ,
@@ -13,10 +13,8 @@ import {
1313 type SearchDocument ,
1414 type SearchResult ,
1515} from '../src/docs/searchIndex' ;
16- import { extractDoc , iterContentDocs } from '../src/docs/search/extract' ;
17- import { findContentRoot } from './helpers/content' ;
16+ import { extractDoc } from '../src/docs/search/extract' ;
1817
19- const CONTENT_DIR = findContentRoot ( ) ;
2018const paths = ( results : SearchResult [ ] ) : string [ ] => results . map ( ( r ) => String ( r . path ) ) ;
2119
2220describe ( 'docs search - field config (parity with docs_index.py)' , ( ) => {
@@ -144,51 +142,39 @@ describe('docs search - save/load round-trip (json-1)', () => {
144142 } ) ;
145143} ) ;
146144
147- // Smoke fixtures updated for TF-IDF (minsearch) -> BM25-lite (zerosearch).
148- // Captured against a minsearch top-k baseline: top-1 is identical for every
149- // query below; ordering shifts inside the relevant set, recall stays on par
150- // (see issue #85 notes). Each fixture asserts the BM25-lite top-1 plus a doc
151- // that must remain in the top-5 (recall guard).
152- const SMOKE : { query : string ; top1 : string ; recall : string } [ ] = [
153- {
154- query : 'podcast intake' ,
155- top1 : 'content/media/podcast/templates/podcast-share-the-podcast-page-template.md' ,
156- recall : 'content/tasks/templates/podcast.md' ,
157- } ,
158- {
159- query : 'newsletter sponsor' ,
160- top1 : 'content/overview/reference/newsletter.md' ,
161- recall : 'content/newsletter/sponsorship/sops/creating-a-document-for-sponsored-content-for-a-newsletter.md' ,
162- } ,
163- {
164- query : 'course certificate' ,
165- top1 : 'content/tasks/templates/course.md' ,
166- recall : 'content/courses/reference/course-guide.md' ,
167- } ,
168- {
169- query : 'youtube upload' ,
170- top1 : 'content/media/open-source-spotlight/reference/for-update-download-open-source-spotlight-video-from-zoom-and-upload-it-to-youtube.md' ,
171- recall : 'content/media/video-youtube/sops/downloading-and-uploading-videos-from-loom-to-youtube.md' ,
172- } ,
145+ // Public synthetic relevance fixtures: title/summary matches should outrank
146+ // incidental single-word matches, while related body text remains discoverable.
147+ const SMOKE = [
148+ { query : 'podcast intake' , title : 'Podcast intake checklist' , related : 'Recording preparation' , distractor : 'Podcast audio settings' } ,
149+ { query : 'newsletter sponsor' , title : 'Newsletter sponsor overview' , related : 'Publication planning' , distractor : 'Newsletter typography' } ,
150+ { query : 'course certificate' , title : 'Course certificate guide' , related : 'Completion records' , distractor : 'Course exercises' } ,
151+ { query : 'youtube upload' , title : 'YouTube upload checklist' , related : 'Video preparation' , distractor : 'YouTube analytics' } ,
173152] ;
174153
175- describe ( 'docs search - smoke query relevance over the document corpus (BM25-lite)' , ( ) => {
176- // The corpus lives in the private knowledge repository, so these rank real
177- // documents only when it is checked out.
178- let index : ReturnType < typeof createSearchIndex > ;
179- before ( ( ) => {
180- if ( ! CONTENT_DIR ) return ;
181- const docs = iterContentDocs ( CONTENT_DIR ) ;
182- index = createSearchIndex ( ) . fit ( docs ) ;
183- } ) ;
184-
185- for ( const { query, top1, recall } of SMOKE ) {
186- it ( `ranks the expected doc first and keeps recall for "${ query } "` , ( t ) => {
187- if ( ! CONTENT_DIR ) return t . skip ( 'knowledge repository not checked out' ) ;
154+ describe ( 'docs search - smoke query relevance over a public synthetic corpus (BM25-lite)' , ( ) => {
155+ const corpus : SearchDocument [ ] = SMOKE . flatMap ( ( { query, title, related, distractor } , index ) => [
156+ {
157+ path : `synthetic/topic-${ index } /overview.md` , id : `overview-${ index } ` ,
158+ title, summary : `A synthetic overview of ${ query } .` ,
159+ body : 'Reference material for a test topic.' ,
160+ } ,
161+ {
162+ path : `synthetic/topic-${ index } /related.md` , id : `related-${ index } ` ,
163+ title : related , body : `This synthetic reference also discusses ${ query } .` ,
164+ } ,
165+ {
166+ path : `synthetic/topic-${ index } /incidental.md` , id : `incidental-${ index } ` ,
167+ title : distractor , body : 'A separate topic with only an incidental keyword match.' ,
168+ } ,
169+ ] ) ;
170+ const index = createSearchIndex ( ) . fit ( corpus ) ;
171+
172+ for ( const [ topic , { query } ] of SMOKE . entries ( ) ) {
173+ it ( `ranks the expected doc first and keeps recall for "${ query } "` , ( ) => {
188174 const results = index . search ( query , { numResults : 5 } ) ;
189- assert . ok ( results . length > 0 , 'expected at least one result' ) ;
190- assert . strictEqual ( results [ 0 ] . path , top1 , `top-1 for " ${ query } "` ) ;
191- assert . ok ( paths ( results ) . includes ( recall ) , `" ${ recall } " should be in top-5 for " ${ query } "` ) ;
175+ assert . strictEqual ( results [ 0 ] ?. path , `synthetic/topic- ${ topic } /overview.md` , `top-1 for " ${ query } "` ) ;
176+ assert . ok ( paths ( results ) . includes ( `synthetic/topic- ${ topic } /related.md` ) , 'related body-text match remains in top-5' ) ;
177+ assert . ok ( paths ( results ) . includes ( `synthetic/topic- ${ topic } /incidental.md` ) , 'a competing single-term document was also searched' ) ;
192178 } ) ;
193179 }
194180} ) ;
0 commit comments