diff --git a/.version b/.version index 64a3b790..d5bd67d2 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -v0.14.1 +v0.15.3 diff --git a/frontend/src/api/__tests__/dnsProviders.test.ts b/frontend/src/api/__tests__/dnsProviders.test.ts index dc893408..a23c3195 100644 --- a/frontend/src/api/__tests__/dnsProviders.test.ts +++ b/frontend/src/api/__tests__/dnsProviders.test.ts @@ -199,7 +199,7 @@ describe('createDNSProvider', () => { }) await expect( - createDNSProvider({ ...validRequest, provider_type: 'invalid' as any }) + createDNSProvider({ ...validRequest, provider_type: 'invalid' as DNSProviderRequest['provider_type'] }) ).rejects.toMatchObject({ response: { status: 400 }, }) diff --git a/frontend/src/components/CredentialManager.tsx b/frontend/src/components/CredentialManager.tsx index f270e5d3..becfcfb4 100644 --- a/frontend/src/components/CredentialManager.tsx +++ b/frontend/src/components/CredentialManager.tsx @@ -68,11 +68,12 @@ export default function CredentialManager({ toast.success(t('credentials.deleteSuccess', 'Credential deleted successfully')) setDeleteConfirm(null) refetch() - } catch (error: any) { + } catch (error: unknown) { + const err = error as { response?: { data?: { error?: string } }; message?: string } toast.error( t('credentials.deleteFailed', 'Failed to delete credential') + ': ' + - (error.response?.data?.error || error.message) + (err.response?.data?.error || err.message) ) } } @@ -90,11 +91,12 @@ export default function CredentialManager({ toast.error(result.error || t('credentials.testFailed', 'Credential test failed')) } refetch() - } catch (error: any) { + } catch (error: unknown) { + const err = error as { response?: { data?: { error?: string } }; message?: string } toast.error( t('credentials.testFailed', 'Failed to test credential') + ': ' + - (error.response?.data?.error || error.message) + (err.response?.data?.error || err.message) ) } finally { setTestingId(null) @@ -367,7 +369,8 @@ function CredentialForm({ } } setErrors((prev) => { - const { zone_filter, ...rest } = prev + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { zone_filter: _, ...rest } = prev return rest }) return true @@ -426,11 +429,12 @@ function CredentialForm({ await createMutation.mutateAsync({ providerId, data }) } onSuccess() - } catch (error: any) { + } catch (error: unknown) { + const err = error as { response?: { data?: { error?: string } }; message?: string } toast.error( t('credentials.saveFailed', 'Failed to save credential') + ': ' + - (error.response?.data?.error || error.message) + (err.response?.data?.error || err.message) ) } } @@ -451,11 +455,12 @@ function CredentialForm({ } else { toast.error(result.error || t('credentials.testFailed', 'Test failed')) } - } catch (error: any) { + } catch (error: unknown) { + const err = error as { response?: { data?: { error?: string } }; message?: string } toast.error( t('credentials.testFailed', 'Test failed') + ': ' + - (error.response?.data?.error || error.message) + (err.response?.data?.error || err.message) ) } } diff --git a/frontend/src/components/DNSProviderForm.tsx b/frontend/src/components/DNSProviderForm.tsx index f45b906d..30954db5 100644 --- a/frontend/src/components/DNSProviderForm.tsx +++ b/frontend/src/components/DNSProviderForm.tsx @@ -65,7 +65,7 @@ export default function DNSProviderForm({ setPropagationTimeout(provider.propagation_timeout) setPollingInterval(provider.polling_interval) setIsDefault(provider.is_default) - setUseMultiCredentials((provider as any).use_multi_credentials || false) + setUseMultiCredentials((provider as { use_multi_credentials?: boolean }).use_multi_credentials || false) setCredentials({}) // Don't pre-fill credentials (they're encrypted) } else { resetForm() @@ -91,7 +91,7 @@ export default function DNSProviderForm({ // Prefer dynamic fields from API if available if (dynamicFields) { return { - type: dynamicFields.type as any, + type: dynamicFields.type as DNSProviderTypeInfo['type'], name: dynamicFields.name, fields: [ ...dynamicFields.required_fields.map(f => ({ ...f, required: true })), @@ -118,7 +118,7 @@ export default function DNSProviderForm({ const data: DNSProviderRequest = { name: name || 'Test', - provider_type: providerType as any, + provider_type: providerType as DNSProviderRequest['provider_type'], credentials, propagation_timeout: propagationTimeout, polling_interval: pollingInterval, @@ -130,10 +130,11 @@ export default function DNSProviderForm({ success: result.success, message: result.message || result.error || t('dnsProviders.testSuccess'), }) - } catch (error: any) { + } catch (error: unknown) { + const err = error as { response?: { data?: { error?: string } }; message?: string } setTestResult({ success: false, - message: error.response?.data?.error || error.message || t('dnsProviders.testFailed'), + message: err.response?.data?.error || err.message || t('dnsProviders.testFailed'), }) } } @@ -144,7 +145,7 @@ export default function DNSProviderForm({ const data: DNSProviderRequest = { name, - provider_type: providerType as any, + provider_type: providerType as DNSProviderRequest['provider_type'], credentials, propagation_timeout: propagationTimeout, polling_interval: pollingInterval, @@ -348,7 +349,7 @@ export default function DNSProviderForm({ try { await enableMultiCredsMutation.mutateAsync(provider.id) setUseMultiCredentials(true) - } catch (error: any) { + } catch (error: unknown) { console.error('Failed to enable multi-credentials:', error) } } else if (!checked && useMultiCredentials && existingCredentials?.length) { diff --git a/frontend/src/components/__tests__/CredentialManager.test.tsx b/frontend/src/components/__tests__/CredentialManager.test.tsx index cfb23146..57733e95 100644 --- a/frontend/src/components/__tests__/CredentialManager.test.tsx +++ b/frontend/src/components/__tests__/CredentialManager.test.tsx @@ -125,27 +125,27 @@ describe('CredentialManager', () => { data: mockCredentials, isLoading: false, refetch: mockRefetch, - } as any) + } as unknown as ReturnType) vi.mocked(useCreateCredential).mockReturnValue({ mutateAsync: mockMutateAsync, isPending: false, - } as any) + } as unknown as ReturnType) vi.mocked(useUpdateCredential).mockReturnValue({ mutateAsync: mockMutateAsync, isPending: false, - } as any) + } as unknown as ReturnType) vi.mocked(useDeleteCredential).mockReturnValue({ mutateAsync: mockMutateAsync, isPending: false, - } as any) + } as unknown as ReturnType) vi.mocked(useTestCredential).mockReturnValue({ mutateAsync: mockMutateAsync, isPending: false, - } as any) + } as unknown as ReturnType) }) describe('Rendering', () => { @@ -242,7 +242,7 @@ describe('CredentialManager', () => { data: [], isLoading: false, refetch: mockRefetch, - } as any) + } as unknown as ReturnType) renderWithClient( { data: [], isLoading: false, refetch: mockRefetch, - } as any) + } as unknown as ReturnType) renderWithClient( { data: undefined, isLoading: true, refetch: mockRefetch, - } as any) + } as unknown as ReturnType) renderWithClient( { isError: true, error: new Error('Failed to fetch'), refetch: mockRefetch, - } as any) + } as unknown as ReturnType) renderWithClient( { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) }) describe('Rendering', () => { @@ -232,7 +232,7 @@ describe('DNSProviderSelector', () => { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) renderWithClient() @@ -254,7 +254,7 @@ describe('DNSProviderSelector', () => { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) renderWithClient() @@ -272,7 +272,7 @@ describe('DNSProviderSelector', () => { isLoading: true, isError: false, error: null, - } as any) + } as unknown as ReturnType) renderWithClient() @@ -286,7 +286,7 @@ describe('DNSProviderSelector', () => { isLoading: true, isError: false, error: null, - } as any) + } as unknown as ReturnType) renderWithClient() @@ -301,7 +301,7 @@ describe('DNSProviderSelector', () => { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) renderWithClient() @@ -316,7 +316,7 @@ describe('DNSProviderSelector', () => { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) renderWithClient() @@ -421,7 +421,7 @@ describe('DNSProviderSelector', () => { isLoading: true, isError: false, error: null, - } as any) + } as unknown as ReturnType) renderWithClient() diff --git a/frontend/src/components/__tests__/ManualDNSChallenge.test.tsx b/frontend/src/components/__tests__/ManualDNSChallenge.test.tsx index 28290058..82f69ebd 100644 --- a/frontend/src/components/__tests__/ManualDNSChallenge.test.tsx +++ b/frontend/src/components/__tests__/ManualDNSChallenge.test.tsx @@ -144,7 +144,7 @@ describe('ManualDNSChallenge', () => { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) vi.mocked(useManualChallengeMutations).mockReturnValue({ verifyMutation: { @@ -159,7 +159,7 @@ describe('ManualDNSChallenge', () => { mutateAsync: vi.fn(), isPending: false, }, - } as any) + } as unknown as ReturnType) }) afterEach(() => { @@ -401,7 +401,7 @@ describe('ManualDNSChallenge', () => { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) const verifiedChallenge: ManualChallenge = { ...mockChallenge, @@ -424,7 +424,7 @@ describe('ManualDNSChallenge', () => { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) const expiredChallenge: ManualChallenge = { ...mockChallenge, @@ -449,7 +449,7 @@ describe('ManualDNSChallenge', () => { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) const failedChallenge: ManualChallenge = { ...mockChallenge, @@ -477,7 +477,7 @@ describe('ManualDNSChallenge', () => { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) // Re-render to trigger effect const verifiedChallenge: ManualChallenge = { @@ -517,7 +517,7 @@ describe('ManualDNSChallenge', () => { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) const expiredChallenge: ManualChallenge = { ...mockChallenge, @@ -611,7 +611,7 @@ describe('ManualDNSChallenge', () => { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) const verifiedChallenge: ManualChallenge = { ...mockChallenge, @@ -639,7 +639,7 @@ describe('ManualDNSChallenge', () => { mutateAsync: vi.fn(), isPending: false, }, - } as any) + } as unknown as ReturnType) renderComponent() @@ -661,7 +661,7 @@ describe('ManualDNSChallenge', () => { mutateAsync: vi.fn(), isPending: false, }, - } as any) + } as unknown as ReturnType) renderComponent() @@ -683,7 +683,7 @@ describe('ManualDNSChallenge', () => { isLoading: false, isError: false, error: null, - } as any) + } as unknown as ReturnType) const failedChallenge: ManualChallenge = { ...mockChallenge, diff --git a/frontend/src/components/dns-providers/ManualDNSChallenge.tsx b/frontend/src/components/dns-providers/ManualDNSChallenge.tsx index e130853c..b4949ea6 100644 --- a/frontend/src/components/dns-providers/ManualDNSChallenge.tsx +++ b/frontend/src/components/dns-providers/ManualDNSChallenge.tsx @@ -212,8 +212,9 @@ export default function ManualDNSChallenge({ } else if (!result.dns_found) { toast.warning(t('dnsProvider.manual.dnsNotFound')) } - } catch (error: any) { - toast.error(error.response?.data?.message || t('dnsProvider.manual.verifyFailed')) + } catch (error: unknown) { + const err = error as { response?: { data?: { message?: string } } } + toast.error(err.response?.data?.message || t('dnsProvider.manual.verifyFailed')) } }, [verifyMutation, providerId, challenge.id, t]) @@ -226,8 +227,9 @@ export default function ManualDNSChallenge({ }) toast.info(t('dnsProvider.manual.challengeCancelled')) onCancel() - } catch (error: any) { - toast.error(error.response?.data?.message || t('dnsProvider.manual.cancelFailed')) + } catch (error: unknown) { + const err = error as { response?: { data?: { message?: string } } } + toast.error(err.response?.data?.message || t('dnsProvider.manual.cancelFailed')) } }, [deleteMutation, providerId, challenge.id, onCancel, t]) diff --git a/frontend/src/hooks/__tests__/useCredentials.test.tsx b/frontend/src/hooks/__tests__/useCredentials.test.tsx index 32e8d881..5c970093 100644 --- a/frontend/src/hooks/__tests__/useCredentials.test.tsx +++ b/frontend/src/hooks/__tests__/useCredentials.test.tsx @@ -37,8 +37,8 @@ describe('useCredentials', () => { const mockCredentials = [ { id: 1, label: 'Test', zone_filter: 'example.com' }, { id: 2, label: 'Test2', zone_filter: '*.test.com' }, - ] - vi.mocked(credentialsApi.getCredentials).mockResolvedValue(mockCredentials as any) + ] as Awaited> + vi.mocked(credentialsApi.getCredentials).mockResolvedValue(mockCredentials) const { result } = renderHook(() => useCredentials(1), { wrapper: createWrapper() }) @@ -64,8 +64,8 @@ describe('useCredentials', () => { describe('useCredential', () => { it('fetches a single credential', async () => { - const mockCredential = { id: 1, label: 'Test', zone_filter: 'example.com' } - vi.mocked(credentialsApi.getCredential).mockResolvedValue(mockCredential as any) + const mockCredential = { id: 1, label: 'Test', zone_filter: 'example.com' } as Awaited> + vi.mocked(credentialsApi.getCredential).mockResolvedValue(mockCredential) const { result } = renderHook(() => useCredential(1, 1), { wrapper: createWrapper() }) @@ -85,8 +85,8 @@ describe('useCredentials', () => { describe('useCreateCredential', () => { it('creates a credential and invalidates queries', async () => { - const mockCredential = { id: 3, label: 'New', zone_filter: 'new.com' } - vi.mocked(credentialsApi.createCredential).mockResolvedValue(mockCredential as any) + const mockCredential = { id: 3, label: 'New', zone_filter: 'new.com' } as Awaited> + vi.mocked(credentialsApi.createCredential).mockResolvedValue(mockCredential) const { result } = renderHook(() => useCreateCredential(), { wrapper: createWrapper() }) @@ -122,8 +122,8 @@ describe('useCredentials', () => { describe('useUpdateCredential', () => { it('updates a credential and invalidates queries', async () => { - const mockCredential = { id: 1, label: 'Updated', zone_filter: 'updated.com' } - vi.mocked(credentialsApi.updateCredential).mockResolvedValue(mockCredential as any) + const mockCredential = { id: 1, label: 'Updated', zone_filter: 'updated.com' } as Awaited> + vi.mocked(credentialsApi.updateCredential).mockResolvedValue(mockCredential) const { result } = renderHook(() => useUpdateCredential(), { wrapper: createWrapper() }) diff --git a/frontend/src/hooks/useDocker.ts b/frontend/src/hooks/useDocker.ts index 7a8017fc..24e11e0e 100644 --- a/frontend/src/hooks/useDocker.ts +++ b/frontend/src/hooks/useDocker.ts @@ -12,10 +12,11 @@ export function useDocker(host?: string | null, serverId?: string | null) { queryFn: async () => { try { return await dockerApi.listContainers(host || undefined, serverId || undefined) - } catch (err: any) { + } catch (err: unknown) { // Extract helpful error message from response - if (err.response?.status === 503) { - const details = err.response?.data?.details + const error = err as { response?: { status?: number; data?: { details?: string } } } + if (error.response?.status === 503) { + const details = error.response?.data?.details const message = details || 'Docker service unavailable. Check that Docker is running.' throw new Error(message) } diff --git a/frontend/src/pages/DNSProviders.tsx b/frontend/src/pages/DNSProviders.tsx index 4e4be582..5939bb30 100644 --- a/frontend/src/pages/DNSProviders.tsx +++ b/frontend/src/pages/DNSProviders.tsx @@ -30,11 +30,12 @@ export default function DNSProviders() { try { await deleteMutation.mutateAsync(id) toast.success(t('dnsProviders.deleteSuccess')) - } catch (error: any) { + } catch (error: unknown) { + const err = error as { response?: { data?: { error?: string } }; message?: string } toast.error( t('dnsProviders.deleteFailed') + ': ' + - (error.response?.data?.error || error.message) + (err.response?.data?.error || err.message) ) } } @@ -48,11 +49,12 @@ export default function DNSProviders() { } else { toast.error(result.error || t('dnsProviders.testFailed')) } - } catch (error: any) { + } catch (error: unknown) { + const err = error as { response?: { data?: { error?: string } }; message?: string } toast.error( t('dnsProviders.testFailed') + ': ' + - (error.response?.data?.error || error.message) + (err.response?.data?.error || err.message) ) } finally { setTestingProviderId(null) diff --git a/frontend/src/pages/Plugins.tsx b/frontend/src/pages/Plugins.tsx index ee9bd511..05516b99 100644 --- a/frontend/src/pages/Plugins.tsx +++ b/frontend/src/pages/Plugins.tsx @@ -49,9 +49,10 @@ export default function Plugins() { toast.success(t('plugins.enableSuccess', 'Plugin enabled successfully')) } refetch() - } catch (error: any) { + } catch (error: unknown) { + const err = error as { response?: { data?: { error?: string } }; message?: string } const message = - error.response?.data?.error || error.message || t('plugins.toggleFailed', 'Failed to toggle plugin') + err.response?.data?.error || err.message || t('plugins.toggleFailed', 'Failed to toggle plugin') toast.error(message) } } @@ -63,9 +64,10 @@ export default function Plugins() { t('plugins.reloadSuccess', 'Plugins reloaded: {{count}} loaded', { count: result.count }) ) refetch() - } catch (error: any) { + } catch (error: unknown) { + const err = error as { response?: { data?: { error?: string } }; message?: string } const message = - error.response?.data?.error || error.message || t('plugins.reloadFailed', 'Failed to reload plugins') + err.response?.data?.error || err.message || t('plugins.reloadFailed', 'Failed to reload plugins') toast.error(message) } } diff --git a/frontend/src/pages/UsersPage.tsx b/frontend/src/pages/UsersPage.tsx index 93e672a6..1e2f0a08 100644 --- a/frontend/src/pages/UsersPage.tsx +++ b/frontend/src/pages/UsersPage.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react' +import { useState, useEffect, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { Link } from 'react-router-dom' @@ -83,7 +83,7 @@ function InviteModal({ isOpen, onClose, proxyHosts }: InviteModalProps) { useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { - handleClose() + onClose() } } @@ -91,7 +91,7 @@ function InviteModal({ isOpen, onClose, proxyHosts }: InviteModalProps) { document.addEventListener('keydown', handleKeyDown) return () => document.removeEventListener('keydown', handleKeyDown) } - }, [isOpen]) + }, [isOpen, onClose]) // Fetch preview when email changes useEffect(() => { @@ -385,10 +385,14 @@ function PermissionsModal({ isOpen, onClose, user, proxyHosts }: PermissionsModa }, [user]) // Keyboard navigation - close on Escape + const handleClose = useCallback(() => { + onClose() + }, [onClose]) + useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.key === 'Escape') { - onClose() + handleClose() } } @@ -396,7 +400,7 @@ function PermissionsModal({ isOpen, onClose, user, proxyHosts }: PermissionsModa document.addEventListener('keydown', handleKeyDown) return () => document.removeEventListener('keydown', handleKeyDown) } - }, [isOpen, onClose]) + }, [isOpen, handleClose]) const updatePermissionsMutation = useMutation({ mutationFn: async () => { diff --git a/frontend/src/pages/__tests__/Plugins.test.tsx b/frontend/src/pages/__tests__/Plugins.test.tsx index ddf26278..8f4bcf91 100644 --- a/frontend/src/pages/__tests__/Plugins.test.tsx +++ b/frontend/src/pages/__tests__/Plugins.test.tsx @@ -8,7 +8,7 @@ import type { PluginInfo } from '../../api/plugins' // Mock i18n vi.mock('react-i18next', () => ({ useTranslation: () => ({ - t: (key: string, defaultValue?: string | Record) => { + t: (key: string, defaultValue?: string | Record) => { const translations: Record = { 'plugins.title': 'DNS Provider Plugins', 'plugins.description': 'Manage built-in and external DNS provider plugins for certificate automation', @@ -183,7 +183,7 @@ describe('Plugins page', () => { vi.mocked(useReloadPlugins).mockReturnValue({ mutateAsync: mockReloadMutation, isPending: false, - } as any) + } as unknown as ReturnType) renderWithQueryClient() @@ -262,7 +262,7 @@ describe('Plugins page', () => { vi.mocked(useDisablePlugin).mockReturnValue({ mutateAsync: mockDisableMutation, isPending: false, - } as any) + } as unknown as ReturnType) renderWithQueryClient() @@ -278,7 +278,7 @@ describe('Plugins page', () => { data: undefined, isLoading: true, refetch: vi.fn(), - } as any) + } as unknown as ReturnType) renderWithQueryClient() @@ -293,7 +293,7 @@ describe('Plugins page', () => { data: [], isLoading: false, refetch: vi.fn(), - } as any) + } as unknown as ReturnType) renderWithQueryClient() diff --git a/frontend/src/test/setup.ts b/frontend/src/test/setup.ts index 21d39899..0449d66a 100644 --- a/frontend/src/test/setup.ts +++ b/frontend/src/test/setup.ts @@ -1,10 +1,16 @@ +/// +// eslint-disable-next-line @typescript-eslint/triple-slash-reference +/// + +// Import jest-dom matchers at runtime to extend expect() +import '@testing-library/jest-dom/vitest' + // Ensure React's act environment flag is set for React 18+ to avoid warnings // This must be set before importing testing utilities. // See: https://github.com/facebook/react/issues/24560#issuecomment-1021997243 declare global { var IS_REACT_ACT_ENVIRONMENT: boolean | undefined } globalThis.IS_REACT_ACT_ENVIRONMENT = true -import '@testing-library/jest-dom/vitest' import { cleanup } from '@testing-library/react' import { afterEach, vi } from 'vitest' @@ -17,11 +23,10 @@ vi.mock('react-i18next', async () => { // Helper to get nested translation value by dot-notation key function getTranslation(key: string): string { const keys = key.split('.') - // eslint-disable-next-line @typescript-eslint/no-explicit-any - let result: any = enTranslations + let result: unknown = enTranslations for (const k of keys) { - if (result && typeof result === 'object' && k in result) { - result = result[k] + if (result && typeof result === 'object' && k in (result as Record)) { + result = (result as Record)[k] } else { // Key not found, return the key itself return key diff --git a/frontend/tsconfig.build.json b/frontend/tsconfig.build.json index eb1cc0f0..528ca723 100644 --- a/frontend/tsconfig.build.json +++ b/frontend/tsconfig.build.json @@ -1,10 +1,5 @@ { "extends": "./tsconfig.json", - "compilerOptions": { - // Exclude test-only type definitions from production build - // Keep node types but remove vitest/testing-library which are devDependencies - "types": ["node"] - }, "include": ["src"], "exclude": [ "src/**/*.test.ts", diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json index e0966ee3..849da642 100644 --- a/frontend/tsconfig.json +++ b/frontend/tsconfig.json @@ -18,8 +18,7 @@ "strict": true, "noUnusedLocals": true, "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true, - "types": ["vitest/globals", "@testing-library/jest-dom/vitest"] + "noFallthroughCasesInSwitch": true }, "include": ["src", "**/*.test.ts", "**/*.test.tsx"], "references": [{ "path": "./tsconfig.node.json" }] diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index bce5d470..4e2f9969 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -8,6 +8,10 @@ export default defineConfig({ globals: true, environment: 'jsdom', setupFiles: './src/test/setup.ts', + // TypeScript types for test globals - these are automatically available in test files + typecheck: { + tsconfig: './tsconfig.json', + }, exclude: [ 'node_modules/**', 'dist/**',