From 4d5f7cfb07fbe59c25421786c0ef583aeb4519dc Mon Sep 17 00:00:00 2001 From: Git Agent Date: Mon, 24 Aug 2026 16:32:27 +0200 Subject: [PATCH] =?UTF-8?q?fix(web):=20lecteur=20CBZ/CBR=20mode=20vertical?= =?UTF-8?q?=20=E2=80=94=20capturer=20naturalWidth/naturalHeight=20avant=20?= =?UTF-8?q?neutralisation=20de=20currentTarget=20par=20React=20(r=C3=A9gre?= =?UTF-8?q?ssion=20#37)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/web/src/reader/CbzReader.tsx | 3 +- .../src/reader/CbzReaderNaturalWidth.test.tsx | 94 +++++++++++++++++++ 2 files changed, 96 insertions(+), 1 deletion(-) create mode 100644 apps/web/src/reader/CbzReaderNaturalWidth.test.tsx diff --git a/apps/web/src/reader/CbzReader.tsx b/apps/web/src/reader/CbzReader.tsx index 2c86cd8..c1c33a7 100644 --- a/apps/web/src/reader/CbzReader.tsx +++ b/apps/web/src/reader/CbzReader.tsx @@ -217,9 +217,10 @@ export function CbzReader({ loading="lazy" style={verticalImageStyle(item.page)} onLoad={(event) => { + const { naturalWidth, naturalHeight } = event.currentTarget; setImageSizes((current) => ({ ...current, - [item.page]: { width: event.currentTarget.naturalWidth, height: event.currentTarget.naturalHeight } + [item.page]: { width: naturalWidth, height: naturalHeight } })); }} onError={() => setPageError(`Page ${item.page} indisponible.`)} diff --git a/apps/web/src/reader/CbzReaderNaturalWidth.test.tsx b/apps/web/src/reader/CbzReaderNaturalWidth.test.tsx new file mode 100644 index 0000000..dece013 --- /dev/null +++ b/apps/web/src/reader/CbzReaderNaturalWidth.test.tsx @@ -0,0 +1,94 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const hookState = vi.hoisted(() => ({ + stateIndex: 0, + states: [] as unknown[], + updates: [] as unknown[][] +})); + +vi.mock("react", async () => { + const actual = await vi.importActual("react"); + return { + ...actual, + useCallback: (callback: unknown) => callback, + useEffect: () => undefined, + useRef: (current: unknown) => ({ current }), + useState: (initial: unknown) => { + const index = hookState.stateIndex; + hookState.stateIndex += 1; + hookState.updates[index] = []; + return [ + hookState.states[index] ?? initial, + (next: unknown) => { + hookState.updates[index].push(next); + } + ]; + } + }; +}); + +vi.mock("../api/client", () => ({ + api: { + cbzPages: vi.fn().mockResolvedValue({ bookId: 2, pageCount: 1, pages: [{ page: 1, name: "page-1.jpg" }] }), + cbzPageUrl: (bookId: number, page: number) => `/books/${bookId}/pages/${page}` + } +})); + +import { CbzReader } from "./CbzReader"; + +type ElementLike = { + type: unknown; + props?: Record & { children?: unknown }; +}; + +function isElementLike(value: unknown): value is ElementLike { + return Boolean(value && typeof value === "object" && "type" in value); +} + +function findElementsByType(node: unknown, type: string): ElementLike[] { + if (Array.isArray(node)) return node.flatMap((child) => findElementsByType(child, type)); + if (!isElementLike(node)) return []; + + const matches = node.type === type ? [node] : []; + return [...matches, ...findElementsByType(node.props?.children, type)]; +} + +describe("CbzReader vertical image load", () => { + beforeEach(() => { + hookState.stateIndex = 0; + hookState.updates = []; + hookState.states = [ + { bookId: 2, pageCount: 1, pages: [{ page: 1, name: "page-1.jpg" }] }, + undefined, + undefined, + 0, + 0, + { width: 800, height: 1200 }, + null, + {} + ]; + }); + + it("records vertical image dimensions before React clears the load event target", () => { + const tree = CbzReader({ + bookId: 2, + page: 1, + zoom: 100, + mode: "vertical", + onPageCommit: vi.fn(), + onControlsChange: vi.fn() + }); + const image = findElementsByType(tree, "img")[0]; + const onLoad = image.props?.onLoad as (event: { currentTarget: { naturalWidth: number; naturalHeight: number } | null }) => void; + const event: { currentTarget: { naturalWidth: number; naturalHeight: number } | null } = { currentTarget: { naturalWidth: 480, naturalHeight: 960 } }; + + onLoad(event); + event.currentTarget = null; + + let nextSizes: unknown; + expect(() => { + nextSizes = (hookState.updates[7][0] as (current: Record) => unknown)({}); + }).not.toThrow(); + expect(nextSizes).toEqual({ 1: { width: 480, height: 960 } }); + }); +});