feat(cbz): pure chapter-window helpers (chapterIndexAt/chapterWin) with table tests

This commit is contained in:
2026-09-08 14:56:46 +08:00
parent bba82e5529
commit cd251b940c
2 changed files with 63 additions and 1 deletions
+30
View File
@@ -76,3 +76,33 @@ export class PageHeights {
return [a, b];
}
}
/** 页号 → 所在章下标;无章(扁平包或单组)恒 0;首章 start 之前的散页归首章。 */
export function chapterIndexAt(chs: { start: number }[] | null, page: number): number {
if (!chs || chs.length < 2) return 0;
return chs.reduce((acc, c, i) => (c.start <= page ? i : acc), 0);
}
export interface ChapterWindow {
start: number; // 窗口首页(含)——滚动/挂载下界
end: number; // 窗口末页(不含)——锁章时的滚动边界
mountEnd: number; // 挂载上界(含预读章),恒 ≤ count 且 ≥ end
}
/** 阅读窗口:连读或扁平包 = 全书;锁章 = 本章滚动、本章 + prefetch 章挂载。 */
export function chapterWin(
chs: { start: number }[] | null,
count: number,
ci: number,
continuous: boolean,
prefetch: number,
): ChapterWindow {
if (!chs || chs.length < 2 || continuous) return { start: 0, end: count, mountEnd: count };
const c = Math.max(0, Math.min(ci, chs.length - 1));
const end = chs[c + 1]?.start ?? count;
return {
start: c === 0 ? 0 : chs[c].start,
end,
mountEnd: chs[c + 1 + Math.max(0, prefetch)]?.start ?? count,
};
}
+33 -1
View File
@@ -1,5 +1,5 @@
import { expect, it } from "vitest";
import { PageHeights } from "../src/lib/virt";
import { chapterIndexAt, chapterWin, PageHeights } from "../src/lib/virt";
it("估算高度:offset/total/pageAt/range(带 overscan、夹边)", () => {
const ph = new PageHeights(10, 100);
@@ -37,3 +37,35 @@ it("n=0 时一切归空", () => {
expect(ph.range(0, 100, 2)).toEqual([0, 0]);
expect(ph.set(0, 50)).toBe(0);
});
const CH4 = [{ start: 0 }, { start: 10 }, { start: 20 }, { start: 30 }];
it("chapterIndexAt:无章/单章恒 0;页号归章;首章 start 之前的散页归 0", () => {
expect(chapterIndexAt(null, 7)).toBe(0);
expect(chapterIndexAt([{ start: 0 }], 7)).toBe(0);
expect(chapterIndexAt(CH4, 0)).toBe(0);
expect(chapterIndexAt(CH4, 9)).toBe(0);
expect(chapterIndexAt(CH4, 10)).toBe(1);
expect(chapterIndexAt(CH4, 999)).toBe(3);
expect(chapterIndexAt([{ start: 5 }, { start: 10 }], 3)).toBe(0); // 根散页
});
it("chapterWin:扁平/连读 = 全书窗口", () => {
expect(chapterWin(null, 40, 0, false, 2)).toEqual({ start: 0, end: 40, mountEnd: 40 });
expect(chapterWin(CH4, 40, 2, true, 2)).toEqual({ start: 0, end: 40, mountEnd: 40 });
});
it("chapterWin:锁章窗口 = 本章;mountEnd 按预读章数延伸、夹到 count", () => {
expect(chapterWin(CH4, 40, 1, false, 0)).toEqual({ start: 10, end: 20, mountEnd: 20 });
expect(chapterWin(CH4, 40, 1, false, 1)).toEqual({ start: 10, end: 20, mountEnd: 30 });
expect(chapterWin(CH4, 40, 1, false, 2)).toEqual({ start: 10, end: 20, mountEnd: 40 });
expect(chapterWin(CH4, 40, 1, false, 3)).toEqual({ start: 10, end: 20, mountEnd: 40 }); // 越界夹住
expect(chapterWin(CH4, 40, 3, false, 2)).toEqual({ start: 30, end: 40, mountEnd: 40 }); // 末章
});
it("chapterWin:ci 越界夹边;首章窗口含首章 start 之前的散页;prefetch 负数按 0", () => {
expect(chapterWin(CH4, 40, 9, false, 1)).toEqual({ start: 30, end: 40, mountEnd: 40 });
expect(chapterWin(CH4, 40, -2, false, 1)).toEqual({ start: 0, end: 10, mountEnd: 20 });
expect(chapterWin([{ start: 5 }, { start: 10 }], 20, 0, false, 0)).toEqual({ start: 0, end: 10, mountEnd: 10 });
expect(chapterWin(CH4, 40, 1, false, -1)).toEqual({ start: 10, end: 20, mountEnd: 20 });
});