156 lines
7.2 KiB
TypeScript
156 lines
7.2 KiB
TypeScript
|
|
import { describe, expect, it } from "vitest";
|
||
|
|
import {
|
||
|
|
TOOLS,
|
||
|
|
TOOL_MENU_KEYS,
|
||
|
|
TOOL_SHORTCUTS,
|
||
|
|
canSwitchTo,
|
||
|
|
menuIntentAfterClick,
|
||
|
|
toolButtonFace,
|
||
|
|
visibleToolsFor,
|
||
|
|
} from "@/components/pdf/toolbar-tools";
|
||
|
|
|
||
|
|
// spec ตาม PLAN-TOOLBAR.md ขั้น 1 — ค่า expected เป็น literal จาก
|
||
|
|
// .claude/rules/pdf-viewer.md + พฤติกรรม toolbar ของ commit 8571ef9
|
||
|
|
// (ไม่คัดลอกสูตรจากโค้ดปลายทาง กัน tautological)
|
||
|
|
|
||
|
|
describe("TOOLS", () => {
|
||
|
|
it("ครบ 10 ตัวตาม union และลำดับปุ่มบนแถบ", () => {
|
||
|
|
expect(TOOLS.map((t) => t.key)).toEqual([
|
||
|
|
"select",
|
||
|
|
"pan",
|
||
|
|
"highlight",
|
||
|
|
"draw",
|
||
|
|
"comment",
|
||
|
|
"shape",
|
||
|
|
"erase",
|
||
|
|
"redact",
|
||
|
|
"textbox",
|
||
|
|
"stamp",
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("text สั้นแสดงบนปุ่มครบทุกตัว (คำขอผู้ใช้ 2026-09-21: ปุ่มต้องมีชื่อ ไม่ใช่ icon ล้วน)", () => {
|
||
|
|
expect(Object.fromEntries(TOOLS.map((t) => [t.key, t.text]))).toEqual({
|
||
|
|
select: "เลือก",
|
||
|
|
pan: "เลื่อนหน้า",
|
||
|
|
highlight: "มาร์กข้อความ",
|
||
|
|
draw: "วาดเส้น",
|
||
|
|
comment: "คอมเมนต์",
|
||
|
|
shape: "รูปทรง",
|
||
|
|
erase: "ยางลบ",
|
||
|
|
redact: "ปิดพราง",
|
||
|
|
textbox: "กล่องข้อความ",
|
||
|
|
stamp: "ตรายาง",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("label เต็ม (title/aria-label) มีคีย์ลัด H/D/C ตาม rules และทุกตัวมี Icon", () => {
|
||
|
|
const byKey = Object.fromEntries(TOOLS.map((t) => [t.key, t]));
|
||
|
|
expect(byKey.highlight.label).toBe("มาร์กข้อความ (H)");
|
||
|
|
expect(byKey.draw.label).toBe("วาดเส้น (D)");
|
||
|
|
expect(byKey.comment.label).toBe("คอมเมนต์ (C)");
|
||
|
|
for (const t of TOOLS) {
|
||
|
|
expect(t.label.trim().length).toBeGreaterThan(0);
|
||
|
|
expect(t.Icon).toBeTruthy();
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("visibleToolsFor", () => {
|
||
|
|
it("hide=false → ทั้งชุด 10 ตัว", () => {
|
||
|
|
expect(visibleToolsFor(false).map((t) => t.key)).toHaveLength(10);
|
||
|
|
expect(visibleToolsFor(false)[0].key).toBe("select");
|
||
|
|
expect(visibleToolsFor(false).at(-1)?.key).toBe("stamp");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("hide=true (ไฟล์ภาพ/สแกนล้วน) → ตัดเฉพาะ highlight เหลือ 9 ตัว ลำดับเดิม", () => {
|
||
|
|
expect(visibleToolsFor(true).map((t) => t.key)).toEqual([
|
||
|
|
"select",
|
||
|
|
"pan",
|
||
|
|
"draw",
|
||
|
|
"comment",
|
||
|
|
"shape",
|
||
|
|
"erase",
|
||
|
|
"redact",
|
||
|
|
"textbox",
|
||
|
|
"stamp",
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("canSwitchTo", () => {
|
||
|
|
it("highlight บนไฟล์ที่ซ่อนปุ่ม (ภาพ/สแกนล้วน) → false กันคีย์ลัด H", () => {
|
||
|
|
expect(canSwitchTo("highlight", true)).toBe(false);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("highlight บนไฟล์ปกติ → true", () => {
|
||
|
|
expect(canSwitchTo("highlight", false)).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("tool อื่นทุกตัวสลับได้แม้ไฟล์ภาพ/สแกนล้วน", () => {
|
||
|
|
for (const t of TOOLS.map((x) => x.key)) {
|
||
|
|
if (t === "highlight") continue;
|
||
|
|
expect(canSwitchTo(t, true)).toBe(true);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("toolButtonFace", () => {
|
||
|
|
it("ไม่ active → ชื่อ tool เสมอแม้มีค่าย่อย (คำขอผู้ใช้ 2026-09-29: 'วาดเส้น' ต้องไม่กลายเป็น 'ปากกา')", () => {
|
||
|
|
expect(toolButtonFace(false, { label: "วาดเส้น (D)", text: "วาดเส้น" }, "ปากกา")).toEqual({
|
||
|
|
text: "วาดเส้น",
|
||
|
|
title: "วาดเส้น (D)",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("active + มีค่าย่อย → แสดงค่าที่เลือก + title 'label — ค่า' (stamp คำกำหนดเองก็เป็นค่าหนึ่ง)", () => {
|
||
|
|
expect(
|
||
|
|
toolButtonFace(true, { label: "ตรายาง (stamp)", text: "ตรายาง" }, "กำหนดเอง"),
|
||
|
|
).toEqual({ text: "กำหนดเอง", title: "ตรายาง (stamp) — กำหนดเอง" });
|
||
|
|
// textbox ไม่มีค่าย่อย — active อยู่ชื่อเดิม ไม่กลายเป็น "กล่องข้อความ — กล่องข้อความ"
|
||
|
|
expect(toolButtonFace(true, { label: "กล่องข้อความ", text: "กล่องข้อความ" })).toEqual({
|
||
|
|
text: "กล่องข้อความ",
|
||
|
|
title: "กล่องข้อความ",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("menuIntentAfterClick", () => {
|
||
|
|
it("กดจาก tool อื่น / tool เดิมที่เมนูปิด = เปิดเมนู; tool เดิมที่เมนูเปิด = พับ", () => {
|
||
|
|
expect(menuIntentAfterClick("select", "highlight", false)).toBe(true);
|
||
|
|
expect(menuIntentAfterClick("select", "highlight", true)).toBe(true);
|
||
|
|
expect(menuIntentAfterClick("highlight", "highlight", false)).toBe(true);
|
||
|
|
expect(menuIntentAfterClick("highlight", "highlight", true)).toBe(false);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("TOOL_MENU_KEYS", () => {
|
||
|
|
it("ครบเฉพาะ tool ที่ปุ่มเป็น dropdown (มาร์กข้อความ/วาดเส้น/รูปทรง/กล่องข้อความ/ตรายาง) — จุดที่ปิดเมนูทั้งชุดวนจากชุดนี้ชุดเดียว", () => {
|
||
|
|
expect([...TOOL_MENU_KEYS].sort()).toEqual(["draw", "highlight", "shape", "stamp", "textbox"]);
|
||
|
|
// ทุก key เป็นค่าของ Tool จริง (กันชื่อคำนวณพลาด)
|
||
|
|
for (const k of TOOL_MENU_KEYS) {
|
||
|
|
expect(TOOLS.some((t) => t.key === k)).toBe(true);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe("TOOL_SHORTCUTS", () => {
|
||
|
|
it("จับคู่ e.code ตำแหน่งปุ่ม (layout-independent) — บน layout ไทย e.key เป็น 'ภ' ก็ยังใช้ได้", () => {
|
||
|
|
expect(TOOL_SHORTCUTS.KeyH).toBe("highlight");
|
||
|
|
expect(TOOL_SHORTCUTS.KeyD).toBe("draw");
|
||
|
|
expect(TOOL_SHORTCUTS.KeyC).toBe("comment");
|
||
|
|
// คีย์อื่น/ตัวอักษร layout ไทยไม่ใช่คีย์ลัด
|
||
|
|
expect(TOOL_SHORTCUTS.KeyZ).toBeUndefined();
|
||
|
|
expect(TOOL_SHORTCUTS.KeyB).toBeUndefined(); // B = bookmark ไม่ใช่ tool
|
||
|
|
expect(TOOL_SHORTCUTS["ภ"]).toBeUndefined();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("ตรงกับคีย์ลัดที่ label ของ TOOLS ประกาศ ((H)/(D)/(C)) — กัน drift สองฝั่ง", () => {
|
||
|
|
for (const [code, tool] of Object.entries(TOOL_SHORTCUTS)) {
|
||
|
|
const entry = TOOLS.find((t) => t.key === tool);
|
||
|
|
expect(entry, `tool ${tool} ต้องอยู่ใน TOOLS`).toBeTruthy();
|
||
|
|
expect(entry!.label).toContain(`(${code.replace("Key", "")})`);
|
||
|
|
}
|
||
|
|
});
|
||
|
|
});
|