diff options
Diffstat (limited to 'tools/feed-wheel-reverse-test.js')
| -rw-r--r-- | tools/feed-wheel-reverse-test.js | 90 |
1 files changed, 56 insertions, 34 deletions
diff --git a/tools/feed-wheel-reverse-test.js b/tools/feed-wheel-reverse-test.js index 632bbc6..80e2fde 100644 --- a/tools/feed-wheel-reverse-test.js +++ b/tools/feed-wheel-reverse-test.js @@ -5,15 +5,18 @@ const source = await Bun.file('src/app.js').text(); const start = source.indexOf('(() => {', source.indexOf('// The feed scrolls like a phone')); const end = source.indexOf("\nwindow.addEventListener('load'", start); assert(start > 0 && end > start); -function fixture() { +function fixture(inside = '', { feedOnPage = true } = {}) { let now = 100, id = 0; const handlers = new Map(), frames = new Map(), steps = []; const window = { scrollY: 5000, addEventListener: (name, fn) => handlers.set(name, fn), scrollBy: ({ top }) => { steps.push(top); window.scrollY += top; }, }; - const document = { body: {}, addEventListener() {}, removeEventListener() {} }; - const target = { closest: (selector) => selector.includes('.feed-page') ? target : null, + // the page shows a feed (or, for a thread case, a thread) unless told otherwise + const document = { body: {}, addEventListener() {}, removeEventListener() {}, + querySelector: (selector) => (feedOnPage && selector.split(/,\s*/).some((p) => p === (inside === '.thread-page' ? '.thread-page' : '.feed-page')) ? {} : null) }; + // the target sits inside the given element (over the posts unless it names something else) + const target = { closest: (selector) => selector.split(/,\s*/).some((p) => p === inside) ? target : null, scrollHeight: 100, clientHeight: 100, parentElement: document.body }; runInNewContext(source.slice(start, end), { window, document, matchMedia: () => ({ matches: false }), performance: { now: () => now }, @@ -33,42 +36,61 @@ for (const dir of [1, -1]) { const f = fixture(); f.wheel(dir); f.advance(17); f.frame(); f.wheel(dir); f.advance(17); f.frame(); - // A busy main thread has missed several frames when the reverse arrives. + // A busy main thread has missed several frames when the turn back arrives. f.advance(120); const before = f.y; + // [flying: the first turn back is a brake] f.wheel(-dir); - assert((f.y - before) * dir < 0, 'first reverse notch responds immediately'); - assert(Math.abs(f.y - before) < 20, 'reverse starts with a small bounded step'); - const reversed = f.y; + assert.equal(f.y, before, 'the brake notch moves nothing by itself'); f.frame(0); // a queued animation frame can carry an older timestamp - assert.equal(f.y, reversed, 'old frame time cannot revive the previous direction'); - f.advance(17); f.frame(); - assert((f.y - reversed) * dir < 0, 'next frame continues in the new direction'); - assert(Math.abs(f.y - reversed) < 20, 'new velocity is not applied to the old frame delay'); + assert.equal(f.y, before, 'old frame time moves nothing'); + settle(f, 30); + const over = (f.y - before) * dir; + assert(over >= 0 && over < 120, 'the brake eases the flight out within a short way, never the other way: ' + over); + // [the next notch back: the gentle start] + const stopped = f.y; + f.wheel(-dir); + assert((stopped - f.y) * dir > 0 && (stopped - f.y) * dir < 20, 'the gentle start responds at once with a small step'); settle(f); - // The turn back is a plain notch from rest: its full travel, no flight the other way. - const back = (before - f.y) * dir; - assert(back > 280 && back < 330, 'a reverse notch against a coast travels one notch: ' + back); - // For a moment after a reversal notches don't wind the speed up: a - // reversal and a quick second notch travel about two notches, not a flight. - let calm; - { f.wheel(-dir); f.advance(17); f.frame(); f.wheel(-dir); f.advance(120); f.frame(); // coasting again, the other way - const y = f.y; f.wheel(dir); f.advance(60); f.frame(); f.wheel(dir); settle(f); calm = (f.y - y) * dir; - assert(calm > 540 && calm < 700, 'a reversal and a quick second notch: about two notches of travel: ' + calm); } - // From rest, well after, the wind-up is back: the same two quick notches go further. - { f.advance(2500); const y = f.y; f.wheel(dir); f.advance(60); f.frame(); f.wheel(dir); settle(f); const s = (f.y - y) * dir; assert(s > calm + 40, 'two quick notches with the wind-up go further: ' + s + ' vs ' + calm); } - // A spin that keeps going after a reversal winds up again from its third - // notch: a reversal plus four quick notches goes well past five plain ones. - let spin; - { f.wheel(-dir); f.advance(17); f.frame(); f.wheel(-dir); f.advance(120); f.frame(); - const y = f.y; f.wheel(dir); for (let i = 0; i < 4; i++) { f.advance(60); f.frame(); f.wheel(dir); } settle(f); spin = (f.y - y) * dir; - assert(spin > 5 * 300 + 150, 'a spin on after a reversal winds up again: ' + spin); } - // ...and the same five quick notches from rest go about as far (the two calm ones cost little). - { f.advance(2500); const y = f.y; f.wheel(dir); for (let i = 0; i < 4; i++) { f.advance(60); f.frame(); f.wheel(dir); } settle(f); const s = (f.y - y) * dir; - assert(s < spin * 1.25, 'five from rest vs five after a reversal: ' + s + ' vs ' + spin); settle(f, 200); } + const back = (stopped - f.y) * dir; + assert(back > 130 && back < 170, 'the notch after the brake creeps back half a notch: ' + back); + // [then the wind-up] + { const y = f.y; f.wheel(-dir); f.advance(60); f.frame(); f.wheel(-dir); settle(f); + const on = (y - f.y) * dir; assert(on > 600, 'the notches after wind up: ' + on); } + // The gentle start waits a while for its notch: a second and a half after the brake, still gentle. + { f.advance(2500); f.wheel(dir); f.advance(17); f.frame(); f.wheel(dir); f.advance(120); f.frame(); + f.wheel(-dir); settle(f, 30); f.advance(1500); const y = f.y; f.wheel(-dir); settle(f); + const s = (y - f.y) * dir; assert(s > 130 && s < 170, 'a notch back a while after the brake is still the gentle start: ' + s); } + // ...but long after, the brake forgotten, a notch back is a plain push from rest. + { f.advance(2500); f.wheel(dir); f.advance(17); f.frame(); f.wheel(dir); f.advance(120); f.frame(); + f.wheel(-dir); settle(f, 30); f.advance(3000); const y = f.y; f.wheel(-dir); settle(f); + const s = (y - f.y) * dir; assert(s > 280 && s < 330, 'long after the brake, a notch back is a full push: ' + s); } + // A stroll (one notch) turned back has nothing worth braking: the gentle start at once. + { f.advance(2500); f.wheel(dir); f.advance(100); f.frame(); const y = f.y; f.wheel(-dir); + assert((y - f.y) * dir > 0 && (y - f.y) * dir < 20, 'against a stroll the turn back starts at once, with a small step'); + settle(f); const s = (y - f.y) * dir; assert(s > 130 && s < 170, 'against a stroll the turn back is the gentle start: ' + s); } // From rest a notch either way is a full push. { f.advance(2500); const y = f.y; f.wheel(dir); settle(f); const s = (f.y - y) * dir; assert(s > 280 && s < 330, 'from rest a notch either way is a full push: ' + s); } - // A turn back against a coast that only just died is a reversal too (caught, no wind-up). - { f.wheel(dir); settle(f, 40); f.advance(100); const y = f.y; f.wheel(-dir); f.advance(60); f.frame(); f.wheel(-dir); settle(f); const s = (y - f.y) * dir; assert(s > 520 && s < 700, 'reversing right after the coast dies: no wind-up: ' + s); } + // A turn back against a coast that only just died is the gentle start too. + { f.wheel(dir); settle(f, 40); f.advance(100); const y = f.y; f.wheel(-dir); settle(f); const s = (y - f.y) * dir; assert(s > 130 && s < 170, 'reversing right after the coast dies is the gentle start: ' + s); } + // Well after the coast died it is a plain notch from rest, whichever way. + { f.advance(2500); const y = f.y; f.wheel(-dir); settle(f); const s = (y - f.y) * dir; assert(s > 280 && s < 330, 'a notch the other way from rest is a full push: ' + s); } +} +// A notch over a video (or an audio player) in the feed coasts like any +// other — the wheel does nothing to a player itself — and so does one on a +// thread page, and one with the pointer anywhere else on the page while a +// feed is up (the rail, the wallet column, the header). +for (const sel of ['video', 'audio', '.thread-page', '.wallet-column', '']) { + const f = fixture(sel); + const y = f.y; f.wheel(1); settle(f); + assert(f.y - y > 280 && f.y - y < 330, 'a notch over ' + sel + ' coasts a full push: ' + (f.y - y)); +} +// ...while a notch in a text box, or in the media pager, is left alone — and +// so is every notch on a page with no feed on it (the wallet). +for (const sel of ['textarea', '.media-pager']) { + const f = fixture(sel); + const y = f.y; f.wheel(1); settle(f); + assert.equal(f.y, y, 'a notch in ' + sel + ' is not ours'); } -console.log('✓ immediate reversal both ways, stale frame timestamps, busy frame recovery, a reversal is a plain notch, two calm notches, then the wind-up is back'); +{ const f = fixture('', { feedOnPage: false }); const y = f.y; f.wheel(1); settle(f); assert.equal(f.y, y, 'no feed on the page: the wheel is the browser\'s'); } +console.log('✓ a turn back against a flight brakes, the next notch is the gentle start, then the wind-up; against a stroll or a dead coast the gentle start at once; stale frames; videos and threads coast'); |
