bookmark: ดักความยาวชื่อ/โน้ตที่ช่องกรอก + F19 สรุปสิ่งที่เปลี่ยนใน dialog ประวัติ + formatThaiDate — (1) bookmark limits: utils/bookmark-limits.ts ใหม่เป็น source of truth เดียว (ชื่อ 120/โน้ต 200 ตัวอักษร) server action import ร่วมแทน const ท้องถิ่น + ใส่ maxLength ที่ popup บันทึก bookmark (PDFViewer) และฟอร์มแก้ไข ✏ ใน sidebar ดักทั้งพิมพ์/paste ตั้งแต่ในช่อง ไม่รอ error ตอนกดบันทึก + แก้ off-by-one ของ normalizeBookmarkTitle (เดิม slice 120 แล้วต่อ … = 121 ตัว ทำให้คลุมข้อความยาวแล้วกด B บันทึกเลยโดน limit ของ server ตีกลับทั้งที่ไม่ได้พิมพ์เอง — ตอนนี้ slice 119 + … = 120 พอดีตามสเปก §7 N2 ที่เขียนไว้ ≤ 120 อยู่แล้ว) + PLAN-FEAT.md §7 N2 บันทึกเพิ่ม — (2) งานคู่ขนาดจากอีก session (แชร์ไฟล์ PDFViewer/sidebar เลยรวม commit): dialog ประวัติ F19 แสดงสรุปสิ่งที่เปลี่ยนต่อรอบผ่าน describeAnnotationChange ใหม่ใน utils/annotation-display.ts (เทียบ snapshot ก่อนแก้ v.data กับ state ถัดไป — snapshot รอบใหม่กว่า หรือค่าปัจจุบันจาก realtime lookup สด historyCurrent — สรุปเป็นหน่วยเดียวกับตัวปรับ UI: ความหนา ×10 ความทึบ % fontSize px ชื่อสี preset ย้าย/ปรับขนาดจาก bbox ผลต่างเกิน GEOM_EPS_PCT 0.5% ข้อความ/โน้ต/label) + dialog ขยาย w-96 max-h-[60vh] แถวเปลี่ยน items-start + utils/date-format.ts ใหม่ formatThaiDate วันที่ไทยเต็ม "18 กันยายน พ.ศ. 2569" (+เวลา) แทน toLocaleString("th-TH") ทุกจุดแสดงวันที่ใน UI (หน้ารายการไฟล์ sidebar ประวัติ/รายการ timestamps ใน PDFViewer) กัน server/client locale ไม่ตรงกัน + tests ใหม่/ขยาย (date-format + describeAnnotationChange) — CLAUDE.md sync โครงสร้าง utils — verify: tsc strict ผ่าน + vitest 139/139 ผ่าน
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 43s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 3s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
All checks were successful
Deploy eCourt Note Web to Server / Validate Database Schema (Drizzle) (push) Successful in 3s
Deploy eCourt Note Web to Server / Build Web Docker Image (push) Successful in 43s
Deploy eCourt Note Web to Server / Deploy eCourt Note Web to Server (push) Successful in 3s
Deploy eCourt Note Web to Server / Notify Deployment Status (push) Successful in 1s
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
parent
8ba911a119
commit
e0c78d68fe
11 changed files with 469 additions and 38 deletions
|
|
@ -1,5 +1,14 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import type { AnnotationDTO, AnnotationData, AnnotationType } from "@/db/schema";
|
||||
import type {
|
||||
AnnotationDTO,
|
||||
AnnotationData,
|
||||
AnnotationType,
|
||||
CommentData,
|
||||
DrawData,
|
||||
HighlightData,
|
||||
StampData,
|
||||
TextboxData,
|
||||
} from "@/db/schema";
|
||||
import {
|
||||
DRAW_PENS,
|
||||
STAMP_FONT_RATIO,
|
||||
|
|
@ -8,6 +17,7 @@ import {
|
|||
annotationNote,
|
||||
annotationPreviewText,
|
||||
annotationTypeLabel,
|
||||
describeAnnotationChange,
|
||||
effectiveDrawOpacity,
|
||||
sanitizePdfText,
|
||||
stampFitFontSize,
|
||||
|
|
@ -138,6 +148,94 @@ describe("stampFitFontSize — ย่อฟอนต์ตรายางให
|
|||
});
|
||||
|
||||
|
||||
describe("describeAnnotationChange — สรุปสิ่งที่เปลี่ยนของ version history (F19)", () => {
|
||||
it("HIGHLIGHT เปลี่ยนสี/ความทึบ → ชื่อสีไทยจาก palette + % ตามหน่วย UI", () => {
|
||||
const prev: HighlightData = { text: "ข้อ 3", color: "#ef4444", rects: [], opacity: 0.5 };
|
||||
const next: HighlightData = { text: "ข้อ 3", color: "#3b82f6", rects: [], opacity: 1 };
|
||||
expect(describeAnnotationChange("HIGHLIGHT", prev, next)).toEqual([
|
||||
"สี: แดง → น้ำเงิน",
|
||||
"ความทึบ: 50% → 100%",
|
||||
]);
|
||||
});
|
||||
|
||||
it("สีนอก palette (ปุ่มสีรุ้ง) → แสดง hex ตรง ๆ", () => {
|
||||
const prev: HighlightData = { text: "a", color: "#123456", rects: [] };
|
||||
const next: HighlightData = { text: "a", color: "#abcdef", rects: [] };
|
||||
expect(describeAnnotationChange("HIGHLIGHT", prev, next)).toEqual(["สี: #123456 → #abcdef"]);
|
||||
});
|
||||
|
||||
it("COMMENT แก้ข้อความ + ย้าย pin", () => {
|
||||
const prev: CommentData = { position: { x: 10, y: 20 }, message: "ดูจุดนี้" };
|
||||
const next: CommentData = { position: { x: 40, y: 20 }, message: "ดูจุดนี้ (แก้)" };
|
||||
expect(describeAnnotationChange("COMMENT", prev, next)).toEqual([
|
||||
'แก้ข้อความ: "ดูจุดนี้" → "ดูจุดนี้ (แก้)"',
|
||||
"ย้ายตำแหน่ง",
|
||||
]);
|
||||
});
|
||||
|
||||
it("DRAW ย้ายทั้งเส้น → ย้ายตำแหน่ง / eraser ตัดจุดโดย bbox เดิม → แก้เส้น", () => {
|
||||
const prev: DrawData = {
|
||||
strokeColor: "#ef4444",
|
||||
strokeWidth: 0.4,
|
||||
paths: [
|
||||
{ x: 10, y: 10 },
|
||||
{ x: 20, y: 10 },
|
||||
{ x: 30, y: 10 },
|
||||
{ x: 40, y: 10 },
|
||||
],
|
||||
};
|
||||
const moved: DrawData = { ...prev, paths: prev.paths.map((p) => ({ ...p, y: p.y + 5 })) };
|
||||
expect(describeAnnotationChange("DRAW", prev, moved)).toEqual(["ย้ายตำแหน่ง"]);
|
||||
|
||||
const erased: DrawData = {
|
||||
...prev,
|
||||
paths: [prev.paths[0], prev.paths[1], prev.paths[3]], // ตัดจุดกลางทิ้ง bbox คงเดิม
|
||||
};
|
||||
expect(describeAnnotationChange("DRAW", prev, erased)).toEqual(["แก้เส้น"]);
|
||||
});
|
||||
|
||||
it("DRAW/SHAPE เปลี่ยนความหนา → แสดง ×10 ตามหน่วยของตัวปรับใน UI", () => {
|
||||
const prev: DrawData = { strokeColor: "#ef4444", strokeWidth: 0.4, paths: [{ x: 1, y: 1 }] };
|
||||
const next: DrawData = { ...prev, strokeWidth: 0.8 };
|
||||
expect(describeAnnotationChange("DRAW", prev, next)).toEqual(["ความหนา: 4 → 8"]);
|
||||
});
|
||||
|
||||
it("โน้ต N1: เพิ่ม / ลบ / แก้", () => {
|
||||
const base: DrawData = { strokeColor: "#ef4444", strokeWidth: 0.4, paths: [] };
|
||||
expect(describeAnnotationChange("DRAW", base, { ...base, note: "เหตุผล" })).toEqual([
|
||||
"เพิ่มโน้ต: เหตุผล",
|
||||
]);
|
||||
expect(describeAnnotationChange("DRAW", { ...base, note: "เหตุผล" }, base)).toEqual(["ลบโน้ต"]);
|
||||
expect(
|
||||
describeAnnotationChange("DRAW", { ...base, note: "เหตุผล" }, { ...base, note: "เหตุผลใหม่" }),
|
||||
).toEqual(["แก้โน้ต: เหตุผล → เหตุผลใหม่"]);
|
||||
});
|
||||
|
||||
it("ไม่มีอะไรเปลี่ยน → ว่าง", () => {
|
||||
const same: CommentData = { position: { x: 5, y: 5 }, message: "m" };
|
||||
expect(describeAnnotationChange("COMMENT", same, { ...same })).toEqual([]);
|
||||
});
|
||||
|
||||
it("TEXTBOX แก้ข้อความแล้วกล่อง wrap ใหม่ → ไม่แจ้ง ปรับขนาด ที่เป็นผลพลอง", () => {
|
||||
const prev: TextboxData = {
|
||||
position: { x: 10, y: 10 },
|
||||
text: "สั้น",
|
||||
color: "#111827",
|
||||
fontSize: 2.5,
|
||||
};
|
||||
const next: TextboxData = { ...prev, text: "ข้อความที่ยาวขึ้นมาก" };
|
||||
const changes = describeAnnotationChange("TEXTBOX", prev, next);
|
||||
expect(changes).toContain('แก้ข้อความ: "สั้น" → "ข้อความที่ยาวขึ้นมาก"');
|
||||
expect(changes).not.toContain("ปรับขนาด");
|
||||
});
|
||||
|
||||
it("STAMP เปลี่ยนสีไม่ระบุ (undefined) ต้องเทียบกับ default STAMP_COLOR ก่อน", () => {
|
||||
const prev: StampData = { label: "สำคัญ", position: { x: 10, y: 10 }, scale: 18 };
|
||||
const next: StampData = { ...prev, color: "#b91c1c" }; // = default ไม่ถือว่าเปลี่ยน
|
||||
expect(describeAnnotationChange("STAMP", prev, next)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("sanitizePdfText — ล้างข้อความเพี้ยนจาก textLayer ของ PDF ไทย (glyph ต่อ glyph)", () => {
|
||||
// control char ขยะที่พบจริงใน textLayer ของคำสั่งศาลปกครอง เช่น 04/05/0e/0f/03/12/1b
|
||||
const ctrl = (n: number) => String.fromCharCode(n);
|
||||
|
|
|
|||
26
tests/utils/date-format.test.ts
Normal file
26
tests/utils/date-format.test.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { formatThaiDate } from "@/utils/date-format";
|
||||
|
||||
// สร้าง Date จาก local components แล้ววนผ่าน ISO กลับ — ผล test ไม่ขึ้นกับ
|
||||
// timezone ของเครื่องที่รัน (เว้นช่วง DST transition ทับพอดี ซึ่งไทยไม่มี)
|
||||
describe("formatThaiDate — วันที่ไทยแบบเต็ม พ.ศ. ตามธรรมเนียมเอกสารราชการ", () => {
|
||||
it("ไม่มีเวลา → 18 กันยายน พ.ศ. 2569", () => {
|
||||
const d = new Date(2026, 8, 18, 16, 33, 0);
|
||||
expect(formatThaiDate(d.toISOString())).toBe("18 กันยายน พ.ศ. 2569");
|
||||
});
|
||||
|
||||
it("มีเวลา → 18 กันยายน พ.ศ. 2569 16:33:00 น.", () => {
|
||||
const d = new Date(2026, 8, 18, 16, 33, 0);
|
||||
expect(formatThaiDate(d.toISOString(), true)).toBe("18 กันยายน พ.ศ. 2569 16:33:00 น.");
|
||||
});
|
||||
|
||||
it("วัน/ชั่วโมง/นาที/วินาทีหลักเดียวไม่มีศูนย์นำหน้าที่วัน + ศูนย์นำหน้าที่เวลา", () => {
|
||||
const d = new Date(2026, 10, 5, 9, 5, 3);
|
||||
expect(formatThaiDate(d.toISOString(), true)).toBe("5 พฤศจิกายน พ.ศ. 2569 09:05:03 น.");
|
||||
});
|
||||
|
||||
it("29 กุมภาพันธ์ปีอธิกสุรทิน (2028 → พ.ศ. 2571)", () => {
|
||||
const d = new Date(2028, 1, 29, 23, 59, 59);
|
||||
expect(formatThaiDate(d.toISOString(), true)).toBe("29 กุมภาพันธ์ พ.ศ. 2571 23:59:59 น.");
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue