eCourt-note/tests/utils/pdf-export.test.ts

151 lines
6 KiB
TypeScript
Raw Normal View History

import { describe, expect, it } from "vitest";
import {
displayRectToPdf,
displaySize,
displayToPdfPoint,
encodeRFC5987,
hexToRgb01,
wrapStyledText,
wrapTextByWidth,
} from "@/utils/pdf-export";
// หน้า A4 portrait จริง (pt) — display ของ 90/270 จะสลับเป็น 792×612
const PAGE = { width: 612, height: 792 };
describe("displaySize", () => {
it("/Rotate 0 และ 180 → ขนาดหน้าเดิม", () => {
expect(displaySize(PAGE, 0)).toEqual(PAGE);
expect(displaySize(PAGE, 180)).toEqual(PAGE);
});
it("/Rotate 90 และ 270 → สลับกว้าง-สูง", () => {
expect(displaySize(PAGE, 90)).toEqual({ width: 792, height: 612 });
expect(displaySize(PAGE, 270)).toEqual({ width: 792, height: 612 });
});
});
describe("displayToPdfPoint", () => {
it("rot 0: พลิกแกน y (จอ y ลง → PDF y ขึ้น)", () => {
expect(displayToPdfPoint({ x: 10, y: 20 }, PAGE, 0)).toEqual({ x: 10, y: 772 });
});
it("rot 90: จอมุมบนซ้าย (0,0) → PDF (0,0)", () => {
expect(displayToPdfPoint({ x: 0, y: 0 }, PAGE, 90)).toEqual({ x: 0, y: 0 });
expect(displayToPdfPoint({ x: 10, y: 20 }, PAGE, 90)).toEqual({ x: 20, y: 10 });
});
it("rot 180: มุมบนซ้ายจอ → มุมขวาล่างของ PDF", () => {
expect(displayToPdfPoint({ x: 10, y: 20 }, PAGE, 180)).toEqual({ x: 602, y: 20 });
});
it("rot 270: มุมบนซ้ายจอ → มุมขวาบนของ PDF", () => {
expect(displayToPdfPoint({ x: 10, y: 20 }, PAGE, 270)).toEqual({ x: 592, y: 782 });
});
});
describe("displayRectToPdf", () => {
it("rot 0: y พลิก (y เดิม = ขอบบนจอ → กลายเป็น y บนของ rect PDF)", () => {
expect(displayRectToPdf({ x: 10, y: 20, width: 30, height: 40 }, PAGE, 0)).toEqual({
x: 10,
y: 732,
width: 30,
height: 40,
});
});
it("rot 90: กว้าง-สูงสลับกัน และ normalized เป็นบวกเสมอ", () => {
expect(displayRectToPdf({ x: 10, y: 20, width: 30, height: 40 }, PAGE, 90)).toEqual({
x: 20,
y: 10,
width: 40,
height: 30,
});
});
});
describe("hexToRgb01", () => {
it("แปลง #rrggbb เป็นสัดส่วน 0–1", () => {
expect(hexToRgb01("#ff0000")).toEqual({ r: 1, g: 0, b: 0 });
expect(hexToRgb01("#00ff00")).toEqual({ g: 1, r: 0, b: 0 });
expect(hexToRgb01("#0000ff")).toEqual({ r: 0, g: 0, b: 1 });
});
it("ไม่มี # นำหน้า / ตัวพิมพ์ใหญ่ก็รับ", () => {
expect(hexToRgb01("ff8000")).toEqual({ r: 1, g: 128 / 255, b: 0 });
expect(hexToRgb01("#FF8000")).toEqual({ r: 1, g: 128 / 255, b: 0 });
});
it("รูปแบบผิด → ดำ (fallback กัน parse พัง)", () => {
expect(hexToRgb01("abc")).toEqual({ r: 0, g: 0, b: 0 });
expect(hexToRgb01("#zzzzzz")).toEqual({ r: 0, g: 0, b: 0 });
expect(hexToRgb01("")).toEqual({ r: 0, g: 0, b: 0 });
});
});
describe("wrapTextByWidth", () => {
// จำลองฟอนต์ที่ตัวอักษรกว้าง 1 หน่วยทุกตัว
const w = (s: string) => s.length;
it("ตัดทีละตัวอักษร (ภาษาไทยไม่มีช่องว่างคำ)", () => {
expect(wrapTextByWidth("อบจทดสอบ", 5, w)).toEqual(["อบจทด", "สอบ"]);
});
it("ขึ้นบรรทัดใหม่ด้วย \\n แยกจากการ wrap", () => {
expect(wrapTextByWidth("aaaa\nbb", 5, w)).toEqual(["aaaa", "bb"]);
});
it("คำยาวเกิน maxWidth → แตกต่อทันทีไม่มีการกลืน", () => {
expect(wrapTextByWidth("x".repeat(12), 5, w)).toEqual(["xxxxx", "xxxxx", "xx"]);
});
it("ข้อความว่าง → บรรทัดว่างเดียว", () => {
expect(wrapTextByWidth("", 5, w)).toEqual([""]);
});
});
describe("wrapStyledText", () => {
// ตัวหนากว้าง 2 เท่าของปกติ
const w = (s: string, bold: boolean) => s.length * (bold ? 2 : 1);
it("run ผสมหนา/ปกติ wrap ตามความกว้างจริงของแต่ละตัว แล้วม้วนกลับเป็น run ต่อบรรทัด", () => {
const out = wrapStyledText(
[
{ text: "ab", bold: true },
{ text: "cdef", bold: false },
],
6,
w,
);
// a(2)+b(2)+c(1)+d(1) = 6 พอดีบรรทัดแรก · e,f ไปบรรทัดสอง
expect(out).toEqual([
[
{ text: "ab", bold: true },
{ text: "cd", bold: false },
],
[{ text: "ef", bold: false }],
]);
});
it("\\n ใน run ตัดบรรทัดแบบชัด ๆ", () => {
const out = wrapStyledText([{ text: "a\nb", bold: false }], 10, w);
expect(out).toEqual([[{ text: "a", bold: false }], [{ text: "b", bold: false }]]);
});
it("ตัวอักษรที่กว้างเกิน maxWidth ทั้งบรรทัด ยังวางลงบรรทัดใหม่ได้ (ไม่ทิ้ง)", () => {
const out = wrapStyledText([{ text: "ab", bold: true }], 1, w);
expect(out).toEqual([[{ text: "a", bold: true }], [{ text: "b", bold: true }]]);
});
});
describe("encodeRFC5987", () => {
it("ชื่อไฟล์ภาษาไทย encode เป็น percent-encoding UTF-8", () => {
expect(encodeRFC5987("คำพิพากษา.pdf")).toBe(
"%E0%B8%84%E0%B8%B3%E0%B8%9E%E0%B8%B4%E0%B8%9E%E0%B8%B2%E0%B8%81%E0%B8%A9%E0%B8%B2.pdf",
);
});
it("อักขระ '()* ที่ encodeURIComponent ปล่อยไว้ ต้องถูก encode เพิ่มตาม RFC 5987", () => {
expect(encodeRFC5987("a'b(c)*d")).toBe("a%27b%28c%29%2Ad");
});
});