fix: page through all chapters when the API omits total
listAllChapters used `page.total ?? all.length` as the loop bound. The /chapters endpoint always returns `total: null`, so a full 200-row first page broke the loop immediately — the reader dead-ended past chapter 200. Stop on a short page instead; only trust `total` when actually reported. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2
backend/Cargo.lock
generated
2
backend/Cargo.lock
generated
@@ -1558,7 +1558,7 @@ checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||
|
||||
[[package]]
|
||||
name = "mangalord"
|
||||
version = "0.128.3"
|
||||
version = "0.128.4"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"argon2",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "mangalord"
|
||||
version = "0.128.3"
|
||||
version = "0.128.4"
|
||||
edition = "2021"
|
||||
default-run = "mangalord"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mangalord-frontend",
|
||||
"version": "0.128.3",
|
||||
"version": "0.128.4",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -158,6 +158,23 @@ describe('chapters api client', () => {
|
||||
expect(fetchSpy.mock.calls[1][0]).toContain('offset=200');
|
||||
});
|
||||
|
||||
it('listAllChapters keeps paging when the API omits total (total: null)', async () => {
|
||||
// The real /chapters endpoint returns `total: null` on every page
|
||||
// (PagedResponse::new). A full 200-row first page must NOT be mistaken
|
||||
// for the end — otherwise the reader dead-ends past chapter 200.
|
||||
const mk = (n: number) => ({ ...chapterFixture, id: `c${n}`, number: n });
|
||||
const first = Array.from({ length: 200 }, (_, i) => mk(i + 1));
|
||||
const second = Array.from({ length: 50 }, (_, i) => mk(i + 201));
|
||||
fetchSpy
|
||||
.mockResolvedValueOnce(ok({ items: first, page: { limit: 200, offset: 0, total: null } }))
|
||||
.mockResolvedValueOnce(ok({ items: second, page: { limit: 200, offset: 200, total: null } }));
|
||||
|
||||
const all = await listAllChapters('m1');
|
||||
expect(all).toHaveLength(250);
|
||||
expect(all[249].number).toBe(250);
|
||||
expect(fetchSpy).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('listAllChapters stops after a short final page (single request)', async () => {
|
||||
fetchSpy.mockResolvedValueOnce(
|
||||
ok({ items: [chapterFixture], page: { limit: 200, offset: 0, total: 1 } })
|
||||
|
||||
@@ -56,8 +56,11 @@ export async function listAllChapters(mangaId: string): Promise<Chapter[]> {
|
||||
const { items, page } = await listChapters(mangaId, { limit: PAGE, offset });
|
||||
all.push(...items);
|
||||
offset += items.length;
|
||||
const total = page.total ?? all.length;
|
||||
if (items.length < PAGE || all.length >= total) break;
|
||||
// A short page is the only reliable end-of-list signal: the /chapters
|
||||
// endpoint returns `total: null`, so we can't infer completion from it.
|
||||
// Only stop early on `total` when the API actually reports one.
|
||||
if (items.length < PAGE) break;
|
||||
if (page.total != null && all.length >= page.total) break;
|
||||
}
|
||||
return all;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user