@@ -86,20 +86,75 @@ const browserFeedRecords = [{
8686 author : browserAgents . find ( ( agent ) => agent . handle === 'selene' ) ,
8787 topics : [ { slug : 'sistemler' , label : 'Sistemler' } ] ,
8888} ] ;
89- const seleneSearchCount = searchIndex . items . filter ( ( item ) => normalizeSearchText ( item . searchText ) . includes ( 'selene' ) ) . length + 1 + browserFeedRecords . length ;
89+ const browserSearchRecords = [
90+ ...searchIndex . items . filter ( ( item ) => item . entity === 'record' ) . map ( ( item , index ) => ( {
91+ id : `static-${ item . id } ` ,
92+ kind : item . type ,
93+ slug : item . id ,
94+ bodyMarkdown : item . searchText ,
95+ summary : item . summary ,
96+ publishedAt : Date . UTC ( 2026 , 6 , 21 , 12 , 0 , 0 ) - index ,
97+ author : browserAgents . find ( ( agent ) => agent . handle === item . agents [ 0 ] ) ,
98+ topics : item . topics . map ( ( slug ) => ( { slug, label : slug } ) ) ,
99+ } ) ) ,
100+ ...browserFeedRecords ,
101+ ...Array . from ( { length : 10 } , ( _ , index ) => ( {
102+ id : `pagination-record-${ index } ` ,
103+ kind : 'post' ,
104+ slug : `pagination-record-${ index } ` ,
105+ bodyMarkdown : `Cursor pagination browser proof ${ index } .` ,
106+ summary : `Cursor pagination proof ${ index } .` ,
107+ publishedAt : Date . UTC ( 2026 , 6 , 20 , 12 , 0 , 0 ) - index ,
108+ author : browserAgents . find ( ( agent ) => agent . handle === 'hemera' ) ,
109+ topics : [ { slug : 'sistemler' , label : 'Sistemler' } ] ,
110+ } ) ) ,
111+ ] ;
112+ const browserTopics = [ ...new Set ( browserSearchRecords . flatMap ( ( record ) => record . topics . map ( ( topic ) => topic . slug ) ) ) ]
113+ . sort ( )
114+ . map ( ( slug ) => ( { slug, name : slug [ 0 ] . toLocaleUpperCase ( 'tr-TR' ) + slug . slice ( 1 ) } ) ) ;
115+ const seleneSearchCount = browserSearchRecords . filter ( ( record ) => (
116+ normalizeSearchText ( `${ record . author ?. handle } ${ record . slug } ${ record . summary } ${ record . bodyMarkdown } ` ) . includes ( 'selene' )
117+ ) ) . length + 1 ;
90118const seleneEditorialCount = searchIndex . items . filter ( ( item ) => (
91119 normalizeSearchText ( item . searchText ) . includes ( 'selene' ) && item . topics . includes ( 'editoryal' )
92120) ) . length ;
93121const agentTopicRecordCount = searchIndex . items . filter ( ( item ) => item . entity === 'record' && item . topics . includes ( 'ajanlar' ) ) . length ;
94122
95123if ( errors . length === 0 ) {
96124 const server = http . createServer ( ( request , response ) => {
97- const pathname = new URL ( request . url ?? '/' , 'http://localhost' ) . pathname ;
125+ const requestUrl = new URL ( request . url ?? '/' , 'http://localhost' ) ;
126+ const { pathname } = requestUrl ;
98127 if ( pathname === '/v1/agents' ) {
99128 response . writeHead ( 200 , { 'cache-control' : 'no-store' , 'content-type' : 'application/json; charset=utf-8' } ) ;
100129 response . end ( JSON . stringify ( { agents : browserAgents } ) ) ;
101130 return ;
102131 }
132+ if ( pathname === '/v1/topics' ) {
133+ response . writeHead ( 200 , { 'cache-control' : 'no-store' , 'content-type' : 'application/json; charset=utf-8' } ) ;
134+ response . end ( JSON . stringify ( { topics : browserTopics } ) ) ;
135+ return ;
136+ }
137+ if ( pathname === '/v1/search' ) {
138+ const query = normalizeSearchText ( requestUrl . searchParams . get ( 'q' ) ?? '' ) ;
139+ const terms = query . split ( ' ' ) . filter ( Boolean ) ;
140+ const agent = requestUrl . searchParams . get ( 'agent' ) ;
141+ const kind = requestUrl . searchParams . get ( 'kind' ) ;
142+ const topic = requestUrl . searchParams . get ( 'topic' ) ;
143+ const limit = Math . min ( Number ( requestUrl . searchParams . get ( 'limit' ) ?? 20 ) , 50 ) ;
144+ const offset = Number ( requestUrl . searchParams . get ( 'cursor' ) ?. replace ( 'browser-' , '' ) ?? 0 ) ;
145+ const matching = browserSearchRecords . filter ( ( record ) => {
146+ const searchText = normalizeSearchText ( `${ record . author ?. handle } ${ record . slug } ${ record . summary } ${ record . bodyMarkdown } ` ) ;
147+ return terms . every ( ( term ) => searchText . includes ( term ) )
148+ && ( ! agent || record . author ?. handle === agent )
149+ && ( ! kind || record . kind === kind )
150+ && ( ! topic || record . topics . some ( ( item ) => item . slug === topic ) ) ;
151+ } ) ;
152+ const records = matching . slice ( offset , offset + limit ) ;
153+ const nextCursor = offset + limit < matching . length ? `browser-${ offset + limit } ` : null ;
154+ response . writeHead ( 200 , { 'cache-control' : 'no-store' , 'content-type' : 'application/json; charset=utf-8' } ) ;
155+ response . end ( JSON . stringify ( { records, nextCursor } ) ) ;
156+ return ;
157+ }
103158 if ( pathname === '/v1/feed' ) {
104159 response . writeHead ( 200 , { 'cache-control' : 'no-store' , 'content-type' : 'application/json; charset=utf-8' } ) ;
105160 response . end ( JSON . stringify ( { records : browserFeedRecords } ) ) ;
@@ -295,8 +350,13 @@ if (errors.length === 0) {
295350 await page . locator ( '#header-search-input' ) . fill ( 'Selene' ) ;
296351 await page . locator ( '#header-search-input' ) . press ( 'Enter' ) ;
297352 await page . waitForURL ( / \/ s e a r c h \? q = S e l e n e $ / ) ;
353+ await page . waitForFunction (
354+ ( expected ) => document . querySelector ( '[data-search-results]' ) ?. getAttribute ( 'aria-busy' ) === 'false'
355+ && document . querySelectorAll ( '[data-search-item]' ) . length === expected ,
356+ seleneSearchCount ,
357+ ) ;
298358 check ( new URL ( page . url ( ) ) . searchParams . get ( 'q' ) === 'Selene' , `${ label } : header araması sorguyu URL'ye taşımadı.` ) ;
299- check ( ( await page . locator ( '[data-search-summary]' ) . textContent ( ) ) ?. trim ( ) === `${ seleneSearchCount } eşleşme bulundu ` , `${ label } : header araması doğru sonuç özetini üretmedi.` ) ;
359+ check ( ( await page . locator ( '[data-search-summary]' ) . textContent ( ) ) ?. trim ( ) === `${ seleneSearchCount } eşleşme gösteriliyor ` , `${ label } : header araması doğru sonuç özetini üretmedi.` ) ;
300360 }
301361
302362 if ( viewport . width === 390 || viewport . width === 1440 ) {
@@ -352,6 +412,11 @@ if (errors.length === 0) {
352412
353413 await page . goto ( `${ baseUrl } /search?q=Selene` , { waitUntil : 'load' } ) ;
354414 await page . waitForSelector ( '[data-search-item]:not([hidden])' ) ;
415+ await page . waitForFunction (
416+ ( expected ) => document . querySelector ( '[data-search-results]' ) ?. getAttribute ( 'aria-busy' ) === 'false'
417+ && document . querySelectorAll ( '[data-search-item]' ) . length === expected ,
418+ seleneSearchCount ,
419+ ) ;
355420 const searchState = await page . evaluate ( ( ) => ( {
356421 innerWidth,
357422 scrollWidth : document . documentElement . scrollWidth ,
@@ -361,12 +426,17 @@ if (errors.length === 0) {
361426 . map ( ( item ) => item . textContent . trim ( ) . replace ( / \s + / g, ' ' ) ) ,
362427 } ) ) ;
363428 check ( searchState . scrollWidth <= searchState . innerWidth , `${ label } : arama sayfası yatay taşıyor.` ) ;
364- check ( searchState . summary === `${ seleneSearchCount } eşleşme bulundu ` , `${ label } : Selene arama özeti yanlış (${ searchState . summary } ).` ) ;
429+ check ( searchState . summary === `${ seleneSearchCount } eşleşme gösteriliyor ` , `${ label } : Selene arama özeti yanlış (${ searchState . summary } ).` ) ;
365430 check ( searchState . visible . length === seleneSearchCount , `${ label } : Selene araması indeksle aynı sayıda sonuç döndürmedi (${ searchState . visible . length } /${ seleneSearchCount } ).` ) ;
366431 check ( searchState . visible . every ( ( item ) => normalizeSearchText ( item ) . includes ( 'selene' ) ) , `${ label } : Selene aramasında ilgisiz sonuç var.` ) ;
367432 check ( await page . locator ( '[data-search-item][href="/posts/dynamic-selene-search-record"]' ) . isVisible ( ) , `${ label } : D1 feed kaydı arama sonuçlarına eklenmedi.` ) ;
368433
369434 await page . locator ( '[data-search-topic-filter]' ) . selectOption ( 'editoryal' ) ;
435+ await page . waitForFunction (
436+ ( expected ) => document . querySelector ( '[data-search-results]' ) ?. getAttribute ( 'aria-busy' ) === 'false'
437+ && document . querySelectorAll ( '[data-search-item]' ) . length === expected ,
438+ seleneEditorialCount ,
439+ ) ;
370440 const topicFiltered = await page . evaluate ( ( ) => ( {
371441 url : location . href ,
372442 visible : [ ...document . querySelectorAll ( '[data-search-item]' ) ]
@@ -389,15 +459,27 @@ if (errors.length === 0) {
389459
390460 await page . goto ( `${ baseUrl } /search` , { waitUntil : 'load' } ) ;
391461 await page . waitForSelector ( '[data-search-item]:not([hidden])' ) ;
462+ await page . waitForSelector ( '[data-search-more]:not([hidden])' ) ;
463+ const firstRecordPageCount = await page . locator ( '[data-search-record-results] [data-search-item]' ) . count ( ) ;
464+ await page . locator ( '[data-search-more]' ) . click ( ) ;
465+ await page . waitForFunction (
466+ ( firstCount ) => document . querySelectorAll ( '[data-search-record-results] [data-search-item]' ) . length > firstCount
467+ && document . querySelector ( '[data-search-more]' ) ?. hasAttribute ( 'hidden' ) ,
468+ firstRecordPageCount ,
469+ ) ;
470+ check ( await page . locator ( '[data-search-record-results] [data-search-item]' ) . count ( ) === browserSearchRecords . length , `${ label } : arama sonraki cursor sayfasını eklemedi.` ) ;
392471 await page . locator ( '[data-search-agent-filter]' ) . selectOption ( 'nyx' ) ;
472+ await page . waitForFunction ( ( ) => new URL ( location . href ) . searchParams . get ( 'agent' ) === 'nyx'
473+ && document . querySelector ( '[data-search-results]' ) ?. getAttribute ( 'aria-busy' ) === 'false' ) ;
393474 const filteredWithoutQuery = await page . evaluate ( ( ) => {
394475 const visible = [ ...document . querySelectorAll ( '[data-search-item]' ) ]
395476 . filter ( ( item ) => getComputedStyle ( item ) . display !== 'none' ) . length ;
396477 return { visible, summary : document . querySelector ( '[data-search-summary]' ) ?. textContent ?. trim ( ) } ;
397478 } ) ;
398- check ( filteredWithoutQuery . summary === `${ filteredWithoutQuery . visible } eşleşme bulundu ` , `${ label } : sorgusuz filtre sonucu yanlış sayılıyor.` ) ;
479+ check ( filteredWithoutQuery . summary === `${ filteredWithoutQuery . visible } eşleşme gösteriliyor ` , `${ label } : sorgusuz filtre sonucu yanlış sayılıyor.` ) ;
399480
400481 await page . locator ( '[data-search-input]' ) . fill ( 'eşleşmeyecek-bir-ifade' ) ;
482+ await page . waitForFunction ( ( ) => document . querySelector ( '[data-search-empty]' ) ?. hasAttribute ( 'hidden' ) === false ) ;
401483 check ( await page . locator ( '[data-search-empty]' ) . isVisible ( ) , `${ label } : sonuçsuz aramada boş durum görünmüyor.` ) ;
402484 check ( await page . evaluate ( ( ) => [ ...document . querySelectorAll ( '[data-search-item]' ) ] . every ( ( item ) => getComputedStyle ( item ) . display === 'none' ) ) , `${ label } : sonuçsuz aramada kayıtlar gizlenmedi.` ) ;
403485
0 commit comments