import { flushPromises, mount } from "@vue/test-utils"; import { beforeEach, describe, expect, it, vi } from "vitest"; const getDocumentMock = vi.hoisted(() => vi.fn()); const renderMock = vi.hoisted(() => vi.fn()); const destroyMock = vi.hoisted(() => vi.fn()); vi.mock("pdfjs-dist", () => ({ GlobalWorkerOptions: { workerSrc: "" }, getDocument: getDocumentMock, })); import PdfViewer from "./PdfViewer.vue"; describe("PdfViewer", () => { beforeEach(() => { vi.clearAllMocks(); renderMock.mockReturnValue({ promise: Promise.resolve(), cancel: vi.fn() }); destroyMock.mockResolvedValue(undefined); getDocumentMock.mockReturnValue({ promise: Promise.resolve({ numPages: 2, getPage: vi.fn().mockResolvedValue({ getViewport: ({ scale }: { scale: number }) => ({ width: 600 * scale, height: 800 * scale }), render: renderMock, }), destroy: destroyMock, }), destroy: destroyMock, }); }); it("renders PDF pages to canvas without an iframe", async () => { const wrapper = mount(PdfViewer, { props: { src: "blob:test-pdf" } }); await flushPromises(); expect(getDocumentMock).toHaveBeenCalledWith({ url: "blob:test-pdf" }); expect(wrapper.findAll("canvas")).toHaveLength(2); expect(wrapper.find("iframe").exists()).toBe(false); expect(renderMock).toHaveBeenCalled(); expect(wrapper.text()).toContain("/ 2"); await wrapper.get("button:nth-of-type(2)").trigger("click"); await flushPromises(); expect(wrapper.text()).toContain("/ 2"); wrapper.unmount(); await flushPromises(); expect(destroyMock).toHaveBeenCalled(); }); it("integrates the dialog close action into the viewer toolbar", async () => { const wrapper = mount(PdfViewer, { props: { src: "blob:test-pdf", showClose: true } }); await flushPromises(); await wrapper.get('[aria-label="关闭预览"]').trigger("click"); expect(wrapper.emitted("close")).toHaveLength(1); }); it("keeps the visible document center anchored while zooming", async () => { const wrapper = mount(PdfViewer, { props: { src: "blob:test-pdf" } }); await flushPromises(); const viewport = wrapper.get(".pdf-viewer__viewport").element as HTMLElement; Object.defineProperties(viewport, { clientWidth: { configurable: true, value: 800 }, clientHeight: { configurable: true, value: 600 }, scrollWidth: { configurable: true, get: () => Math.max(800, Number.parseInt(wrapper.get("canvas").element.style.width, 10) || 0), }, scrollHeight: { configurable: true, get: () => Math.max(600, Number.parseInt(wrapper.get("canvas").element.style.height, 10) || 0), }, }); await wrapper.get("button.is-active").trigger("click"); await flushPromises(); await wrapper.get('[aria-label="放大"]').trigger("click"); await flushPromises(); expect(viewport.scrollLeft).toBeGreaterThan(0); expect(viewport.scrollTop).toBeGreaterThan(0); }); it("shows the original filename in the center of the toolbar", async () => { const wrapper = mount(PdfViewer, { props: { src: "blob:test-pdf", filename: "研究方案最终版.pdf" }, }); await flushPromises(); expect(wrapper.get(".pdf-viewer__filename").text()).toBe("研究方案最终版.pdf"); expect(wrapper.text()).not.toContain("页文档"); }); it("updates the current page while scrolling through the continuous document", async () => { const wrapper = mount(PdfViewer, { props: { src: "blob:test-pdf" } }); await flushPromises(); const viewport = wrapper.get(".pdf-viewer__viewport"); const pages = wrapper.findAll(".pdf-viewer__page"); Object.defineProperties(viewport.element, { clientHeight: { configurable: true, value: 600 }, scrollTop: { configurable: true, writable: true, value: 850 }, }); Object.defineProperties(pages[0].element, { offsetTop: { configurable: true, value: 0 }, offsetHeight: { configurable: true, value: 800 }, }); Object.defineProperties(pages[1].element, { offsetTop: { configurable: true, value: 816 }, offsetHeight: { configurable: true, value: 800 }, }); await viewport.trigger("scroll"); expect((wrapper.get(".pdf-viewer__page-input").element as HTMLInputElement).value).toBe("2"); }); });