j
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
extraSheetSchema,
|
||||
extraSheetRangeToTsv,
|
||||
extraSheetToMatrix,
|
||||
extraSheetUsedRange,
|
||||
matrixToExtraSheet,
|
||||
moveExtraSheetColumn,
|
||||
moveExtraSheetRow,
|
||||
@@ -29,14 +30,17 @@ function idFactory() {
|
||||
}
|
||||
|
||||
describe("Extra sheet", () => {
|
||||
it("creates a valid two-by-two sheet", () => {
|
||||
it("creates a valid four-by-six 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 }],
|
||||
]);
|
||||
expect(sheet.columns.map((column) => column.label)).toEqual(
|
||||
Array.from({ length: 6 }, (_, index) => `คอลัมน์ ${index + 1}`),
|
||||
);
|
||||
expect(extraSheetToMatrix(sheet)).toEqual(
|
||||
Array.from({ length: 4 }, () =>
|
||||
Array.from({ length: 6 }, () => ({ value: "", backgroundColor: undefined })),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
it("preserves cell identity while rows and columns move", () => {
|
||||
@@ -49,10 +53,11 @@ describe("Extra sheet", () => {
|
||||
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 }],
|
||||
]);
|
||||
const matrix = extraSheetToMatrix(moved);
|
||||
expect(matrix).toHaveLength(4);
|
||||
expect(matrix[0]).toHaveLength(6);
|
||||
expect(matrix[0].slice(0, 2).map((cell) => cell?.value)).toEqual(["", "C"]);
|
||||
expect(matrix[1].slice(0, 2).map((cell) => cell?.value)).toEqual(["B", "A"]);
|
||||
});
|
||||
|
||||
it("adds and removes rows and columns without orphaned cells", () => {
|
||||
@@ -63,14 +68,14 @@ describe("Extra sheet", () => {
|
||||
addExtraSheetColumn(initial, "Notes", columnId),
|
||||
createId("row"),
|
||||
);
|
||||
expect(expanded.rows).toHaveLength(3);
|
||||
expect(expanded.rows).toHaveLength(5);
|
||||
expect(expanded.rows.every((row) => row.cells[columnId] === "")).toBe(true);
|
||||
|
||||
const reduced = deleteExtraSheetColumn(expanded, 2);
|
||||
const reduced = deleteExtraSheetColumn(expanded, expanded.columns.length - 1);
|
||||
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).toHaveLength(4);
|
||||
expect(withoutFirstRow.rows[0].id).toBe(secondRowId);
|
||||
});
|
||||
|
||||
@@ -78,14 +83,16 @@ describe("Extra sheet", () => {
|
||||
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" }],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
[],
|
||||
Array.from({ length: 9 }, (_, index) => ({ value: index === 8 ? "=1+1" : "" })),
|
||||
], createId);
|
||||
|
||||
expect(expanded.columns).toHaveLength(3);
|
||||
expect(expanded.rows).toHaveLength(3);
|
||||
expect(extraSheetToMatrix(expanded)[2][2]?.value).toBe("=1+1");
|
||||
expect(expanded.columns).toHaveLength(9);
|
||||
expect(expanded.rows).toHaveLength(5);
|
||||
expect(extraSheetToMatrix(expanded)[4][8]?.value).toBe("=1+1");
|
||||
expect(extraSheetSchema.safeParse(expanded).success).toBe(true);
|
||||
});
|
||||
|
||||
@@ -101,6 +108,23 @@ describe("Extra sheet", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("returns only the used display range", () => {
|
||||
const sheet = createExtraSheet(idFactory());
|
||||
expect(extraSheetUsedRange(sheet)).toBeNull();
|
||||
|
||||
sheet.rows[1].cells[sheet.columns[2].id] = "<p>Start</p>";
|
||||
sheet.rows[3].backgrounds[sheet.columns[4].id] = "#123456";
|
||||
const used = extraSheetUsedRange(sheet);
|
||||
|
||||
expect(used?.columns.map((column) => column.label)).toEqual([
|
||||
"คอลัมน์ 3",
|
||||
"คอลัมน์ 4",
|
||||
"คอลัมน์ 5",
|
||||
]);
|
||||
expect(used?.rows).toHaveLength(3);
|
||||
expect(used && extraSheetSchema.safeParse(used).success).toBe(true);
|
||||
});
|
||||
|
||||
it("updates and sanitizes rich-text cells while preserving legacy text", () => {
|
||||
const initial = createExtraSheet(idFactory());
|
||||
const updated = updateExtraSheetCell(initial, 0, 0, "legacy\ntext");
|
||||
@@ -121,10 +145,11 @@ describe("Extra sheet", () => {
|
||||
{ row: 1, column: 1 },
|
||||
], { value: "<p>Shared</p>", backgroundColor: "#16A34A" });
|
||||
|
||||
expect(extraSheetToMatrix(updated)).toEqual([
|
||||
[{ value: "<p>Shared</p>", backgroundColor: "#16A34A" }, { value: "", backgroundColor: undefined }],
|
||||
[{ value: "", backgroundColor: undefined }, { value: "<p>Shared</p>", backgroundColor: "#16A34A" }],
|
||||
]);
|
||||
const matrix = extraSheetToMatrix(updated);
|
||||
expect(matrix[0][0]).toMatchObject({ value: "<p>Shared</p>", backgroundColor: "#16A34A" });
|
||||
expect(matrix[1][1]).toMatchObject({ value: "<p>Shared</p>", backgroundColor: "#16A34A" });
|
||||
expect(matrix[0][1]).toMatchObject({ value: "", backgroundColor: undefined });
|
||||
expect(matrix[1][0]).toMatchObject({ value: "", backgroundColor: undefined });
|
||||
expect(extraSheetSchema.safeParse(updated).success).toBe(true);
|
||||
expect(updateExtraSheetCells(updated, [{ row: 0, column: 0 }], { backgroundColor: null })
|
||||
.rows[0].backgrounds).toEqual({});
|
||||
@@ -204,10 +229,10 @@ describe("Extra sheet", () => {
|
||||
expect(copied).toBe('"Alpha\nBeta"');
|
||||
expect(parseExtraSheetTsv('"Alpha\nBeta"\tGamma')).toEqual([["Alpha\nBeta", "Gamma"]]);
|
||||
|
||||
const pasted = pasteExtraSheetText(sheet, { row: 2, column: 2 }, '"Alpha\nBeta"\tGamma', createId);
|
||||
expect(pasted.rows).toHaveLength(3);
|
||||
expect(pasted.columns).toHaveLength(4);
|
||||
expect(pasted.rows[2].cells[pasted.columns[2].id]).toBe("<p>Alpha<br>Beta</p>");
|
||||
expect(pasted.rows[2].cells[pasted.columns[3].id]).toBe("<p>Gamma</p>");
|
||||
const pasted = pasteExtraSheetText(sheet, { row: 2, column: 7 }, '"Alpha\nBeta"\tGamma', createId);
|
||||
expect(pasted.rows).toHaveLength(4);
|
||||
expect(pasted.columns).toHaveLength(9);
|
||||
expect(pasted.rows[2].cells[pasted.columns[7].id]).toBe("<p>Alpha<br>Beta</p>");
|
||||
expect(pasted.rows[2].cells[pasted.columns[8].id]).toBe("<p>Gamma</p>");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -68,6 +68,40 @@ export type ExtraSheetCell = {
|
||||
export type ExtraSheetMatrix = Array<Array<ExtraSheetCell | undefined>>;
|
||||
type SheetPoint = { row: number; column: number };
|
||||
|
||||
export function extraSheetUsedRange(sheet: ExtraSheet): ExtraSheet | null {
|
||||
const usedPoints = sheet.rows.flatMap((row, rowIndex) =>
|
||||
sheet.columns.flatMap((column, columnIndex) => {
|
||||
const used = extraRichTextToPlainText(row.cells[column.id] ?? "").trim()
|
||||
|| row.backgrounds?.[column.id]
|
||||
|| row.alignments?.[column.id];
|
||||
return used ? [{ row: rowIndex, column: columnIndex }] : [];
|
||||
}),
|
||||
);
|
||||
if (!usedPoints.length) return null;
|
||||
|
||||
const rowIndexes = usedPoints.map((point) => point.row);
|
||||
const columnIndexes = usedPoints.map((point) => point.column);
|
||||
const firstRow = Math.min(...rowIndexes);
|
||||
const lastRow = Math.max(...rowIndexes);
|
||||
const firstColumn = Math.min(...columnIndexes);
|
||||
const lastColumn = Math.max(...columnIndexes);
|
||||
|
||||
const columns = sheet.columns.slice(firstColumn, lastColumn + 1);
|
||||
return {
|
||||
columns,
|
||||
rows: sheet.rows.slice(firstRow, lastRow + 1).map((row) => ({
|
||||
...row,
|
||||
cells: Object.fromEntries(columns.map((column) => [column.id, row.cells[column.id] ?? ""])),
|
||||
backgrounds: Object.fromEntries(columns.flatMap((column) =>
|
||||
row.backgrounds?.[column.id] ? [[column.id, row.backgrounds[column.id]]] : [],
|
||||
)),
|
||||
alignments: Object.fromEntries(columns.flatMap((column) =>
|
||||
row.alignments?.[column.id] ? [[column.id, row.alignments[column.id]]] : [],
|
||||
)),
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function nextExtraSheetColumnLabel(columns: ExtraSheet["columns"]): string {
|
||||
const labels = new Set(columns.map((column) => column.label.toLocaleLowerCase()));
|
||||
let index = columns.length + 1;
|
||||
@@ -76,14 +110,14 @@ export function nextExtraSheetColumnLabel(columns: ExtraSheet["columns"]): strin
|
||||
}
|
||||
|
||||
export function createExtraSheet(createId: (prefix: string) => string): ExtraSheet {
|
||||
const columns = [1, 2].map((index) => ({
|
||||
const columns = Array.from({ length: 6 }, (_, index) => ({
|
||||
id: createId("extra-sheet-column"),
|
||||
label: `คอลัมน์ ${index}`,
|
||||
label: `คอลัมน์ ${index + 1}`,
|
||||
width: DEFAULT_EXTRA_SHEET_COLUMN_WIDTH,
|
||||
}));
|
||||
return {
|
||||
columns,
|
||||
rows: [1, 2].map(() => ({
|
||||
rows: Array.from({ length: 4 }, () => ({
|
||||
id: createId("extra-sheet-row"),
|
||||
cells: Object.fromEntries(columns.map((column) => [column.id, ""])),
|
||||
backgrounds: {},
|
||||
|
||||
Reference in New Issue
Block a user