// A continuous coast reuses the mounted overscan instead of rebuilding it // one row at a time. Runs the production window/measurement/anchor functions. import assert from 'node:assert/strict'; import puppeteer from './puppeteer.js'; const src = await Bun.file('src/features/messages.js').text(); const start = src.indexOf(' const WINDOW_AHEAD ='); const end = src.indexOf(' // While the feed is', start); assert(start > 0 && end > start); const script = ` const h = (tag, attrs = {}, ...kids) => { const el = document.createElement(tag); for (const [k,v] of Object.entries(attrs)) el.setAttribute(k,v); for (const child of kids.flat()) el.append(child); return el; }; const notes = Array.from({ length: 200 }, (_, i) => ({ id: String(i), height: 140 + (i % 4) * 60 })); const feed = { heights: new Map(notes.map(e => [e.id, e.height])), winList: () => notes }; let renders = 0, reads = 0; const rect = Element.prototype.getBoundingClientRect; Element.prototype.getBoundingClientRect = function () { if (this.matches('.row')) reads++; return rect.call(this); }; ${src.slice(start,end)} function render() { renders++; document.querySelector('.notes-feed').replaceChildren(...windowedRows(feed, notes, e => h('div', { class: 'row', 'data-key': e.id, style: 'height:'+e.height+'px' }, 'Post '+e.id))); } window.addEventListener('scroll', () => windowScrolled(feed)); render(); window.runCoast = async () => { const frame = () => new Promise(requestAnimationFrame); await frame(); await frame(); renders = reads = 0; let gaps = 0; for (const direction of [1, -1]) { for (let i = 0; i < 180; i++) { window.scrollBy(0, direction * 50); await frame(); await frame(); const rows = [...document.querySelectorAll('.row')]; if (!rows.some(r => { const b = rect.call(r); return b.top <= 0 && b.bottom >= 0; }) && !rows.some(r => { const b = rect.call(r); return b.top >= 0 && b.top <= 2; })) gaps++; } } const result = { renders, reads, gaps, y: scrollY, mounted: document.querySelectorAll('.row').length }; // Large jumps must still replace the window immediately. scrollTo(0, 30000); await frame(); await frame(); await frame(); const visible = [...document.querySelectorAll('.row')].some(r => { const b = rect.call(r); return b.bottom > 0 && b.top < innerHeight; }); return { ...result, jumpCovered: visible }; };`; const html = `
`; const server = Bun.serve({ port: 0, fetch: () => new Response(html, { headers: { 'Content-Type': 'text/html' } }) }); const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: true, args: ['--no-sandbox'] }); try { const page = await browser.newPage(); await page.setViewport({ width: 800, height: 800 }); await page.goto(server.url.href); const result = await page.evaluate(() => runCoast()); console.log(result); assert.equal(result.gaps, 0, 'mounted posts always cover the viewport'); assert.equal(result.y, 0, 'no accumulated anchor drift'); assert(result.jumpCovered, 'a scrollbar jump is covered'); assert(result.mounted < 35, 'DOM remains bounded'); assert(result.renders <= 20, 'reuse the overscan during a coast'); assert(result.reads < 2200, 'do not measure every mounted post on every scroll frame'); } finally { await browser.close(); server.stop(true); }