// @vitest-environment jsdom // เทสต์ serialize/segmentsToHtml ของ editor กล่องข้อความ (utils/textbox-editor.ts) // ที่ต้องมี DOM จริง — แยกจากชุด pure utils (environment node ตาม vitest.config.ts) // ด้วย pragma ต่อไฟล์ เหมือน tests/hooks/useCustomSelection-dom.test.ts // พฤติกรรม editor จริง (keydown/execCommand/gesture) ยืนยันด้วย Playwright แทน import { describe, expect, it } from "vitest"; import { segmentsToHtml, serializeEditor } from "@/utils/textbox-editor"; /** div root พร้อม innerHTML ตามที่ contentEditable ค้างไว้ตอน blur */ function mount(html: string): HTMLElement { const el = document.createElement("div"); el.innerHTML = html; return el; } describe("serializeEditor — ขึ้นบรรทัดใหม่", () => { it("โครงสร้าง Enter ของ Chrome text→div ต่อท้าย → ได้ newline คั่น (เดิมหายเป็น abcdef)", () => { const r = serializeEditor(mount("abc
def
")); expect(r.text).toBe("abc\ndef"); }); it("หลายบรรทัดเป็น div เรียงกันทั้งก้อน → คั่นครบทุกบรรทัด", () => { const r = serializeEditor(mount("
a
b
c
")); expect(r.text).toBe("a\nb\nc"); }); it("
(insertLineBreak ของ Enter ที่เราดักเอง) → newline เหมือนกัน", () => { const r = serializeEditor(mount("abc
def")); expect(r.text).toBe("abc\ndef"); }); it("
ตามด้วย
ติดกัน → newline เดียว ไม่ซ้ำสอง", () => { const r = serializeEditor(mount("a
b
")); expect(r.text).toBe("a\nb"); }); it("

ก็เป็น block คั่นบรรทัดเหมือน

", () => { const r = serializeEditor(mount("abc

def

")); expect(r.text).toBe("abc\ndef"); }); }); describe("serializeEditor — ตัวหนาเป็น segments", () => { it(" กลางข้อความ → 3 segments ปกติ/หนา/ปกติ + text ต่อกันไม่มีช่องว่าง", () => { const r = serializeEditor(mount("aboldc")); expect(r.text).toBe("aboldc"); expect(r.segments).toEqual([ { text: "a" }, { text: "bold", bold: true }, { text: "c" }, ]); }); it("segment ติดกันสไตล์เดียวกัน (รวมข้าม สองอัน) → merge เป็น segment เดียว", () => { const r = serializeEditor(mount("ab")); expect(r.segments).toEqual([{ text: "ab", bold: true }]); }); it("newline ที่รอยต่อ block → segment ปกติของตัวเอง (newline ไม่มี glyph จึงไม่กระทบการแสดงผล)", () => { const r = serializeEditor(mount("a
b
")); expect(r.text).toBe("a\nb"); expect(r.segments).toEqual([ { text: "a", bold: true }, { text: "\n" }, { text: "b", bold: true }, ]); }); }); describe("serializeEditor — whitespace ท้าย", () => { it("
ปิดท้ายที่ browser เติมให้ contentEditable → ไม่เหลือ newline ค้างใน DB", () => { const r = serializeEditor(mount("hello
")); expect(r.text).toBe("hello"); expect(r.segments).toEqual([{ text: "hello" }]); }); it("newline ท้ายหลายตัว + space ตัดหมด และตัดจาก segments ให้ text กับ segments ตรงกัน", () => { const r = serializeEditor(mount("ab

")); expect(r.text).toBe("ab"); expect(r.segments).toEqual([{ text: "a" }, { text: "b", bold: true }]); }); it("ก้อนว่างทั้งหมด (แค่
) → text ว่าง + segments ว่าง", () => { const r = serializeEditor(mount("
")); expect(r.text).toBe(""); expect(r.segments).toEqual([]); }); it("space หน้าไม่ถูกตัด (เว้นวรรคตั้งใจยังเก็บ)", () => { const r = serializeEditor(mount(" a")); expect(r.text).toBe(" a"); }); }); describe("segmentsToHtml", () => { it("escape HTML ของข้อความ + แปลง \\n เป็น
", () => { expect(segmentsToHtml("a&x\ny")).toBe("a<b>&x
y"); }); it("segments bold → ตามส่วน", () => { expect( segmentsToHtml("ab", [{ text: "a" }, { text: "b", bold: true }]), ).toBe("ab"); }); it("round-trip: segmentsToHtml → DOM → serializeEditor ได้ text/segments เดิม", () => { const text = "หนา\nปกติ\nธรรมดา"; const segments = [ { text: "หนา", bold: true }, { text: "\nปกติ\nธรรมดา" }, ]; const html = segmentsToHtml(text, segments); expect(serializeEditor(mount(html))).toEqual({ text, segments }); }); });