vitest.config.ts

import { defineConfig } from 'vitest/config'
import { resolve } from 'path'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@main': resolve(__dirname, 'src/main'),
      '@shared': resolve(__dirname, 'src/shared'),
      '@renderer': resolve(__dirname, 'src/renderer')
    }
  },
  test: {
    globals: true,
    environment: 'node',
    include: ['tests/**/*.test.{ts,tsx}'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'html', 'json-summary'],
      reportsDirectory: 'coverage',
      include: ['src/main/**/*.ts', 'src/shared/**/*.ts', 'src/renderer/**/*.{ts,tsx}'],
      exclude: [
        '**/*.d.ts',
        '**/types.ts',
        'src/main/index.ts',
        'src/preload/**',
        'src/renderer/main.tsx',
        'src/renderer/routeTree.gen.ts',
        'tests/**',
        'e2e/**',
        'extension/**',
        'out/**',
        'dist/**'
      ],
      // Coverage is a barbell: the forensic core is locked high, the
      // contract/data layers are held in their target band, and the global
      // floor only ratchets (most remaining uncovered code is presentational
      // .tsx exercised by E2E). With perFile unset (the default), each glob
      // threshold is checked against the AGGREGATE of its matched files, so
      // single-file globs are used for the contract layer to keep each file
      // gated on its own merit. Numbers sit a few points below the measured
      // values so the gate catches regressions without breaking on trivial
      // edits.
      thresholds: {
        // Global ratchet — prevents backslide, does not chase a single number.
        lines: 38,
        statements: 38,
        functions: 75,
        branches: 75,
        // Forensic core (top-level services) — locked at ~90%.
        'src/main/services/*.ts': {
          lines: 90,
          statements: 90,
          functions: 90,
          branches: 78
        },
        // Shared verification / schema / ipc contract types.
        'src/shared/**/*.ts': {
          lines: 90,
          statements: 90,
          functions: 90,
          branches: 70
        },
        // IPC contract layer — gated per file (measured: handlers ~92%,
        // wrap ~78%) so neither can backslide behind the other's coverage.
        'src/main/ipcHandlers.ts': {
          lines: 88,
          statements: 88,
          functions: 90,
          branches: 80
        },
        'src/main/ipcWrap.ts': {
          lines: 75,
          statements: 75,
          functions: 90,
          branches: 78
        },
        // React Query data layer — query keys + invalidation correctness.
        'src/renderer/lib/queries.ts': {
          lines: 90,
          statements: 90,
          functions: 90,
          branches: 90
        }
      }
    },
    projects: [
      {
        resolve: {
          alias: {
            '@main': resolve(__dirname, 'src/main'),
            '@shared': resolve(__dirname, 'src/shared'),
            '@renderer': resolve(__dirname, 'src/renderer')
          }
        },
        test: {
          name: 'node',
          environment: 'node',
          include: ['tests/**/*.test.ts'],
          exclude: ['tests/renderer/**', 'tests/hooks/**'],
          setupFiles: ['./tests/setup/signing-key.ts']
        }
      },
      {
        plugins: [react()],
        resolve: {
          alias: {
            '@main': resolve(__dirname, 'src/main'),
            '@shared': resolve(__dirname, 'src/shared'),
            '@renderer': resolve(__dirname, 'src/renderer')
          }
        },
        test: {
          name: 'jsdom',
          environment: 'jsdom',
          include: [
            'tests/components/**/*.test.tsx',
            'tests/renderer/**/*.test.ts',
            'tests/hooks/**/*.test.ts'
          ]
        }
      }
    ]
  }
})