remove test

This commit is contained in:
2026-03-18 10:18:39 +08:00
parent c3a333e2ed
commit e0d0cac91c
6 changed files with 15 additions and 389 deletions

View File

@@ -22,17 +22,8 @@ npm run package:linux # Linux
# 代码质量 # 代码质量
npm run lint # ESLint 检查 npm run lint # ESLint 检查
npm run format # Prettier 格式化 npm run format # Prettier 格式化
# 测试
npm test # 运行测试
npm run test:watch # 监听模式
npm run test:coverage # 测试覆盖率
``` ```
**重要**: 修改 `src/main/` 目录下的文件后,必须运行 `npm test` 确保测试通过。
**注意**: 仅修改 `src/renderer/`(前端/UI`src/preload/` 文件时,不需要运行测试,只需确保类型检查通过(`npx tsc --noEmit`)即可。
## 2. 项目架构 ## 2. 项目架构
``` ```
@@ -41,23 +32,19 @@ src/
│ ├── index.ts # 入口,创建窗口 │ ├── index.ts # 入口,创建窗口
│ ├── ipc-handlers.ts # IPC 处理器(所有通信入口) │ ├── ipc-handlers.ts # IPC 处理器(所有通信入口)
│ ├── storage.ts # 文件存储 + 密码加密 │ ├── storage.ts # 文件存储 + 密码加密
── kintone-api.ts # Kintone REST API 封装 ── kintone-api.ts # Kintone REST API 封装
│ └── __tests__/ # 主进程测试
├── preload/ # Preload 脚本 ├── preload/ # Preload 脚本
│ ├── index.ts # 暴露 API 到渲染进程 │ ├── index.ts # 暴露 API 到渲染进程
│ └── index.d.ts # 类型声明 │ └── index.d.ts # 类型声明
├── shared/ # 跨进程共享代码 ├── shared/ # 跨进程共享代码
│ └── types/ # 共享类型定义 │ └── types/ # 共享类型定义
── renderer/ # React 渲染进程 ── renderer/ # React 渲染进程
└── src/ └── src/
├── main.tsx # React 入口 ├── main.tsx # React 入口
├── App.tsx # 根组件 ├── App.tsx # 根组件
├── components/ # React 组件 ├── components/ # React 组件
├── stores/ # Zustand Stores ├── stores/ # Zustand Stores
└── locales/ # i18n 翻译文件 └── locales/ # i18n 翻译文件
└── tests/ # 测试配置
├── setup.ts # 测试环境设置
└── mocks/ # Mock 文件
``` ```
### 数据流 ### 数据流
@@ -139,12 +126,14 @@ type Result<T> = { success: true; data: T } | { success: false; error: string };
**UI Kit 优先使用 LobeHub UI** (`@lobehub/ui`),其次使用 Ant Design 6 + antd-style **UI Kit 优先使用 LobeHub UI** (`@lobehub/ui`),其次使用 Ant Design 6 + antd-style
```typescript ```typescript
import { Button } from '@lobehub/ui'; import { Button } from "@lobehub/ui";
import { createStyles } from 'antd-style'; import { createStyles } from "antd-style";
// antd-style 仅用于自定义样式 // antd-style 仅用于自定义样式
export const useStyles = createStyles(({ token, css }) => ({ export const useStyles = createStyles(({ token, css }) => ({
container: css`padding: ${token.paddingLG}px;` container: css`
padding: ${token.paddingLG}px;
`,
})); }));
``` ```
@@ -217,34 +206,3 @@ eval "$(fnm env --use-on-cd)" && npm run dev
3. Explain the reasoning and benefits 3. Explain the reasoning and benefits
4. If significant, ask user for confirmation before implementing 4. If significant, ask user for confirmation before implementing
5. Update related documentation after implementation 5. Update related documentation after implementation
## 14. 测试规范
### 测试框架
使用 **Vitest** 作为测试框架,与 Vite 原生集成。
### 测试文件结构
```
src/main/__tests__/ # 主进程测试
tests/
├── setup.ts # 全局测试设置
└── mocks/
└── electron.ts # Electron API Mocks
```
### 测试要求
1. **修改 `src/main/` 目录下的文件后,必须运行 `npm test` 确保测试通过**
2. 新增 API 功能时,应在 `src/main/__tests__/` 中添加对应测试
3. 测试文件命名:`*.test.ts`
### Mock Electron API
测试中需要 Mock Electron 的 API`safeStorage``app` 等),已在 `tests/mocks/electron.ts` 中提供。
```typescript
// tests/setup.ts 中自动 Mock
vi.mock("electron", () => import("./mocks/electron"));
```

View File

