diff options
Diffstat (limited to 'tools/pager-loading-test.js')
| -rw-r--r-- | tools/pager-loading-test.js | 82 |
1 files changed, 82 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'); |
