@@ -311,4 +311,119 @@ describe("UI endpoints", () => {
311311 expect ( html ) . toContain ( "BlinkMacSystemFont" ) ;
312312 expect ( html ) . toContain ( "Segoe UI" ) ;
313313 } ) ;
314+
315+ test ( "GET /ui/events returns HTML events timeline page" , async ( ) => {
316+ const res = await fetch ( `${ baseUrl } /ui/events` ) ;
317+
318+ expect ( res . status ) . toBe ( 200 ) ;
319+ expect ( res . headers . get ( "Content-Type" ) ) . toContain ( "text/html" ) ;
320+
321+ const html = await res . text ( ) ;
322+
323+ // Verify it's HTML
324+ expect ( html ) . toContain ( "<!DOCTYPE html>" ) ;
325+ expect ( html ) . toContain ( "<html" ) ;
326+ expect ( html ) . toContain ( "</html>" ) ;
327+ expect ( html ) . toContain ( "Event Timeline" ) ;
328+
329+ // Verify init function
330+ expect ( html ) . toContain ( "loadEvents" ) ;
331+
332+ // Verify WS connection
333+ expect ( html ) . toContain ( "connectWebSocket" ) ;
334+ expect ( html ) . toContain ( "new WebSocket" ) ;
335+
336+ // Verify auth token is embedded
337+ expect ( html ) . toContain ( authToken ) ;
338+ } ) ;
339+
340+ test ( "Events page includes filtering controls" , async ( ) => {
341+ const res = await fetch ( `${ baseUrl } /ui/events` ) ;
342+ const html = await res . text ( ) ;
343+
344+ // Verify filter inputs
345+ expect ( html ) . toContain ( 'id="filter-name"' ) ;
346+ expect ( html ) . toContain ( 'id="filter-channel"' ) ;
347+ expect ( html ) . toContain ( 'id="filter-topic"' ) ;
348+
349+ // Verify pause button
350+ expect ( html ) . toContain ( 'id="pause-btn"' ) ;
351+ } ) ;
352+
353+ test ( "Events page uses XSS-safe rendering" , async ( ) => {
354+ const res = await fetch ( `${ baseUrl } /ui/events` ) ;
355+ const html = await res . text ( ) ;
356+
357+ // Verify textContent usage (not innerHTML for user data)
358+ expect ( html ) . toContain ( "textContent" ) ;
359+
360+ // Verify JSON rendering is safe
361+ expect ( html ) . toContain ( "JSON.stringify" ) ;
362+
363+ // No eval
364+ expect ( html ) . not . toContain ( "eval(" ) ;
365+ } ) ;
366+
367+ test ( "Events page includes reconnection logic" , async ( ) => {
368+ const res = await fetch ( `${ baseUrl } /ui/events` ) ;
369+ const html = await res . text ( ) ;
370+
371+ // Verify reconnection handling
372+ expect ( html ) . toContain ( "attemptReconnect" ) ;
373+ expect ( html ) . toContain ( "reconnectAttempts" ) ;
374+ expect ( html ) . toContain ( "retry-btn" ) ;
375+ } ) ;
376+
377+ test ( "Events page includes pause/resume with buffering" , async ( ) => {
378+ const res = await fetch ( `${ baseUrl } /ui/events` ) ;
379+ const html = await res . text ( ) ;
380+
381+ // Verify pause/resume logic
382+ expect ( html ) . toContain ( "togglePause" ) ;
383+ expect ( html ) . toContain ( "pausedBuffer" ) ;
384+ expect ( html ) . toContain ( "flushPausedBuffer" ) ;
385+
386+ // Verify buffer limits
387+ expect ( html ) . toContain ( "MAX_BUFFER" ) ;
388+ } ) ;
389+
390+ test ( "Events page includes navigation link in other pages" , async ( ) => {
391+ // Check channels page
392+ const channelsRes = await fetch ( `${ baseUrl } /ui` ) ;
393+ const channelsHtml = await channelsRes . text ( ) ;
394+ expect ( channelsHtml ) . toContain ( '/ui/events' ) ;
395+ expect ( channelsHtml ) . toContain ( 'Events' ) ;
396+
397+ // Check topics page
398+ const topicsRes = await fetch ( `${ baseUrl } /ui/channels/${ channelId } ` ) ;
399+ const topicsHtml = await topicsRes . text ( ) ;
400+ expect ( topicsHtml ) . toContain ( '/ui/events' ) ;
401+
402+ // Check messages page
403+ const messagesRes = await fetch ( `${ baseUrl } /ui/topics/${ topicId } ` ) ;
404+ const messagesHtml = await messagesRes . text ( ) ;
405+ expect ( messagesHtml ) . toContain ( '/ui/events' ) ;
406+ } ) ;
407+
408+ test ( "Events page validates IDs before creating links" , async ( ) => {
409+ const res = await fetch ( `${ baseUrl } /ui/events` ) ;
410+ const html = await res . text ( ) ;
411+
412+ // Verify ID validation
413+ expect ( html ) . toContain ( "isValidId" ) ;
414+ expect ( html ) . toContain ( "ID_REGEX" ) ;
415+ } ) ;
416+
417+ test ( "Events page includes entity linking logic" , async ( ) => {
418+ const res = await fetch ( `${ baseUrl } /ui/events` ) ;
419+ const html = await res . text ( ) ;
420+
421+ // Verify entity rendering with links
422+ expect ( html ) . toContain ( "renderEntity" ) ;
423+ expect ( html ) . toContain ( "entity-link" ) ;
424+
425+ // Verify topic and message link generation
426+ expect ( html ) . toContain ( "entity.type === 'topic'" ) ;
427+ expect ( html ) . toContain ( "entity.type === 'message'" ) ;
428+ } ) ;
314429} ) ;
0 commit comments