🎉 init: 小龙的工作空间
This commit is contained in:
@@ -0,0 +1,453 @@
|
||||
/**
|
||||
* 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("包含 extraResources(web + 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 是 commonjs(Electron 主进程需要)", () => {
|
||||
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`
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user