diff options
| author | Adam Soltys <[email protected]> | 2026-10-14 23:38:07 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-10-14 23:38:07 -0700 |
| commit | c0f1f1de0c7dca3d35cc19bb3e18487666c3ba5c (patch) | |
| tree | 64996bfca176d9b6112235de94d7d70cae6a1202 /tools | |
| parent | 14de2121fa1c806048689010cf85d4f65b6e28c1 (diff) | |
| download | coinosv3-c0f1f1de0c7dca3d35cc19bb3e18487666c3ba5c.tar.gz coinosv3-c0f1f1de0c7dca3d35cc19bb3e18487666c3ba5c.zip | |
Share eager media pager loading across feed, profile, and grid
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/pager-loading-test.js | 82 | ||||
| -rw-r--r-- | tools/pager-wheel-test.js | 2 |
2 files changed, 84 insertions, 0 deletions
diff --git a/tools/pager-loading-test.js b/tools/pager-loading-test.js new file mode 100644 index 0000000..76e2764 --- /dev/null +++ b/tools/pager-loading-test.js @@ -0,0 +1,82 @@ +// Exercise the production refill scheduler with delayed and sparse sources. +// Run: bun tools/pager-loading-test.js +import { readFileSync } from 'node:fs'; +import vm from 'node:vm'; +import assert from 'node:assert/strict'; + +const source = readFileSync(new URL('../src/features/messages.js', import.meta.url), 'utf8'); +const scheduler = source.slice(source.indexOf(' const PAGER_INITIAL ='), source.indexOf(' // Slides for feed items', source.indexOf(' const PAGER_INITIAL ='))); +function fixture(kind = 'images', grid = false) { + let clock = 100000, calls = 0, pending = null, count = 3; + const groups = Array.from({ length: 300 }, () => ({})); + const cache = { shown: 3, notes: Array(3), stopped: false }; + const index = { until: 10000, images: new Map(), videos: new Map() }; + const p = { kind, cur: { closest: () => groups[0] } }; + const more = async () => { + calls++; + if (pending) await pending; + count += 10; + cache.shown += 10; cache.notes.length += 10; + index.until -= 100; + }; + const src = { c: cache, more, done: () => false }; + const ctx = vm.createContext({ + pager: p, pagerGroups: () => groups.slice(0, count), + pagerSource: () => grid ? null : src, feedNow: () => cache, mediaIndex: () => index, feedDef: () => ({}), + crawlMedia: (c, k) => { assert.equal(c, cache); assert.equal(k, kind); return more(); }, + growPager: () => {}, Date: { now: () => clock }, + }); + vm.runInContext(scheduler, ctx); + return { + ctx, p, src, cache, index, load: () => ctx.pagerLoadAhead(), + go: (i) => { p.cur = { closest: () => groups[i] }; }, + advance: () => { clock += 11000; }, + delay: () => { let resolve; pending = new Promise((r) => { resolve = r; }); return () => { pending = null; resolve(); }; }, + count: () => count, calls: () => calls, + }; +} + +for (const grid of [false, true]) for (const kind of ['images', 'videos']) { + const f = fixture(kind, grid); + await f.load(); + assert.equal(f.calls(), 3, 'opening starts loading immediately to at least 30 posts'); + f.advance(); await f.load(); + assert.equal(f.calls(), 3, 'a full buffer does not download forever'); + f.go(29); f.advance(); + const finish = f.delay(); + const loading = f.load(); + assert.equal(f.calls(), 4, 'refill starts well before the end'); + f.go(0); await f.load(); + assert.equal(f.calls(), 4, 'wrapping during a request does not start a concurrent one'); + finish(); await loading; + assert.equal(f.count(), 53, 'refill continues after wrapping, keeping 20 beyond the furthest post'); + console.log('✓ early loading and wrap refill:', grid ? 'grid' : 'feed/profile', kind); +} +{ + const f = fixture(); + f.src.more = async () => { f.cache.notes.length++; }; + await f.load(); + assert.equal(f.cache.notes.length, 11, 'sparse results are limited to eight requests per run'); + f.advance(); await f.load(); + assert.equal(f.cache.notes.length, 19, 'timer can resume sparse results from the first post'); +} +{ + const f = fixture(); + let calls = 0; + f.src.more = async () => { calls++; }; + await f.load(); await f.load(); + assert.equal(calls, 1, 'an empty result backs off'); + f.advance(); await f.load(); + assert.equal(calls, 2, 'an empty source can retry'); + f.src.done = () => true; + f.advance(); await f.load(); + assert.equal(calls, 2, 'an exhausted source is not requested'); +} +{ + const f = fixture(); + const finish = f.delay(), loading = f.load(); + f.ctx.pager = { kind: 'videos' }; + finish(); await loading; + assert.equal(f.calls(), 1, 'closing or switching pagers stops the old refill'); +} +console.log('✓ sparse sources, backoff, exhaustion, and pager lifecycle'); diff --git a/tools/pager-wheel-test.js b/tools/pager-wheel-test.js index 1828f77..45a5115 100644 --- a/tools/pager-wheel-test.js +++ b/tools/pager-wheel-test.js @@ -94,6 +94,8 @@ try { console.log('\n[a wheel notch is a step]'); await openPic('two pictures'); check('the picture opens in the pager', await resting('a1.png'), await curPic()); + check('opening fills the media buffer without scrolling to the end', await waitFor(() => document.querySelectorAll('.mp-group').length === 15, null, 8000)); + check('background loading keeps the first picture on screen', await curPic() === 'a1.png'); await page.mouse.move(640, 450); await wheel(100); check('a notch down: the next post\'s picture', await resting('b.png'), await curPic()); |
