Initial QOffice implementation

This commit is contained in:
Курнат Андрей
2026-06-01 05:31:05 +03:00
commit 9bf4dd3672
44 changed files with 16854 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
import { describe, expect, it } from "vitest";
import {
cellId,
columnLabel,
columnLabelsForCount,
evaluateCell,
expandRange,
formatCellValueForDisplay,
mergedCellInfo,
normalizeCellRange,
parseCellId,
summarizeColumn,
usedSheetBounds
} from "./spreadsheet";
describe("spreadsheet utilities", () => {
it("expands rectangular ranges", () => {
expect(expandRange("A1:B2")).toEqual(["A1", "B1", "A2", "B2"]);
});
it("supports Excel-style columns beyond H", () => {
expect(columnLabel(0)).toBe("A");
expect(columnLabel(25)).toBe("Z");
expect(columnLabel(26)).toBe("AA");
expect(columnLabelsForCount(28).slice(-2)).toEqual(["AA", "AB"]);
expect(cellId(27, 19)).toBe("AB20");
expect(parseCellId("ab20")).toEqual({ column: 27, row: 19 });
expect(expandRange("Z1:AA2")).toEqual(["Z1", "AA1", "Z2", "AA2"]);
});
it("evaluates aggregate formulas", () => {
const cells = { A1: "2", A2: "3", A3: "=SUM(A1:A2)", A4: "=AVG(A1:A2)" };
expect(evaluateCell("A3", cells)).toBe("5");
expect(evaluateCell("A4", cells)).toBe("2.50");
});
it("evaluates arithmetic formulas with cell references", () => {
const cells = { A1: "10", B1: "5", C1: "=A1*B1+2" };
expect(evaluateCell("C1", cells)).toBe("52");
});
it("evaluates formulas outside the default visible grid", () => {
const cells = { AA20: "7", AB20: "8", AC20: "=SUM(AA20:AB20)", AD20: "=AC20*2" };
expect(evaluateCell("AC20", cells)).toBe("15");
expect(evaluateCell("AD20", cells)).toBe("30");
});
it("reports cycles instead of silently treating them as zero", () => {
const cells = { A1: "=SUM(A1:A2)", A2: "2", B1: "=B1+1" };
expect(evaluateCell("A1", cells)).toBe("#CYCLE");
expect(evaluateCell("B1", cells)).toBe("#CYCLE");
});
it("calculates used sheet bounds from populated cells", () => {
expect(usedSheetBounds({ C3: "x", AA20: "y" })).toEqual({ columnCount: 27, rowCount: 20 });
expect(usedSheetBounds({}, [{ start: "A1", end: "J24" }])).toEqual({ columnCount: 10, rowCount: 24 });
expect(usedSheetBounds({})).toEqual({ columnCount: 8, rowCount: 16 });
});
it("normalizes and locates merged cell ranges", () => {
expect(normalizeCellRange({ start: "c2", end: "A1" })).toEqual({ start: "A1", end: "C2" });
expect(mergedCellInfo("B1", [{ start: "A1", end: "C2" }])).toEqual({
range: { start: "A1", end: "C2" },
isOrigin: false,
columnSpan: 3,
rowSpan: 2
});
expect(mergedCellInfo("A1", [{ start: "A1", end: "C2" }])?.isOrigin).toBe(true);
});
it("uses the final aggregate formula as the column summary when present", () => {
const cells = {
E2: "=SUM(B2:D2)",
E3: "=SUM(B3:D3)",
E4: "=SUM(B4:D4)",
E6: "=SUM(E2:E4)",
B2: "12500",
C2: "14300",
D2: "16800",
B3: "8200",
C3: "9100",
D3: "9900",
B4: "19600",
C4: "21400",
D4: "23800"
};
expect(summarizeColumn(cells, 4)).toBe(135600);
});
it("formats numeric cell display values from XLSX number formats", () => {
expect(formatCellValueForDisplay("44927", { numberFormat: "dd.mm.yyyy" })).toBe("01.01.2023");
expect(formatCellValueForDisplay("44927.5", { numberFormat: "dd.mm.yyyy hh:mm" })).toBe("01.01.2023 12:00");
expect(formatCellValueForDisplay("0.25", { numberFormat: "0%" })).toBe("25%");
expect(formatCellValueForDisplay("1250.5", { numberFormat: "# ##0.00 ₽" })).toBe("1 250,50 ₽");
expect(formatCellValueForDisplay("Текст", { numberFormat: "0.00" })).toBe("Текст");
});
});