Files
readest/apps/readest-app/src/__tests__/utils/reference-pages.test.ts
T
Huang Xin 9dc41e7adf feat(reader): reference page numbers from EPUB page-list with manual page count fallback (#4549)
Add a 'Reference Pages' reading progress style that shows physical book
page numbers in the footer progress info:

- When the book carries a page list (EPUB3 nav page-list or EPUB2 NCX
  pageList — foliate-js already parses both and resolves the current
  pageItem on relocate; it was just never consumed), display the current
  page label and use the highest numeric label as the total, so a
  trailing roman-numeral index page can't corrupt the total (#672).
- When the book has none, a per-book 'Reference Page Count' input
  appears; the reading fraction is mapped linearly onto the entered
  count (#4542). The count is saved per book only and never propagates
  to global view settings.
- Falls back to percentage display when neither source is available.

Verified with the sample books from #672: Caleb's Crossing (EPUB3
page-list, 419 pages) and Count Zero (EPUB2 NCX pageList/page-map,
346 pages — chapter 2 lands exactly on page 22 per its page-map), plus
a stripped no-pagelist copy for the manual-count path (175/350 at 50%).

Closes #672
Closes #4542

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 08:44:18 +02:00

135 lines
4.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { getReferencePageInfo } from '@/utils/progress';
const makePageList = (labels: string[]) => labels.map((label) => ({ label, href: '#' }));
describe('getReferencePageInfo', () => {
describe('with a page list from the book (issue #672)', () => {
it('uses the current page item label and the highest numeric label as total', () => {
const pageList = makePageList(['1', '2', '3', '4', '5']);
const info = getReferencePageInfo({
pageList,
pageItem: { label: '3' },
fraction: 0.5,
referencePageCount: 0,
});
expect(info).toEqual({ current: '3', total: 5 });
});
it('keeps non-numeric current labels (front matter roman numerals) as-is', () => {
const pageList = makePageList(['i', 'ii', '1', '2', '3']);
const info = getReferencePageInfo({
pageList,
pageItem: { label: 'ii' },
fraction: 0.1,
referencePageCount: 0,
});
expect(info).toEqual({ current: 'ii', total: 3 });
});
it('ignores trailing non-numeric labels when computing the total', () => {
// Reported in #672: a book ends with an index page labeled "XII" after
// page 553 — the total must stay 553, not "XII".
const pageList = makePageList(['551', '552', '553', 'XII']);
const info = getReferencePageInfo({
pageList,
pageItem: { label: '552' },
fraction: 0.6,
referencePageCount: 0,
});
expect(info).toEqual({ current: '552', total: 553 });
});
it('falls back to the entry count when no label is numeric', () => {
const pageList = makePageList(['i', 'ii', 'iii']);
const info = getReferencePageInfo({
pageList,
pageItem: { label: 'iii' },
fraction: 0.9,
referencePageCount: 0,
});
expect(info).toEqual({ current: 'iii', total: 3 });
});
it('counts pages in nested subitems', () => {
const pageList = [
{ label: '1', href: '#', subitems: makePageList(['2', '3']) },
{ label: '4', href: '#' },
];
const info = getReferencePageInfo({
pageList,
pageItem: { label: '2' },
fraction: 0.3,
referencePageCount: 0,
});
expect(info).toEqual({ current: '2', total: 4 });
});
it('estimates the current page linearly when no page item is resolved yet', () => {
// e.g. on the cover, before the first page anchor
const pageList = makePageList(['1', '2', '3', '4', '5', '6', '7', '8', '9', '10']);
const info = getReferencePageInfo({
pageList,
pageItem: null,
fraction: 0.45,
referencePageCount: 0,
});
expect(info).toEqual({ current: '5', total: 10 });
});
it('prefers the page list over a user-entered page count', () => {
const pageList = makePageList(['1', '2', '3']);
const info = getReferencePageInfo({
pageList,
pageItem: { label: '2' },
fraction: 0.5,
referencePageCount: 999,
});
expect(info).toEqual({ current: '2', total: 3 });
});
});
describe('with a user-entered page count (issue #4542)', () => {
it('maps the reading fraction linearly onto the page count', () => {
const info = getReferencePageInfo({
pageList: null,
pageItem: null,
fraction: 0.5,
referencePageCount: 350,
});
expect(info).toEqual({ current: '175', total: 350 });
});
it('shows page 1 at the start of the book', () => {
const info = getReferencePageInfo({
pageList: undefined,
pageItem: null,
fraction: 0,
referencePageCount: 350,
});
expect(info).toEqual({ current: '1', total: 350 });
});
it('shows the last page at the end of the book', () => {
const info = getReferencePageInfo({
pageList: [],
pageItem: null,
fraction: 1,
referencePageCount: 350,
});
expect(info).toEqual({ current: '350', total: 350 });
});
});
it('returns null when the book has no page list and no page count is set', () => {
expect(
getReferencePageInfo({
pageList: null,
pageItem: null,
fraction: 0.5,
referencePageCount: 0,
}),
).toBeNull();
});
});