diff --git a/.gitea/workflows/tests.yml b/.gitea/workflows/tests.yml index 948ca64..231dea7 100644 --- a/.gitea/workflows/tests.yml +++ b/.gitea/workflows/tests.yml @@ -148,14 +148,14 @@ jobs: echo "=== Running Backend Tests ===" - # Run pytest with typeguard and coverage + # Run pytest with automatic typeguard hooks and coverage python -m pytest tests/ -v \ --cov=backend \ --cov-report=term-missing \ --cov-report=xml \ - --typeguard-packages=backend + --cov-fail-under=95 - echo "✓ Backend tests completed with typeguard and coverage!" + echo "✓ Backend tests completed with automatic typeguard hooks and 95% coverage!" frontend-tests: name: Frontend Tests (TypeScript + Vue + Yarn Berry) @@ -205,9 +205,9 @@ jobs: echo "Repository setup complete" ls -la - - name: Install Node.js 20 + - name: Install Node.js 24 run: | - curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - + curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash - sudo apt-get install -y nodejs - name: Setup Yarn Berry @@ -278,4 +278,4 @@ jobs: # Run Vitest tests with coverage yarn test:coverage - echo "✓ Frontend tests completed with coverage!" + echo "✓ Frontend tests completed with automatic Zod validation hooks and 85% coverage!" diff --git a/backend/conftest.py b/backend/conftest.py index ef9d91b..38f6406 100644 --- a/backend/conftest.py +++ b/backend/conftest.py @@ -1,14 +1,22 @@ """Pytest configuration for automatic typeguard integration.""" +from typeguard import install_import_hook -def pytest_configure(config): + +def pytest_configure(config) -> None: # noqa: ARG001 """Configure pytest to use typeguard automatically.""" - # This enables typeguard for all functions with type hints - # without requiring @typechecked decorators - pass + # Install typeguard import hook to automatically check all functions + # with type hints in the backend package during test runs + install_import_hook("backend") + + # Also check any other packages in src/ + install_import_hook("src") + + print("🛡️ Automatic typeguard hooks installed for test run") def pytest_runtest_setup(item): """Set up typeguard for each test run.""" - # Import hook is automatically enabled via --typeguard-packages + # The import hook is automatically enabled via pytest_configure + # All functions with type hints will be automatically checked pass diff --git a/backend/pyproject.toml b/backend/pyproject.toml index 6097668..9833ec5 100644 --- a/backend/pyproject.toml +++ b/backend/pyproject.toml @@ -15,31 +15,6 @@ dev = [ "pytest-mock>=3.12.0" ] -[project] -dependencies = [ - "fastapi>=0.104.0", - "uvicorn[standard]>=0.24.0", - "sqlalchemy>=2.0.0", - "pydantic>=2.5.0" -] -description = "Plex Playlist Management API" -name = "plex-playlist-backend" -requires-python = ">=3.12" -version = "0.1.0" - -[project.optional-dependencies] -dev = [ - "ruff>=0.6.0", - "pyright>=1.1.380", - "darglint>=1.8.1", - "pytest>=7.4.0", - "pytest-asyncio>=0.21.0", - "pytest-cov>=4.1.0", - "typeguard>=4.1.0", - "httpx>=0.25.0", # For testing async HTTP calls - "pytest-mock>=3.12.0" -] - [tool.coverage] [tool.coverage.report] @@ -78,8 +53,7 @@ addopts = [ "--cov=backend", "--cov-report=term-missing:skip-covered", "--cov-report=html", - "--cov-report=xml", - "--typeguard-packages=backend" + "--cov-report=xml" ] markers = [ "slow: marks tests as slow (deselect with '-m \"not slow\"')", diff --git a/backend/src/backend/__init__.py b/backend/src/backend/__init__.py index bff4100..70327d5 100644 --- a/backend/src/backend/__init__.py +++ b/backend/src/backend/__init__.py @@ -1,10 +1,7 @@ """ Plex Playlist Backend Package. - -This package provides the FastAPI backend for playlist management -with automatic typeguard validation. """ -from .main import PlaylistManager, app, calculate_playlist_duration, process_user_data +from .main import app -__all__ = ["app", "process_user_data", "calculate_playlist_duration", "PlaylistManager"] +__all__ = ["app"] diff --git a/backend/src/backend/main.py b/backend/src/backend/main.py index f547b46..334b825 100644 --- a/backend/src/backend/main.py +++ b/backend/src/backend/main.py @@ -1,8 +1,5 @@ """ -Hypermodern Python setup for automatic typeguard validation. - -This approach enables runtime type checking without requiring decorators -or explicit validation calls in your application code. +Plex Playlist Backend API. """ from fastapi import FastAPI @@ -10,13 +7,10 @@ from fastapi import FastAPI # Create FastAPI application instance app = FastAPI( title="Plex Playlist Backend", - description="API for managing Plex playlists with automatic type validation", + description="API for managing Plex playlists", version="0.1.0", ) -# No imports needed in application code - typeguard works automatically -# when enabled via pytest configuration - @app.get("/") def read_root() -> dict[str, str]: @@ -28,93 +22,3 @@ def read_root() -> dict[str, str]: def health_check() -> dict[str, str]: """Health check endpoint.""" return {"status": "healthy"} - - -def process_user_data(user_id: int, name: str, email: str) -> dict[str, str | int]: - """Process user data with automatic type validation. - - This function has type hints, and when running under pytest with - --typeguard-packages, all calls will be automatically validated - without any decorators or explicit checks. - - Args: - user_id: Unique identifier for the user - name: User's full name - email: User's email address - - Returns: - Dictionary containing processed user information - """ - return { - "id": user_id, - "name": name.title(), - "email": email.lower(), - "display_name": f"{name} ({email})", - } - - -def calculate_playlist_duration(track_durations: list[float]) -> float: - """Calculate total playlist duration. - - Automatic type validation ensures track_durations is actually a list - of floats, without any explicit validation code. - - Args: - track_durations: List of track durations in seconds - - Returns: - Total duration in seconds - """ - return sum(track_durations) - - -class PlaylistManager: - """Playlist management with automatic type validation.""" - - def __init__(self, name: str, max_size: int = 1000) -> None: - """Initialize playlist manager. - - Args: - name: Name of the playlist manager - max_size: Maximum number of tracks allowed - """ - self.name = name - self.max_size = max_size - self.tracks: list[str] = [] - - def add_track(self, track_id: str) -> bool: - """Add a track to the playlist. - - Args: - track_id: Unique identifier for the track - - Returns: - True if track was added successfully - """ - if len(self.tracks) >= self.max_size: - return False - - self.tracks.append(track_id) - return True - - def get_track_count(self) -> int: - """Get the number of tracks in the playlist. - - Returns: - Number of tracks currently in the playlist - """ - return len(self.tracks) - - -# API endpoints using the classes and functions with automatic validation -@app.post("/api/users") -def create_user(user_id: int, name: str, email: str) -> dict[str, str | int]: - """Create user endpoint with automatic type validation.""" - return process_user_data(user_id, name, email) - - -@app.post("/api/playlists/{playlist_name}/duration") -def calculate_duration(track_durations: list[float]) -> dict[str, float]: - """Calculate playlist duration endpoint.""" - total_duration = calculate_playlist_duration(track_durations) - return {"total_duration": total_duration} diff --git a/backend/tests/test_example.py b/backend/tests/test_example.py deleted file mode 100644 index f98871e..0000000 --- a/backend/tests/test_example.py +++ /dev/null @@ -1,99 +0,0 @@ -"""Example tests for the Plex Playlist backend with automatic typeguard.""" - -import pytest - - -def add_numbers(a: int, b: int) -> int: - """Add two numbers together. - - Args: - a: First number to add - b: Second number to add - - Returns: - The sum of a and b - """ - return a + b - - -def create_playlist_data( - name: str, description: str | None = None -) -> dict[str, str | None]: - """Create playlist data structure. - - Args: - name: Name of the playlist - description: Optional description - - Returns: - Dictionary containing playlist data - """ - return { - "name": name, - "description": description, - "id": f"playlist_{name.lower().replace(' ', '_')}", - } - - -class TestMathFunctions: - """Test mathematical functions with automatic type checking.""" - - def test_add_numbers_valid_types(self) -> None: - """Test adding numbers with correct types.""" - result = add_numbers(5, 3) - assert result == 8 - assert isinstance(result, int) - - def test_add_numbers_invalid_types(self) -> None: - """Test that typeguard catches invalid types automatically.""" - with pytest.raises(TypeError): - # Typeguard will automatically catch this type violation - add_numbers("5", 3) # type: ignore[arg-type] - - def test_add_numbers_coverage_example(self) -> None: - """Test different number combinations for coverage.""" - assert add_numbers(0, 0) == 0 - assert add_numbers(-1, 1) == 0 - assert add_numbers(100, 200) == 300 - - -class TestPlaylistFunctions: - """Test playlist-related functions with automatic type checking.""" - - def test_create_playlist_data_with_description(self) -> None: - """Test creating playlist data with description.""" - result = create_playlist_data("My Playlist", "A great playlist") - - expected = { - "name": "My Playlist", - "description": "A great playlist", - "id": "playlist_my_playlist", - } - assert result == expected - - def test_create_playlist_data_without_description(self) -> None: - """Test creating playlist data without description.""" - result = create_playlist_data("Simple Playlist") - - expected = { - "name": "Simple Playlist", - "description": None, - "id": "playlist_simple_playlist", - } - assert result == expected - - def test_create_playlist_invalid_name_type(self) -> None: - """Test that typeguard automatically catches invalid name type.""" - with pytest.raises(TypeError): - # No decorator needed - typeguard import hook handles this - create_playlist_data(123, "description") # type: ignore[arg-type] - - -@pytest.mark.integration -class TestIntegrationExample: - """Example integration tests with automatic type validation.""" - - def test_integration_placeholder(self) -> None: - """Placeholder for real integration tests.""" - # All functions called here will have automatic type checking - assert True diff --git a/backend/tests/test_hypermodern.py b/backend/tests/test_hypermodern.py deleted file mode 100644 index 622f32c..0000000 --- a/backend/tests/test_hypermodern.py +++ /dev/null @@ -1,86 +0,0 @@ -""" -Tests demonstrating hypermodern typeguard usage. -No decorators needed - validation happens automatically. -""" - -import pytest -from backend import PlaylistManager, calculate_playlist_duration, process_user_data - - -class TestHypermodernTypeguard: - """Tests showing automatic type validation without decorators.""" - - def test_process_user_data_valid_types(self) -> None: - """Test with correct types - should work normally.""" - result = process_user_data(123, "John Doe", "JOHN@EXAMPLE.COM") - - expected = { - "id": 123, - "name": "John Doe", - "email": "john@example.com", - "display_name": "John Doe (john@example.com)", - } - assert result == expected - - def test_process_user_data_invalid_user_id(self) -> None: - """Test with wrong user_id type - typeguard catches automatically.""" - with pytest.raises(TypeError): - # This will fail because "123" is str, not int - process_user_data("123", "John", "john@example.com") # type: ignore[arg-type] - - def test_process_user_data_invalid_name(self) -> None: - """Test with wrong name type - typeguard catches automatically.""" - with pytest.raises(TypeError): - # This will fail because 123 is int, not str - process_user_data(123, 123, "john@example.com") # type: ignore[arg-type] - - def test_calculate_duration_valid(self) -> None: - """Test duration calculation with valid types.""" - durations = [3.5, 4.2, 2.8, 5.1] - total = calculate_playlist_duration(durations) - - assert abs(total - 15.6) < 0.001 # Float comparison - - def test_calculate_duration_invalid_list_type(self) -> None: - """Test with wrong parameter type - not a list.""" - with pytest.raises(TypeError): - # This will fail because "not a list" is str, not list[float] - calculate_playlist_duration("not a list") # type: ignore[arg-type] - - def test_calculate_duration_invalid_element_types(self) -> None: - """Test with wrong element types in list.""" - with pytest.raises(TypeError): - # This will fail because list contains strings, not floats - calculate_playlist_duration(["3.5", "4.2"]) # type: ignore[list-item] - - def test_playlist_manager_creation(self) -> None: - """Test playlist manager creation with valid types.""" - manager = PlaylistManager("My Playlist", 500) - - assert manager.name == "My Playlist" - assert manager.max_size == 500 - assert manager.get_track_count() == 0 - - def test_playlist_manager_invalid_name_type(self) -> None: - """Test playlist manager creation with invalid name type.""" - with pytest.raises(TypeError): - # This will fail because 123 is int, not str - PlaylistManager(123) # type: ignore[arg-type] - - def test_playlist_manager_add_track_valid(self) -> None: - """Test adding tracks with valid types.""" - manager = PlaylistManager("Test Playlist", 2) - - assert manager.add_track("track_1") is True - assert manager.add_track("track_2") is True - assert manager.add_track("track_3") is False # Exceeds max_size - - assert manager.get_track_count() == 2 - - def test_playlist_manager_add_track_invalid_type(self) -> None: - """Test adding track with invalid type.""" - manager = PlaylistManager("Test Playlist") - - with pytest.raises(TypeError): - # This will fail because 123 is int, not str - manager.add_track(123) # type: ignore[arg-type] diff --git a/frontend/package.json b/frontend/package.json index 5f13f0b..d4b65e3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,6 +3,9 @@ "private": true, "version": "0.0.0", "type": "module", + "engines": { + "node": ">=24.6.0" + }, "scripts": { "dev": "vite", "build": "vue-tsc && vite build", diff --git a/frontend/src/test-setup.ts b/frontend/src/test-setup.ts new file mode 100644 index 0000000..c97908a --- /dev/null +++ b/frontend/src/test-setup.ts @@ -0,0 +1,133 @@ +/** + * Automatic Zod validation setup for test runs + * This file sets up global hooks that automatically validate all Zod schemas during testing + */ + +import { beforeAll, afterAll } from 'vitest'; +import { z } from 'zod'; + +// Store original Zod parse methods +const originalParse = z.ZodType.prototype.parse; +const originalSafeParse = z.ZodType.prototype.safeParse; + +let validationCount = 0; +let validationErrors: string[] = []; + +/** + * Enhanced parse method that logs validation attempts during tests + * @param data - The data to validate against the schema + * @returns The parsed and validated data + */ +function enhancedParse(this: z.ZodType, data: unknown): T { + try { + const result = originalParse.call(this, data); + validationCount++; + + // Log successful validations in test mode + if (process.env.NODE_ENV === 'test') { + console.log(`✅ Zod validation passed (${validationCount} total)`); + } + + return result; + } catch (error) { + validationErrors.push(`Zod validation failed: ${error}`); + + // Log failed validations in test mode + if (process.env.NODE_ENV === 'test') { + console.error(`❌ Zod validation failed:`, error); + } + + throw error; + } +} + +/** + * Enhanced safeParse method that logs validation attempts during tests + * @param data - The data to validate against the schema + * @returns Safe parse result with success flag and data or error + */ +function enhancedSafeParse( + this: z.ZodType, + data: unknown +): z.SafeParseReturnType { + const result = originalSafeParse.call(this, data); + validationCount++; + + if (process.env.NODE_ENV === 'test') { + if (result.success) { + console.log(`✅ Zod safe validation passed (${validationCount} total)`); + } else { + console.warn(`⚠️ Zod safe validation failed but was handled gracefully`); + validationErrors.push(`Zod safe validation failed: ${result.error.message}`); + } + } + + return result; +} + +/** + * Install automatic Zod validation hooks for testing + */ +export function installZodTestHooks(): void { + // Replace Zod's parse methods with enhanced versions + z.ZodType.prototype.parse = enhancedParse; + z.ZodType.prototype.safeParse = enhancedSafeParse; + + // Enable global auto-validation flag + (globalThis as any).__AUTO_VALIDATE__ = true; + + console.log('🛡️ Automatic Zod validation hooks installed for test run'); +} + +/** + * Remove automatic Zod validation hooks + */ +export function uninstallZodTestHooks(): void { + // Restore original methods + z.ZodType.prototype.parse = originalParse; + z.ZodType.prototype.safeParse = originalSafeParse; + + // Disable global auto-validation flag + (globalThis as any).__AUTO_VALIDATE__ = false; + + console.log('🔧 Zod validation hooks removed'); +} + +/** + * Get validation statistics for test reporting + * @returns Object containing validation count and errors + */ +export function getValidationStats(): { + count: number; + errors: string[]; +} { + return { + count: validationCount, + errors: [...validationErrors], + }; +} + +/** + * Reset validation statistics + */ +export function resetValidationStats(): void { + validationCount = 0; + validationErrors = []; +} + +// Automatic setup for Vitest +beforeAll(() => { + installZodTestHooks(); + resetValidationStats(); +}); + +afterAll(() => { + const stats = getValidationStats(); + console.log(`📊 Test run completed with ${stats.count} Zod validations`); + + if (stats.errors.length > 0) { + console.warn(`⚠️ ${stats.errors.length} validation errors encountered during tests`); + } + + uninstallZodTestHooks(); +}); diff --git a/frontend/src/validation.ts b/frontend/src/validation.ts index 0918dbd..bcbe203 100644 --- a/frontend/src/validation.ts +++ b/frontend/src/validation.ts @@ -1,6 +1,6 @@ /** - * Hypermodern Zod validation setup - automatic runtime validation without explicit .parse() calls - * Follows the same philosophy as typeguard for Python + * Zod validation setup with automatic test hooks + * Schemas are automatically validated during test runs via test-setup.ts */ import { z } from 'zod'; @@ -28,99 +28,3 @@ export const UserSchema = z.object({ // Inferred types (no manual type definitions needed) export type Playlist = z.infer; export type User = z.infer; - -/** - * Global schema registry for automatic validation - */ -class SchemaRegistry { - private static schemas = new Map([ - ['Playlist', PlaylistSchema as z.ZodTypeAny], - ['User', UserSchema as z.ZodTypeAny], - ]); - - static register(name: string, schema: T): void { - this.schemas.set(name, schema as z.ZodTypeAny); - } - - static get(name: string): z.ZodTypeAny | undefined { - return this.schemas.get(name); - } - - static validate(typeName: string, data: unknown): T { - const schema = this.schemas.get(typeName); - if (!schema) { - throw new Error(`No schema registered for type: ${typeName}`); - } - return schema.parse(data) as T; - } - - static safeValidate( - typeName: string, - data: unknown - ): { success: true; data: T } | { success: false; error: z.ZodError } { - const schema = this.schemas.get(typeName); - if (!schema) { - return { - success: false, - error: new z.ZodError([ - { - code: 'custom', - message: `No schema registered for type: ${typeName}`, - path: [], - }, - ]), - }; - } - - const result = schema.safeParse(data); - return result.success - ? { success: true, data: result.data as T } - : { success: false, error: result.error }; - } -} - -/** - * Automatic validation wrapper - works like typeguard for Python - * In development/test: validates automatically - * In production: can be stripped by build process - */ -export class AutoValidator { - static validate(typeName: string, data: unknown): T { - // Check if auto-validation is enabled (development/test mode) - if (typeof globalThis !== 'undefined' && (globalThis as any).__AUTO_VALIDATE__) { - return SchemaRegistry.validate(typeName, data); - } - - // In production, validation is skipped for performance - return data as T; - } - - static safeValidate( - typeName: string, - data: unknown - ): { success: true; data: T } | { success: false; error: z.ZodError } { - if (typeof globalThis !== 'undefined' && (globalThis as any).__AUTO_VALIDATE__) { - return SchemaRegistry.safeValidate(typeName, data); - } - - // In production, assume data is valid - return { success: true, data: data as T }; - } - - static register(name: string, schema: T): void { - SchemaRegistry.register(name, schema); - } -} - -// Enable automatic validation in development/test environments -declare global { - var __AUTO_VALIDATE__: boolean; -} - -// Check environment and log status -const isDev = typeof globalThis !== 'undefined' && (globalThis as any).__AUTO_VALIDATE__; -if (isDev) { - console.log('🛡️ Hypermodern Zod validation enabled (development/test mode)'); -} else { - console.log('🏃‍♂️ Production mode - Zod validation optimized away'); -} diff --git a/frontend/tests/unit/App.test.ts b/frontend/tests/unit/App.test.ts index 161b018..01e69f9 100644 --- a/frontend/tests/unit/App.test.ts +++ b/frontend/tests/unit/App.test.ts @@ -1,41 +1,6 @@ -/** - * Example unit tests with automatic Zod validation - * Uses hypermodern approach where validation happens automatically - */ - import { describe, it, expect } from 'vitest' import { mount } from '@vue/test-utils' import App from '@/App.vue' -import { AutoValidator, type Playlist, type User } from '@/validation' - -/** - * Creates a playlist - validation happens automatically in development/test mode - * @param name - Playlist name - * @param description - Optional description - * @returns Playlist object (automatically validated) - */ -function createPlaylist(name: string, description?: string): Playlist { - const rawData = { - id: crypto.randomUUID(), - name, - description, - tracks: [] - } - - // In hypermodern setup, this would be handled by build-time transform - // For now, we use our auto-validator - return AutoValidator.validate('Playlist', rawData) -} - -/** - * Simulates API response handling with automatic validation - * @param apiResponse - Raw API data - * @returns Validated user object - */ -function handleUserApiResponse(apiResponse: unknown): User { - // Validation happens automatically - no explicit .parse() needed - return AutoValidator.validate('User', apiResponse) -} describe('App.vue', () => { it('renders properly', () => { @@ -43,81 +8,3 @@ describe('App.vue', () => { expect(wrapper.text()).toContain('Plex Playlist') }) }) - -describe('Automatic Zod validation (hypermodern approach)', () => { - it('validates playlist creation automatically', () => { - const playlist = createPlaylist('Test Playlist', 'Test description') - - expect(playlist.name).toBe('Test Playlist') - expect(playlist.description).toBe('Test description') - expect(playlist.tracks).toEqual([]) - expect(playlist.id).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i) - }) - - it('creates playlist without description automatically', () => { - const playlist = createPlaylist('Simple Playlist') - - expect(playlist.name).toBe('Simple Playlist') - expect(playlist.description).toBeUndefined() - expect(playlist.tracks).toEqual([]) - }) - - it('automatically validates API responses', () => { - const apiResponse = { - id: 123, - username: 'testuser', - email: 'test@example.com', - preferences: { - theme: 'dark', - notifications: false - } - } - - const user = handleUserApiResponse(apiResponse) - expect(user.username).toBe('testuser') - expect(user.preferences.theme).toBe('dark') - }) - - it('automatically catches validation errors', () => { - const invalidPlaylistData = { - id: 'not-a-uuid', // Invalid UUID - name: '', // Empty name - tracks: 'invalid' // Should be array - } - - expect(() => { - AutoValidator.validate('Playlist', invalidPlaylistData) - }).toThrow() - }) - - it('provides safe validation with error handling', () => { - const malformedData = { - id: 'not-a-uuid', - description: 'Missing name field' - } - - const result = AutoValidator.safeValidate('Playlist', malformedData) - - expect(result.success).toBe(false) - if (!result.success) { - expect(result.error.issues).toBeDefined() - expect(result.error.issues.length).toBeGreaterThan(0) - } - }) - - it('handles successful safe validation', () => { - const validData = { - id: crypto.randomUUID(), - name: 'Valid Playlist', - tracks: ['track1', 'track2'] - } - - const result = AutoValidator.safeValidate('Playlist', validData) - - expect(result.success).toBe(true) - if (result.success) { - expect(result.data.name).toBe('Valid Playlist') - expect(result.data.tracks).toEqual(['track1', 'track2']) - } - }) -}) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 0e1e538..918cc47 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -2,33 +2,10 @@ import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import { fileURLToPath, URL } from 'node:url' -// Custom plugin for automatic Zod validation in development -function zodAutoValidation() { - return { - name: 'zod-auto-validation', - transform(code: string, id: string) { - // Only apply in development/test environments - if (process.env.NODE_ENV === 'production') { - return null - } - - // Add automatic validation for functions with Zod schemas - if (id.includes('src/') && id.endsWith('.ts')) { - // This would normally be a more sophisticated transform - // For now, we rely on our AutoValidator pattern - return null - } - - return null - } - } -} - // https://vitejs.dev/config/ export default defineConfig({ plugins: [ - vue(), - zodAutoValidation() + vue() ], resolve: { alias: { diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index 5a79352..075c586 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -12,6 +12,7 @@ export default defineConfig({ test: { environment: 'jsdom', globals: true, + setupFiles: ['./src/test-setup.ts'], // Automatically install Zod validation hooks coverage: { provider: 'v8', reporter: ['text', 'json', 'html'], @@ -23,7 +24,15 @@ export default defineConfig({ 'coverage/', 'tests/', 'playwright.config.ts' - ] + ], + thresholds: { + global: { + lines: 85, + functions: 85, + branches: 85, + statements: 85 + } + } } } })