eCourt-note/tests/components/toolbar-tools.test.ts

156 lines
7.2 KiB
TypeScript
Raw Permalink Normal View History

refactor(toolbar): extract toolbar tool logic to module ย้าย logic ของแถบ tool ออกจาก PDFViewer.tsx ไป pure module ใหม่ components/pdf/toolbar-tools.ts (behavior-preserving) ตามแผน TDD docs/PLAN-TOOLBAR.md ขั้น 1+2 รวม findings #1-#6: - ขั้น 1: ย้าย type Tool/TOOLS/visibleToolsFor/canSwitchTo/ toolButtonFace/menuIntentAfterClick ออกจาก PDFViewer.tsx + rewire + ตัด icon imports ว่าง 9 ตัว + spec test tests/components/toolbar-tools.test.ts - ขั้น 2 (findings #2/#5): TOOL_MENU_KEYS + type MenuTool + TOOL_MENU_TABLE (Record บังคับครบทุก key — เพิ่มเมนูลืมแถว = compile fail) + closeToolMenus()/closePagePopups() ใช้ร่วมครบทุก กลุ่มจุดเดิม รวม 2 จุดที่เดิมไม่เคลียร์เลย (scannedOnly + สลับไฟล์ ภาพ) — popup bookmark/composer/popover ของ pin/แผง selection/editor ข้อความไม่ค้างเปิดซ้อนกันครบทุกทางเข้า - findings #1: คีย์ลัดจับคู่ด้วย e.code แทน e.key (TOOL_SHORTCUTS KeyH/KeyD/KeyC + ขยายครอบ Ctrl+Z undo/redo) — layout ไทยให้ e.key เป็นอักษรไทยจนคีย์ลัด H/D/C/B และ undo ใช้ไม่ได้ - findings #3/#4/#6: ปรับให้ปุ่มใช้ตัวแปร active เดียวกันทั้งชุด (aria-pressed) ผ่าน tsc + vitest 207/207 + e2e Playwright ตามแผน (เมนูค้าง/สลับไฟล์ ภาพ/popup bookmark ลำดับปิด-เปิด/คีย์ลัด synthetic layout ไทย) Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 14:47:12 +07:00
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", "")})`);
}
});
});