aboutsummaryrefslogtreecommitdiffstats
path: root/tools/pager-loading-test.js
blob: 76e27646ee15913b2f85f1f9540ba67542e7a841 (plain) (blame)
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
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');