PRA-only-demo/packages/rpa-core/test/capabilities/excel-common.test.ts

152 lines
8.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { mkdtemp, readFile, readdir, rm } from "node:fs/promises";
import { existsSync } from "node:fs";
import os from "node:os";
import path from "node:path";
import ExcelJS from "exceljs";
import { afterEach, describe, expect, it } from "vitest";
import type { Catalog, CatalogComponent, FlowNode } from "../../src/types";
import type { RunContext } from "../../src/engine/types";
import { getHandler } from "../../src/engine/registry";
import "../../src/capabilities/index";
import { EXCEL_COMMON_IDS } from "../../src/capabilities/excel-common";
const directories: string[] = [];
const context = {} as RunContext;
const component = {} as CatalogComponent;
const node = (componentId: string): FlowNode => ({
id: `excel_common_${componentId}`,
componentId,
position: { x: 0, y: 0 },
values: {},
options: {}
});
async function temporaryWorkbook(): Promise<string> {
const directory = await mkdtemp(path.join(os.tmpdir(), "rpa-excel-common-"));
directories.push(directory);
const filePath = path.join(directory, "common.xlsx");
const workbook = new ExcelJS.Workbook();
const sheet = workbook.addWorksheet("Data");
sheet.addRows([
["name", "score", "group", "parts"],
["Bob", 2, "B", "x,y"],
["Alice", 3, "A", "m,n"],
["Bob", 2, "B", "p,q"]
]);
await workbook.xlsx.writeFile(filePath);
return filePath;
}
async function run(componentId: string, args: Record<string, unknown>): Promise<unknown> {
const handler = getHandler(componentId);
expect(handler, `${componentId} should be registered`).toBeTruthy();
return handler!(context, node(componentId), args, component);
}
async function readWorkbook(filePath: string): Promise<ExcelJS.Workbook> {
const workbook = new ExcelJS.Workbook();
await workbook.xlsx.readFile(filePath);
return workbook;
}
afterEach(async () => {
await Promise.all(directories.splice(0).map((directory) => rm(directory, { recursive: true, force: true })));
});
describe("common Excel capabilities", () => {
it("registers at least 25 real catalog IDs without conflicting with existing modules", async () => {
const catalog = JSON.parse(await readFile("resources/catalog.json", "utf8")) as Catalog;
const catalogIds = new Set(catalog.components.map((item) => item.id));
expect(EXCEL_COMMON_IDS.length).toBeGreaterThanOrEqual(25);
for (const id of EXCEL_COMMON_IDS) {
expect(catalogIds.has(id), `${id} should exist in catalog.json`).toBe(true);
expect(getHandler(id), `${id} should be registered`).toBeTruthy();
}
// 宿主冲突检查是 demo 仓库专有关注点:独立宿主(如对面 backend)无 src/main/capabilities,跳过即可
if (existsSync("src/main/capabilities")) {
const sourceFiles = (await readdir("src/main/capabilities"))
.filter((file) => file.endsWith(".ts") && !file.endsWith(".test.ts") && file !== "excel-common.ts");
const existingSource = (await Promise.all(sourceFiles.map((file) => readFile(path.join("src/main/capabilities", file), "utf8")))).join("\n");
for (const id of EXCEL_COMMON_IDS) expect(existingSource.includes(`"${id}"`), `${id} must not already be registered`).toBe(false);
}
});
it("writes and reads rows, columns, and ranges in a real workbook", async () => {
const filePath = await temporaryWorkbook();
await run("Excel_Cs_WriteRow", { workbook: filePath, sheet: "Data", row: 6, column: "A", contents: ["Carol", 9, "C"], save: 1 });
await run("Excel_Cs_WriteColumn", { workbook: filePath, sheet: "Data", row: 2, column: "E", contents: [10, 20, 30], save: 1 });
await run("Excel_Cs_WriteRangeV2", { workbook: filePath, sheet: "Data", row: 8, column: "A", contents: [["x", 1], ["y", 2]], save: 1 });
await expect(run("Excel_Cs_ReadRow", { workbook: filePath, sheet: "Data", rowIndex: 6 })).resolves.toEqual(["Carol", 9, "C", "", ""]);
await expect(run("Excel_GetColData", { objExcel: filePath, sheet: "Data", cellPos: "E2" })).resolves.toEqual([10, 20, 30, "", "", "", "", ""]);
await expect(run("Excel_Cs_ReadRange", { workbook: filePath, sheet: "Data", startRow: 8, startColumn: "A", lastRow: 9, lastColumn: "B" }))
.resolves.toEqual([["x", 1], ["y", 2]]);
await expect(run("Excel_Cs_ReadColumnCount", { workbook: filePath, sheet: "Data", row: 2 })).resolves.toBe(5);
});
it("finds, replaces, sorts, filters, clears filters, and removes duplicates", async () => {
const filePath = await temporaryWorkbook();
await expect(run("Excel_Cs_Find", { workbook: filePath, sheet: "Data", what: "Alice", matchFirst: 1 }))
.resolves.toEqual(["A3"]);
await run("Excel_Cs_Search", { workbook: filePath, sheet: "Data", mode: "替换", input: "Alice", replace: "Alicia", wholeWord: 1, save: 1 });
await run("Excel_Cs_Sort", { workbook: filePath, sheet: "Data", sortRange: "A1:D4", sortOrder: "2", header: 1, save: 1 });
await run("Excel_Cs_Filter", { workbook: filePath, sheet: "Data", column: "C", criteria: "B", condition: "equal", startRow: 1, endRow: 4, onHeader: 1, save: 1 });
let workbook = await readWorkbook(filePath);
let sheet = workbook.getWorksheet("Data")!;
expect(sheet.getCell("A2").value).toBe("Bob");
expect(sheet.getRow(3).hidden || sheet.getRow(4).hidden).toBe(true);
await run("Excel_Cs_ClearExcelFilter", { workbook: filePath, sheet: "Data", save: 1 });
await run("Excel_Cs_DeleteDuplicates", { workbook: filePath, sheet: "Data", columns: ["A", "B", "C"], header: 1, save: 1 });
workbook = await readWorkbook(filePath);
sheet = workbook.getWorksheet("Data")!;
expect(sheet.getRows(1, sheet.rowCount)!.some((row) => row.hidden)).toBe(false);
expect(sheet.rowCount).toBe(3);
expect(sheet.getColumn("A").values).toContain("Alicia");
});
it("copies, pastes, fills, splits, and deletes real ranges", async () => {
const filePath = await temporaryWorkbook();
await run("Excel_CopyRange", { objExcel: filePath, sheet: "Data", cellPosRange: "A2:B3" });
await run("Excel_Cs_PastRange", { workbook: filePath, sheet: "Data", column: "F", row: 2, save: 1 });
await run("Excel_Cs_AutoFill", { workbook: filePath, sheet: "Data", sourceRange: "B2:B3", destination: "B2:B6", save: 1 });
await run("Excel_Cs_ColumnSplitV2", { workbook: filePath, sheet: "Data", column: "D", destination: "J1", comma: 1, tab: 0, startRow: 2, endRow: 4, save: 1 });
await run("Excel_Cs_DeleteRangeV3", { workbook: filePath, sheet: "Data", excelRng: "C2:C4", save: 1 });
const workbook = await readWorkbook(filePath);
const sheet = workbook.getWorksheet("Data")!;
expect(sheet.getCell("F2").value).toBe("Bob");
expect(sheet.getCell("G3").value).toBe(3);
expect(sheet.getCell("B6").value).toBe(6);
expect(sheet.getCell("J2").value).toBe("x");
expect(sheet.getCell("K2").value).toBe("y");
expect(sheet.getCell("C2").value).toBeNull();
});
it("manages sheets and persists active sheet state", async () => {
const filePath = await temporaryWorkbook();
await run("Excel_CreateSheet", { objExcel: filePath, sheet: "Report", isSave: true });
await expect(run("Excel_Cs_GetName", { workbook: filePath })).resolves.toEqual(["Data", "Report"]);
await expect(run("Excel_Cs_Active", { workbook: filePath, mode: "按名称", sheet: "Report", save: 1 })).resolves.toBe("Report");
await run("Excel_DeleteSheet", { objExcel: filePath, sheet: "Data", isSave: true });
await run("Excel_Cs_Close", { workbook: filePath, save: 1 });
const workbook = await readWorkbook(filePath);
expect(workbook.worksheets.map((sheet) => sheet.name)).toEqual(["Report"]);
});
it("sets and reads cell and range colors", async () => {
const filePath = await temporaryWorkbook();
await run("Excel_Cs_SetCellBackground", { workbook: filePath, sheet: "Data", column: "A", row: 2, color: "#12AB34", save: 1 });
await run("Excel_Cs_SetRangeFontV2", { workbook: filePath, sheet: "Data", range: "A2:B3", color: "#445566", save: 1 });
await expect(run("Excel_Cs_ReadCellBackground", { workbook: filePath, sheet: "Data", column: "A", row: 2 })).resolves.toBe("FF12AB34");
const workbook = await readWorkbook(filePath);
const sheet = workbook.getWorksheet("Data")!;
expect((sheet.getCell("A2").fill as ExcelJS.FillPattern).fgColor?.argb).toBe("FF12AB34");
expect(sheet.getCell("B3").font.color?.argb).toBe("FF445566");
});
});