Files
SnapOtter/tests/unit/api/page-spec.test.ts
T

152 lines
4.7 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { compressPageRuns, parsePageSpec } from "../../../apps/api/src/lib/page-spec.js";
describe("parsePageSpec", () => {
it("resolves a single page", () => {
const result = parsePageSpec("2", 3);
expect(result).toEqual(new Set([2]));
});
it("resolves a range", () => {
const result = parsePageSpec("1-2", 3);
expect(result).toEqual(new Set([1, 2]));
});
it("resolves z as the last page", () => {
const result = parsePageSpec("z", 3);
expect(result).toEqual(new Set([3]));
});
it("resolves r1 as the last page", () => {
const result = parsePageSpec("r1", 3);
expect(result).toEqual(new Set([3]));
});
it("resolves r2 as second-to-last", () => {
const result = parsePageSpec("r2", 3);
expect(result).toEqual(new Set([2]));
});
it("resolves r3 as the first page of 3", () => {
const result = parsePageSpec("r3", 3);
expect(result).toEqual(new Set([1]));
});
it("handles comma-separated values", () => {
const result = parsePageSpec("1,3", 3);
expect(result).toEqual(new Set([1, 3]));
});
it("handles 1-z as all pages", () => {
const result = parsePageSpec("1-z", 3);
expect(result).toEqual(new Set([1, 2, 3]));
});
it("throws on out-of-range page", () => {
expect(() => parsePageSpec("5", 3)).toThrow(/out of range/i);
});
it("throws on r-index that resolves below 1", () => {
expect(() => parsePageSpec("r4", 3)).toThrow(/out of range/i);
});
it("throws on page 0", () => {
expect(() => parsePageSpec("0", 3)).toThrow();
});
// Removal keep-list derivation tests:
// "2" of 3 -> remove set {2}, keep "1,3"
it("remove-pages: '2' of 3 yields keep '1,3'", () => {
const removeSet = parsePageSpec("2", 3);
const keep: number[] = [];
for (let i = 1; i <= 3; i++) {
if (!removeSet.has(i)) keep.push(i);
}
expect(keep.join(",")).toBe("1,3");
});
// "1-2" of 3 -> remove set {1,2}, keep "3"
it("remove-pages: '1-2' of 3 yields keep '3'", () => {
const removeSet = parsePageSpec("1-2", 3);
const keep: number[] = [];
for (let i = 1; i <= 3; i++) {
if (!removeSet.has(i)) keep.push(i);
}
expect(keep.join(",")).toBe("3");
});
// "z" of 3 -> remove set {3}, keep "1,2"
it("remove-pages: 'z' of 3 yields keep '1,2'", () => {
const removeSet = parsePageSpec("z", 3);
const keep: number[] = [];
for (let i = 1; i <= 3; i++) {
if (!removeSet.has(i)) keep.push(i);
}
expect(keep.join(",")).toBe("1,2");
});
// "1-z" of 3 -> all pages removed -> keep list empty
it("remove-pages: '1-z' of 3 leaves nothing to keep", () => {
const removeSet = parsePageSpec("1-z", 3);
const keep: number[] = [];
for (let i = 1; i <= 3; i++) {
if (!removeSet.has(i)) keep.push(i);
}
expect(keep.length).toBe(0);
});
// Reviewer edge-decision tests
it("reversed range '3-1' normalizes to {1,2,3}", () => {
expect(parsePageSpec("3-1", 3)).toEqual(new Set([1, 2, 3]));
});
it("'z-1' of 3 resolves to all pages", () => {
expect(parsePageSpec("z-1", 3)).toEqual(new Set([1, 2, 3]));
});
it("'r2-r1' of 5 resolves to {4,5}", () => {
expect(parsePageSpec("r2-r1", 5)).toEqual(new Set([4, 5]));
});
it("'1-r1' of 5 resolves to all pages", () => {
expect(parsePageSpec("1-r1", 5)).toEqual(new Set([1, 2, 3, 4, 5]));
});
it("'1,1' deduplicates to {1}", () => {
expect(parsePageSpec("1,1", 3)).toEqual(new Set([1]));
});
});
describe("compressPageRuns", () => {
it("collapses a full consecutive run: [2..80] -> '2-80'", () => {
const pages = Array.from({ length: 79 }, (_, i) => i + 2);
expect(compressPageRuns(pages)).toBe("2-80");
});
it("leaves non-consecutive pages as singles: [1,3,5,7] -> '1,3,5,7'", () => {
expect(compressPageRuns([1, 3, 5, 7])).toBe("1,3,5,7");
});
it("mixes runs and singles: [1,2,4,5,6,9] -> '1-2,4-6,9'", () => {
expect(compressPageRuns([1, 2, 4, 5, 6, 9])).toBe("1-2,4-6,9");
});
it("returns empty string for empty input", () => {
expect(compressPageRuns([])).toBe("");
});
it("handles a single page: [3] -> '3'", () => {
expect(compressPageRuns([3])).toBe("3");
});
it("pathological alternation (odd pages 1..399) produces ~800 chars", () => {
// 200 odd pages, none consecutive, so compression cannot help
const pages = Array.from({ length: 200 }, (_, i) => i * 2 + 1);
const result = compressPageRuns(pages);
// Each singleton is up to 3 digits + comma -> no runs to collapse
expect(result).toBe(pages.join(","));
// This exceeds 200 chars, confirming the need for qpdfPagesSpecUnchecked
expect(result.length).toBeGreaterThan(200);
});
});