Files
daily_console_web/src/components/usePaged.test.ts
2026-09-07 11:00:44 +05:30

105 lines
3.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { strict as assert } from 'node:assert';
import { test } from 'node:test';
import { DEFAULT_PAGE_SIZE, PAGE_SIZES } from './usePaged';
/**
* `usePaged` is a hook, and there is no React renderer in this suite — the repo
* runs `tsx --test`, not jsdom. So the arithmetic it depends on is written here
* as the pure function the hook applies, and asserted directly.
*
* That is worth doing rather than skipping: every bug this hook exists to
* prevent is an arithmetic one at a boundary — an empty list, a list that
* shrinks under a page, a page size that divides exactly.
*/
interface Slice {
page: number;
totalPages: number;
from: number;
to: number;
rows: number[];
}
/** Mirrors the derivation in `usePaged`, including the clamp. */
function slice(rows: readonly number[], requestedPage: number, pageSize: number): Slice {
const total = rows.length;
const totalPages = Math.max(1, Math.ceil(total / pageSize));
const page = Math.min(requestedPage, totalPages);
return {
page,
totalPages,
from: total === 0 ? 0 : (page - 1) * pageSize + 1,
to: Math.min(page * pageSize, total),
rows: rows.slice((page - 1) * pageSize, page * pageSize),
};
}
const upTo = (n: number) => Array.from({ length: n }, (_, i) => i + 1);
test('a full first page', () => {
const out = slice(upTo(96), 1, 25);
assert.equal(out.totalPages, 4);
assert.deepEqual([out.from, out.to], [1, 25]);
assert.equal(out.rows[0], 1);
assert.equal(out.rows.at(-1), 25);
});
test('a middle page counts from the right place', () => {
// The off-by-one everybody writes at least once.
const out = slice(upTo(96), 3, 25);
assert.deepEqual([out.from, out.to], [51, 75]);
assert.equal(out.rows[0], 51);
});
test('the last page is short, and says so', () => {
const out = slice(upTo(96), 4, 25);
assert.deepEqual([out.from, out.to], [76, 96]);
assert.equal(out.rows.length, 21);
});
test('an exact multiple does not produce a trailing empty page', () => {
// 100 rows at 25 is four pages, not five.
assert.equal(slice(upTo(100), 1, 25).totalPages, 4);
});
test('no rows is one page reading 0–0, not zero pages', () => {
/*
`Math.ceil(0 / 25)` is 0, and a totalPages of 0 makes the pager render "page 1
of 0" and every control dead. One empty page is the honest shape.
*/
const out = slice([], 1, 25);
assert.equal(out.totalPages, 1);
assert.deepEqual([out.from, out.to], [0, 0]);
assert.deepEqual(out.rows, []);
});
test('a list that shrinks under you clamps instead of going blank', () => {
/*
The bug this hook exists for. Sitting on page 4, somebody narrows the filter
to nine results. Slicing at the requested page would read rows 76–100 of a
nine-row list and render an empty table with the controls saying page 4 —
which looks exactly like the data vanished.
*/
const out = slice(upTo(9), 4, 25);
assert.equal(out.page, 1, 'clamped to the last page that exists');
assert.equal(out.rows.length, 9);
assert.deepEqual([out.from, out.to], [1, 9]);
});
test('clamping lands on the LAST page, not always the first', () => {
// 60 rows at 25 is three pages; from page 9 you belong on 3, not on 1.
const out = slice(upTo(60), 9, 25);
assert.equal(out.page, 3);
assert.deepEqual([out.from, out.to], [51, 60]);
});
test('every offered page size divides the work sensibly', () => {
// Guards the constants themselves: a 0 or a negative here would make
// totalPages Infinity and hang the pager.
for (const size of PAGE_SIZES) {
assert.ok(size > 0 && Number.isInteger(size), `${size} is a usable page size`);
assert.equal(slice(upTo(100), 1, size).rows.length, Math.min(size, 100));
}
assert.ok(PAGE_SIZES.includes(DEFAULT_PAGE_SIZE), 'the default is one of the choices');
});