test/read-order.test.ts (2477 bytes)
1 import fc from 'fast-check'; 2 import { describe, expect, it } from 'vitest'; 3 import { nextPage } from '../src/read-order'; 4 5 /** The whole numbers from `from` up to, but not including, `to`. */ 6 function range(from: number, to: number): number[] { 7 return Array.from({ length: Math.max(0, to - from) }, (_, i) => from + i); 8 } 9 10 /** All pages of a book of `count` pages, in the order of nextPage, while the current page stays. */ 11 function readAll(count: number, current: number): number[] { 12 const pending = new Set(range(0, count)); 13 const order: number[] = []; 14 while (pending.size > 0) { 15 const page = nextPage(pending, current); 16 pending.delete(page); 17 order.push(page); 18 } 19 return order; 20 } 21 22 describe('nextPage', () => { 23 it('reads the current page, then the pages after it, then the pages before it', () => { 24 expect(readAll(6, 2)).toEqual([2, 3, 4, 5, 1, 0]); 25 }); 26 27 it('gives -1 when all pages are read', () => { 28 expect(nextPage(new Set(), 3)).toBe(-1); 29 }); 30 31 it('reads every page once: from the current page to the end, then back to the start', () => { 32 const book = fc 33 .integer({ min: 1, max: 60 }) 34 .chain((count) => fc.tuple(fc.constant(count), fc.nat({ max: count - 1 }))); 35 fc.assert( 36 fc.property(book, ([count, current]) => { 37 expect(readAll(count, current)).toEqual([ 38 ...range(current, count), 39 ...range(0, current).reverse(), 40 ]); 41 }), 42 ); 43 }); 44 45 it('follows the current page when the user turns pages during the read', () => { 46 // At each step the user can be on another page. 47 const read = fc 48 .integer({ min: 1, max: 40 }) 49 .chain((count) => 50 fc.tuple( 51 fc.constant(count), 52 fc.array(fc.nat({ max: count - 1 }), { minLength: count, maxLength: count }), 53 ), 54 ); 55 fc.assert( 56 fc.property(read, ([count, currents]) => { 57 const pending = new Set(range(0, count)); 58 const order: number[] = []; 59 for (const current of currents) { 60 const page = nextPage(pending, current); 61 // The nearest page at or after the current page, else the nearest before it. 62 const after = [...pending].filter((p) => p >= current); 63 expect(page).toBe(after.length > 0 ? Math.min(...after) : Math.max(...pending)); 64 pending.delete(page); 65 order.push(page); 66 } 67 expect(order.sort((x, y) => x - y)).toEqual(range(0, count)); 68 }), 69 ); 70 }); 71 });