feat(web): authed blob image pipeline with LRU, cover card, dir grouping

This commit is contained in:
2026-09-07 15:36:19 +08:00
parent b231b62720
commit 63b41f333e
5 changed files with 145 additions and 0 deletions
+32
View File
@@ -0,0 +1,32 @@
import { afterEach, beforeEach, expect, it, vi } from "vitest";
import { setToken } from "../src/api/client";
import { __resetImageCache, fetchObjectUrl } from "../src/lib/authImage";
beforeEach(() => {
__resetImageCache();
setToken("T");
(URL as any).createObjectURL = vi.fn(() => "blob:gen");
});
afterEach(() => vi.unstubAllGlobals());
it("blob → objectURL;同 URL 只发一次请求", async () => {
const f = vi.fn(async (_url: any, init: any) => {
expect(init.headers.Authorization).toBe("Bearer T");
return new Response(new Blob(["img"]), { status: 200 });
});
vi.stubGlobal("fetch", f);
expect(await fetchObjectUrl("/api/books/1/cover?v=h")).toBe("blob:gen");
expect(await fetchObjectUrl("/api/books/1/cover?v=h")).toBe("blob:gen");
expect(f).toHaveBeenCalledTimes(1);
});
it("失败不入缓存,重试重新请求", async () => {
let n = 0;
vi.stubGlobal("fetch", async () => {
n++;
return n === 1 ? new Response("x", { status: 500 }) : new Response(new Blob(["img"]), { status: 200 });
});
await expect(fetchObjectUrl("/api/books/2/pages/0?v=h")).rejects.toThrow();
expect(await fetchObjectUrl("/api/books/2/pages/0?v=h")).toBe("blob:gen");
expect(n).toBe(2);
});
+20
View File
@@ -0,0 +1,20 @@
import { expect, it } from "vitest";
import { groupByDir } from "../src/lib/group";
import type { Book } from "../src/api/types";
let seq = 1;
const mk = (path: string): Book => ({
id: seq++, library_id: 1, path, title: path, format: "cbz", size: 1, mtime: 1, pages: 0,
state: "ready", error: "", added_at: "", percent: 0, cover_url: "/api/books/0/cover",
});
it("根目录组为空串且排最前;目录组按字典序;组内保持输入顺序", () => {
const gs = groupByDir([mk("b.cbz"), mk("z/1.cbz"), mk("a/2.cbz"), mk("a/sub/3.cbz")]);
expect(gs.map((g) => g.dir)).toEqual(["", "a", "a/sub", "z"]);
expect(gs[0].books.map((x) => x.path)).toEqual(["b.cbz"]);
expect(gs[1].books.map((x) => x.path)).toEqual(["a/2.cbz"]);
});
it("空输入出空数组", () => {
expect(groupByDir([])).toEqual([]);
});