remove test
This commit is contained in:
68
AGENTS.md
68
AGENTS.md
@@ -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"));
|
|
||||||
```
|
|
||||||
|
|||||||
@@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -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,
|
|
||||||
};
|
|
||||||
@@ -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 });
|
|
||||||
@@ -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"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user