test(frontend): CBZ page-mode component test + e2e mode/RTL smoke (B1)

This commit is contained in:
2026-09-16 16:50:55 +08:00
parent 26aafb3f53
commit 174c498757
4 changed files with 75 additions and 0 deletions
+20
View File
@@ -51,6 +51,26 @@ test("登录 → 书架 → CBZ 阅读翻页 → 书签 → 退出", async ({ pa
await expect(page.getByText("2/3")).toBeVisible(); await expect(page.getByText("2/3")).toBeVisible();
}).toPass({ timeout: 20_000 }); }).toPass({ timeout: 20_000 });
// 页模式 + RTL 冒烟:开设置面板 → 单页 → 右缘翻页 → 右开本(RTL) 方向翻转 → 回长卷。
await page.getByRole("button", { name: "更多阅读设置" }).click();
await page.getByRole("button", { name: "单页" }).click();
await expect(page.getByText(/^1\/3 · /)).toBeVisible(); // 模式切换保持当前页:此时在第 2 页
// 回到第 1 页:左缘点击
const box2 = await reader.boundingBox();
if (!box2) throw new Error("阅读器容器无 boundingBox");
await page.mouse.click(box2.x + box2.width * 0.1, box2.y + box2.height / 2);
await expect(page.getByText(/^1\/3 · /)).toBeVisible();
// 右缘点击 → 第 2 页
await page.mouse.click(box2.x + box2.width * 0.9, box2.y + box2.height / 2);
await expect(page.getByText(/^2\/3 · /)).toBeVisible();
// RTL:右缘点击变后退
await page.getByRole("button", { name: "右开本(RTL)" }).click();
await page.mouse.click(box2.x + box2.width * 0.9, box2.y + box2.height / 2);
await expect(page.getByText(/^1\/3 · /)).toBeVisible();
// 回长卷
await page.getByRole("button", { name: "长卷", exact: true }).click();
await expect(page.getByText("1/3")).toBeVisible();
// 导航抽屉(本 fixture 无内嵌目录,抽屉直接落在书签面板)→ 加书签 → toast // 导航抽屉(本 fixture 无内嵌目录,抽屉直接落在书签面板)→ 加书签 → toast
await page.getByRole("button", { name: "导航", exact: true }).click(); await page.getByRole("button", { name: "导航", exact: true }).click();
const nav = page.locator("#reader-nav"); const nav = page.locator("#reader-nav");
Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,53 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { fireEvent, render, screen } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { ThemeProvider } from "@/components/theme";
import CbzReader from "@/readers/CbzReader";
const okResponse = (body: unknown) =>
new Response(JSON.stringify(body), { status: 200, headers: { "Content-Type": "application/json" } });
beforeEach(() => {
localStorage.clear();
vi.restoreAllMocks?.();
// 全局网络打桩:页数接口 + 图片对象 URL + 进度上报(页图数据解不真实也无需:PageImg 有占位态)
vi.spyOn(globalThis, "fetch").mockImplementation(async (input) => {
const url = String(input instanceof Request ? input.url : input);
if (url.includes("/pages")) return okResponse({ count: 3, chapters: null });
if (url.includes("/progress")) return okResponse(null);
return new Response("", { status: 200 }); // 页图 blob 等
});
Object.defineProperty(URL, "createObjectURL", { value: () => "blob:x", configurable: true });
});
const book = {
id: 1,
library_id: 1,
path: "p",
title: "t",
format: "cbz",
state: "ok",
percent: 0,
pages_url: "/pages",
page_url_fmt: "/page/{}",
} as never;
describe("CbzReader 页模式设置区", () => {
it("切「单页」后位置读数 1/3,切「双页」首页不变,切回「长卷」按钮仍可用", async () => {
render(
<ThemeProvider>
<QueryClientProvider client={new QueryClient()}>
<CbzReader book={book} chrome={{ on: true, toggle: () => {}, show: () => {} }} />
</QueryClientProvider>
</ThemeProvider>,
);
await screen.findByLabelText("漫画阅读器,点按左右两侧翻屏,点按中间显示工具栏");
fireEvent.click(await screen.findByRole("button", { name: "更多阅读设置" }));
fireEvent.click(await screen.findByRole("button", { name: "单页" }));
expect(screen.getByText(/^1\/3 · /)).toBeInTheDocument();
fireEvent.click(await screen.findByRole("button", { name: "双页" }));
expect(screen.getByText(/^1\/3 · /)).toBeInTheDocument();
// 模式按钮组仍在设置面板内(长卷收进同一展开区)
expect(screen.getByRole("button", { name: "长卷" })).toBeInTheDocument();
});
});
+2
View File
@@ -221,6 +221,8 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome
} else { } else {
scrollToPage(t, false); scrollToPage(t, false);
} }
// 容器重挂后 scrollTop 归 0 且无 scroll 事件可刷 → 手动同步换算用的 top,避免沿用切走前的旧读数
setTop(boxRef.current?.scrollTop ?? 0);
}, [stripSeek, mode]); }, [stripSeek, mode]);
useEffect(() => { useEffect(() => {