@@ -14,10 +14,7 @@
"package:mac": "electron-vite build && electron-builder --mac", "package:mac": "electron-vite build && electron-builder --mac",
"package:linux": "electron-vite build && electron-builder --linux", "package:linux": "electron-vite build && electron-builder --linux",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx", "lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,json}\"", "format": "prettier --write \"src/**/*.{js,jsx,ts,tsx,css,json}\""
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage"
}, },
"dependencies": { "dependencies": {
"@codemirror/lang-css": "^6.3.0", "@codemirror/lang-css": "^6.3.0",
@@ -57,7 +54,6 @@
"@typescript-eslint/eslint-plugin": "^8.57.0", "@typescript-eslint/eslint-plugin": "^8.57.0",
"@typescript-eslint/parser": "^8.57.0", "@typescript-eslint/parser": "^8.57.0",
"@vitejs/plugin-react": "^4.3.0", "@vitejs/plugin-react": "^4.3.0",
"@vitest/coverage-v8": "^2.1.9",
"electron": "^30.5.1", "electron": "^30.5.1",
"electron-builder": "^26.0.0", "electron-builder": "^26.0.0",
"electron-vite": "^5.0.0", "electron-vite": "^5.0.0",
@@ -67,7 +63,6 @@
"prettier": "^3.2.0", "prettier": "^3.2.0",
"typescript": "^5.7.0", "typescript": "^5.7.0",
"typescript-eslint": "^8.57.0", "typescript-eslint": "^8.57.0",
"vite": "^5.4.0", "vite": "^5.4.0"
"vitest": "^2.1.9"
} }
} }

View File

