Open History to see the last commit
1import { describe, expect, it } from "vite-plus/test";
2import {
3 currentPdfPage,
4 pdfFindIncludes,
5 pdfFindMatchRuns,
6 pdfPageOffsets,
7 pdfPageScrollTop,
8 pdfPageText,
9 pdfPageWidth,
10 pdfRestoredScrollTop,
11 stepPdfZoom,
12 visiblePdfPages,
13} from "./pdfLayout";
14
15describe("pdfPageWidth", () => {
16 it("fits the page to the viewport minus its gutters until a zoom is chosen", () => {
17 expect(pdfPageWidth(500, 612, null)).toEqual({ width: 468, scale: 468 / 612 });
18 expect(pdfPageWidth(500, 612, 1.5)).toEqual({ width: 918, scale: 1.5 });
19 });
20
21 it("never collapses below one pixel in a tiny viewport", () => {
22 expect(pdfPageWidth(10, 612, null).width).toBe(1);
23 });
24});
25
26describe("stepPdfZoom", () => {
27 it("steps by a quarter and clamps to 25%..300%", () => {
28 expect(stepPdfZoom(1, 1)).toBe(1.25);
29 expect(stepPdfZoom(1, -1)).toBe(0.75);
30 expect(stepPdfZoom(2.9, 1)).toBe(3);
31 expect(stepPdfZoom(0.3, -1)).toBe(0.25);
32 });
33
34 it("starts from the fit-width scale when no zoom is set", () => {
35 const { scale } = pdfPageWidth(500, 612, null);
36 expect(stepPdfZoom(scale, 1)).toBeCloseTo(468 / 612 + 0.25);
37 });
38});
39
40describe("mixed-height page layout", () => {
41 // A short banner page, a letter page, then a page half as tall as it is wide.
42 const ratios = [140 / 612, 792 / 612, 306 / 612];
43 const width = 612;
44 const offsets = pdfPageOffsets(ratios, width);
45
46 it("stacks pages with a gap before, between, and after them", () => {
47 expect(offsets).toEqual([16, 172, 980, 1302]);
48 });
49
50 it("reports the page whose bottom is past the top gutter as current", () => {
51 expect(currentPdfPage(offsets, 3, 0)).toBe(0);
52 expect(currentPdfPage(offsets, 3, 154)).toBe(0);
53 expect(currentPdfPage(offsets, 3, 155)).toBe(1);
54 expect(currentPdfPage(offsets, 3, 962)).toBe(1);
55 expect(currentPdfPage(offsets, 3, 964)).toBe(2);
56 });
57
58 it("jumps to each page so that page becomes current", () => {
59 for (const index of [0, 1, 2])
60 expect(currentPdfPage(offsets, 3, pdfPageScrollTop(offsets, index))).toBe(index);
61 expect(pdfPageScrollTop(offsets, 7)).toBe(0);
62 });
63
64 it("mounts pages within a viewport above and two below", () => {
65 const indexes = (scrollTop: number, height: number) =>
66 visiblePdfPages(ratios, offsets, scrollTop, height).map((page) => page.index);
67 expect(indexes(0, 100)).toEqual([0, 1]);
68 expect(indexes(1100, 100)).toEqual([2]);
69 expect(indexes(1100, 900)).toEqual([1, 2]);
70 // Page 2 starts 180px (1.8 viewports) below the visible bottom of 800.
71 expect(indexes(700, 100)).toEqual([1, 2]);
72 expect(visiblePdfPages(ratios, offsets, 0, 100)[0]).toEqual({ index: 0, ratio: ratios[0] });
73 });
74
75 it("scales offsets with the page width when zoomed", () => {
76 expect(pdfPageOffsets(ratios, width * 2)).toEqual([16, 312, 1912, 2540]);
77 });
78
79 it("restores a saved spot scaled to the new width and kept inside pages that remain", () => {
80 expect(pdfRestoredScrollTop(ratios, width, { page: 1, offset: 100 })).toBe(256);
81 expect(pdfRestoredScrollTop(ratios, width * 2, { page: 1, offset: 100 }, width)).toBe(496);
82 // A page that shrank keeps the reader on its last pixel; a removed page falls back to the last.
83 expect(pdfRestoredScrollTop(ratios, width, { page: 2, offset: 600 })).toBe(964 + 305);
84 expect(pdfRestoredScrollTop(ratios.slice(0, 2), width, { page: 2, offset: 0 })).toBe(156);
85 });
86});
87
88describe("pdfPageText", () => {
89 it("joins text items, adds line ends, and skips marked content", () => {
90 expect(
91 pdfPageText([
92 { str: "Quarter", hasEOL: false },
93 { type: "beginMarkedContent" },
94 { str: "ly outlook", hasEOL: true },
95 { str: "Next", hasEOL: false },
96 ]),
97 ).toBe("Quarterly outlook\nNext");
98 });
99});
100
101describe("pdfFindMatchRuns", () => {
102 it("highlights every run a match spans when a word is split across spans", () => {
103 expect(pdfFindMatchRuns(["Quarter", "ly outlook", " for Q3"], "quarterly OUTLOOK")).toEqual([
104 true,
105 true,
106 false,
107 ]);
108 });
109
110 it("highlights each separate match", () => {
111 expect(pdfFindMatchRuns(["risk", " and ", "budget", " ", "Risk"], "risk")).toEqual([
112 true,
113 false,
114 false,
115 false,
116 true,
117 ]);
118 });
119
120 it("matches a space in the query across a line end", () => {
121 expect(
122 pdfFindMatchRuns(["Q3 quarterly", "\n", "outlook", " ahead"], "quarterly outlook"),
123 ).toEqual([true, true, true, false]);
124 });
125
126 it("matches a word hyphenated across a line end", () => {
127 expect(pdfFindMatchRuns(["the quar-", "\n", "terly report"], "quarterly")).toEqual([
128 true,
129 false,
130 true,
131 ]);
132 });
133
134 it("drops a line-end hyphen after a letter outside the BMP", () => {
135 expect(pdfFindMatchRuns(["\u{10400}-", "\n", "\u{10401}"], "\u{10400}\u{10401}")).toEqual([
136 true,
137 false,
138 true,
139 ]);
140 });
141
142 it("finds the same pages the highlights do", () => {
143 const page = "Q3 quarterly\noutlook ahead, the quar-\nterly report";
144 expect(pdfFindIncludes(page, "Quarterly OUTLOOK")).toBe(true);
145 expect(pdfFindIncludes(page, "quarterly report")).toBe(true);
146 expect(pdfFindIncludes(page, "bud-get")).toBe(false);
147 expect(pdfFindIncludes(page, " ")).toBe(false);
148 });
149
150 it("highlights nothing for a blank or missing query", () => {
151 expect(pdfFindMatchRuns(["a b", "c"], " ")).toEqual([false, false]);
152 expect(pdfFindMatchRuns(["a b", "c"], "")).toEqual([false, false]);
153 expect(pdfFindMatchRuns(["a b", "c"], "zzz")).toEqual([false, false]);
154 });
155});