Recently Written · git

scan-subread

PDF reader that follows an audiobook with the SubRead suite

git clone https://github.com/equwal/scan-subread

Log | Files | Refs


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 });