import assert from "node:assert/strict"; export async function runOverlaySmoke({ evaluate }) { const results = await evaluate(`(async () => { const { renderOverlayLeaderboard, renderTeamOverlay, renderObsOverlay } = await import('/src/overlays.js'); const { escapeHtml } = await import('/src/html_escape.js'); const { installListSorting } = await import('/src/list_sorting.js'); const frame = document.createElement('iframe'); frame.style.cssText = 'position:fixed;left:0;top:0;height:900px;border:0;z-index:99999'; document.body.append(frame); const doc = frame.contentDocument; const style = doc.createElement('link'); style.rel='stylesheet'; style.href='/src/styles.css'; const loaded = new Promise((resolve,reject) => {style.onload=resolve;style.onerror=reject;}); doc.head.append(style); await loaded; const labels = {'table.pos':'Pos','table.driver':'Förare','table.laps':'Varv','table.result':'Resultat','table.gap':'Gap','table.ahead_gap':'Gap fram','table.own_delta':'Eget delta','table.best_lap':'Bästa varv'}; let layout='leaderboard'; const deps={t:key=>labels[key]||key, escapeHtml, formatLap:()=> '0:36.619', formatTeamActiveMemberLabel:()=> 'Test Driver', formatPredictedLapDelta:()=> '-', getVisiblePassings:()=>[], getEventName:()=> 'Club Practice', getSessionTypeLabel:()=> 'Practice', getStartModeLabel:()=> 'Mass start', getStatusLabel:()=> 'Running', getObsOverlayConfig:()=>({layout,rows:10,showClock:true,showLaps:true,showResult:true,showBest:true,showGap:true}), normalizeStartMode:v=>v, renderPositionGrid:()=>'', getSessionGridEntries:()=>[]}; const rows=[{key:'a',displayName:'Karl Bodell',laps:27,resultDisplay:'0:36.619',bestLapMs:36619},{key:'b',displayName:'Anders',laps:19,resultDisplay:'0:37.854',bestLapMs:37854}]; const results=[]; const tick=()=>new Promise(resolve=>setTimeout(resolve,0)); try { for (const mode of ['leaderboard','tv','team']) { for (const width of [390,900,1600]) { frame.style.width=width+'px'; doc.body.innerHTML='
'; const root=doc.querySelector('.overlay-board'); root.innerHTML=mode==='team' ? renderTeamOverlay(rows,{}, {},deps) : renderOverlayLeaderboard(rows,deps); const stop=installListSorting(root); await tick(); const list=root.querySelector('.overlay-race-list'); const header=list.querySelector('.list-sort-toolbar'); const row=list.querySelector('.overlay-race-row'); const deltas=[...header.children].map((button,index)=>Math.abs(button.getBoundingClientRect().left-row.children[index].getBoundingClientRect().left)); for(let attempt=0;attempt<3 && !header.children[1].textContent.includes("▲");attempt++) header.children[1].click(); await tick(); const firstName=list.querySelector('.overlay-race-driver strong').textContent; const firstPosition=list.querySelector('.pos-pill').textContent; results.push({mode,width,aligned:deltas.every(delta=>delta<1),firstName,firstPosition,columns:header.children.length,tableScroll:list.scrollWidth>list.clientWidth}); stop(); } } for (layout of ['leaderboard','grid']) { for (const width of [390,1600]) { frame.style.width=width+'px'; doc.body.innerHTML='
'; const root=doc.body.firstElementChild; root.innerHTML=renderObsOverlay({name:'Practice',eventId:'e',status:'running'},rows,{}, {remainingMs:300000},{},deps); const stop=installListSorting(root);await tick(); results.push({mode:'obs-'+layout,width,controls:root.querySelectorAll('.list-sort-toolbar').length}); stop(); } } } finally {frame.remove();} return results; })()`); for (const result of results) { if (result.mode.startsWith("obs")) assert.equal(result.controls, 0, JSON.stringify(result)); else { assert.equal(result.aligned, true, JSON.stringify(result)); assert.equal(result.columns, 8, JSON.stringify(result)); assert.equal(result.firstName, "Anders", JSON.stringify(result)); assert.equal(result.firstPosition, "2", JSON.stringify(result)); } } console.log("PASS overlay heading alignment at 390/900/1600px, sorting preserves race position, OBS has no sort controls"); // OBS tower: in a final, places gained/lost since the start; in practice the best lap is // shown once (result and best were the same time twice). const obs = await evaluate(`(async () => { const { renderObsOverlay } = await import('/src/overlays.js'); const { escapeHtml } = await import('/src/html_escape.js'); const config = { layout: 'leaderboard', rows: 10, showClock: true, showLaps: true, showResult: true, showBest: true, showGap: true, showPositionChange: true }; const deps = { t: (key) => key, escapeHtml, formatLap: (ms) => '0:' + (ms / 1000).toFixed(3), formatTeamActiveMemberLabel: () => '', getVisiblePassings: () => [], getEventName: () => 'Club', getSessionTypeLabel: () => 'Final', getStartModeLabel: () => 'Position', getStatusLabel: () => 'Running', getObsOverlayConfig: () => config, normalizeStartMode: (v) => v, renderPositionGrid: () => '', getSessionGridEntries: () => [], getSessionGridOrder: () => ['a', 'b', 'c', 'd', 'e'] }; const rows = [ { key: 'e', driverId: 'e', displayName: 'Eve', laps: 14, resultDisplay: '14/5:02.312', bestLapMs: 20100, gapDisplay: '-' }, { key: 'a', driverId: 'a', displayName: 'Ann', laps: 14, resultDisplay: '14/5:05.000', bestLapMs: 20300, gapDisplay: '+2.688' }, { key: 'c', driverId: 'c', displayName: 'Cid', laps: 13, resultDisplay: '13/5:01.000', bestLapMs: 21000, gapDisplay: '+1L' }, ]; const box = document.createElement('div'); box.innerHTML = renderObsOverlay({ type: 'final', startMode: 'position', status: 'running' }, rows, {}, { remainingMs: 1000 }, {}, deps); const finalRows = [...box.querySelectorAll('.overlay-obs-tower-row')].map((row) => [...row.querySelector('.overlay-obs-tower-trail').children].map((span) => span.textContent.trim()).join(' ')); box.innerHTML = renderObsOverlay({ type: 'open_practice', startMode: 'mass', status: 'running' }, [{ key: 'p', driverId: 'p', displayName: 'Pia', laps: 23, resultDisplay: '0:15.833', bestLapMs: 15833, gapDisplay: '-' }], {}, { untimed: true, elapsedMs: 1000 }, {}, deps); const practiceRow = [...box.querySelector('.overlay-obs-tower-trail').children].map((span) => span.textContent.trim()).join(' '); return { finalRows, practiceRow }; })()`); assert.equal(obs.finalRows[0], "▲4 14L 14/5:02.312 0:20.100 -", "started 5th, leads: ▲4, laps and result"); assert.equal(obs.finalRows[1], "▼1 14L 14/5:05.000 0:20.300 +2.688", "started 1st, now 2nd: ▼1"); assert.equal(obs.finalRows[2], "13L 13/5:01.000 0:21.000 +1L", "same place as at the start: no arrow"); assert.equal(obs.practiceRow, "23L 0:15.833 -", "practice: the best lap once, not twice"); console.log("PASS OBS overlay shows places gained/lost since the start in finals, and the best lap once in practice"); }