import { describe, expect, it } from "vitest"; import { addExtraSheetColumn, addExtraSheetRow, createExtraSheet, deleteExtraSheetColumn, deleteExtraSheetRow, extraSheetSchema, extraSheetToMatrix, matrixToExtraSheet, moveExtraSheetColumn, moveExtraSheetRow, normalizeExtraSheetRichText, styleExtraSheetCells, updateExtraSheetCell, updateExtraSheetCells, type ExtraSheet, } from "@/lib/guides/extra-sheet"; function idFactory() { let index = 0; return (prefix: string) => `${prefix}-${++index}`; } describe("Extra sheet", () => { it("creates a valid two-by-two sheet", () => { const sheet = createExtraSheet(idFactory()); expect(extraSheetSchema.safeParse(sheet).success).toBe(true); expect(sheet.columns.map((column) => column.label)).toEqual(["คอลัมน์ 1", "คอลัมน์ 2"]); expect(extraSheetToMatrix(sheet)).toEqual([ [{ value: "", backgroundColor: undefined }, { value: "", backgroundColor: undefined }], [{ value: "", backgroundColor: undefined }, { value: "", backgroundColor: undefined }], ]); }); it("preserves cell identity while rows and columns move", () => { const createId = idFactory(); const sheet = createExtraSheet(createId); const firstColumnId = sheet.columns[0].id; const secondColumnId = sheet.columns[1].id; sheet.rows[0].cells[firstColumnId] = "A"; sheet.rows[0].cells[secondColumnId] = "B"; sheet.rows[1].cells[firstColumnId] = "C"; const moved = moveExtraSheetRow(moveExtraSheetColumn(sheet, 0, 1), 0, 1); expect(extraSheetToMatrix(moved)).toEqual([ [{ value: "", backgroundColor: undefined }, { value: "C", backgroundColor: undefined }], [{ value: "B", backgroundColor: undefined }, { value: "A", backgroundColor: undefined }], ]); }); it("adds and removes rows and columns without orphaned cells", () => { const createId = idFactory(); const initial = createExtraSheet(createId); const columnId = createId("column"); const expanded = addExtraSheetRow( addExtraSheetColumn(initial, "Notes", columnId), createId("row"), ); expect(expanded.rows).toHaveLength(3); expect(expanded.rows.every((row) => row.cells[columnId] === "")).toBe(true); const reduced = deleteExtraSheetColumn(expanded, 2); expect(reduced.rows.every((row) => !(columnId in row.cells))).toBe(true); const secondRowId = reduced.rows[1].id; const withoutFirstRow = deleteExtraSheetRow(reduced, 0); expect(withoutFirstRow.rows).toHaveLength(2); expect(withoutFirstRow.rows[0].id).toBe(secondRowId); }); it("expands rows and columns when a pasted matrix is larger", () => { const createId = idFactory(); const initial = createExtraSheet(createId); const expanded = matrixToExtraSheet(initial, [ [{ value: "1" }, { value: "2" }, { value: "3" }], [{ value: "4" }, { value: "5" }, { value: "6" }], [{ value: "7" }, { value: "8" }, { value: "=1+1" }], ], createId); expect(expanded.columns).toHaveLength(3); expect(expanded.rows).toHaveLength(3); expect(extraSheetToMatrix(expanded)[2][2]?.value).toBe("=1+1"); expect(extraSheetSchema.safeParse(expanded).success).toBe(true); }); it("rejects duplicate labels and cells for missing columns", () => { const sheet = createExtraSheet(idFactory()); sheet.columns[1].label = sheet.columns[0].label.toUpperCase(); sheet.rows[0].cells.missing = "orphan"; const result = extraSheetSchema.safeParse(sheet); expect(result.success).toBe(false); if (!result.success) { expect(result.error.issues.map((issue) => issue.message)).toContain("ชื่อคอลัมน์ต้องไม่ซ้ำกัน"); expect(result.error.issues.map((issue) => issue.message)).toContain("ไม่พบคอลัมน์ของเซลล์นี้"); } }); it("updates and sanitizes rich-text cells while preserving legacy text", () => { const initial = createExtraSheet(idFactory()); const updated = updateExtraSheetCell(initial, 0, 0, "legacy\ntext"); const columnId = updated.columns[0].id; expect(normalizeExtraSheetRichText(updated).rows[0].cells[columnId]).toBe( "

legacy
text

", ); const unsafe = updateExtraSheetCell(updated, 0, 0, "

safe

"); expect(normalizeExtraSheetRichText(unsafe).rows[0].cells[columnId]).toBe( "

safe

", ); }); it("updates rich text and backgrounds across a cell selection", () => { const sheet = createExtraSheet(idFactory()); const updated = updateExtraSheetCells(sheet, [ { row: 0, column: 0 }, { row: 1, column: 1 }, ], { value: "

Shared

", backgroundColor: "#16A34A" }); expect(extraSheetToMatrix(updated)).toEqual([ [{ value: "

Shared

", backgroundColor: "#16A34A" }, { value: "", backgroundColor: undefined }], [{ value: "", backgroundColor: undefined }, { value: "

Shared

", backgroundColor: "#16A34A" }], ]); expect(extraSheetSchema.safeParse(updated).success).toBe(true); expect(updateExtraSheetCells(updated, [{ row: 0, column: 0 }], { backgroundColor: null }) .rows[0].backgrounds).toEqual({}); }); it("styles selected cells without replacing their individual text", () => { const sheet = createExtraSheet(idFactory()); const firstColumn = sheet.columns[0].id; const secondColumn = sheet.columns[1].id; sheet.rows[0].cells[firstColumn] = "

Alpha

"; sheet.rows[1].cells[secondColumn] = "

Beta

"; const styled = styleExtraSheetCells(sheet, [ { row: 0, column: 0 }, { row: 1, column: 1 }, ], { bold: true }); expect(styled.rows[0].cells[firstColumn]).toBe("

Alpha

"); expect(styled.rows[1].cells[secondColumn]).toBe("

Beta

"); expect(styled.rows[0].cells[secondColumn]).toBe(""); }); it("renders legacy sheets that do not have background metadata yet", () => { const sheet = createExtraSheet(idFactory()); const legacy = { ...sheet, rows: sheet.rows.map((row) => ({ id: row.id, cells: row.cells })), } as ExtraSheet; expect(extraSheetToMatrix(legacy)[0][0]).toEqual({ value: "", backgroundColor: undefined, }); }); });