// @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("ab
"));
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 });
});
});