Files
buzz-sheet/components/admin/extra-sheet-editor.test.tsx
T
gunshiz 35f9dc40ba
CI / Verify (push) Successful in 2m20s
CI / Build immutable images and deploy (push) Successful in 2m57s
feat : i dont even know what am i doing right now
2026-10-10 04:33:08 +07:00

178 lines
8.8 KiB
TypeScript

// @vitest-environment happy-dom
import { act, useEffect, useState } from "react";
import { createRoot, type Root } from "react-dom/client";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { ExtraSheetEditor } from "./extra-sheet-editor";
import { createExtraSheet, mergeExtraSheetRange, type ExtraSheet } from "@/lib/guides/extra-sheet";
let root: Root;
let container: HTMLDivElement;
let latest: ExtraSheet;
let sequence = 0;
const createId = (prefix: string) => `${prefix}-${++sequence}`;
function Harness({ initial }: { initial: ExtraSheet }) {
const [sheet, setSheet] = useState(initial);
useEffect(() => { latest = sheet; }, [sheet]);
return <ExtraSheetEditor value={sheet} createId={createId} onChange={setSheet} onRemove={() => {}} />;
}
async function mouseDown(element: Element, shiftKey = false) {
await act(async () => {
element.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, shiftKey }));
});
}
async function click(element: Element) {
await mouseDown(element);
await act(async () => { element.dispatchEvent(new MouseEvent("click", { bubbles: true })); });
}
function cell(row: number, column: number) {
return container.querySelector<HTMLTableCellElement>(`[data-sheet-row="${row}"][data-sheet-column="${column}"]`)!;
}
function button(label: string) {
return Array.from(container.querySelectorAll("button")).find((item) => item.getAttribute("aria-label") === label || item.textContent?.trim() === label)!;
}
async function typeText(element: HTMLTextAreaElement, text: string) {
await act(async () => {
Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, "value")!.set!.call(element, text);
element.dispatchEvent(new Event("input", { bubbles: true }));
});
}
async function mount(initial = createExtraSheet(createId)) {
await act(async () => { root.render(<Harness initial={initial} />); });
}
beforeEach(() => {
sequence = 0;
vi.stubGlobal("IS_REACT_ACT_ENVIRONMENT", true);
// Supply grid measurements; happy-dom does not run browser layout.
vi.spyOn(HTMLElement.prototype, "offsetWidth", "get").mockImplementation(function (this: HTMLElement) { return this.tagName === "TD" ? 140 : 1000; });
vi.spyOn(HTMLElement.prototype, "offsetHeight", "get").mockImplementation(function (this: HTMLElement) { return this.tagName === "TD" ? 36 : 180; });
vi.spyOn(HTMLElement.prototype, "offsetTop", "get").mockImplementation(function (this: HTMLElement) { return (Number(this.dataset.sheetRow ?? -1) + 1) * 36; });
vi.spyOn(HTMLElement.prototype, "offsetLeft", "get").mockImplementation(function (this: HTMLElement) { return (Number(this.dataset.sheetColumn ?? -1) + 1) * 140; });
container = document.createElement("div");
document.body.append(container);
root = createRoot(container);
});
afterEach(async () => {
await act(async () => { root.unmount(); });
container.remove();
vi.restoreAllMocks();
vi.unstubAllGlobals();
});
describe("extra spreadsheet interactions", () => {
it("saves inline text when another cell is clicked without Enter", async () => {
await mount();
await mouseDown(cell(0, 0));
await click(container.querySelector(".Spreadsheet__active-cell--view")!);
const input = container.querySelector<HTMLTextAreaElement>(".Spreadsheet__data-editor textarea")!;
expect(input).not.toBeNull();
await typeText(input, "Keep this text");
await mouseDown(cell(1, 1));
expect(latest.rows[0].cells[latest.columns[0].id]).toBe("<p>Keep this text</p>");
expect(latest.rows[1].cells[latest.columns[1].id]).toBe("");
});
it("saves inline text on blur and cancels Escape", async () => {
await mount();
await mouseDown(cell(0, 0));
await click(container.querySelector(".Spreadsheet__active-cell--view")!);
let input = container.querySelector<HTMLTextAreaElement>(".Spreadsheet__data-editor textarea")!;
await typeText(input, "Saved on blur");
await act(async () => { input.blur(); });
expect(latest.rows[0].cells[latest.columns[0].id]).toBe("<p>Saved on blur</p>");
await mouseDown(cell(0, 1));
await click(container.querySelector(".Spreadsheet__active-cell--view")!);
input = container.querySelector<HTMLTextAreaElement>(".Spreadsheet__data-editor textarea")!;
await typeText(input, "Discard");
await act(async () => { input.dispatchEvent(new KeyboardEvent("keydown", { key: "Escape", bubbles: true })); });
expect(latest.rows[0].cells[latest.columns[1].id]).toBe("");
});
it.each(["เลือกสีพื้นหลังเซลล์", "เลือกสีตัวอักษร"])("keeps %s on the selection where its picker opened", async (label) => {
const sheet = createExtraSheet(createId);
sheet.rows[0].cells[sheet.columns[0].id] = "<p>First</p>";
sheet.rows[1].cells[sheet.columns[1].id] = "<p>Second</p>";
await mount(sheet);
await mouseDown(cell(0, 0));
const picker = container.querySelector<HTMLInputElement>(`input[aria-label="${label}"]`)!;
await act(async () => { picker.dispatchEvent(new Event("pointerdown", { bubbles: true })); });
await mouseDown(cell(1, 1));
await act(async () => {
Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, "value")!.set!.call(picker, "#123456");
picker.dispatchEvent(new Event("change", { bubbles: true }));
});
if (label === "เลือกสีพื้นหลังเซลล์") {
expect(latest.rows[0].backgrounds[latest.columns[0].id]).toBe("#123456");
expect(latest.rows[1].backgrounds).toEqual({});
} else {
expect(latest.rows[0].cells[latest.columns[0].id]).toContain("#123456");
expect(latest.rows[1].cells[latest.columns[1].id]).toBe("<p>Second</p>");
}
});
it("merges a selection, supports undo/redo, and unmerges", async () => {
const sheet = createExtraSheet(createId);
sheet.rows[0].cells[sheet.columns[0].id] = "<p>First</p>";
sheet.rows[1].cells[sheet.columns[1].id] = "<p>Last</p>";
await mount(sheet);
await mouseDown(cell(0, 0));
await mouseDown(cell(1, 1), true);
expect(button("ผสานเซลล์").disabled).toBe(false);
await click(button("ผสานเซลล์"));
expect(latest.merges).toHaveLength(1);
expect(latest.rows[0].cells[latest.columns[0].id]).toBe("<p>First</p><p>Last</p>");
expect(cell(0, 0).getAttribute("aria-rowspan")).toBe("2");
expect(cell(1, 1).getAttribute("aria-hidden")).toBe("true");
await click(button("ย้อนกลับการแก้ไขตาราง"));
expect(latest.merges).toBeUndefined();
expect(latest.rows[1].cells[latest.columns[1].id]).toBe("<p>Last</p>");
await click(button("ทำซ้ำการแก้ไขตาราง"));
expect(latest.merges).toHaveLength(1);
await click(button("ยกเลิกผสาน"));
expect(latest.merges).toEqual([]);
});
it.each(["Enter", "Tab"])("commits merged inline edits and skips coverage with %s", async (key) => {
const merged = mergeExtraSheetRange(createExtraSheet(createId), { start: { row: 0, column: 0 }, end: { row: 1, column: 1 } });
await mount(merged);
await mouseDown(cell(0, 0));
await click(container.querySelector(".Spreadsheet__active-cell--view")!);
const input = container.querySelector<HTMLTextAreaElement>(".Spreadsheet__data-editor textarea")!;
await typeText(input, "Committed with key");
await act(async () => {
input.dispatchEvent(new KeyboardEvent("keydown", { key, bubbles: true }));
await new Promise((resolve) => requestAnimationFrame(resolve));
});
expect(latest.rows[0].cells[latest.columns[0].id]).toBe("<p>Committed with key</p>");
const expected = key === "Enter" ? cell(2, 0) : cell(0, 2);
const overlay = container.querySelector<HTMLElement>(".Spreadsheet__active-cell--view")!;
expect(overlay.style.top).toBe(`${expected.offsetTop}px`);
expect(overlay.style.left).toBe(`${expected.offsetLeft}px`);
expect(document.activeElement).toBe(overlay);
});
it("activates merge anchors, skips covered cells, and persists middle alignment", async () => {
const merged = mergeExtraSheetRange(createExtraSheet(createId), { start: { row: 0, column: 0 }, end: { row: 1, column: 1 } });
await mount(merged);
await mouseDown(cell(1, 1));
await click(button("กึ่งกลางแนวตั้ง"));
expect(latest.rows[0].verticalAlignments[latest.columns[0].id]).toBe("middle");
expect(cell(0, 0).querySelector<HTMLElement>(".extra-sheet-merged-cell")?.style.justifyContent).toBe("center");
await act(async () => {
cell(0, 0).dispatchEvent(new KeyboardEvent("keydown", { key: "ArrowRight", bubbles: true }));
await new Promise((resolve) => requestAnimationFrame(resolve));
});
expect(container.querySelector(".Spreadsheet__active-cell")?.getAttribute("style")).toContain("left: 420px");
});
});