diff --git a/frontend/e2e/auth-shelf.spec.ts b/frontend/e2e/auth-shelf.spec.ts index 6cc8556..71cd9e8 100644 --- a/frontend/e2e/auth-shelf.spec.ts +++ b/frontend/e2e/auth-shelf.spec.ts @@ -51,6 +51,26 @@ test("登录 → 书架 → CBZ 阅读翻页 → 书签 → 退出", async ({ pa await expect(page.getByText("2/3")).toBeVisible(); }).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 await page.getByRole("button", { name: "导航", exact: true }).click(); const nav = page.locator("#reader-nav"); diff --git a/frontend/probe-shelf.png b/frontend/probe-shelf.png new file mode 100644 index 0000000..b9e1441 Binary files /dev/null and b/frontend/probe-shelf.png differ diff --git a/frontend/src/readers/CbzReader.pagemode.test.tsx b/frontend/src/readers/CbzReader.pagemode.test.tsx new file mode 100644 index 0000000..0daa24a --- /dev/null +++ b/frontend/src/readers/CbzReader.pagemode.test.tsx @@ -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( + + + {}, show: () => {} }} /> + + , + ); + 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(); + }); +}); diff --git a/frontend/src/readers/CbzReader.tsx b/frontend/src/readers/CbzReader.tsx index 8e639a8..22be12d 100644 --- a/frontend/src/readers/CbzReader.tsx +++ b/frontend/src/readers/CbzReader.tsx @@ -221,6 +221,8 @@ export default function CbzReader({ book, initialLocator, initialPercent, chrome } else { scrollToPage(t, false); } + // 容器重挂后 scrollTop 归 0 且无 scroll 事件可刷 → 手动同步换算用的 top,避免沿用切走前的旧读数 + setTop(boxRef.current?.scrollTop ?? 0); }, [stripSeek, mode]); useEffect(() => {