Files
16gagent/test/electron-builder-agent.test.mjs
T
2026-06-06 10:40:48 +08:00

454 lines
17 KiB
JavaScript
Raw 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.
/**
* Electron Builder Agent Test — SF-06
*
* 验证 Electron Wrapper 生成器的所有关键功能。
*/
import { describe, it, before } from "node:test";
import assert from "node:assert/strict";
import { resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { existsSync, rmSync, readFileSync } from "node:fs";
const __dirname = resolve(fileURLToPath(new URL(".", import.meta.url)));
const WORKSPACE = resolve(__dirname, "..");
let buildElectron, writeElectron;
before(async () => {
const mod = await import("../scripts/electron-builder-agent.mjs");
buildElectron = mod.buildElectron;
writeElectron = mod.writeElectron;
});
// ═══════════════════════════════════════════════════════
// 1 — Desktop Project 生成
// ═══════════════════════════════════════════════════════
describe("1 — Desktop Project 结构生成", () => {
let result;
before(() => {
result = buildElectron({ projectName: "PetCare" });
});
it("生成成功(无错误)", () => {
assert.ok(!result.error, `Unexpected error: ${result.error}`);
assert.ok(result.stats.totalFiles >= 8, `Expected >= 8 files, got ${result.stats.totalFiles}`);
});
it("包含 electron/ 目录下的所有核心文件", () => {
assert.ok(result.files["electron/main.ts"], "Missing main.ts");
assert.ok(result.files["electron/preload.ts"], "Missing preload.ts");
assert.ok(result.files["electron/ipc.ts"], "Missing ipc.ts");
assert.ok(result.files["electron/tray.ts"], "Missing tray.ts");
assert.ok(result.files["electron/updater.ts"], "Missing updater.ts");
assert.ok(result.files["electron/utils.ts"], "Missing utils.ts");
});
it("包含 assets/icon.png", () => {
assert.ok(result.files["assets/icon.png"], "Missing icon.png");
assert.ok(result.files["assets/icon.png"].length > 0, "Empty icon.png");
});
});
// ═══════════════════════════════════════════════════════
// 2 — main.ts 内容验证
// ═══════════════════════════════════════════════════════
describe("2 — main.ts 内容验证", () => {
let result;
before(() => {
result = buildElectron({ projectName: "PetCare" });
});
it("包含 BrowserWindow", () => {
assert.ok(result.files["electron/main.ts"].includes("BrowserWindow"));
});
it("包含 Menu", () => {
assert.ok(result.files["electron/main.ts"].includes("Menu"));
});
it("包含 Tray setup", () => {
assert.ok(result.files["electron/main.ts"].includes("setupTray"));
});
it("包含 IPC setup", () => {
assert.ok(result.files["electron/main.ts"].includes("setupIPC"));
});
it("包含 Single Instance Lock", () => {
assert.ok(result.files["electron/main.ts"].includes("checkSingleInstance"));
assert.ok(result.files["electron/utils.ts"].includes("requestSingleInstanceLock"));
});
it("包含 Auto Start 配置", () => {
assert.ok(result.files["electron/main.ts"].includes("activate"));
});
it("包含 contextIsolation: true", () => {
assert.ok(result.files["electron/main.ts"].includes("contextIsolation: true"));
});
it("包含 nodeIntegration: false", () => {
assert.ok(result.files["electron/main.ts"].includes("nodeIntegration: false"));
});
it("开发环境加载 localhost URL", () => {
assert.ok(result.files["electron/main.ts"].includes("localhost"));
});
it("生产环境加载 file 或 resources 路径", () => {
const main = result.files["electron/main.ts"];
assert.ok(main.includes("resourcesPath") || main.includes("loadFile"));
});
it("包含外部链接处理", () => {
assert.ok(result.files["electron/main.ts"].includes("setWindowOpenHandler"));
});
it("包含 GPU crash 处理", () => {
assert.ok(result.files["electron/main.ts"].includes("gpu-process-crashed"));
});
it("包含 API server 生产环境启动", () => {
assert.ok(result.files["electron/main.ts"].includes("startApiServer"));
});
});
// ═══════════════════════════════════════════════════════
// 3 — preload.ts 内容验证
// ═══════════════════════════════════════════════════════
describe("3 — preload.ts 内容验证", () => {
let result;
before(() => {
result = buildElectron({ projectName: "CRM" });
});
it("使用 contextBridge", () => {
assert.ok(result.files["electron/preload.ts"].includes("contextBridge"));
});
it("使用 ipcRenderer", () => {
assert.ok(result.files["electron/preload.ts"].includes("ipcRenderer"));
});
it("暴露 electronAPI", () => {
assert.ok(result.files["electron/preload.ts"].includes("electronAPI"));
});
it("包含窗口控制 API", () => {
const preload = result.files["electron/preload.ts"];
assert.ok(preload.includes("minimizeWindow"));
assert.ok(preload.includes("maximizeWindow"));
assert.ok(preload.includes("closeWindow"));
});
it("包含文件对话框 API", () => {
const preload = result.files["electron/preload.ts"];
assert.ok(preload.includes("openFile"));
assert.ok(preload.includes("saveFile"));
});
it("包含通知 API", () => {
assert.ok(result.files["electron/preload.ts"].includes("showNotification"));
});
it("包含持久化存储 API", () => {
const preload = result.files["electron/preload.ts"];
assert.ok(preload.includes("storeGet"));
assert.ok(preload.includes("storeSet"));
assert.ok(preload.includes("storeDelete"));
});
it("包含更新 API", () => {
const preload = result.files["electron/preload.ts"];
assert.ok(preload.includes("checkForUpdates"));
assert.ok(preload.includes("downloadUpdate"));
assert.ok(preload.includes("quitAndInstall"));
});
it("包含 TypeScript 类型声明", () => {
assert.ok(result.files["electron/preload.ts"].includes("interface ElectronAPI"));
assert.ok(result.files["electron/preload.ts"].includes("declare global"));
});
});
// ═══════════════════════════════════════════════════════
// 4 — package.json 验证
// ═══════════════════════════════════════════════════════
describe("4 — package.json 验证", () => {
let result, pkg;
before(() => {
result = buildElectron({ projectName: "Inventory" });
pkg = JSON.parse(result.files["package.json"]);
});
it("包含正确名称", () => {
assert.ok(pkg.name.includes("inventory-desktop"));
});
it("包含 main 入口", () => {
assert.equal(pkg.main, "dist/electron/main.js");
});
it("包含 dev 脚本", () => {
assert.ok(pkg.scripts.dev, "Missing dev script");
assert.ok(pkg.scripts.dev.includes("concurrently"));
});
it("包含 build 脚本", () => {
assert.ok(pkg.scripts.build, "Missing build script");
});
it("包含平台构建脚本", () => {
assert.ok(pkg.scripts["build:electron:win"], "Missing win build");
assert.ok(pkg.scripts["build:electron:mac"], "Missing mac build");
assert.ok(pkg.scripts["build:electron:linux"], "Missing linux build");
});
it("依赖 electron", () => {
assert.ok(pkg.devDependencies.electron, "Missing electron");
});
it("依赖 electron-builder", () => {
assert.ok(pkg.devDependencies["electron-builder"], "Missing electron-builder");
});
it("依赖 electron-updater", () => {
assert.ok(pkg.dependencies["electron-updater"], "Missing electron-updater");
});
it("依赖 concurrently", () => {
assert.ok(pkg.devDependencies.concurrently, "Missing concurrently");
});
it("依赖 typescript", () => {
assert.ok(pkg.devDependencies.typescript, "Missing typescript");
});
it("build 配置包含 appId", () => {
assert.ok(pkg.build.appId.includes("inventory.desktop"));
});
it("build 配置包含 productName", () => {
assert.equal(pkg.build.productName, "Inventory");
});
});
// ═══════════════════════════════════════════════════════
// 5 — electron-builder.yml 验证
// ═══════════════════════════════════════════════════════
describe("5 — electron-builder.yml 验证", () => {
let result, yml;
before(() => {
result = buildElectron({ projectName: "PetCare" });
yml = result.files["electron-builder.yml"];
});
it("包含 appId", () => {
assert.ok(yml.includes("appId: com.petcare.desktop"));
});
it("包含 productName", () => {
assert.ok(yml.includes("productName: PetCare"));
});
it("包含 macOS 配置", () => {
assert.ok(yml.includes("mac:"));
assert.ok(yml.includes("dmg"));
assert.ok(yml.includes("arm64"));
});
it("包含 Windows 配置", () => {
assert.ok(yml.includes("win:"));
assert.ok(yml.includes("nsis"));
});
it("包含 Linux 配置", () => {
assert.ok(yml.includes("linux:"));
assert.ok(yml.includes("AppImage"));
assert.ok(yml.includes("deb"));
});
it("包含 extraResourcesweb + api", () => {
assert.ok(yml.includes("extraResources"));
assert.ok(yml.includes("../web/out"));
assert.ok(yml.includes("../api/dist"));
});
it("包含 publish 配置", () => {
assert.ok(yml.includes("publish:"));
assert.ok(yml.includes("provider: generic"));
});
});
// ═══════════════════════════════════════════════════════
// 6 — tsconfig.json 验证
// ═══════════════════════════════════════════════════════
describe("6 — tsconfig.json 验证", () => {
let result, tsconfig;
before(() => {
result = buildElectron();
tsconfig = JSON.parse(result.files["tsconfig.json"]);
});
it("target 是 ES2022", () => {
assert.equal(tsconfig.compilerOptions.target, "ES2022");
});
it("module 是 commonjsElectron 主进程需要)", () => {
assert.equal(tsconfig.compilerOptions.module, "commonjs");
});
it("strict 模式开启", () => {
assert.equal(tsconfig.compilerOptions.strict, true);
});
it("outDir 是 dist", () => {
assert.equal(tsconfig.compilerOptions.outDir, "dist");
});
it("include 包含 electron/", () => {
assert.ok(tsconfig.include.some(p => p.includes("electron")));
});
});
// ═══════════════════════════════════════════════════════
// 7 — 其他文件验证
// ═══════════════════════════════════════════════════════
describe("7 — 辅助文件验证", () => {
let result;
before(() => {
result = buildElectron({ projectName: "CRM" });
});
it("README.md 存在且包含关键内容", () => {
const readme = result.files["README.md"];
assert.ok(readme, "Missing README.md");
assert.ok(readme.includes("CRM"), "Missing project name");
assert.ok(readme.includes("Development"), "Missing Development section");
assert.ok(readme.includes("Build"), "Missing Build section");
assert.ok(readme.includes("IPC API"), "Missing IPC API section");
});
it("entitlements.mac.plist 存在", () => {
const plist = result.files["entitlements.mac.plist"];
assert.ok(plist, "Missing entitlements.mac.plist");
assert.ok(plist.includes("com.apple.security.network.client"));
});
it("icon.png 存在且非空", () => {
assert.ok(result.files["assets/icon.png"], "Missing icon.png");
assert.ok(result.files["assets/icon.png"].length > 0);
});
});
// ═══════════════════════════════════════════════════════
// 8 — 自定义端口配置
// ═══════════════════════════════════════════════════════
describe("8 — 自定义端口配置", () => {
it("使用自定义 web 端口", () => {
const result = buildElectron({ webPort: 4000 });
assert.ok(result.files["electron/main.ts"].includes("4000"));
});
it("使用自定义 api 端口", () => {
const result = buildElectron({ apiPort: 4001 });
assert.ok(result.files["electron/main.ts"].includes("4001"));
assert.ok(result.files["electron/utils.ts"].includes("4001"));
});
it("默认端口是 3000/3001", () => {
const result = buildElectron();
assert.ok(result.files["electron/main.ts"].includes("3000"));
assert.ok(result.files["electron/main.ts"].includes("3001"));
});
});
// ═══════════════════════════════════════════════════════
// 9 — 写入 I/O
// ═══════════════════════════════════════════════════════
describe("9 — 文件写入 I/O", () => {
it("writeElectron 写入完整目录树", () => {
const result = buildElectron({ projectName: "TestApp" });
const tmpDir = resolve(WORKSPACE, ".tmp-electron-test");
writeElectron(result, tmpDir);
assert.ok(existsSync(resolve(tmpDir, "package.json")));
assert.ok(existsSync(resolve(tmpDir, "electron-builder.yml")));
assert.ok(existsSync(resolve(tmpDir, "tsconfig.json")));
assert.ok(existsSync(resolve(tmpDir, "electron/main.ts")));
assert.ok(existsSync(resolve(tmpDir, "electron/preload.ts")));
assert.ok(existsSync(resolve(tmpDir, "electron/ipc.ts")));
assert.ok(existsSync(resolve(tmpDir, "electron/tray.ts")));
assert.ok(existsSync(resolve(tmpDir, "electron/updater.ts")));
assert.ok(existsSync(resolve(tmpDir, "electron/utils.ts")));
assert.ok(existsSync(resolve(tmpDir, "assets/icon.png")));
assert.ok(existsSync(resolve(tmpDir, "entitlements.mac.plist")));
assert.ok(existsSync(resolve(tmpDir, "README.md")));
rmSync(tmpDir, { recursive: true, force: true });
});
it("所有生成的文件非空", () => {
const result = buildElectron();
for (const [path, content] of Object.entries(result.files)) {
const len = typeof content === "string" ? content.length : content.byteLength;
assert.ok(len > 0, `${path}: empty content`);
}
});
it("所有 package.json 可解析", () => {
const result = buildElectron();
const pkg = JSON.parse(result.files["package.json"]);
assert.ok(pkg.name, "Missing package name");
assert.ok(pkg.scripts, "Missing scripts");
});
});
// ═══════════════════════════════════════════════════════
// 10 — 多领域覆盖
// ═══════════════════════════════════════════════════════
describe("10 — 多领域覆盖(PetCare / CRM / Inventory", () => {
const domains = [
{ name: "PetCare", expected: "petcare" },
{ name: "CRM", expected: "crm" },
{ name: "Inventory", expected: "inventory" },
];
for (const { name, expected } of domains) {
it(`${name} — 生成成功`, () => {
const result = buildElectron({ projectName: name });
assert.ok(!result.error, `${name}: ${result.message}`);
assert.ok(result.files["electron/main.ts"], `${name}: missing main.ts`);
assert.ok(result.files["electron/preload.ts"], `${name}: missing preload.ts`);
assert.ok(result.files["package.json"], `${name}: missing package.json`);
assert.ok(result.files["electron-builder.yml"], `${name}: missing electron-builder.yml`);
});
it(`${name} — package.json 包含正确名称`, () => {
const result = buildElectron({ projectName: name });
const pkg = JSON.parse(result.files["package.json"]);
assert.ok(pkg.name.includes(expected), `${name}: expected '${expected}' in name, got '${pkg.name}'`);
});
it(`${name} — electron-builder.yml 包含正确 appId`, () => {
const result = buildElectron({ projectName: name });
assert.ok(
result.files["electron-builder.yml"].includes(`com.${expected}.desktop`),
`${name}: expected 'com.${expected}.desktop' in appId`
);
});
}
});