diff --git a/package.json b/package.json index d6bea0a..c8773e7 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,10 @@ "dev:mock": "VITE_USE_MOCK=true vite", "build": "vue-tsc && vite build", "preview": "vite preview", - "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" + "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage" }, "dependencies": { "vue": "^3.4.21", @@ -25,7 +28,10 @@ "vite": "^5.1.4", "vue-tsc": "^2.0.6", "msw": "^2.2.1", - "@types/node": "^20.11.19" + "@types/node": "^20.11.19", + "vitest": "^1.3.1", + "@vitest/coverage-v8": "^1.3.1", + "happy-dom": "^13.6.2" }, "msw": { "workerDirectory": [ diff --git a/src/api/__tests__/api.test.ts b/src/api/__tests__/api.test.ts new file mode 100644 index 0000000..b61297e --- /dev/null +++ b/src/api/__tests__/api.test.ts @@ -0,0 +1,183 @@ +/** + * 前端 API 客户端单元测试 + * 测试所有 API 模块的方法定义和类型 + */ + +import { describe, it, expect, vi } from 'vitest' + +// Mock axios +vi.mock('axios', () => ({ + default: { + create: vi.fn(() => ({ + interceptors: { + request: { use: vi.fn() }, + response: { use: vi.fn() } + }, + get: vi.fn(), + post: vi.fn(), + put: vi.fn(), + delete: vi.fn() + })) + } +})) + +// ==================== Account API 测试 ==================== +describe('AccountAPI', () => { + it('should export AccountAPI class', async () => { + const { AccountAPI } = await import('../account') + expect(AccountAPI).toBeDefined() + }) + + it('should have physical account methods', async () => { + const { AccountAPI } = await import('../account') + + // 实体账户相关方法 + expect(typeof AccountAPI.createPhysicalAccount).toBe('function') + expect(typeof AccountAPI.getPhysicalAccounts).toBe('function') + expect(typeof AccountAPI.getPhysicalAccount).toBe('function') + expect(typeof AccountAPI.freezePhysicalAccount).toBe('function') + expect(typeof AccountAPI.unfreezePhysicalAccount).toBe('function') + }) + + it('should have sub account methods', async () => { + const { AccountAPI } = await import('../account') + + // 虚拟子账户相关方法 + expect(typeof AccountAPI.createSubAccount).toBe('function') + expect(typeof AccountAPI.getSubAccount).toBe('function') + expect(typeof AccountAPI.getSubAccountBalance).toBe('function') + expect(typeof AccountAPI.freezeSubAccount).toBe('function') + expect(typeof AccountAPI.unfreezeSubAccount).toBe('function') + expect(typeof AccountAPI.closeSubAccount).toBe('function') + }) +}) + +// ==================== Transaction API 测试 ==================== +describe('TransactionAPI', () => { + it('should export TransactionAPI class', async () => { + const { TransactionAPI } = await import('../transaction') + expect(TransactionAPI).toBeDefined() + }) + + it('should have all required methods', async () => { + const { TransactionAPI } = await import('../transaction') + + // 5 个核心交易 API 方法 + expect(typeof TransactionAPI.transfer).toBe('function') + expect(typeof TransactionAPI.deposit).toBe('function') + expect(typeof TransactionAPI.withdraw).toBe('function') + expect(typeof TransactionAPI.getTransaction).toBe('function') + expect(typeof TransactionAPI.getTransactions).toBe('function') + }) + + it('should have DepositRequest and WithdrawRequest types', async () => { + // 类型检查 - 如果编译通过则测试通过 + const { TransactionAPI } = await import('../transaction') + expect(TransactionAPI.deposit).toBeDefined() + expect(TransactionAPI.withdraw).toBeDefined() + }) +}) + +// ==================== Ledger API 测试 ==================== +describe('LedgerAPI', () => { + it('should export LedgerAPI class', async () => { + const { LedgerAPI } = await import('../ledger') + expect(LedgerAPI).toBeDefined() + }) + + it('should have all required methods', async () => { + const { LedgerAPI } = await import('../ledger') + + // 3 个账务 API 方法 + expect(typeof LedgerAPI.getSubjects).toBe('function') + expect(typeof LedgerAPI.getEntry).toBe('function') + expect(typeof LedgerAPI.getAccountEntries).toBe('function') + }) +}) + +// ==================== Reconciliation API 测试 ==================== +describe('ReconciliationAPI', () => { + it('should export ReconciliationAPI class', async () => { + const { ReconciliationAPI } = await import('../reconciliation') + expect(ReconciliationAPI).toBeDefined() + }) + + it('should have all required methods', async () => { + const { ReconciliationAPI } = await import('../reconciliation') + + // 8 个对账 API 方法 + expect(typeof ReconciliationAPI.runReconciliation).toBe('function') + expect(typeof ReconciliationAPI.getBatch).toBe('function') + expect(typeof ReconciliationAPI.getBatchItems).toBe('function') + expect(typeof ReconciliationAPI.verifyThreeAccounts).toBe('function') + expect(typeof ReconciliationAPI.createAdjustment).toBe('function') + expect(typeof ReconciliationAPI.approveAdjustment).toBe('function') + expect(typeof ReconciliationAPI.rejectAdjustment).toBe('function') + expect(typeof ReconciliationAPI.getPendingAdjustments).toBe('function') + }) + + it('should export status label mappings', async () => { + const { + ReconciliationStatusLabels, + ReconciliationItemStatusLabels, + AdjustmentTypeLabels, + AdjustmentStatusLabels + } = await import('../reconciliation') + + expect(ReconciliationStatusLabels).toBeDefined() + expect(ReconciliationItemStatusLabels).toBeDefined() + expect(AdjustmentTypeLabels).toBeDefined() + expect(AdjustmentStatusLabels).toBeDefined() + }) +}) + +// ==================== Points API 测试 ==================== +describe('PointsAPI', () => { + it('should export PointsAPI class', async () => { + const { PointsAPI } = await import('../points') + expect(PointsAPI).toBeDefined() + }) + + it('should have all required methods', async () => { + const { PointsAPI } = await import('../points') + + // 5 个积分 API 方法 + expect(typeof PointsAPI.getAccounts).toBe('function') + expect(typeof PointsAPI.earnPoints).toBe('function') + expect(typeof PointsAPI.spendPoints).toBe('function') + expect(typeof PointsAPI.transferPoints).toBe('function') + expect(typeof PointsAPI.getTransactions).toBe('function') + }) +}) + +// ==================== Types 测试 ==================== +describe('Type Definitions', () => { + it('should export points types', async () => { + const types = await import('@/types/points') + expect(types.PointsTypeLabels).toBeDefined() + expect(types.PointsTransactionTypeLabels).toBeDefined() + }) + + it('should export ledger types', async () => { + const types = await import('@/types/ledger') + expect(types.SubjectCategoryLabels).toBeDefined() + expect(types.DirectionLabels).toBeDefined() + expect(types.EntryStatusLabels).toBeDefined() + expect(types.PredefinedSubjects).toBeDefined() + }) +}) + +// ==================== API Index 导出测试 ==================== +describe('API Index Exports', () => { + it('should export all API modules from index', async () => { + const api = await import('../index') + + expect(api.AccountAPI).toBeDefined() + expect(api.TransactionAPI).toBeDefined() + expect(api.ReconciliationAPI).toBeDefined() + expect(api.PointsAPI).toBeDefined() + expect(api.LedgerAPI).toBeDefined() + expect(api.apiClient).toBeDefined() + }) +}) + diff --git a/src/api/ledger.ts b/src/api/ledger.ts index 2a11463..40017fd 100644 --- a/src/api/ledger.ts +++ b/src/api/ledger.ts @@ -2,7 +2,7 @@ * 账务 API 客户端 * 提供会计科目、分录查询等功能 */ -import apiClient from './client' +import { apiClient } from './client' import type { AccountingSubject, LedgerEntry, diff --git a/src/api/points.ts b/src/api/points.ts index 9065fb2..e70c8a5 100644 --- a/src/api/points.ts +++ b/src/api/points.ts @@ -2,7 +2,7 @@ * 积分 API 客户端 * 提供积分账户管理、积分获取/消费/转移等功能 */ -import apiClient from './client' +import { apiClient } from './client' import type { PointsAccount, PointsTransaction, diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..c9ffa71 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,24 @@ +import { defineConfig } from 'vitest/config' +import vue from '@vitejs/plugin-vue' +import { fileURLToPath, URL } from 'node:url' + +export default defineConfig({ + plugins: [vue()], + test: { + globals: true, + environment: 'happy-dom', + include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + include: ['src/**/*.ts'], + exclude: ['src/**/*.d.ts', 'src/**/*.test.ts', 'src/mocks/**'], + }, + }, + resolve: { + alias: { + '@': fileURLToPath(new URL('./src', import.meta.url)) + } + } +}) +