From 4ec09661cdde44f296190fa001bab7c318ecde04 Mon Sep 17 00:00:00 2001 From: xue jiahao Date: Fri, 13 Mar 2026 10:28:10 +0800 Subject: [PATCH] add test --- AGENTS.md | 111 ++++- package-lock.json | 632 ++++++++++++++++++++++++- package.json | 9 +- src/main/__tests__/kintone-api.test.ts | 147 ++++++ src/main/index.ts | 3 - src/preload/index.ts | 6 +- tests/mocks/electron.ts | 142 ++++++ tests/setup.ts | 14 + vitest.config.ts | 25 + 9 files changed, 1071 insertions(+), 18 deletions(-) create mode 100644 src/main/__tests__/kintone-api.test.ts create mode 100644 tests/mocks/electron.ts create mode 100644 tests/setup.ts create mode 100644 vitest.config.ts diff --git a/AGENTS.md b/AGENTS.md index 4fb3e96..f54fa6c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -22,9 +22,14 @@ npm run package:linux # Linux # 代码质量 npm run lint # ESLint 检查 npm run format # Prettier 格式化 + +# 测试 +npm test # 运行测试 +npm run test:watch # 监听模式 +npm run test:coverage # 测试覆盖率 ``` -**注意**: 无测试框架,项目暂无测试文件。 +**重要**: 修改 `src/main/` 目录下的文件后,必须运行 `npm test` 确保测试通过。 ## 2. 项目架构 @@ -34,22 +39,34 @@ src/ │ ├── index.ts # 入口,创建窗口 │ ├── ipc-handlers.ts # IPC 处理器(所有通信入口) │ ├── storage.ts # 文件存储 + 密码加密 -│ └── kintone-api.ts # Kintone REST API 封装 +│ ├── kintone-api.ts # Kintone REST API 封装 +│ └── __tests__/ # 主进程测试 ├── preload/ # Preload 脚本 │ ├── index.ts # 暴露 API 到渲染进程 │ └── index.d.ts # 类型声明 ├── shared/ # 跨进程共享代码 │ └── types/ # 共享类型定义 -└── renderer/ # React 渲染进程 - └── src/ - ├── main.tsx # React 入口 - ├── App.tsx # 根组件 - ├── components/ # React 组件 - └── stores/ # Zustand Stores +├── renderer/ # React 渲染进程 +│ └── src/ +│ ├── main.tsx # React 入口 +│ ├── App.tsx # 根组件 +│ ├── components/ # React 组件 +│ └── stores/ # Zustand Stores +└── tests/ # 测试配置 + ├── setup.ts # 测试环境设置 + └── mocks/ # Mock 文件 ``` ## 3. 路径别名 +| 别名 | 路径 | +| ------------- | -------------------- | +| `@renderer/*` | `src/renderer/src/*` | +| `@main/*` | `src/main/*` | +| `@preload/*` | `src/preload/*` | +| `@shared/*` | `src/shared/*` | + +## 4. 代码风格 | 别名 | 路径 | | ------------- | -------------------- | | `@renderer/*` | `src/renderer/src/*` | @@ -291,3 +308,81 @@ eval "$(fnm env --use-on-cd)" && npm run dev 3. Explain the reasoning and benefits 4. If significant, ask user for confirmation before implementing 5. Update related documentation after implementation + +## 12. 测试规范 + +### 测试框架 + +使用 **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` + +### 运行测试 + +```bash +# 运行所有测试 +npm test + +# 监听模式 +npm run test:watch + +# 测试覆盖率 +npm run test:coverage +``` + +### Mock Electron API + +测试中需要 Mock Electron 的 API(如 `safeStorage`、`app` 等),已在 `tests/mocks/electron.ts` 中提供。 + +```typescript +// tests/setup.ts 中自动 Mock +vi.mock("electron", () => import("./mocks/electron")); +``` + +### 测试示例 + +```typescript +import { describe, expect, it, beforeAll } from "vitest"; +import { SelfKintoneClient, createKintoneClient } from "@main/kintone-api"; +import type { DomainWithPassword } from "@shared/types/domain"; + +describe("SelfKintoneClient", () => { + let client: SelfKintoneClient; + + beforeAll(() => { + client = createKintoneClient({ + id: "test", + name: "Test", + domain: "example.cybozu.com", + username: "user", + password: "pass", + authType: "password", + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }); + }); + + it("should return the correct domain", () => { + expect(client.getDomain()).toBe("example.cybozu.com"); + }); + + it("should test connection successfully", async () => { + const result = await client.testConnection(); + expect(result.success).toBe(true); + }); +}); +``` diff --git a/package-lock.json b/package-lock.json index 9e00096..e47ef79 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "@typescript-eslint/eslint-plugin": "^8.57.0", "@typescript-eslint/parser": "^8.57.0", "@vitejs/plugin-react": "^4.3.0", + "@vitest/coverage-v8": "^2.1.9", "electron": "^30.5.1", "electron-builder": "^26.0.0", "electron-vite": "^5.0.0", @@ -46,7 +47,22 @@ "prettier": "^3.2.0", "typescript": "^5.7.0", "typescript-eslint": "^8.57.0", - "vite": "^5.4.0" + "vite": "^5.4.0", + "vitest": "^2.1.9" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" } }, "node_modules/@ant-design/colors": { @@ -506,6 +522,13 @@ } } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmmirror.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@braintree/sanitize-url": { "version": "7.1.2", "resolved": "https://registry.npmmirror.com/@braintree/sanitize-url/-/sanitize-url-7.1.2.tgz", @@ -2341,6 +2364,16 @@ "node": ">=18.0.0" } }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", + "resolved": "https://registry.npmmirror.com/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -5554,6 +5587,166 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", + "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.9", + "vitest": "2.1.9" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@xmldom/xmldom": { "version": "0.8.11", "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.11.tgz", @@ -6138,6 +6331,16 @@ "node": ">=0.8" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/assign-symbols/-/assign-symbols-1.0.0.tgz", @@ -6719,6 +6922,23 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmmirror.com/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmmirror.com/chalk/-/chalk-4.1.2.tgz", @@ -6776,6 +6996,16 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmmirror.com/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "node_modules/chevrotain": { "version": "11.1.2", "resolved": "https://registry.npmmirror.com/chevrotain/-/chevrotain-11.1.2.tgz", @@ -7900,6 +8130,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmmirror.com/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz", @@ -8757,6 +8997,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmmirror.com/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -9305,6 +9552,16 @@ "node": ">=0.10.0" } }, + "node_modules/expect-type": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/expect-type/-/expect-type-1.3.0.tgz", + "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/exponential-backoff": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/exponential-backoff/-/exponential-backoff-3.1.3.tgz", @@ -10469,6 +10726,13 @@ "dev": true, "license": "ISC" }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-url-attributes": { "version": "3.0.1", "resolved": "https://registry.npmmirror.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz", @@ -11244,6 +11508,60 @@ "node": ">=0.10.0" } }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmmirror.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmmirror.com/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmmirror.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmmirror.com/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmmirror.com/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -11683,6 +12001,13 @@ "loose-envify": "cli.js" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmmirror.com/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -11727,6 +12052,47 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmmirror.com/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/make-fetch-happen": { "version": "14.0.3", "resolved": "https://registry.npmmirror.com/make-fetch-happen/-/make-fetch-happen-14.0.3.tgz", @@ -13946,6 +14312,16 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/pe-library": { "version": "0.4.1", "resolved": "https://registry.npmmirror.com/pe-library/-/pe-library-0.4.1.tgz", @@ -15720,6 +16096,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmmirror.com/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmmirror.com/signal-exit/-/signal-exit-3.0.7.tgz", @@ -15917,6 +16300,13 @@ "node": "^18.17.0 || >=20.5.0" } }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmmirror.com/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/stat-mode": { "version": "1.0.0", "resolved": "https://registry.npmmirror.com/stat-mode/-/stat-mode-1.0.0.tgz", @@ -15927,6 +16317,13 @@ "node": ">= 6" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmmirror.com/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -16346,6 +16743,75 @@ "node": ">= 10.0.0" } }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmmirror.com/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/test-exclude/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmmirror.com/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/test-exclude/node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmmirror.com/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/throttle-debounce": { "version": "5.0.2", "resolved": "https://registry.npmmirror.com/throttle-debounce/-/throttle-debounce-5.0.2.tgz", @@ -16381,6 +16847,13 @@ "integrity": "sha512-qVtvMxeXbVej0cQWKqVSSAHmKZEHAvxdF8HEUBFWts8h+xEo5m/lEiPakuyZ3BnCBjOD8i24kzNOiOLLgsSxhA==", "license": "MIT" }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, "node_modules/tinyexec": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.0.2.tgz", @@ -16407,6 +16880,36 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmmirror.com/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.2.5", "resolved": "https://registry.npmmirror.com/tmp/-/tmp-0.2.5.tgz", @@ -17103,6 +17606,36 @@ } } }, + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -17533,6 +18066,86 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmmirror.com/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest/node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmmirror.com/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, "node_modules/vscode-jsonrpc": { "version": "8.2.0", "resolved": "https://registry.npmmirror.com/vscode-jsonrpc/-/vscode-jsonrpc-8.2.0.tgz", @@ -17719,6 +18332,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmmirror.com/word-wrap/-/word-wrap-1.2.5.tgz", diff --git a/package.json b/package.json index 9a35685..01e12d9 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,10 @@ "package:mac": "electron-vite build && electron-builder --mac", "package:linux": "electron-vite build && electron-builder --linux", "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": { "@codemirror/lang-css": "^6.3.0", @@ -45,6 +48,7 @@ "@typescript-eslint/eslint-plugin": "^8.57.0", "@typescript-eslint/parser": "^8.57.0", "@vitejs/plugin-react": "^4.3.0", + "@vitest/coverage-v8": "^2.1.9", "electron": "^30.5.1", "electron-builder": "^26.0.0", "electron-vite": "^5.0.0", @@ -54,6 +58,7 @@ "prettier": "^3.2.0", "typescript": "^5.7.0", "typescript-eslint": "^8.57.0", - "vite": "^5.4.0" + "vite": "^5.4.0", + "vitest": "^2.1.9" } } diff --git a/src/main/__tests__/kintone-api.test.ts b/src/main/__tests__/kintone-api.test.ts new file mode 100644 index 0000000..01526ca --- /dev/null +++ b/src/main/__tests__/kintone-api.test.ts @@ -0,0 +1,147 @@ +/** + * 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 { SelfKintoneClient, 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", + authType: "password", + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), +}; + +describe("SelfKintoneClient - API Integration Tests", () => { + let client: SelfKintoneClient; + + 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(SelfKintoneClient); + }); + + it("should return the correct domain", () => { + const client = createKintoneClient(TEST_CONFIG); + expect(client.getDomain()).toBe(TEST_CONFIG.domain); + }); +}); diff --git a/src/main/index.ts b/src/main/index.ts index d2cdda6..35cf0ae 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -73,9 +73,6 @@ app.whenReady().then(() => { optimizer.watchWindowShortcuts(window); }); - // IPC test (keep for debugging) - ipcMain.on("ping", () => console.log("pong")); - createWindow(); app.on("activate", function () { diff --git a/src/preload/index.ts b/src/preload/index.ts index 95e505c..8736d2b 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -32,8 +32,6 @@ if (process.contextIsolated) { console.error(error); } } else { - // @ts-ignore (define in dts) - window.electron = electronAPI; - // @ts-ignore (define in dts) - window.api = api; + (window as Window & { electron: typeof electronAPI; api: SelfAPI }).electron = electronAPI; + (window as Window & { electron: typeof electronAPI; api: SelfAPI }).api = api; } diff --git a/tests/mocks/electron.ts b/tests/mocks/electron.ts new file mode 100644 index 0000000..0ea9e67 --- /dev/null +++ b/tests/mocks/electron.ts @@ -0,0 +1,142 @@ +/** + * 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(); + +/** + * 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 = { + 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(); + + 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 { + 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, +}; diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..2a90a1a --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,14 @@ +/** + * 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 }); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..5254a9b --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,25 @@ +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"), + }, + }, +});