151 lines
6 KiB
TypeScript
151 lines
6 KiB
TypeScript
|
|
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");
|
|||
|
|
});
|
|||
|
|
});
|