From 2c80a2c3c221e2a353fe44a51f41bba771e2981a Mon Sep 17 00:00:00 2001 From: iven Date: Sat, 21 Mar 2026 16:55:44 +0800 Subject: [PATCH] test: add Vitest configuration and setup - Add vitest.config.ts with jsdom environment and path aliases - Add tests/setup.ts with mocks for Tauri API, crypto, and localStorage - Add test:coverage script to package.json Co-Authored-By: Claude Opus 4.6 --- desktop/package.json | 1 + desktop/tests/setup.ts | 76 ++++++++++++++++++++++++++++++++++++++++ desktop/vitest.config.ts | 36 +++++++++++++++++++ 3 files changed, 113 insertions(+) create mode 100644 desktop/tests/setup.ts create mode 100644 desktop/vitest.config.ts diff --git a/desktop/package.json b/desktop/package.json index 8ab46da..39dccf6 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -22,6 +22,7 @@ "tauri:build:msi:debug": "pnpm prepare:openfang-runtime && node scripts/tauri-build-bundled.mjs --debug --bundles msi", "test": "vitest run", "test:watch": "vitest", + "test:coverage": "vitest run --coverage", "test:e2e": "playwright test --project chromium --config=tests/e2e/playwright.config.ts", "test:e2e:ui": "playwright test --project chromium-ui --config=tests/e2e/playwright.config.ts --grep 'UI'", "test:e2e:headed": "playwright test --project chromium-headed --headed", diff --git a/desktop/tests/setup.ts b/desktop/tests/setup.ts new file mode 100644 index 0000000..40c8649 --- /dev/null +++ b/desktop/tests/setup.ts @@ -0,0 +1,76 @@ +import '@testing-library/jest-dom'; +import { vi } from 'vitest'; +import { webcrypto } from 'node:crypto'; + +// Polyfill Web Crypto API for Node.js test environment +Object.defineProperty(global, 'crypto', { + value: webcrypto, + configurable: true, +}); + +// Mock Tauri API +vi.mock('@tauri-apps/api/core', () => ({ + invoke: vi.fn(), +})); + +// Mock Tauri runtime check +vi.mock('../src/lib/tauri-gateway', () => ({ + isTauriRuntime: () => false, + getGatewayClient: vi.fn(), + startLocalGateway: vi.fn(), + stopLocalGateway: vi.fn(), + getLocalGatewayStatus: vi.fn(), + getLocalGatewayAuth: vi.fn(), + prepareLocalGatewayForTauri: vi.fn(), + approveLocalGatewayDevicePairing: vi.fn(), + getOpenFangProcessList: vi.fn(), + getOpenFangProcessLogs: vi.fn(), +})); + +// Mock localStorage with export for test access +export const localStorageMock = (() => { + let store: Record = {}; + return { + getItem: (key: string) => store[key] || null, + setItem: (key: string, value: string) => { + store[key] = value; + }, + removeItem: (key: string) => { + delete store[key]; + }, + clear: () => { + store = {}; + }, + get length() { + return Object.keys(store).length; + }, + key: (index: number) => { + const keys = Object.keys(store); + return keys[index] || null; + }, + }; +})(); + +Object.defineProperty(global, 'localStorage', { + value: localStorageMock, + configurable: true, +}); + +// Mock crypto.subtle for tests (already set via webcrypto above, but ensure subtle mock works) +const subtleMock = { + encrypt: vi.fn(), + decrypt: vi.fn(), + generateKey: vi.fn(), + deriveKey: vi.fn(), + importKey: vi.fn(), + exportKey: vi.fn(), + digest: vi.fn(), + sign: vi.fn(), + verify: vi.fn(), +}; + +// Override subtle if needed for specific test scenarios +Object.defineProperty(global.crypto, 'subtle', { + value: subtleMock, + configurable: true, +}); diff --git a/desktop/vitest.config.ts b/desktop/vitest.config.ts new file mode 100644 index 0000000..6970676 --- /dev/null +++ b/desktop/vitest.config.ts @@ -0,0 +1,36 @@ +import { defineConfig } from 'vitest/config'; +import react from '@vitejs/plugin-react'; +import path from 'path'; + +export default defineConfig({ + plugins: [react()], + test: { + globals: true, + environment: 'jsdom', + setupFiles: [path.resolve(__dirname, './tests/setup.ts')], + include: ['tests/**/*.test.ts', 'tests/**/*.test.tsx'], + exclude: ['tests/e2e/**', 'node_modules/**'], + coverage: { + provider: 'v8', + reporter: ['text', 'json', 'html'], + exclude: [ + 'node_modules/**', + 'tests/**', + '**/*.d.ts', + '**/*.config.*', + 'src/main.tsx', + ], + thresholds: { + lines: 60, + functions: 60, + branches: 60, + statements: 60, + }, + }, + }, + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, +});