@@ -1,146 +0,0 @@
/**
* Kintone API Integration Tests
* Tests actual API connectivity to verify the client works correctly
*
* Test credentials:
* - Domain: https://alicorn.cybozu.com
* - Username: maxz
* - Password: 7ld7i8vd
*/
import { describe, expect, it, beforeAll, afterAll } from "vitest";
import { KintoneClient, createKintoneClient } from "@main/kintone-api";
import type { DomainWithPassword } from "@shared/types/domain";
// Test configuration
const TEST_CONFIG: DomainWithPassword = {
id: "test-domain-id",
name: "Test Domain",
domain: "alicorn.cybozu.com",
username: "maxz",
password: "7ld7i8vd",
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
describe("SelfKintoneClient - API Integration Tests", () => {
let client: KintoneClient;
beforeAll(() => {
// Create client with test credentials
client = createKintoneClient(TEST_CONFIG);
console.log(`Testing connection to: https://${TEST_CONFIG.domain}`);
});
describe("Connection Tests", () => {
it("should successfully test connection", async () => {
const result = await client.testConnection();
console.log("Test connection result:", result);
expect(result).toBeDefined();
expect(result.success).toBe(true);
expect(result.error).toBeUndefined();
});
it("should return the correct domain", () => {
const domain = client.getDomain();
expect(domain).toBe(TEST_CONFIG.domain);
});
});
describe("Get Apps Tests", () => {
it("should fetch apps successfully", async () => {
const apps = await client.getApps();
console.log(`Fetched ${apps.length} apps`);
if (apps.length > 0) {
console.log("First app:", JSON.stringify(apps[0], null, 2));
}
expect(apps).toBeDefined();
expect(Array.isArray(apps)).toBe(true);
});
it("should fetch apps with pagination (limit=5)", async () => {
const apps = await client.getApps({ limit: 5 });
console.log(`Fetched ${apps.length} apps with limit=5`);
expect(apps).toBeDefined();
expect(Array.isArray(apps)).toBe(true);
expect(apps.length).toBeLessThanOrEqual(5);
});
});
describe("Get App Detail Tests", () => {
let firstAppId: string | null = null;
beforeAll(async () => {
// Get an app ID to test with
const apps = await client.getApps({ limit: 1 });
if (apps.length > 0 && apps[0].appId) {
firstAppId = String(apps[0].appId);
console.log(`Using app ID for detail test: ${firstAppId}`);
}
});
it("should fetch app detail if apps exist", async () => {
if (!firstAppId) {
console.log("Skipping: No apps available to test");
return;
}
const detail = await client.getAppDetail(firstAppId);
console.log("App detail:", JSON.stringify(detail, null, 2).slice(0, 500));
expect(detail).toBeDefined();
expect(detail.appId).toBe(firstAppId);
expect(detail.name).toBeDefined();
expect(detail.customization).toBeDefined();
});
});
describe("Error Handling Tests", () => {
it("should handle invalid credentials gracefully", async () => {
const invalidConfig: DomainWithPassword = {
...TEST_CONFIG,
password: "invalid-password-12345",
};
const invalidClient = createKintoneClient(invalidConfig);
const result = await invalidClient.testConnection();
console.log("Invalid credentials test result:", result);
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
it("should handle invalid domain gracefully", async () => {
const invalidConfig: DomainWithPassword = {
...TEST_CONFIG,
domain: "non-existent-domain-12345.cybozu.com",
};
const invalidClient = createKintoneClient(invalidConfig);
const result = await invalidClient.testConnection();
// testConnection catches errors and returns { success: false }
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
});
});
describe("Create Kintone Client Factory", () => {
it("should create a client instance", () => {
const client = createKintoneClient(TEST_CONFIG);
expect(client).toBeInstanceOf(KintoneClient);
});
it("should return the correct domain", () => {
const client = createKintoneClient(TEST_CONFIG);
expect(client.getDomain()).toBe(TEST_CONFIG.domain);
});
});

View File

@@ -1,142 +0,0 @@
/**
* Electron API mocks for testing
* Mocks safeStorage, app, and other Electron APIs
*/
import { vi } from "vitest";
// In-memory storage for encrypted passwords (simulates OS keychain)
const encryptedStore = new Map<string, string>();
/**
* Mock safeStorage for password encryption
* In tests, we use a simple reversible encoding instead of actual encryption
*/
export const safeStorage = {
/**
* Encrypt a string (simulated)
* In production, this uses OS-specific encryption
*/
encryptString: vi.fn((plaintext: string): Buffer => {
const encoded = Buffer.from(`encrypted:${plaintext}`).toString("base64");
return Buffer.from(encoded);
}),
/**
* Decrypt a buffer (simulated)
*/
decryptString: vi.fn((encrypted: Buffer): string => {
const decoded = encrypted.toString();
const base64Content = decoded.replace("encrypted:", "");
return Buffer.from(base64Content, "base64")
.toString()
.replace("encrypted:", "");
}),
/**
* Check if encryption is available
*/
isEncryptionAvailable: vi.fn((): boolean => true),
/**
* Get the current storage backend
*/
getSelectedStorageBackend: vi.fn((): string => "mock_backend"),
};
/**
* Mock Electron app
*/
export const app = {
getName: vi.fn(() => "Kintone Customize Manager"),
getVersion: vi.fn(() => "1.0.0"),
getPath: vi.fn((key: string): string => {
const paths: Record<string, string> = {
userData: "/tmp/kintone-manager-test/userData",
home: "/tmp/kintone-manager-test/home",
temp: "/tmp/kintone-manager-test/temp",
appData: "/tmp/kintone-manager-test/appData",
desktop: "/tmp/kintone-manager-test/desktop",
documents: "/tmp/kintone-manager-test/documents",
downloads: "/tmp/kintone-manager-test/downloads",
};
return paths[key] || "/tmp/kintone-manager-test";
}),
whenReady: vi.fn(() => Promise.resolve()),
on: vi.fn(),
quit: vi.fn(),
isReady: vi.fn(() => true),
};
/**
* Mock ipcMain for IPC handler tests
*/
class IPCMainMock {
private handlers = new Map<string, Function>();
handle = vi.fn((channel: string, handler: Function) => {
this.handlers.set(channel, handler);
});
handleOnce = vi.fn((channel: string, handler: Function) => {
this.handlers.set(channel, handler);
});
removeHandler = vi.fn((channel: string) => {
this.handlers.delete(channel);
});
// Helper to call a handler directly in tests
async invoke(channel: string, ...args: unknown[]): Promise<unknown> {
const handler = this.handlers.get(channel);
if (!handler) {
throw new Error(`No handler registered for channel: ${channel}`);
}
return handler({}, ...args);
}
}
export const ipcMain = new IPCMainMock();
/**
* Mock BrowserWindow
*/
export const BrowserWindow = vi.fn().mockImplementation(() => ({
loadURL: vi.fn(),
loadFile: vi.fn(),
on: vi.fn(),
webContents: {
on: vi.fn(),
send: vi.fn(),
openDevTools: vi.fn(),
},
show: vi.fn(),
close: vi.fn(),
}));
/**
* Mock dialog
*/
export const dialog = {
showOpenDialog: vi.fn().mockResolvedValue({ canceled: false, filePaths: [] }),
showMessageBox: vi.fn().mockResolvedValue({ response: 0 }),
showSaveDialog: vi.fn().mockResolvedValue({ canceled: false, filePath: "" }),
};
/**
* Mock clipboard
*/
export const clipboard = {
writeText: vi.fn(),
readText: vi.fn(() => ""),
};
// Default export with all mocked APIs
export default {
app,
safeStorage,
ipcMain,
BrowserWindow,
dialog,
clipboard,
};

View File

@@ -1,14 +0,0 @@
/**
* Test setup file
* Configures mocks before tests run
*/
import { vi } from "vitest";
// Mock electron module before any imports
vi.mock("electron", () => {
return import("./mocks/electron");
});
// Increase timeout for integration tests
vi.setConfig({ testTimeout: 30000 });

View File

@@ -1,25 +0,0 @@
import { defineConfig } from "vitest/config";
import { resolve } from "path";
export default defineConfig({
test: {
globals: true,
environment: "node",
setupFiles: ["./tests/setup.ts"],
include: ["src/**/*.test.ts", "tests/**/*.test.ts"],
testTimeout: 30000, // 30 seconds for API calls
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
exclude: ["node_modules/", "tests/", "**/*.d.ts", "**/*.config.*"],
},
},
resolve: {
alias: {
"@main": resolve(__dirname, "src/main"),
"@preload": resolve(__dirname, "src/preload"),
"@renderer": resolve(__dirname, "src/renderer/src"),
"@shared": resolve(__dirname, "src/shared"),
},
},
});