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/paging.test.ts (5792 bytes)

1 import fc from 'fast-check';
2 import { describe, expect, it } from 'vitest';
3 import {
4   arrowsOff,
5   arrowStep,
6   defaultRtl,
7   keyStep,
8   LONG_PRESS_MOVE,
9   movedTooFar,
10   parsePage,
11   SWIPE_MIN,
12   SWIPE_RATIO,
13   swipeStep,
14   type Swipe,
15 } from '../src/paging';
16 
17 const flat = { zoomed: false, wide: false };
18 
19 describe('movedTooFar', () => {
20   it('lets a finger shake up to 10 CSS pixels in a long press', () => {
21     expect(LONG_PRESS_MOVE).toBe(10);
22     expect(movedTooFar(0, 0)).toBe(false);
23     expect(movedTooFar(6, -8)).toBe(false);
24     expect(movedTooFar(7, 8)).toBe(true);
25   });
26 
27   it('never gives a long press for a swipe that turns a page', () => {
28     // A swipe, a tap and a long press never mix.
29     const move = fc.integer({ min: -400, max: 400 });
30     fc.assert(
31       fc.property(move, move, fc.boolean(), (dx, dy, rtl) => {
32         if (swipeStep({ dx, dy, ...flat }, rtl) !== 0) expect(movedTooFar(dx, dy)).toBe(true);
33       }),
34     );
35   });
36 });
37 
38 describe('keyStep', () => {
39   it('turns with the arrow keys and the page keys', () => {
40     expect(keyStep('ArrowRight', false)).toBe(1);
41     expect(keyStep('ArrowLeft', false)).toBe(-1);
42     expect(keyStep('PageDown', false)).toBe(1);
43     expect(keyStep('PageUp', false)).toBe(-1);
44     expect(keyStep('ArrowDown', false)).toBe(0);
45     expect(keyStep(' ', false)).toBe(0);
46   });
47 
48   it('goes forward with ArrowLeft in right-to-left mode', () => {
49     expect(keyStep('ArrowLeft', true)).toBe(1);
50     expect(keyStep('ArrowRight', true)).toBe(-1);
51     expect(keyStep('PageDown', true)).toBe(1);
52     expect(keyStep('PageUp', true)).toBe(-1);
53   });
54 });
55 
56 describe('swipeStep', () => {
57   it('turns forward for a swipe to the left, back for a swipe to the right', () => {
58     expect(swipeStep({ dx: -80, dy: 10, ...flat }, false)).toBe(1);
59     expect(swipeStep({ dx: 80, dy: -10, ...flat }, false)).toBe(-1);
60   });
61 
62   it('turns forward for a swipe to the right in right-to-left mode', () => {
63     expect(swipeStep({ dx: 80, dy: 0, ...flat }, true)).toBe(1);
64     expect(swipeStep({ dx: -80, dy: 0, ...flat }, true)).toBe(-1);
65   });
66 
67   it('does not turn for a short swipe, a steep swipe, a zoomed page or a wide page', () => {
68     expect(swipeStep({ dx: -49, dy: 0, ...flat }, false)).toBe(0);
69     expect(swipeStep({ dx: -60, dy: 40, ...flat }, false)).toBe(0);
70     expect(swipeStep({ dx: -80, dy: 0, zoomed: true, wide: false }, false)).toBe(0);
71     expect(swipeStep({ dx: -80, dy: 0, zoomed: false, wide: true }, false)).toBe(0);
72   });
73 
74   const swipe: fc.Arbitrary<Swipe> = fc.record({
75     dx: fc.integer({ min: -400, max: 400 }),
76     dy: fc.integer({ min: -400, max: 400 }),
77     zoomed: fc.boolean(),
78     wide: fc.boolean(),
79   });
80 
81   it('turns exactly for a long, flat swipe on a page that is not zoomed or wide', () => {
82     fc.assert(
83       fc.property(swipe, fc.boolean(), (s, rtl) => {
84         const turns =
85           !s.zoomed &&
86           !s.wide &&
87           Math.abs(s.dx) >= SWIPE_MIN &&
88           Math.abs(s.dx) > SWIPE_RATIO * Math.abs(s.dy);
89         expect(swipeStep(s, rtl) !== 0).toBe(turns);
90       }),
91     );
92   });
93 
94   it('turns the other way for the mirror swipe, and for the other direction', () => {
95     fc.assert(
96       fc.property(swipe, fc.boolean(), (s, rtl) => {
97         const step = swipeStep(s, rtl);
98         expect(swipeStep({ ...s, dx: -s.dx }, rtl)).toBe(-step || 0);
99         expect(swipeStep(s, !rtl)).toBe(-step || 0);
100       }),
101     );
102   });
103 });
104 
105 describe('arrows', () => {
106   it('gives the right arrow the next page, except in right-to-left mode', () => {
107     expect(arrowStep('right', false)).toBe(1);
108     expect(arrowStep('left', false)).toBe(-1);
109     expect(arrowStep('right', true)).toBe(-1);
110     expect(arrowStep('left', true)).toBe(1);
111   });
112 
113   it('turns off the arrow that leads to no page', () => {
114     expect(arrowsOff(0, 40, false)).toEqual({ left: true, right: false });
115     expect(arrowsOff(39, 40, false)).toEqual({ left: false, right: true });
116     expect(arrowsOff(0, 40, true)).toEqual({ left: false, right: true });
117     expect(arrowsOff(0, 1, false)).toEqual({ left: true, right: true });
118     // No PDF.
119     expect(arrowsOff(-1, 0, false)).toEqual({ left: true, right: true });
120   });
121 
122   it('turns an arrow off exactly when its step leaves the book', () => {
123     const book = fc
124       .integer({ min: 1, max: 500 })
125       .chain((pages) => fc.tuple(fc.constant(pages), fc.nat({ max: pages - 1 }), fc.boolean()));
126     fc.assert(
127       fc.property(book, ([pages, page, rtl]) => {
128         const off = arrowsOff(page, pages, rtl);
129         for (const side of ['left', 'right'] as const) {
130           const to = page + arrowStep(side, rtl);
131           expect(off[side]).toBe(to < 0 || to >= pages);
132         }
133       }),
134     );
135   });
136 });
137 
138 describe('parsePage', () => {
139   it('reads a page number from 1 to the page count', () => {
140     expect(parsePage('30', 40)).toBe(29);
141     expect(parsePage(' 1 ', 40)).toBe(0);
142     expect(parsePage('30', 40)).toBe(29);
143   });
144 
145   it('gives null for text that is no page of the book', () => {
146     for (const text of ['', '0', '41', '-3', '2.5', '1e1', 'abc', '3 4']) {
147       expect(parsePage(text, 40)).toBeNull();
148     }
149   });
150 
151   it('reads back each page number that it can show', () => {
152     const book = fc
153       .integer({ min: 1, max: 5000 })
154       .chain((pages) => fc.tuple(fc.constant(pages), fc.nat({ max: pages - 1 })));
155     fc.assert(
156       fc.property(book, ([pages, index]) => {
157         expect(parsePage(String(index + 1), pages)).toBe(index);
158       }),
159     );
160   });
161 });
162 
163 describe('defaultRtl', () => {
164   it('is right to left for vertical Japanese only', () => {
165     expect(defaultRtl('jpn_vert')).toBe(true);
166     expect(defaultRtl('jpn')).toBe(false);
167     expect(defaultRtl('jpn+eng')).toBe(false);
168     expect(defaultRtl('eng')).toBe(false);
169   });
170 });