aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-10-14 23:38:07 -0700
committerAdam Soltys <[email protected]>2026-10-14 23:38:07 -0700
commitc0f1f1de0c7dca3d35cc19bb3e18487666c3ba5c (patch)
tree64996bfca176d9b6112235de94d7d70cae6a1202 /tools
parent14de2121fa1c806048689010cf85d4f65b6e28c1 (diff)
downloadcoinosv3-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.js82
-rw-r--r--tools/pager-wheel-test.js2
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());