Skip to content
Source
Sign inSign up

pdfPreview.test.ts

103 lines3.8 KB

1import { beforeEach, describe, expect, it, vi } from "vite-plus/test";
2​
3const mocks = vi.hoisted(() => ({
4 download: vi.fn(),
5 dispose: vi.fn(),
6 read: vi.fn(),
7 close: vi.fn(),
8}));
9vi.mock("./attachmentDownload", () => ({ downloadAttachmentForPreview: mocks.download }));
10vi.mock("expo-file-system", () => ({
11 File: class {
12 open() {
13 return { readBytes: mocks.read, close: mocks.close };
14 }
15 },
16 FileMode: { ReadOnly: "r" },
17}));
18​
19import { isPdfPreviewUri, pdfLinkUrl, preparePdfPreview } from "./pdfPreview";
20​
21beforeEach(() => {
22 vi.resetAllMocks();
23 mocks.download.mockResolvedValue({ uri: "file:///cache/preview.pdf", dispose: mocks.dispose });
24 mocks.read.mockResolvedValue(new TextEncoder().encode("%PDF-"));
25});
26​
27describe("PDF navigation", () => {
28 it("resolves web links against the original PDF rather than its download cache", () => {
29 expect(
30 pdfLinkUrl("../next.pdf#page=2", "https://environment.test/api/assets/token/files/a.pdf"),
31 ).toBe("https://environment.test/api/assets/token/next.pdf#page=2");
32 expect(pdfLinkUrl("https://example.com/?q=one#section", "file:///cache/preview.pdf")).toBe(
33 "https://example.com/?q=one#section",
34 );
35 });
36​
37 it.each([
38 "",
39 "javascript:alert(1)",
40 "data:text/html,hello",
41 "file:///private/a.pdf",
42 "intent://open",
43 "https://[",
44 ])("ignores unsupported or invalid PDF links: %s", (href) => {
45 expect(pdfLinkUrl(href, "https://environment.test/a.pdf")).toBeNull();
46 });
47​
48 it("recognizes signed PDF links with queries and page fragments", () => {
49 expect(
50 isPdfPreviewUri("https://environment.test/api/assets/token/Syllabus.PDF?x=1#page=8"),
51 ).toBe(true);
52 expect(isPdfPreviewUri("file:///local/document.pdf")).toBe(true);
53 expect(isPdfPreviewUri("https://environment.test/index.html?file=syllabus.pdf")).toBe(false);
54 expect(isPdfPreviewUri("invalid")).toBe(false);
55 });
56});
57​
58describe("preparing native PDFs", () => {
59 it("downloads from the selected environment without sending the page fragment", async () => {
60 const signal = new AbortController().signal;
61 const result = await preparePdfPreview("https://remote.test/a.pdf?token=secret#page=8", signal);
62 expect(mocks.download).toHaveBeenCalledWith({
63 url: "https://remote.test/a.pdf?token=secret",
64 attachment: { name: "preview.pdf", mimeType: "application/pdf" },
65 signal,
66 });
67 expect(result?.uri).toBe("file:///cache/preview.pdf");
68 expect(mocks.close).toHaveBeenCalledOnce();
69 expect(mocks.dispose).not.toHaveBeenCalled();
70 result?.dispose();
71 expect(mocks.dispose).toHaveBeenCalledOnce();
72 });
73​
74 it("rejects an HTML response and removes the download", async () => {
75 mocks.read.mockResolvedValue(new TextEncoder().encode("<html"));
76 await expect(
77 preparePdfPreview("https://remote.test/a.pdf", new AbortController().signal),
78 ).rejects.toThrow("did not return a PDF");
79 expect(mocks.close).toHaveBeenCalledOnce();
80 expect(mocks.dispose).toHaveBeenCalledOnce();
81 });
82​
83 it("opens local files without downloading or deleting the caller's file", async () => {
84 const result = await preparePdfPreview(
85 "file:///draft.pdf#page=3",
86 new AbortController().signal,
87 );
88 expect(result?.uri).toBe("file:///draft.pdf");
89 result?.dispose();
90 expect(mocks.download).not.toHaveBeenCalled();
91 expect(mocks.dispose).not.toHaveBeenCalled();
92 });
93​
94 it("removes a download if the preview is dismissed during validation", async () => {
95 const controller = new AbortController();
96 mocks.read.mockImplementation(async () => {
97 controller.abort();
98 return new TextEncoder().encode("%PDF-");
99 });
100 expect(await preparePdfPreview("https://remote.test/a.pdf", controller.signal)).toBeNull();
101 expect(mocks.dispose).toHaveBeenCalledOnce();
102 });
103});