1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
|
// 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 = `<style>body{margin:0}.notes-feed{overflow-anchor:none}</style><div class="notes-feed"></div><script>${script}</script>`;
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); }
|