test/scroll.test.ts (3015 bytes)
1 import fc from 'fast-check'; 2 import { describe, expect, it } from 'vitest'; 3 import { LEAD, scrollTarget } from '../src/scroll'; 4 5 describe('scrollTarget', () => { 6 const view = { top: 500, height: 800 }; 7 8 it('gives null for a mark in view', () => { 9 expect(scrollTarget({ top: 600, bottom: 640 }, view, 2000)).toBeNull(); 10 }); 11 12 it('brings a mark above or below the view to the upper third', () => { 13 // The follower turned to page 2: the view stayed at 500, the mark was at -220. 14 expect(scrollTarget({ top: 280, bottom: 320 }, view, 2000)).toBe(280 - 800 * LEAD); 15 expect(scrollTarget({ top: 1400, bottom: 1440 }, view, 2000)).toBe(1400 - 800 * LEAD); 16 }); 17 18 it('scrolls for a mark that is only partly in view', () => { 19 expect(scrollTarget({ top: 1280, bottom: 1330 }, view, 2000)).toBe(1280 - 800 * LEAD); 20 }); 21 22 it('stays inside the content', () => { 23 expect(scrollTarget({ top: 50, bottom: 90 }, view, 2000)).toBe(0); 24 expect(scrollTarget({ top: 2700, bottom: 2740 }, view, 2000)).toBe(2000); 25 }); 26 27 it('gives a position for a mark in view when asked', () => { 28 expect(scrollTarget({ top: 600, bottom: 640 }, view, 2000, true)).toBe(400); 29 }); 30 31 const input = fc 32 .record({ height: fc.integer({ min: 100, max: 2000 }), max: fc.nat({ max: 5000 }) }) 33 .chain(({ height, max }) => 34 fc.record({ 35 height: fc.constant(height), 36 max: fc.constant(max), 37 viewTop: fc.integer({ min: 0, max }), 38 // A mark inside the content, which is max + height high. 39 top: fc.integer({ min: 0, max: max + height }), 40 size: fc.nat({ max: 200 }), 41 always: fc.boolean(), 42 }), 43 ); 44 45 it('gives null only for a mark in view, and a position inside the content', () => { 46 fc.assert( 47 fc.property(input, ({ height, max, viewTop, top, size, always }) => { 48 const mark = { top, bottom: top + size }; 49 const view = { top: viewTop, height }; 50 const target = scrollTarget(mark, view, max, always); 51 const inView = mark.top >= viewTop && mark.bottom <= viewTop + height; 52 expect(target === null).toBe(inView && !always); 53 if (target !== null) { 54 expect(target).toBeGreaterThanOrEqual(0); 55 expect(target).toBeLessThanOrEqual(max); 56 } 57 }), 58 ); 59 }); 60 61 it('shows the top of the mark after the scroll, in the upper third when it can', () => { 62 fc.assert( 63 fc.property(input, ({ height, max, viewTop, top, size, always }) => { 64 const target = scrollTarget( 65 { top, bottom: top + size }, 66 { top: viewTop, height }, 67 max, 68 always, 69 ); 70 if (target === null) return; 71 expect(top).toBeGreaterThanOrEqual(target); 72 expect(top).toBeLessThanOrEqual(target + height); 73 if (target > 0 && target < max) { 74 expect(Math.abs(top - target - height * LEAD)).toBeLessThanOrEqual(0.5); 75 expect(top - target).toBeLessThanOrEqual(height / 3); 76 } 77 }), 78 ); 79 }); 80 });