diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 8c5d387b..0f4080d9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,13 +1,13 @@ import { Suspense, lazy } from 'react' -import { Navigate } from 'react-router-dom' -import { BrowserRouter as Router, Routes, Route, Outlet } from 'react-router-dom' import { Toaster } from 'react-hot-toast' +import { BrowserRouter as Router, Routes, Route, Outlet, Navigate } from 'react-router-dom' + import Layout from './components/Layout' -import { ToastContainer } from './components/Toast' -import { SetupGuard } from './components/SetupGuard' import { LoadingOverlay } from './components/LoadingStates' import RequireAuth from './components/RequireAuth' import RequireRole from './components/RequireRole' +import { SetupGuard } from './components/SetupGuard' +import { ToastContainer } from './components/Toast' import { AuthProvider } from './context/AuthContext' // Lazy load pages for code splitting diff --git a/frontend/src/__tests__/i18n.test.ts b/frontend/src/__tests__/i18n.test.ts index cac1d1b0..6df0c0a5 100644 --- a/frontend/src/__tests__/i18n.test.ts +++ b/frontend/src/__tests__/i18n.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest' + import i18n from '../i18n' describe('i18n configuration', () => { @@ -13,9 +14,9 @@ describe('i18n configuration', () => { it('has all required language resources', () => { const languages = ['en', 'es', 'fr', 'de', 'zh'] - languages.forEach((lang) => { + for (const lang of languages) { expect(i18n.hasResourceBundle(lang, 'translation')).toBe(true) - }) + } }) it('translates common keys', () => { diff --git a/frontend/src/api/__tests__/accessLists.test.ts b/frontend/src/api/__tests__/accessLists.test.ts index a2283c82..6942df56 100644 --- a/frontend/src/api/__tests__/accessLists.test.ts +++ b/frontend/src/api/__tests__/accessLists.test.ts @@ -1,7 +1,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { accessListsApi } from '../accessLists'; + +import { accessListsApi, type AccessList } from '../accessLists'; import client from '../client'; -import type { AccessList } from '../accessLists'; + // Mock the client module vi.mock('../client', () => ({ diff --git a/frontend/src/api/__tests__/backups.test.ts b/frontend/src/api/__tests__/backups.test.ts index eb063070..dd18506f 100644 --- a/frontend/src/api/__tests__/backups.test.ts +++ b/frontend/src/api/__tests__/backups.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' + import client from '../../api/client' import { getBackups, createBackup, restoreBackup, deleteBackup } from '../backups' diff --git a/frontend/src/api/__tests__/certificates.test.ts b/frontend/src/api/__tests__/certificates.test.ts index 3d1ba01c..751e82b3 100644 --- a/frontend/src/api/__tests__/certificates.test.ts +++ b/frontend/src/api/__tests__/certificates.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; + +import { getCertificates, uploadCertificate, deleteCertificate, type Certificate } from '../certificates'; import client from '../client'; -import { getCertificates, uploadCertificate, deleteCertificate, Certificate } from '../certificates'; vi.mock('../client', () => ({ default: { diff --git a/frontend/src/api/__tests__/client.test.ts b/frontend/src/api/__tests__/client.test.ts index ee5c2b12..6d031bb4 100644 --- a/frontend/src/api/__tests__/client.test.ts +++ b/frontend/src/api/__tests__/client.test.ts @@ -1,5 +1,8 @@ +import axios from 'axios' import { beforeEach, describe, it, expect, vi, afterEach } from 'vitest' +import { setAuthErrorHandler, setAuthToken } from '../client' + type ResponseHandler = (value: unknown) => unknown type ErrorHandler = (error: ResponseError) => Promise @@ -45,10 +48,6 @@ vi.mock('axios', () => { } }) -// Must import AFTER mock definition -import { setAuthErrorHandler, setAuthToken } from '../client' -import axios from 'axios' - // Get mock client instance for header assertions const getMockClient = () => { const mockAxios = vi.mocked(axios) diff --git a/frontend/src/api/__tests__/consoleEnrollment.test.ts b/frontend/src/api/__tests__/consoleEnrollment.test.ts index e1f890b4..4b90508b 100644 --- a/frontend/src/api/__tests__/consoleEnrollment.test.ts +++ b/frontend/src/api/__tests__/consoleEnrollment.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -import * as consoleEnrollment from '../consoleEnrollment' + import client from '../client' +import * as consoleEnrollment from '../consoleEnrollment' vi.mock('../client') @@ -480,13 +481,10 @@ describe('consoleEnrollment API', () => { } vi.mocked(client.post).mockRejectedValue(error) - try { - await consoleEnrollment.enrollConsole(payload) - } catch (e: unknown) { - // Error message should NOT contain the key - const error = e as { response?: { data?: { error?: string } } } - expect(error.response?.data?.error).not.toContain('cs-enroll-sensitive-key') - } + const thrown = await consoleEnrollment.enrollConsole(payload).catch((e: unknown) => e) + const caughtError = thrown as { response?: { data?: { error?: string } } } + // Error message should NOT contain the key + expect(caughtError.response?.data?.error).not.toContain('cs-enroll-sensitive-key') }) it('should handle correlation_id for debugging without exposing keys', async () => { diff --git a/frontend/src/api/__tests__/credentials.test.ts b/frontend/src/api/__tests__/credentials.test.ts index 29ddb7b9..3322b850 100644 --- a/frontend/src/api/__tests__/credentials.test.ts +++ b/frontend/src/api/__tests__/credentials.test.ts @@ -1,4 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' + +import client from '../client' import { getCredentials, getCredential, @@ -11,7 +13,6 @@ import { type CredentialRequest, type CredentialTestResult, } from '../credentials' -import client from '../client' vi.mock('../client') diff --git a/frontend/src/api/__tests__/crowdsec.test.ts b/frontend/src/api/__tests__/crowdsec.test.ts index 34460efa..38e46d4a 100644 --- a/frontend/src/api/__tests__/crowdsec.test.ts +++ b/frontend/src/api/__tests__/crowdsec.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -import * as crowdsec from '../crowdsec' + import client from '../client' +import * as crowdsec from '../crowdsec' vi.mock('../client') diff --git a/frontend/src/api/__tests__/dnsDetection.test.ts b/frontend/src/api/__tests__/dnsDetection.test.ts index 5daf5b1c..6b20ad8f 100644 --- a/frontend/src/api/__tests__/dnsDetection.test.ts +++ b/frontend/src/api/__tests__/dnsDetection.test.ts @@ -1,7 +1,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -import { detectDNSProvider, getDetectionPatterns } from '../dnsDetection' + import client from '../client' -import type { DetectionResult, NameserverPattern } from '../dnsDetection' +import { detectDNSProvider, getDetectionPatterns, type DetectionResult, type NameserverPattern } from '../dnsDetection' + vi.mock('../client') diff --git a/frontend/src/api/__tests__/dnsProviders.test.ts b/frontend/src/api/__tests__/dnsProviders.test.ts index a23c3195..fa825a36 100644 --- a/frontend/src/api/__tests__/dnsProviders.test.ts +++ b/frontend/src/api/__tests__/dnsProviders.test.ts @@ -1,4 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' + +import client from '../client' import { getDNSProviders, getDNSProvider, @@ -12,7 +14,6 @@ import { type DNSProviderRequest, type DNSProviderTypeInfo, } from '../dnsProviders' -import client from '../client' vi.mock('../client') diff --git a/frontend/src/api/__tests__/docker.test.ts b/frontend/src/api/__tests__/docker.test.ts index 0a435e6c..9eebc353 100644 --- a/frontend/src/api/__tests__/docker.test.ts +++ b/frontend/src/api/__tests__/docker.test.ts @@ -1,6 +1,7 @@ import { vi, describe, it, expect, beforeEach } from 'vitest'; -import { dockerApi } from '../docker'; + import client from '../client'; +import { dockerApi } from '../docker'; vi.mock('../client', () => ({ default: { diff --git a/frontend/src/api/__tests__/domains.test.ts b/frontend/src/api/__tests__/domains.test.ts index 3181876e..4ccdfabd 100644 --- a/frontend/src/api/__tests__/domains.test.ts +++ b/frontend/src/api/__tests__/domains.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; + import client from '../client'; -import { getDomains, createDomain, deleteDomain, Domain } from '../domains'; +import { getDomains, createDomain, deleteDomain, type Domain } from '../domains'; vi.mock('../client', () => ({ default: { diff --git a/frontend/src/api/__tests__/encryption.test.ts b/frontend/src/api/__tests__/encryption.test.ts index 1b62433a..b8ad3ad2 100644 --- a/frontend/src/api/__tests__/encryption.test.ts +++ b/frontend/src/api/__tests__/encryption.test.ts @@ -1,4 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' + +import client from '../client' import { getEncryptionStatus, rotateEncryptionKey, @@ -9,7 +11,6 @@ import { type RotationHistoryEntry, type KeyValidationResult, } from '../encryption' -import client from '../client' vi.mock('../client') diff --git a/frontend/src/api/__tests__/import.test.ts b/frontend/src/api/__tests__/import.test.ts index 60cccf04..ba4c9587 100644 --- a/frontend/src/api/__tests__/import.test.ts +++ b/frontend/src/api/__tests__/import.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { uploadCaddyfile, uploadCaddyfilesMulti, getImportPreview, commitImport, cancelImport, getImportStatus } from '../import'; + import client from '../client'; +import { uploadCaddyfile, uploadCaddyfilesMulti, getImportPreview, commitImport, cancelImport, getImportStatus } from '../import'; vi.mock('../client', () => ({ default: { diff --git a/frontend/src/api/__tests__/jsonImport.test.ts b/frontend/src/api/__tests__/jsonImport.test.ts index ca41af35..011271af 100644 --- a/frontend/src/api/__tests__/jsonImport.test.ts +++ b/frontend/src/api/__tests__/jsonImport.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { uploadJSONExport, commitJSONImport, cancelJSONImport } from '../jsonImport'; + import client from '../client'; +import { uploadJSONExport, commitJSONImport, cancelJSONImport } from '../jsonImport'; vi.mock('../client', () => ({ default: { diff --git a/frontend/src/api/__tests__/logs-websocket.test.ts b/frontend/src/api/__tests__/logs-websocket.test.ts index 1fb98906..a18c6017 100644 --- a/frontend/src/api/__tests__/logs-websocket.test.ts +++ b/frontend/src/api/__tests__/logs-websocket.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; + import { connectLiveLogs } from '../logs'; // Mock WebSocket diff --git a/frontend/src/api/__tests__/logs.http.test.ts b/frontend/src/api/__tests__/logs.http.test.ts index b9e0067f..96d02285 100644 --- a/frontend/src/api/__tests__/logs.http.test.ts +++ b/frontend/src/api/__tests__/logs.http.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' + import client from '../client' import { downloadLog, getLogContent, getLogs } from '../logs' diff --git a/frontend/src/api/__tests__/manualChallenge.test.ts b/frontend/src/api/__tests__/manualChallenge.test.ts index f7a81987..16ac76f2 100644 --- a/frontend/src/api/__tests__/manualChallenge.test.ts +++ b/frontend/src/api/__tests__/manualChallenge.test.ts @@ -1,4 +1,6 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' + +import client from '../client' import { getChallenge, createChallenge, @@ -6,7 +8,6 @@ import { pollChallenge, deleteChallenge, } from '../manualChallenge' -import client from '../client' vi.mock('../client', () => ({ default: { diff --git a/frontend/src/api/__tests__/notifications.test.ts b/frontend/src/api/__tests__/notifications.test.ts index 2e0464df..94ce67d8 100644 --- a/frontend/src/api/__tests__/notifications.test.ts +++ b/frontend/src/api/__tests__/notifications.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' + import client from '../client' import { getProviders, diff --git a/frontend/src/api/__tests__/npmImport.test.ts b/frontend/src/api/__tests__/npmImport.test.ts index 0eebf1f7..45b3b419 100644 --- a/frontend/src/api/__tests__/npmImport.test.ts +++ b/frontend/src/api/__tests__/npmImport.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { uploadNPMExport, commitNPMImport, cancelNPMImport } from '../npmImport'; + import client from '../client'; +import { uploadNPMExport, commitNPMImport, cancelNPMImport } from '../npmImport'; vi.mock('../client', () => ({ default: { diff --git a/frontend/src/api/__tests__/plugins.test.ts b/frontend/src/api/__tests__/plugins.test.ts index 755d5b1a..ff2ed384 100644 --- a/frontend/src/api/__tests__/plugins.test.ts +++ b/frontend/src/api/__tests__/plugins.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; + import client from '../client'; import { getPlugins, diff --git a/frontend/src/api/__tests__/presets.test.ts b/frontend/src/api/__tests__/presets.test.ts index 064ed91d..1a6f7948 100644 --- a/frontend/src/api/__tests__/presets.test.ts +++ b/frontend/src/api/__tests__/presets.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -import * as presets from '../presets' + import client from '../client' +import * as presets from '../presets' vi.mock('../client') diff --git a/frontend/src/api/__tests__/proxyHosts-bulk.test.ts b/frontend/src/api/__tests__/proxyHosts-bulk.test.ts index e29cb091..d0b8efc5 100644 --- a/frontend/src/api/__tests__/proxyHosts-bulk.test.ts +++ b/frontend/src/api/__tests__/proxyHosts-bulk.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { bulkUpdateACL } from '../proxyHosts'; -import type { BulkUpdateACLResponse } from '../proxyHosts'; + +import { bulkUpdateACL, type BulkUpdateACLResponse } from '../proxyHosts'; + // Mock the client module const mockPut = vi.fn(); diff --git a/frontend/src/api/__tests__/proxyHosts.test.ts b/frontend/src/api/__tests__/proxyHosts.test.ts index 026d03af..face6ad6 100644 --- a/frontend/src/api/__tests__/proxyHosts.test.ts +++ b/frontend/src/api/__tests__/proxyHosts.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; + import client from '../client'; import { getProxyHosts, @@ -7,7 +8,7 @@ import { updateProxyHost, deleteProxyHost, testProxyHostConnection, - ProxyHost + type ProxyHost } from '../proxyHosts'; vi.mock('../client', () => ({ diff --git a/frontend/src/api/__tests__/remoteServers.test.ts b/frontend/src/api/__tests__/remoteServers.test.ts index 84f5cd1c..025da449 100644 --- a/frontend/src/api/__tests__/remoteServers.test.ts +++ b/frontend/src/api/__tests__/remoteServers.test.ts @@ -1,4 +1,6 @@ import { vi, describe, it, expect, beforeEach } from 'vitest'; + +import client from '../client'; import { getRemoteServers, getRemoteServer, @@ -8,7 +10,6 @@ import { testRemoteServerConnection, testCustomRemoteServerConnection, } from '../remoteServers'; -import client from '../client'; vi.mock('../client', () => ({ default: { diff --git a/frontend/src/api/__tests__/security.test.ts b/frontend/src/api/__tests__/security.test.ts index f548eb21..f681b929 100644 --- a/frontend/src/api/__tests__/security.test.ts +++ b/frontend/src/api/__tests__/security.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -import * as security from '../security' + import client from '../client' +import * as security from '../security' vi.mock('../client') diff --git a/frontend/src/api/__tests__/securityHeaders.test.ts b/frontend/src/api/__tests__/securityHeaders.test.ts index 67826f7c..4375066a 100644 --- a/frontend/src/api/__tests__/securityHeaders.test.ts +++ b/frontend/src/api/__tests__/securityHeaders.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { securityHeadersApi } from '../securityHeaders'; + import client from '../client'; +import { securityHeadersApi } from '../securityHeaders'; vi.mock('../client', () => ({ default: { diff --git a/frontend/src/api/__tests__/settings.test.ts b/frontend/src/api/__tests__/settings.test.ts index 257e8ffc..9974edb5 100644 --- a/frontend/src/api/__tests__/settings.test.ts +++ b/frontend/src/api/__tests__/settings.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -import * as settings from '../settings' + import client from '../client' +import * as settings from '../settings' vi.mock('../client') diff --git a/frontend/src/api/__tests__/setup.test.ts b/frontend/src/api/__tests__/setup.test.ts index c2c633a5..52f1f2bb 100644 --- a/frontend/src/api/__tests__/setup.test.ts +++ b/frontend/src/api/__tests__/setup.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' + import client from '../../api/client' import { getSetupStatus, performSetup } from '../setup' diff --git a/frontend/src/api/__tests__/system.test.ts b/frontend/src/api/__tests__/system.test.ts index 1b7a8891..268a8847 100644 --- a/frontend/src/api/__tests__/system.test.ts +++ b/frontend/src/api/__tests__/system.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, afterEach } from 'vitest' + import client from '../client' import { checkUpdates, getNotifications, markNotificationRead, markAllNotificationsRead } from '../system' diff --git a/frontend/src/api/__tests__/uptime.test.ts b/frontend/src/api/__tests__/uptime.test.ts index d11affa9..f6e9b09e 100644 --- a/frontend/src/api/__tests__/uptime.test.ts +++ b/frontend/src/api/__tests__/uptime.test.ts @@ -1,6 +1,8 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -import * as uptime from '../uptime' + import client from '../client' +import * as uptime from '../uptime' + import type { UptimeMonitor, UptimeHeartbeat } from '../uptime' vi.mock('../client') diff --git a/frontend/src/api/__tests__/user.test.ts b/frontend/src/api/__tests__/user.test.ts index 167f523b..4b92def9 100644 --- a/frontend/src/api/__tests__/user.test.ts +++ b/frontend/src/api/__tests__/user.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' + import client from '../client' import { getProfile, regenerateApiKey, updateProfile } from '../users' diff --git a/frontend/src/api/__tests__/users.test.ts b/frontend/src/api/__tests__/users.test.ts index bab06a01..7043d81f 100644 --- a/frontend/src/api/__tests__/users.test.ts +++ b/frontend/src/api/__tests__/users.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' + import client from '../client' import { listUsers, diff --git a/frontend/src/api/__tests__/websocket.test.ts b/frontend/src/api/__tests__/websocket.test.ts index cc3cf9b6..9fa06d36 100644 --- a/frontend/src/api/__tests__/websocket.test.ts +++ b/frontend/src/api/__tests__/websocket.test.ts @@ -1,6 +1,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { getWebSocketConnections, getWebSocketStats } from '../websocket'; + import client from '../client'; +import { getWebSocketConnections, getWebSocketStats } from '../websocket'; vi.mock('../client'); diff --git a/frontend/src/api/auditLogs.test.ts b/frontend/src/api/auditLogs.test.ts index 0b23908e..73d5a771 100644 --- a/frontend/src/api/auditLogs.test.ts +++ b/frontend/src/api/auditLogs.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' -import client from './client' + import { getAuditLogs, getAuditLog, @@ -8,6 +8,7 @@ import { type AuditLog, type AuditLogFilters, } from './auditLogs' +import client from './client' vi.mock('./client', () => ({ default: { diff --git a/frontend/src/api/dnsDetection.ts b/frontend/src/api/dnsDetection.ts index fe4f0049..43c74890 100644 --- a/frontend/src/api/dnsDetection.ts +++ b/frontend/src/api/dnsDetection.ts @@ -1,4 +1,5 @@ import client from './client' + import type { DNSProvider } from './dnsProviders' /** DNS provider detection result */ diff --git a/frontend/src/api/featureFlags.test.ts b/frontend/src/api/featureFlags.test.ts index 66d3ec25..c183de63 100644 --- a/frontend/src/api/featureFlags.test.ts +++ b/frontend/src/api/featureFlags.test.ts @@ -8,8 +8,8 @@ vi.mock('./client', () => ({ }, })) -import { getFeatureFlags, updateFeatureFlags } from './featureFlags' import client from './client' +import { getFeatureFlags, updateFeatureFlags } from './featureFlags' describe('featureFlags API', () => { it('fetches feature flags', async () => { diff --git a/frontend/src/api/logs.test.ts b/frontend/src/api/logs.test.ts index 02c03c42..328060d7 100644 --- a/frontend/src/api/logs.test.ts +++ b/frontend/src/api/logs.test.ts @@ -1,7 +1,8 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + import client from './client' -import { getLogs, getLogContent, downloadLog, connectLiveLogs, connectSecurityLogs } from './logs' -import type { LiveLogEntry, SecurityLogEntry } from './logs' +import { getLogs, getLogContent, downloadLog, connectLiveLogs, connectSecurityLogs, type LiveLogEntry, type SecurityLogEntry } from './logs' + vi.mock('./client', () => ({ default: { diff --git a/frontend/src/api/notifications.test.ts b/frontend/src/api/notifications.test.ts index f0ca144c..d19ae094 100644 --- a/frontend/src/api/notifications.test.ts +++ b/frontend/src/api/notifications.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' + import client from './client' import { getProviders, diff --git a/frontend/src/api/users.test.ts b/frontend/src/api/users.test.ts index 09f014de..15a89ae1 100644 --- a/frontend/src/api/users.test.ts +++ b/frontend/src/api/users.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach } from 'vitest' + import client from './client' import { listUsers, diff --git a/frontend/src/components/AccessListForm.tsx b/frontend/src/components/AccessListForm.tsx index e82ab147..1386e4d1 100644 --- a/frontend/src/components/AccessListForm.tsx +++ b/frontend/src/components/AccessListForm.tsx @@ -1,12 +1,16 @@ +import { X, Plus, ExternalLink, Shield, AlertTriangle, Info, Download, Trash2 } from 'lucide-react'; import { useState } from 'react'; +import toast from 'react-hot-toast'; + import { Button } from './ui/Button'; import { Input } from './ui/Input'; import { Switch } from './ui/Switch'; -import { X, Plus, ExternalLink, Shield, AlertTriangle, Info, Download, Trash2 } from 'lucide-react'; -import type { AccessList, AccessListRule } from '../api/accessLists'; -import { SECURITY_PRESETS, calculateTotalIPs, formatIPCount, type SecurityPreset } from '../data/securityPresets'; import { getMyIP } from '../api/system'; -import toast from 'react-hot-toast'; +import { SECURITY_PRESETS, calculateTotalIPs, formatIPCount, type SecurityPreset } from '../data/securityPresets'; + +import type { AccessList, AccessListRule } from '../api/accessLists'; + + interface AccessListFormProps { initialData?: AccessList; diff --git a/frontend/src/components/AccessListSelector.tsx b/frontend/src/components/AccessListSelector.tsx index 282bde45..51b03764 100644 --- a/frontend/src/components/AccessListSelector.tsx +++ b/frontend/src/components/AccessListSelector.tsx @@ -1,5 +1,6 @@ -import { useAccessLists } from '../hooks/useAccessLists'; import { ExternalLink } from 'lucide-react'; + +import { useAccessLists } from '../hooks/useAccessLists'; import { Select, SelectContent, diff --git a/frontend/src/components/CSPBuilder.tsx b/frontend/src/components/CSPBuilder.tsx index 51d82b0b..d2b59e3e 100644 --- a/frontend/src/components/CSPBuilder.tsx +++ b/frontend/src/components/CSPBuilder.tsx @@ -1,11 +1,13 @@ -import { useState, useEffect } from 'react'; import { Plus, X, AlertCircle, Check, Code } from 'lucide-react'; +import { useState, useEffect } from 'react'; + +import { Alert } from './ui/Alert'; +import { Badge } from './ui/Badge'; import { Button } from './ui/Button'; +import { Card } from './ui/Card'; import { Input } from './ui/Input'; import { NativeSelect } from './ui/NativeSelect'; -import { Card } from './ui/Card'; -import { Badge } from './ui/Badge'; -import { Alert } from './ui/Alert'; + import type { CSPDirective } from '../api/securityHeaders'; interface CSPBuilderProps { diff --git a/frontend/src/components/CertificateList.tsx b/frontend/src/components/CertificateList.tsx index 4673a71a..d001c4f6 100644 --- a/frontend/src/components/CertificateList.tsx +++ b/frontend/src/components/CertificateList.tsx @@ -1,11 +1,12 @@ -import { useState, useMemo } from 'react' import { useMutation, useQueryClient } from '@tanstack/react-query' import { Trash2, ChevronUp, ChevronDown } from 'lucide-react' -import { useCertificates } from '../hooks/useCertificates' -import { deleteCertificate } from '../api/certificates' -import { useProxyHosts } from '../hooks/useProxyHosts' -import { createBackup } from '../api/backups' +import { useState, useMemo } from 'react' + import { LoadingSpinner, ConfigReloadOverlay } from './LoadingStates' +import { createBackup } from '../api/backups' +import { deleteCertificate } from '../api/certificates' +import { useCertificates } from '../hooks/useCertificates' +import { useProxyHosts } from '../hooks/useProxyHosts' import { toast } from '../utils/toast' type SortColumn = 'name' | 'expires' diff --git a/frontend/src/components/CertificateStatusCard.tsx b/frontend/src/components/CertificateStatusCard.tsx index 2a8e3b17..65f98729 100644 --- a/frontend/src/components/CertificateStatusCard.tsx +++ b/frontend/src/components/CertificateStatusCard.tsx @@ -1,7 +1,9 @@ +import { FileKey, Loader2 } from 'lucide-react' import { useMemo } from 'react' import { Link } from 'react-router-dom' -import { FileKey, Loader2 } from 'lucide-react' + import { Card, CardHeader, CardContent, Badge, Skeleton, Progress } from './ui' + import type { Certificate } from '../api/certificates' import type { ProxyHost } from '../api/proxyHosts' @@ -21,15 +23,15 @@ export default function CertificateStatusCard({ certificates, hosts, isLoading } // so we match by domain name instead const certifiedDomains = useMemo(() => { const domains = new Set() - certificates.forEach(cert => { + for (const cert of certificates) { // Handle missing or undefined domain field - if (!cert.domain) return + if (!cert.domain) continue // Certificate domain field can be comma-separated - cert.domain.split(',').forEach(d => { + for (const d of cert.domain.split(',')) { const trimmed = d.trim().toLowerCase() if (trimmed) domains.add(trimmed) - }) - }) + } + } return domains }, [certificates]) @@ -38,13 +40,13 @@ export default function CertificateStatusCard({ certificates, hosts, isLoading } const sslHosts = hosts.filter(h => h.ssl_forced && h.enabled) let withCerts = 0 - sslHosts.forEach(host => { + for (const host of sslHosts) { // Check if any of the host's domains have a certificate const hostDomains = host.domain_names.split(',').map(d => d.trim().toLowerCase()) if (hostDomains.some(domain => certifiedDomains.has(domain))) { withCerts++ } - }) + } return { pendingCount: sslHosts.length - withCerts, diff --git a/frontend/src/components/CrowdSecBouncerKeyDisplay.tsx b/frontend/src/components/CrowdSecBouncerKeyDisplay.tsx index 78ed7f69..f5f06dc3 100644 --- a/frontend/src/components/CrowdSecBouncerKeyDisplay.tsx +++ b/frontend/src/components/CrowdSecBouncerKeyDisplay.tsx @@ -1,13 +1,14 @@ -import { useState } from 'react' import { useQuery } from '@tanstack/react-query' import { Copy, Check, Key, AlertCircle } from 'lucide-react' +import { useState } from 'react' import { useTranslation } from 'react-i18next' + +import { Badge } from './ui/Badge' import { Button } from './ui/Button' import { Card, CardContent, CardHeader, CardTitle } from './ui/Card' -import { Badge } from './ui/Badge' import { Skeleton } from './ui/Skeleton' -import { toast } from '../utils/toast' import client from '../api/client' +import { toast } from '../utils/toast' interface BouncerInfo { name: string diff --git a/frontend/src/components/CrowdSecKeyWarning.tsx b/frontend/src/components/CrowdSecKeyWarning.tsx index bdf3894b..8b671460 100644 --- a/frontend/src/components/CrowdSecKeyWarning.tsx +++ b/frontend/src/components/CrowdSecKeyWarning.tsx @@ -1,11 +1,12 @@ -import { useState, useEffect } from 'react' import { useQuery } from '@tanstack/react-query' import { Copy, Check, AlertTriangle, X, Eye, EyeOff } from 'lucide-react' +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' + import { Alert } from './ui/Alert' import { Button } from './ui/Button' -import { toast } from '../utils/toast' import { getCrowdsecKeyStatus, type CrowdSecKeyStatus } from '../api/crowdsec' +import { toast } from '../utils/toast' const DISMISSAL_STORAGE_KEY = 'crowdsec-key-warning-dismissed' diff --git a/frontend/src/components/DNSDetectionResult.tsx b/frontend/src/components/DNSDetectionResult.tsx index 0a23ad52..e0859014 100644 --- a/frontend/src/components/DNSDetectionResult.tsx +++ b/frontend/src/components/DNSDetectionResult.tsx @@ -1,6 +1,8 @@ import { CheckCircle2, AlertCircle, Info } from 'lucide-react' import { useTranslation } from 'react-i18next' + import { Badge, Button, Alert } from './ui' + import type { DetectionResult } from '../api/dnsDetection' import type { DNSProvider } from '../api/dnsProviders' diff --git a/frontend/src/components/DNSProviderCard.tsx b/frontend/src/components/DNSProviderCard.tsx index 495c9103..12c7a8f5 100644 --- a/frontend/src/components/DNSProviderCard.tsx +++ b/frontend/src/components/DNSProviderCard.tsx @@ -1,5 +1,4 @@ -import { useState } from 'react' -import { useTranslation } from 'react-i18next' +import { formatDistanceToNow } from 'date-fns' import { Edit, Trash2, @@ -9,7 +8,9 @@ import { XCircle, AlertTriangle, } from 'lucide-react' -import { formatDistanceToNow } from 'date-fns' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' + import { Card, CardHeader, @@ -24,6 +25,7 @@ import { DialogDescription, DialogFooter, } from './ui' + import type { DNSProvider } from '../api/dnsProviders' interface DNSProviderCardProps { diff --git a/frontend/src/components/DNSProviderForm.tsx b/frontend/src/components/DNSProviderForm.tsx index 9c26e5bb..5d0f7117 100644 --- a/frontend/src/components/DNSProviderForm.tsx +++ b/frontend/src/components/DNSProviderForm.tsx @@ -1,6 +1,8 @@ +import { ChevronDown, ChevronUp, ExternalLink, CheckCircle, XCircle, Settings } from 'lucide-react' import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { ChevronDown, ChevronUp, ExternalLink, CheckCircle, XCircle, Settings } from 'lucide-react' + +import CredentialManager from './CredentialManager' import { Dialog, DialogContent, @@ -19,11 +21,11 @@ import { Alert, Textarea, } from './ui' -import { useDNSProviderTypes, useDNSProviderMutations, type DNSProvider } from '../hooks/useDNSProviders' -import type { DNSProviderRequest, DNSProviderTypeInfo } from '../api/dnsProviders' import { defaultProviderSchemas } from '../data/dnsProviderSchemas' import { useEnableMultiCredentials, useCredentials } from '../hooks/useCredentials' -import CredentialManager from './CredentialManager' +import { useDNSProviderTypes, useDNSProviderMutations, type DNSProvider } from '../hooks/useDNSProviders' + +import type { DNSProviderRequest, DNSProviderTypeInfo } from '../api/dnsProviders' interface DNSProviderFormProps { open: boolean @@ -136,11 +138,7 @@ export default function DNSProviderForm({ } try { - if (provider) { - await updateMutation.mutateAsync({ id: provider.id, data }) - } else { - await createMutation.mutateAsync(data) - } + await (provider ? updateMutation.mutateAsync({ id: provider.id, data }) : createMutation.mutateAsync(data)); onSuccess() onOpenChange(false) resetForm() diff --git a/frontend/src/components/DNSProviderSelector.tsx b/frontend/src/components/DNSProviderSelector.tsx index b4d7acec..84967dc8 100644 --- a/frontend/src/components/DNSProviderSelector.tsx +++ b/frontend/src/components/DNSProviderSelector.tsx @@ -1,5 +1,6 @@ -import { useTranslation } from 'react-i18next' import { Star } from 'lucide-react' +import { useTranslation } from 'react-i18next' + import { Select, SelectContent, diff --git a/frontend/src/components/ImportReviewTable.tsx b/frontend/src/components/ImportReviewTable.tsx index 05c324ed..9c2d0a90 100644 --- a/frontend/src/components/ImportReviewTable.tsx +++ b/frontend/src/components/ImportReviewTable.tsx @@ -1,5 +1,5 @@ -import React, { useState } from 'react' import { AlertTriangle, CheckCircle2 } from 'lucide-react' +import React, { useState } from 'react' interface HostPreview { domain_names: string @@ -48,10 +48,10 @@ export default function ImportReviewTable({ hosts, conflicts, conflictDetails, e }) const [names, setNames] = useState>(() => { const init: Record = {} - hosts.forEach((h) => { + for (const h of hosts) { // Default name to domain name (first domain if comma-separated) init[h.domain_names] = h.name || h.domain_names.split(',')[0].trim() - }) + } return init }) const [submitting, setSubmitting] = useState(false) diff --git a/frontend/src/components/ImportSitesModal.test.tsx b/frontend/src/components/ImportSitesModal.test.tsx index 0dda738a..3643b865 100644 --- a/frontend/src/components/ImportSitesModal.test.tsx +++ b/frontend/src/components/ImportSitesModal.test.tsx @@ -1,7 +1,8 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react' -import ImportSitesModal from './ImportSitesModal' import { vi } from 'vitest' -import { CaddyFile } from '../api/import' + +import ImportSitesModal from './ImportSitesModal' +import { type CaddyFile } from '../api/import' // Mock the upload API used by the component const mockUpload = vi.fn() @@ -37,8 +38,8 @@ describe('ImportSitesModal', () => { expect(textareasAfterRemove.length).toBe(1) // type into textarea - const ta = screen.getAllByRole('textbox').filter(el => el.tagName === 'TEXTAREA')[0] - fireEvent.change(ta, { target: { value: 'example.com { reverse_proxy 127.0.0.1:8080 }' } }) + const ta = screen.getAllByRole('textbox').find(el => el.tagName === 'TEXTAREA') + fireEvent.change(ta!, { target: { value: 'example.com { reverse_proxy 127.0.0.1:8080 }' } }) expect((ta as HTMLTextAreaElement).value).toContain('example.com') }) @@ -94,6 +95,6 @@ describe('ImportSitesModal', () => { fireEvent.click(screen.getByText('Parse and Review')) // error message appears - await waitFor(() => expect(screen.getByText(/upload-failed|Upload failed/i)).toBeInTheDocument()) + expect(await screen.findByText(/upload-failed|Upload failed/i)).toBeInTheDocument() }) }) diff --git a/frontend/src/components/ImportSitesModal.tsx b/frontend/src/components/ImportSitesModal.tsx index 31d82160..3e4d661c 100644 --- a/frontend/src/components/ImportSitesModal.tsx +++ b/frontend/src/components/ImportSitesModal.tsx @@ -1,5 +1,6 @@ import React, { useState } from 'react' -import { uploadCaddyfilesMulti, CaddyFile } from '../api/import' + +import { uploadCaddyfilesMulti, type CaddyFile } from '../api/import' type Props = { visible: boolean diff --git a/frontend/src/components/LanguageSelector.tsx b/frontend/src/components/LanguageSelector.tsx index a8e42e2f..3ea5d652 100644 --- a/frontend/src/components/LanguageSelector.tsx +++ b/frontend/src/components/LanguageSelector.tsx @@ -1,6 +1,7 @@ import { Globe } from 'lucide-react' + +import { type Language } from '../context/LanguageContextValue' import { useLanguage } from '../hooks/useLanguage' -import { Language } from '../context/LanguageContextValue' const languageOptions: { code: Language; label: string; nativeLabel: string }[] = [ { code: 'en', label: 'English', nativeLabel: 'English' }, diff --git a/frontend/src/components/Layout.tsx b/frontend/src/components/Layout.tsx index 8ab187fd..5cb696dc 100644 --- a/frontend/src/components/Layout.tsx +++ b/frontend/src/components/Layout.tsx @@ -1,15 +1,17 @@ -import { ReactNode, useState, useEffect } from 'react' -import { Link, useLocation } from 'react-router-dom' import { useQuery } from '@tanstack/react-query' +import { Menu, ChevronDown, ChevronRight } from 'lucide-react' +import { type ReactNode, useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { ThemeToggle } from './ThemeToggle' -import { Button } from './ui/Button' -import { useAuth } from '../hooks/useAuth' -import { checkHealth } from '../api/health' -import { getFeatureFlags } from '../api/featureFlags' +import { Link, useLocation } from 'react-router-dom' + import NotificationCenter from './NotificationCenter' import SystemStatus from './SystemStatus' -import { Menu, ChevronDown, ChevronRight } from 'lucide-react' +import { ThemeToggle } from './ThemeToggle' +import { Button } from './ui/Button' +import { getFeatureFlags } from '../api/featureFlags' +import { checkHealth } from '../api/health' +import { useAuth } from '../hooks/useAuth' + interface LayoutProps { children: ReactNode @@ -151,7 +153,7 @@ export default function Layout({ children }: LayoutProps) { {isCollapsed ? ( Charon ) : ( - Charon + Charon )} diff --git a/frontend/src/components/LiveLogViewer.tsx b/frontend/src/components/LiveLogViewer.tsx index 62b98c53..ad668076 100644 --- a/frontend/src/components/LiveLogViewer.tsx +++ b/frontend/src/components/LiveLogViewer.tsx @@ -1,14 +1,15 @@ +import { Pause, Play, Trash2, Filter, Shield, Globe } from 'lucide-react'; import { useEffect, useRef, useState, useCallback } from 'react'; + import { connectLiveLogs, connectSecurityLogs, - LiveLogEntry, - LiveLogFilter, - SecurityLogEntry, - SecurityLogFilter, + type LiveLogEntry, + type LiveLogFilter, + type SecurityLogEntry, + type SecurityLogFilter, } from '../api/logs'; import { Button } from './ui/Button'; -import { Pause, Play, Trash2, Filter, Shield, Globe } from 'lucide-react'; /** * Log viewing mode: application logs vs security access logs diff --git a/frontend/src/components/LogFilters.tsx b/frontend/src/components/LogFilters.tsx index 6bb8795a..f2e7b38c 100644 --- a/frontend/src/components/LogFilters.tsx +++ b/frontend/src/components/LogFilters.tsx @@ -1,5 +1,6 @@ -import React from 'react'; import { Search, Download, RefreshCw } from 'lucide-react'; +import React from 'react'; + import { Button } from './ui/Button'; interface LogFiltersProps { diff --git a/frontend/src/components/LogTable.tsx b/frontend/src/components/LogTable.tsx index d935dab7..442a9957 100644 --- a/frontend/src/components/LogTable.tsx +++ b/frontend/src/components/LogTable.tsx @@ -1,6 +1,7 @@ -import React from 'react'; -import { CaddyAccessLog } from '../api/logs'; import { format } from 'date-fns'; +import React from 'react'; + +import { type CaddyAccessLog } from '../api/logs'; interface LogTableProps { logs: CaddyAccessLog[]; diff --git a/frontend/src/components/NotificationCenter.tsx b/frontend/src/components/NotificationCenter.tsx index 18bc2c2b..003f8e59 100644 --- a/frontend/src/components/NotificationCenter.tsx +++ b/frontend/src/components/NotificationCenter.tsx @@ -1,6 +1,7 @@ -import { useState, type FC } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { Bell, X, Info, AlertTriangle, AlertCircle, CheckCircle, ExternalLink } from 'lucide-react'; +import { useState, type FC } from 'react'; + import { getNotifications, markNotificationRead, markAllNotificationsRead, checkUpdates } from '../api/system'; const NotificationCenter: FC = () => { diff --git a/frontend/src/components/PasswordStrengthMeter.tsx b/frontend/src/components/PasswordStrengthMeter.tsx index a018b03b..7671b503 100644 --- a/frontend/src/components/PasswordStrengthMeter.tsx +++ b/frontend/src/components/PasswordStrengthMeter.tsx @@ -1,4 +1,5 @@ import React from 'react'; + import { calculatePasswordStrength } from '../utils/passwordStrength'; interface Props { diff --git a/frontend/src/components/PermissionsPolicyBuilder.tsx b/frontend/src/components/PermissionsPolicyBuilder.tsx index 25af7657..2b95d9ce 100644 --- a/frontend/src/components/PermissionsPolicyBuilder.tsx +++ b/frontend/src/components/PermissionsPolicyBuilder.tsx @@ -1,11 +1,12 @@ -import { useState, useEffect } from 'react'; import { Plus, X, Code } from 'lucide-react'; +import { useState, useEffect } from 'react'; + +import { Alert } from './ui/Alert'; +import { Badge } from './ui/Badge'; import { Button } from './ui/Button'; +import { Card } from './ui/Card'; import { Input } from './ui/Input'; import { NativeSelect } from './ui/NativeSelect'; -import { Card } from './ui/Card'; -import { Badge } from './ui/Badge'; -import { Alert } from './ui/Alert'; interface PermissionsPolicyItem { feature: string; @@ -127,11 +128,11 @@ export function PermissionsPolicyBuilder({ value, onChange }: PermissionsPolicyB // Merge with existing (don't duplicate) const merged = [...policies]; - newPolicies.forEach((newPolicy) => { + for (const newPolicy of newPolicies) { if (!merged.some((p) => p.feature === newPolicy.feature)) { merged.push(newPolicy); } - }); + } updatePolicies(merged); }; diff --git a/frontend/src/components/ProxyHostForm.tsx b/frontend/src/components/ProxyHostForm.tsx index cfdbeb28..19d7f246 100644 --- a/frontend/src/components/ProxyHostForm.tsx +++ b/frontend/src/components/ProxyHostForm.tsx @@ -1,23 +1,22 @@ -import { useState, useEffect, useRef, useCallback } from 'react' import { CircleHelp, AlertCircle, Check, X, Loader2, Copy, Info, AlertTriangle } from 'lucide-react' +import { useState, useEffect, useRef, useCallback } from 'react' import { toast } from 'react-hot-toast' -import type { ProxyHost, ApplicationPreset } from '../api/proxyHosts' -import { testProxyHostConnection } from '../api/proxyHosts' -import { syncMonitors } from '../api/uptime' -import { useRemoteServers } from '../hooks/useRemoteServers' -import { useDomains } from '../hooks/useDomains' -import { useCertificates } from '../hooks/useCertificates' -import { useDocker } from '../hooks/useDocker' -import AccessListSelector from './AccessListSelector' -import { useSecurityHeaderProfiles } from '../hooks/useSecurityHeaders' -import { SecurityScoreDisplay } from './SecurityScoreDisplay' import { parse } from 'tldts' + +import AccessListSelector from './AccessListSelector' +import { DNSDetectionResult } from './DNSDetectionResult' +import DNSProviderSelector from './DNSProviderSelector' +import { SecurityScoreDisplay } from './SecurityScoreDisplay' +import { testProxyHostConnection, type ProxyHost, type ApplicationPreset } from '../api/proxyHosts' +import { syncMonitors } from '../api/uptime' +import { useCertificates } from '../hooks/useCertificates' +import { useDetectDNSProvider } from '../hooks/useDNSDetection' +import { useDocker } from '../hooks/useDocker' +import { useDomains } from '../hooks/useDomains' +import { useRemoteServers } from '../hooks/useRemoteServers' +import { useSecurityHeaderProfiles } from '../hooks/useSecurityHeaders' import { Alert } from './ui/Alert' import { isLikelyDockerContainerIP, isPrivateOrDockerIP } from '../utils/validation' -import DNSProviderSelector from './DNSProviderSelector' -import { useDetectDNSProvider } from '../hooks/useDNSDetection' -import { DNSDetectionResult } from './DNSDetectionResult' -import type { DNSProvider } from '../api/dnsProviders' import { Select, SelectContent, @@ -26,6 +25,8 @@ import { SelectValue, } from './ui/Select' +import type { DNSProvider } from '../api/dnsProviders' + // Application preset configurations const APPLICATION_PRESETS: { value: ApplicationPreset; label: string; description: string }[] = [ { value: 'none', label: 'None', description: 'Standard reverse proxy' }, diff --git a/frontend/src/components/RemoteServerForm.tsx b/frontend/src/components/RemoteServerForm.tsx index 11c74497..b6eaa246 100644 --- a/frontend/src/components/RemoteServerForm.tsx +++ b/frontend/src/components/RemoteServerForm.tsx @@ -1,5 +1,6 @@ -import { useEffect, useState } from 'react' import { Loader2, Check, X, CircleHelp } from 'lucide-react' +import { useEffect, useState } from 'react' + import { type RemoteServer, testCustomRemoteServerConnection } from '../api/remoteServers' interface Props { diff --git a/frontend/src/components/RequireAuth.tsx b/frontend/src/components/RequireAuth.tsx index 3e577eb7..11c588d1 100644 --- a/frontend/src/components/RequireAuth.tsx +++ b/frontend/src/components/RequireAuth.tsx @@ -1,7 +1,8 @@ import React from 'react'; import { Navigate, useLocation } from 'react-router-dom'; -import { useAuth } from '../hooks/useAuth'; + import { LoadingOverlay } from './LoadingStates'; +import { useAuth } from '../hooks/useAuth'; const RequireAuth: React.FC<{ children: React.ReactNode }> = ({ children }) => { const { isAuthenticated, isLoading, user } = useAuth(); diff --git a/frontend/src/components/RequireRole.tsx b/frontend/src/components/RequireRole.tsx index 0ab14b85..80821a78 100644 --- a/frontend/src/components/RequireRole.tsx +++ b/frontend/src/components/RequireRole.tsx @@ -1,5 +1,6 @@ import React from 'react' import { Navigate } from 'react-router-dom' + import { useAuth } from '../hooks/useAuth' interface RequireRoleProps { diff --git a/frontend/src/components/SecurityHeaderProfileForm.tsx b/frontend/src/components/SecurityHeaderProfileForm.tsx index dd94fef6..fda0ea43 100644 --- a/frontend/src/components/SecurityHeaderProfileForm.tsx +++ b/frontend/src/components/SecurityHeaderProfileForm.tsx @@ -1,16 +1,18 @@ -import { useState, useEffect } from 'react'; import { AlertTriangle, Save, X } from 'lucide-react'; -import { Button } from './ui/Button'; -import { Input } from './ui/Input'; -import { Textarea } from './ui/Textarea'; -import { Switch } from './ui/Switch'; -import { NativeSelect } from './ui/NativeSelect'; -import { Card } from './ui/Card'; -import { Alert } from './ui/Alert'; +import { useState, useEffect } from 'react'; + import { CSPBuilder } from './CSPBuilder'; import { PermissionsPolicyBuilder } from './PermissionsPolicyBuilder'; import { SecurityScoreDisplay } from './SecurityScoreDisplay'; +import { Alert } from './ui/Alert'; +import { Button } from './ui/Button'; +import { Card } from './ui/Card'; +import { Input } from './ui/Input'; +import { NativeSelect } from './ui/NativeSelect'; +import { Switch } from './ui/Switch'; +import { Textarea } from './ui/Textarea'; import { useCalculateSecurityScore } from '../hooks/useSecurityHeaders'; + import type { SecurityHeaderProfile, CreateProfileRequest } from '../api/securityHeaders'; interface SecurityHeaderProfileFormProps { diff --git a/frontend/src/components/SecurityScoreDisplay.tsx b/frontend/src/components/SecurityScoreDisplay.tsx index f574475a..f47cce41 100644 --- a/frontend/src/components/SecurityScoreDisplay.tsx +++ b/frontend/src/components/SecurityScoreDisplay.tsx @@ -1,7 +1,8 @@ -import { useState } from 'react'; import { Shield, ChevronDown, ChevronRight, AlertCircle } from 'lucide-react'; -import { Card } from './ui/Card'; +import { useState } from 'react'; + import { Badge } from './ui/Badge'; +import { Card } from './ui/Card'; import { Progress } from './ui/Progress'; interface SecurityScoreDisplayProps { diff --git a/frontend/src/components/SetupGuard.tsx b/frontend/src/components/SetupGuard.tsx index 625c2f55..2198e517 100644 --- a/frontend/src/components/SetupGuard.tsx +++ b/frontend/src/components/SetupGuard.tsx @@ -1,6 +1,7 @@ +import { useQuery } from '@tanstack/react-query'; import { useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; -import { useQuery } from '@tanstack/react-query'; + import { getSetupStatus } from '../api/setup'; interface SetupGuardProps { diff --git a/frontend/src/components/SystemStatus.tsx b/frontend/src/components/SystemStatus.tsx index f405fd08..855c3d2d 100644 --- a/frontend/src/components/SystemStatus.tsx +++ b/frontend/src/components/SystemStatus.tsx @@ -1,7 +1,10 @@ -import React from 'react'; import { useQuery } from '@tanstack/react-query'; + import { checkUpdates } from '../api/system'; +import type React from 'react'; + + const SystemStatus: React.FC = () => { // We still query for updates here to keep the cache fresh, // but the UI is now handled by NotificationCenter diff --git a/frontend/src/components/Toast.tsx b/frontend/src/components/Toast.tsx index 70e42fa6..603c0973 100644 --- a/frontend/src/components/Toast.tsx +++ b/frontend/src/components/Toast.tsx @@ -1,5 +1,6 @@ import { useEffect, useState } from 'react' -import { toastCallbacks, Toast } from '../utils/toast' + +import { toastCallbacks, type Toast } from '../utils/toast' export function ToastContainer() { const [toasts, setToasts] = useState([]) @@ -29,7 +30,7 @@ export function ToastContainer() { role={toast.type === 'error' || toast.type === 'warning' ? 'alert' : 'status'} aria-live={toast.type === 'error' || toast.type === 'warning' ? 'assertive' : 'polite'} data-testid={`toast-${toast.type}`} - className={`pointer-events-auto px-4 py-3 rounded-lg shadow-lg flex items-center gap-3 min-w-75 max-w-125 animate-slide-in ${ + className={`pointer-events-auto px-4 py-3 rounded-lg shadow-lg flex items-center gap-3 min-w-[300px] max-w-[500px] animate-slide-in ${ toast.type === 'success' ? 'bg-green-600 text-white' : toast.type === 'error' diff --git a/frontend/src/components/UptimeWidget.tsx b/frontend/src/components/UptimeWidget.tsx index 3082176e..c284a822 100644 --- a/frontend/src/components/UptimeWidget.tsx +++ b/frontend/src/components/UptimeWidget.tsx @@ -1,8 +1,9 @@ import { useQuery } from '@tanstack/react-query' -import { Link } from 'react-router-dom' import { Activity, CheckCircle2, XCircle, AlertCircle, ArrowRight } from 'lucide-react' -import { getMonitors } from '../api/uptime' +import { Link } from 'react-router-dom' + import { Card, CardHeader, CardContent, Badge, Skeleton } from './ui' +import { getMonitors } from '../api/uptime' export default function UptimeWidget() { const { data: monitors, isLoading } = useQuery({ diff --git a/frontend/src/components/WebSocketStatusCard.tsx b/frontend/src/components/WebSocketStatusCard.tsx index e8b37415..8b8e2e6c 100644 --- a/frontend/src/components/WebSocketStatusCard.tsx +++ b/frontend/src/components/WebSocketStatusCard.tsx @@ -1,6 +1,7 @@ -import { useState } from 'react'; +import { formatDistanceToNow } from 'date-fns'; import { Wifi, WifiOff, Activity, Clock, Filter, Globe } from 'lucide-react'; -import { useWebSocketConnections, useWebSocketStats } from '../hooks/useWebSocketStatus'; +import { useState } from 'react'; + import { Card, CardHeader, @@ -11,7 +12,8 @@ import { Skeleton, Alert, } from './ui'; -import { formatDistanceToNow } from 'date-fns'; +import { useWebSocketConnections, useWebSocketStats } from '../hooks/useWebSocketStatus'; + interface WebSocketStatusCardProps { className?: string; diff --git a/frontend/src/components/__tests__/AccessListForm.test.tsx b/frontend/src/components/__tests__/AccessListForm.test.tsx index 554147be..0e99e737 100644 --- a/frontend/src/components/__tests__/AccessListForm.test.tsx +++ b/frontend/src/components/__tests__/AccessListForm.test.tsx @@ -1,9 +1,10 @@ import { render, screen, waitFor } from '@testing-library/react'; -import { AccessListForm } from '../AccessListForm'; -import { vi, describe, it, expect, beforeEach } from 'vitest'; import userEvent from '@testing-library/user-event'; -import * as systemApi from '../../api/system'; import toast from 'react-hot-toast'; +import { vi, describe, it, expect, beforeEach } from 'vitest'; + +import * as systemApi from '../../api/system'; +import { AccessListForm } from '../AccessListForm'; vi.mock('../../api/system', () => ({ getMyIP: vi.fn(), @@ -100,12 +101,9 @@ describe('AccessListForm', () => { // We use querySelector because the icon is inside the button const removeButton = screen.getAllByRole('button').find(b => b.querySelector('.lucide-x')); - if (removeButton) { - await user.click(removeButton); - expect(screen.queryByText('1.2.3.4')).not.toBeInTheDocument(); - } else { - throw new Error('Remove button not found'); - } + expect(removeButton).toBeDefined(); + await user.click(removeButton!); + expect(screen.queryByText('1.2.3.4')).not.toBeInTheDocument(); }); it('fetches and populates My IP', async () => { @@ -416,10 +414,9 @@ describe('AccessListForm', () => { // Look for Apply buttons in presets const applyButtons = screen.getAllByRole('button', { name: /Apply/i }); - if (applyButtons.length > 0) { - await user.click(applyButtons[0]); - expect(toast.success).toHaveBeenCalled(); - } + expect(applyButtons.length).toBeGreaterThan(0); + await user.click(applyButtons[0]); + expect(toast.success).toHaveBeenCalled(); }); it('applies geo preset correctly', async () => { @@ -434,10 +431,9 @@ describe('AccessListForm', () => { await user.click(showBtn); const applyButtons = screen.getAllByRole('button', { name: /Apply/i }); - if (applyButtons.length > 0) { - await user.click(applyButtons[0]); - expect(toast.success).toHaveBeenCalled(); - } + expect(applyButtons.length).toBeGreaterThan(0); + await user.click(applyButtons[0]); + expect(toast.success).toHaveBeenCalled(); }); it('toggles enabled switch', async () => { diff --git a/frontend/src/components/__tests__/AccessListSelector-token-coverage.test.tsx b/frontend/src/components/__tests__/AccessListSelector-token-coverage.test.tsx index fdb48b3b..e0c47b4a 100644 --- a/frontend/src/components/__tests__/AccessListSelector-token-coverage.test.tsx +++ b/frontend/src/components/__tests__/AccessListSelector-token-coverage.test.tsx @@ -1,8 +1,9 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import AccessListSelector from '../AccessListSelector'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + import * as useAccessListsHook from '../../hooks/useAccessLists'; +import AccessListSelector from '../AccessListSelector'; vi.mock('../../hooks/useAccessLists'); diff --git a/frontend/src/components/__tests__/AccessListSelector.test.tsx b/frontend/src/components/__tests__/AccessListSelector.test.tsx index 15c06316..7d6ba6a7 100644 --- a/frontend/src/components/__tests__/AccessListSelector.test.tsx +++ b/frontend/src/components/__tests__/AccessListSelector.test.tsx @@ -1,9 +1,11 @@ -import { describe, it, expect, vi } from 'vitest'; -import { render, screen } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render, screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import AccessListSelector from '../AccessListSelector'; +import { describe, it, expect, vi } from 'vitest'; + import * as useAccessListsHook from '../../hooks/useAccessLists'; +import AccessListSelector from '../AccessListSelector'; + import type { AccessList } from '../../api/accessLists'; // Mock the hooks diff --git a/frontend/src/components/__tests__/CSPBuilder.test.tsx b/frontend/src/components/__tests__/CSPBuilder.test.tsx index fe19d0ad..529d1cdd 100644 --- a/frontend/src/components/__tests__/CSPBuilder.test.tsx +++ b/frontend/src/components/__tests__/CSPBuilder.test.tsx @@ -1,5 +1,6 @@ import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { describe, it, expect, vi } from 'vitest'; + import { CSPBuilder } from '../CSPBuilder'; describe('CSPBuilder', () => { diff --git a/frontend/src/components/__tests__/CertificateList.test.tsx b/frontend/src/components/__tests__/CertificateList.test.tsx index bcccb4fa..e1dbcb49 100644 --- a/frontend/src/components/__tests__/CertificateList.test.tsx +++ b/frontend/src/components/__tests__/CertificateList.test.tsx @@ -1,11 +1,13 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClientProvider } from '@tanstack/react-query' -import CertificateList from '../CertificateList' -import { createTestQueryClient } from '../../test/createTestQueryClient' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import { useCertificates } from '../../hooks/useCertificates' import { useProxyHosts } from '../../hooks/useProxyHosts' +import { createTestQueryClient } from '../../test/createTestQueryClient' +import CertificateList from '../CertificateList' + import type { Certificate } from '../../api/certificates' import type { ProxyHost } from '../../api/proxyHosts' @@ -14,7 +16,7 @@ vi.mock('../../hooks/useCertificates', () => ({ })) vi.mock('../../api/certificates', () => ({ - deleteCertificate: vi.fn(async () => undefined), + deleteCertificate: vi.fn(async () => {}), })) vi.mock('../../api/backups', () => ({ diff --git a/frontend/src/components/__tests__/CertificateStatusCard.test.tsx b/frontend/src/components/__tests__/CertificateStatusCard.test.tsx index 7a95c4bd..6055672a 100644 --- a/frontend/src/components/__tests__/CertificateStatusCard.test.tsx +++ b/frontend/src/components/__tests__/CertificateStatusCard.test.tsx @@ -1,7 +1,9 @@ -import { describe, it, expect } from 'vitest' import { render, screen } from '@testing-library/react' import { BrowserRouter } from 'react-router-dom' +import { describe, it, expect } from 'vitest' + import CertificateStatusCard from '../CertificateStatusCard' + import type { Certificate } from '../../api/certificates' import type { ProxyHost } from '../../api/proxyHosts' diff --git a/frontend/src/components/__tests__/CredentialManager.test.tsx b/frontend/src/components/__tests__/CredentialManager.test.tsx index d2bac91d..cbfc4df3 100644 --- a/frontend/src/components/__tests__/CredentialManager.test.tsx +++ b/frontend/src/components/__tests__/CredentialManager.test.tsx @@ -1,8 +1,8 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider, type UseMutationResult } from '@tanstack/react-query' import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider, type UseMutationResult } from '@tanstack/react-query' -import CredentialManager from '../CredentialManager' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import { useCredentials, useCreateCredential, @@ -10,6 +10,7 @@ import { useDeleteCredential, useTestCredential, } from '../../hooks/useCredentials' +import CredentialManager from '../CredentialManager' vi.mock('react-i18next', () => ({ useTranslation: () => ({ @@ -19,8 +20,8 @@ vi.mock('react-i18next', () => ({ }, }), })) -import type { DNSProvider, DNSProviderTypeInfo } from '../../api/dnsProviders' import type { CredentialRequest, CredentialTestResult, DNSProviderCredential } from '../../api/credentials' +import type { DNSProvider, DNSProviderTypeInfo } from '../../api/dnsProviders' vi.mock('../../hooks/useCredentials') vi.mock('../../utils/toast', () => ({ diff --git a/frontend/src/components/__tests__/CrowdSecBouncerKeyDisplay.test.tsx b/frontend/src/components/__tests__/CrowdSecBouncerKeyDisplay.test.tsx index d2defd5a..e04c89b4 100644 --- a/frontend/src/components/__tests__/CrowdSecBouncerKeyDisplay.test.tsx +++ b/frontend/src/components/__tests__/CrowdSecBouncerKeyDisplay.test.tsx @@ -2,9 +2,11 @@ * CrowdSecBouncerKeyDisplay Component Tests * Tests the bouncer API key display functionality for CrowdSec integration */ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { render, screen, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { render, screen, waitFor } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach } from 'vitest' + +import client from '../../api/client' import { CrowdSecBouncerKeyDisplay } from '../CrowdSecBouncerKeyDisplay' // Create mock axios instance @@ -48,9 +50,6 @@ vi.mock('react-i18next', () => ({ }), })) -// Re-import client after mocking axios -import client from '../../api/client' - const mockBouncerInfo = { name: 'caddy-bouncer', key_preview: 'abc***xyz', diff --git a/frontend/src/components/__tests__/CrowdSecKeyWarning.test.tsx b/frontend/src/components/__tests__/CrowdSecKeyWarning.test.tsx index 8b3ae3ce..c7f6bd31 100644 --- a/frontend/src/components/__tests__/CrowdSecKeyWarning.test.tsx +++ b/frontend/src/components/__tests__/CrowdSecKeyWarning.test.tsx @@ -1,10 +1,11 @@ -import { render, screen, waitFor } from '@testing-library/react' -import { describe, it, expect, vi, beforeEach } from 'vitest' -import userEvent from '@testing-library/user-event' -import { CrowdSecKeyWarning } from '../CrowdSecKeyWarning' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as crowdsecApi from '../../api/crowdsec' import { toast } from '../../utils/toast' +import { CrowdSecKeyWarning } from '../CrowdSecKeyWarning' vi.mock('../../api/crowdsec') vi.mock('react-i18next', () => ({ diff --git a/frontend/src/components/__tests__/DNSDetectionResult.test.tsx b/frontend/src/components/__tests__/DNSDetectionResult.test.tsx index 45fca79f..1f001d5b 100644 --- a/frontend/src/components/__tests__/DNSDetectionResult.test.tsx +++ b/frontend/src/components/__tests__/DNSDetectionResult.test.tsx @@ -1,7 +1,9 @@ -import { describe, it, expect, vi } from 'vitest' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { describe, it, expect, vi } from 'vitest' + import { DNSDetectionResult } from '../DNSDetectionResult' + import type { DetectionResult } from '../../api/dnsDetection' import type { DNSProvider } from '../../api/dnsProviders' diff --git a/frontend/src/components/__tests__/DNSProviderForm.test.tsx b/frontend/src/components/__tests__/DNSProviderForm.test.tsx index e73621a9..0064ffd4 100644 --- a/frontend/src/components/__tests__/DNSProviderForm.test.tsx +++ b/frontend/src/components/__tests__/DNSProviderForm.test.tsx @@ -1,7 +1,8 @@ import { render, screen, waitFor } from '@testing-library/react'; -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import DNSProviderForm from '../DNSProviderForm'; import userEvent from '@testing-library/user-event'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +import DNSProviderForm from '../DNSProviderForm'; // Mock the hooks const mockCreateMutation = { diff --git a/frontend/src/components/__tests__/DNSProviderSelector.test.tsx b/frontend/src/components/__tests__/DNSProviderSelector.test.tsx index c6c08745..b9078aa1 100644 --- a/frontend/src/components/__tests__/DNSProviderSelector.test.tsx +++ b/frontend/src/components/__tests__/DNSProviderSelector.test.tsx @@ -1,8 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { render, screen } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import DNSProviderSelector from '../DNSProviderSelector' +import { render, screen } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import { useDNSProviders } from '../../hooks/useDNSProviders' +import DNSProviderSelector from '../DNSProviderSelector' + import type { DNSProvider } from '../../api/dnsProviders' vi.mock('../../hooks/useDNSProviders') diff --git a/frontend/src/components/__tests__/ImportReviewTable-warnings.test.tsx b/frontend/src/components/__tests__/ImportReviewTable-warnings.test.tsx index be819e72..e0ce1482 100644 --- a/frontend/src/components/__tests__/ImportReviewTable-warnings.test.tsx +++ b/frontend/src/components/__tests__/ImportReviewTable-warnings.test.tsx @@ -1,5 +1,6 @@ -import { describe, it, expect, vi } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' +import { describe, it, expect, vi } from 'vitest' + import ImportReviewTable from '../ImportReviewTable' describe('ImportReviewTable - Status Display', () => { diff --git a/frontend/src/components/__tests__/ImportReviewTable.test.tsx b/frontend/src/components/__tests__/ImportReviewTable.test.tsx index 269b68d9..9b20077d 100644 --- a/frontend/src/components/__tests__/ImportReviewTable.test.tsx +++ b/frontend/src/components/__tests__/ImportReviewTable.test.tsx @@ -1,8 +1,9 @@ -import { describe, it, expect, vi, afterEach } from 'vitest' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import ImportReviewTable from '../ImportReviewTable' +import { describe, it, expect, vi, afterEach } from 'vitest' + import { mockImportPreview } from '../../test/mockData' +import ImportReviewTable from '../ImportReviewTable' describe('ImportReviewTable', () => { const mockOnCommit = vi.fn(() => Promise.resolve()) diff --git a/frontend/src/components/__tests__/LanguageSelector.test.tsx b/frontend/src/components/__tests__/LanguageSelector.test.tsx index 26b3209c..2ecf9af0 100644 --- a/frontend/src/components/__tests__/LanguageSelector.test.tsx +++ b/frontend/src/components/__tests__/LanguageSelector.test.tsx @@ -1,7 +1,8 @@ -import { describe, it, expect, vi } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' -import { LanguageSelector } from '../LanguageSelector' +import { describe, it, expect, vi } from 'vitest' + import { LanguageProvider } from '../../context/LanguageContext' +import { LanguageSelector } from '../LanguageSelector' // Mock i18next vi.mock('react-i18next', () => ({ diff --git a/frontend/src/components/__tests__/Layout.test.tsx b/frontend/src/components/__tests__/Layout.test.tsx index 11bf9221..986244f4 100644 --- a/frontend/src/components/__tests__/Layout.test.tsx +++ b/frontend/src/components/__tests__/Layout.test.tsx @@ -1,12 +1,13 @@ -import { ReactNode } from 'react' -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { type ReactNode } from 'react' import { BrowserRouter } from 'react-router-dom' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import Layout from '../Layout' -import { ThemeProvider } from '../../context/ThemeContext' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as featureFlagsApi from '../../api/featureFlags' +import { ThemeProvider } from '../../context/ThemeContext' +import Layout from '../Layout' const mockLogout = vi.fn() diff --git a/frontend/src/components/__tests__/LiveLogViewer.test.tsx b/frontend/src/components/__tests__/LiveLogViewer.test.tsx index 41fdc89a..a4a7568f 100644 --- a/frontend/src/components/__tests__/LiveLogViewer.test.tsx +++ b/frontend/src/components/__tests__/LiveLogViewer.test.tsx @@ -1,8 +1,9 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; import { render, screen, waitFor, act } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { LiveLogViewer } from '../LiveLogViewer'; +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; + import * as logsApi from '../../api/logs'; +import { LiveLogViewer } from '../LiveLogViewer'; // Mock the connectLiveLogs and connectSecurityLogs functions vi.mock('../../api/logs', async () => { @@ -320,7 +321,7 @@ describe('LiveLogViewer', () => { mockOnMessage({ level: 'error', timestamp: '2025-12-09T10:30:01Z', message: 'Hidden' }); } - await waitFor(() => expect(screen.getByText('Visible')).toBeTruthy()); + expect(await screen.findByText('Visible')).toBeTruthy(); await user.type(screen.getByPlaceholderText('Filter by text...'), 'nomatch'); @@ -332,13 +333,13 @@ describe('LiveLogViewer', () => { it('marks connection as disconnected when WebSocket closes', async () => { render(); - await waitFor(() => expect(screen.getByText('Connected')).toBeTruthy()); + expect(await screen.findByText('Connected')).toBeTruthy(); act(() => { mockOnClose?.(); }); - await waitFor(() => expect(screen.getByText('Disconnected')).toBeTruthy()); + expect(await screen.findByText('Disconnected')).toBeTruthy(); }); // ============================================================ @@ -357,7 +358,7 @@ describe('LiveLogViewer', () => { render(); // Wait for connection to establish - await waitFor(() => expect(screen.getByText('Connected')).toBeTruthy()); + expect(await screen.findByText('Connected')).toBeTruthy(); const securityLog: logsApi.SecurityLogEntry = { timestamp: '2025-12-12T10:30:00Z', @@ -390,7 +391,7 @@ describe('LiveLogViewer', () => { render(); // Wait for connection to establish - await waitFor(() => expect(screen.getByText('Connected')).toBeTruthy()); + expect(await screen.findByText('Connected')).toBeTruthy(); const blockedLog: logsApi.SecurityLogEntry = { timestamp: '2025-12-12T10:30:00Z', @@ -446,7 +447,7 @@ describe('LiveLogViewer', () => { render(); // Wait for connection - await waitFor(() => expect(screen.getByText('Connected')).toBeTruthy()); + expect(await screen.findByText('Connected')).toBeTruthy(); // Add logs from different sources if (mockOnSecurityMessage) { @@ -523,7 +524,7 @@ describe('LiveLogViewer', () => { render(); // Wait for connection to establish - await waitFor(() => expect(screen.getByText('Connected')).toBeTruthy()); + expect(await screen.findByText('Connected')).toBeTruthy(); const securityLog: logsApi.SecurityLogEntry = { timestamp: '2025-12-12T10:30:00Z', @@ -554,7 +555,7 @@ describe('LiveLogViewer', () => { render(); // Wait for connection to establish - await waitFor(() => expect(screen.getByText('Connected')).toBeTruthy()); + expect(await screen.findByText('Connected')).toBeTruthy(); if (mockOnSecurityMessage) { mockOnSecurityMessage({ diff --git a/frontend/src/components/__tests__/LoadingStates-overlays.test.tsx b/frontend/src/components/__tests__/LoadingStates-overlays.test.tsx index 760cd25c..904a8b0c 100644 --- a/frontend/src/components/__tests__/LoadingStates-overlays.test.tsx +++ b/frontend/src/components/__tests__/LoadingStates-overlays.test.tsx @@ -1,5 +1,6 @@ import { render, screen } from '@testing-library/react' import { describe, it, expect } from 'vitest' + import { CharonLoader, CharonCoinLoader, CerberusLoader, ConfigReloadOverlay } from '../LoadingStates' describe('CharonLoader', () => { diff --git a/frontend/src/components/__tests__/LoadingStates.security.test.tsx b/frontend/src/components/__tests__/LoadingStates.security.test.tsx index b4bd964a..4f712188 100644 --- a/frontend/src/components/__tests__/LoadingStates.security.test.tsx +++ b/frontend/src/components/__tests__/LoadingStates.security.test.tsx @@ -1,5 +1,6 @@ -import { describe, it, expect } from 'vitest' import { render, screen } from '@testing-library/react' +import { describe, it, expect } from 'vitest' + import { CharonLoader, CharonCoinLoader, diff --git a/frontend/src/components/__tests__/ManualDNSChallenge.test.tsx b/frontend/src/components/__tests__/ManualDNSChallenge.test.tsx index 82f69ebd..099ea714 100644 --- a/frontend/src/components/__tests__/ManualDNSChallenge.test.tsx +++ b/frontend/src/components/__tests__/ManualDNSChallenge.test.tsx @@ -1,11 +1,14 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor, act } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import ManualDNSChallenge from '../dns-providers/ManualDNSChallenge' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + import { useChallengePoll, useManualChallengeMutations } from '../../hooks/useManualChallenge' -import type { ManualChallenge } from '../../api/manualChallenge' import { toast } from '../../utils/toast' +import ManualDNSChallenge from '../dns-providers/ManualDNSChallenge' + +import type { ManualChallenge } from '../../api/manualChallenge' + // Mock dependencies vi.mock('../../hooks/useManualChallenge') diff --git a/frontend/src/components/__tests__/NotificationCenter.test.tsx b/frontend/src/components/__tests__/NotificationCenter.test.tsx index a3000cb1..17dc02e2 100644 --- a/frontend/src/components/__tests__/NotificationCenter.test.tsx +++ b/frontend/src/components/__tests__/NotificationCenter.test.tsx @@ -1,9 +1,10 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import NotificationCenter from '../NotificationCenter' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + import * as api from '../../api/system' +import NotificationCenter from '../NotificationCenter' // Mock the API vi.mock('../../api/system', () => ({ diff --git a/frontend/src/components/__tests__/PasswordStrengthMeter.test.tsx b/frontend/src/components/__tests__/PasswordStrengthMeter.test.tsx index 72b93441..e59d4c0f 100644 --- a/frontend/src/components/__tests__/PasswordStrengthMeter.test.tsx +++ b/frontend/src/components/__tests__/PasswordStrengthMeter.test.tsx @@ -1,5 +1,6 @@ -import { describe, it, expect } from 'vitest' import { render, screen } from '@testing-library/react' +import { describe, it, expect } from 'vitest' + import { PasswordStrengthMeter } from '../PasswordStrengthMeter' describe('PasswordStrengthMeter', () => { diff --git a/frontend/src/components/__tests__/PermissionsPolicyBuilder.test.tsx b/frontend/src/components/__tests__/PermissionsPolicyBuilder.test.tsx index 123c6cdb..a471b1d5 100644 --- a/frontend/src/components/__tests__/PermissionsPolicyBuilder.test.tsx +++ b/frontend/src/components/__tests__/PermissionsPolicyBuilder.test.tsx @@ -1,7 +1,8 @@ import { render, screen } from '@testing-library/react'; -import { describe, it, expect, vi } from 'vitest'; -import { PermissionsPolicyBuilder } from '../PermissionsPolicyBuilder'; import userEvent from '@testing-library/user-event'; +import { describe, it, expect, vi } from 'vitest'; + +import { PermissionsPolicyBuilder } from '../PermissionsPolicyBuilder'; describe('PermissionsPolicyBuilder', () => { const defaultProps = { diff --git a/frontend/src/components/__tests__/ProxyHostForm-dns.test.tsx b/frontend/src/components/__tests__/ProxyHostForm-dns.test.tsx index 77bb92a5..025e9e9c 100644 --- a/frontend/src/components/__tests__/ProxyHostForm-dns.test.tsx +++ b/frontend/src/components/__tests__/ProxyHostForm-dns.test.tsx @@ -1,11 +1,14 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import ProxyHostForm from '../ProxyHostForm' -import type { ProxyHost } from '../../api/proxyHosts' -import { mockRemoteServers } from '../../test/mockData' import { toast } from 'react-hot-toast' +import { describe, it, expect, vi, beforeEach } from 'vitest' + +import { mockRemoteServers } from '../../test/mockData' +import ProxyHostForm from '../ProxyHostForm' + +import type { ProxyHost } from '../../api/proxyHosts' + // Mock the hooks vi.mock('../../hooks/useRemoteServers', () => ({ diff --git a/frontend/src/components/__tests__/ProxyHostForm-dropdown-changes.test.tsx b/frontend/src/components/__tests__/ProxyHostForm-dropdown-changes.test.tsx index fa97d136..63261e7d 100644 --- a/frontend/src/components/__tests__/ProxyHostForm-dropdown-changes.test.tsx +++ b/frontend/src/components/__tests__/ProxyHostForm-dropdown-changes.test.tsx @@ -1,13 +1,15 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { render, screen, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import ProxyHostForm from '../ProxyHostForm' -import type { ProxyHost } from '../../api/proxyHosts' -import type { AccessList } from '../../api/accessLists' -import type { SecurityHeaderProfile } from '../../api/securityHeaders' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import { useAccessLists } from '../../hooks/useAccessLists' import { useSecurityHeaderProfiles } from '../../hooks/useSecurityHeaders' +import ProxyHostForm from '../ProxyHostForm' + +import type { AccessList } from '../../api/accessLists' +import type { ProxyHost } from '../../api/proxyHosts' +import type { SecurityHeaderProfile } from '../../api/securityHeaders' // Mock all required hooks vi.mock('../../hooks/useRemoteServers', () => ({ diff --git a/frontend/src/components/__tests__/ProxyHostForm-token-coverage.test.tsx b/frontend/src/components/__tests__/ProxyHostForm-token-coverage.test.tsx index a659b8af..18050013 100644 --- a/frontend/src/components/__tests__/ProxyHostForm-token-coverage.test.tsx +++ b/frontend/src/components/__tests__/ProxyHostForm-token-coverage.test.tsx @@ -1,8 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + import ProxyHostForm from '../ProxyHostForm'; + import type { ProxyHost } from '../../api/proxyHosts'; vi.mock('../../hooks/useRemoteServers', () => ({ diff --git a/frontend/src/components/__tests__/ProxyHostForm-uptime.test.tsx b/frontend/src/components/__tests__/ProxyHostForm-uptime.test.tsx index 5d77e3c5..8ec78c67 100644 --- a/frontend/src/components/__tests__/ProxyHostForm-uptime.test.tsx +++ b/frontend/src/components/__tests__/ProxyHostForm-uptime.test.tsx @@ -1,7 +1,9 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor, fireEvent } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { type Mock } from 'vitest' + import ProxyHostForm from '../ProxyHostForm' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' vi.mock('../../api/uptime', () => ({ syncMonitors: vi.fn(() => Promise.resolve({})), @@ -103,7 +105,7 @@ describe('ProxyHostForm Add Uptime flow', () => { await waitFor(() => expect(uptime.syncMonitors).toHaveBeenCalledWith({ interval: 30, max_retries: 2 })) // Ensure onSubmit payload does not include temporary uptime keys - const onSubmitMock = onSubmit as unknown as import('vitest').Mock + const onSubmitMock = onSubmit as unknown as Mock const submittedPayload = onSubmitMock.mock.calls[0][0] expect(submittedPayload).not.toHaveProperty('addUptime') expect(submittedPayload).not.toHaveProperty('uptimeInterval') @@ -115,7 +117,7 @@ describe('ProxyHostForm Add Uptime flow', () => { const onCancel = vi.fn() const uptime = await import('../../api/uptime') - const syncMock = uptime.syncMonitors as unknown as import('vitest').Mock + const syncMock = uptime.syncMonitors as unknown as Mock syncMock.mockRejectedValueOnce('') const toastModule = await import('react-hot-toast') diff --git a/frontend/src/components/__tests__/ProxyHostForm.test.tsx b/frontend/src/components/__tests__/ProxyHostForm.test.tsx index c579f072..665e384b 100644 --- a/frontend/src/components/__tests__/ProxyHostForm.test.tsx +++ b/frontend/src/components/__tests__/ProxyHostForm.test.tsx @@ -1,11 +1,13 @@ -import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor, within, fireEvent } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { act } from 'react' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import ProxyHostForm from '../ProxyHostForm' -import type { ProxyHost } from '../../api/proxyHosts' +import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest' + +import { testProxyHostConnection, type ProxyHost } from '../../api/proxyHosts' import { mockRemoteServers } from '../../test/mockData' +import ProxyHostForm from '../ProxyHostForm' + // Mock the hooks vi.mock('../../hooks/useRemoteServers', () => ({ @@ -163,7 +165,6 @@ const selectComboboxOption = async (label: string | RegExp, optionText: string) await userEvent.click(option) } -import { testProxyHostConnection } from '../../api/proxyHosts' describe('ProxyHostForm', () => { const mockOnSubmit = vi.fn(() => Promise.resolve()) @@ -250,7 +251,7 @@ describe('ProxyHostForm', () => { }) it('tests connection successfully', async () => { - vi.mocked(testProxyHostConnection).mockResolvedValue(undefined) + vi.mocked(testProxyHostConnection).mockResolvedValue() await renderWithClientAct( @@ -1297,9 +1298,7 @@ describe('ProxyHostForm', () => { const advancedConfigField = screen.getByLabelText(/Advanced Caddy Config/i) as HTMLTextAreaElement // Verify it contains JSON (Plex has some default config) - if (advancedConfigField.value) { - expect(advancedConfigField.value).toContain('handler') - } + expect(advancedConfigField.value).toContain('handler') }) it('allows manual advanced config input', async () => { diff --git a/frontend/src/components/__tests__/RemoteServerForm.test.tsx b/frontend/src/components/__tests__/RemoteServerForm.test.tsx index 3a5e7dfa..780bfa0e 100644 --- a/frontend/src/components/__tests__/RemoteServerForm.test.tsx +++ b/frontend/src/components/__tests__/RemoteServerForm.test.tsx @@ -1,8 +1,9 @@ -import { describe, it, expect, vi, afterEach } from 'vitest' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import RemoteServerForm from '../RemoteServerForm' +import { describe, it, expect, vi, afterEach } from 'vitest' + import * as remoteServersApi from '../../api/remoteServers' +import RemoteServerForm from '../RemoteServerForm' // Mock the API vi.mock('../../api/remoteServers', () => ({ diff --git a/frontend/src/components/__tests__/SecurityHeaderProfileForm.test.tsx b/frontend/src/components/__tests__/SecurityHeaderProfileForm.test.tsx index 6ff03777..47d8e80f 100644 --- a/frontend/src/components/__tests__/SecurityHeaderProfileForm.test.tsx +++ b/frontend/src/components/__tests__/SecurityHeaderProfileForm.test.tsx @@ -1,8 +1,9 @@ -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { describe, it, expect, vi } from 'vitest'; -import { SecurityHeaderProfileForm } from '../SecurityHeaderProfileForm'; + import { securityHeadersApi, type SecurityHeaderProfile } from '../../api/securityHeaders'; +import { SecurityHeaderProfileForm } from '../SecurityHeaderProfileForm'; vi.mock('../../api/securityHeaders'); @@ -305,10 +306,9 @@ describe('SecurityHeaderProfileForm', () => { const reportOnlyContainer = reportOnlyText.closest('div')?.parentElement; const reportOnlySwitch = reportOnlyContainer?.querySelector('input[type="checkbox"]'); - if(reportOnlySwitch) { - fireEvent.click(reportOnlySwitch); // Disable - expect(screen.queryByPlaceholderText(/^https:\/\/example\.com\/csp-report$/)).not.toBeInTheDocument(); - } + expect(reportOnlySwitch).toBeDefined(); + fireEvent.click(reportOnlySwitch!); // Disable + expect(screen.queryByPlaceholderText(/^https:\/\/example\.com\/csp-report$/)).not.toBeInTheDocument(); }); it('should disable form for presets', () => { diff --git a/frontend/src/components/__tests__/SecurityNotificationSettingsModal.test.tsx b/frontend/src/components/__tests__/SecurityNotificationSettingsModal.test.tsx index d01d76f9..a46c5d6a 100644 --- a/frontend/src/components/__tests__/SecurityNotificationSettingsModal.test.tsx +++ b/frontend/src/components/__tests__/SecurityNotificationSettingsModal.test.tsx @@ -2,14 +2,15 @@ * Tests for security notification settings on the Notifications page. * The modal has been removed; settings are now managed on /settings/notifications. */ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { QueryClientProvider } from '@tanstack/react-query'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +import * as notificationsApi from '../../api/notifications'; import Notifications from '../../pages/Notifications'; import { createTestQueryClient } from '../../test/createTestQueryClient'; -import * as notificationsApi from '../../api/notifications'; vi.mock('react-i18next', () => ({ useTranslation: () => ({ t: (key: string) => key }), diff --git a/frontend/src/components/__tests__/SecurityScoreDisplay.test.tsx b/frontend/src/components/__tests__/SecurityScoreDisplay.test.tsx index 7831ef51..eb20c184 100644 --- a/frontend/src/components/__tests__/SecurityScoreDisplay.test.tsx +++ b/frontend/src/components/__tests__/SecurityScoreDisplay.test.tsx @@ -1,5 +1,6 @@ import { render, screen, fireEvent } from '@testing-library/react'; import { describe, it, expect } from 'vitest'; + import { SecurityScoreDisplay } from '../SecurityScoreDisplay'; describe('SecurityScoreDisplay', () => { diff --git a/frontend/src/components/__tests__/SystemStatus.test.tsx b/frontend/src/components/__tests__/SystemStatus.test.tsx index aa972a62..e18f4078 100644 --- a/frontend/src/components/__tests__/SystemStatus.test.tsx +++ b/frontend/src/components/__tests__/SystemStatus.test.tsx @@ -1,8 +1,9 @@ -import { describe, it, expect, vi } from 'vitest' -import { render, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import SystemStatus from '../SystemStatus' +import { render, waitFor } from '@testing-library/react' +import { describe, it, expect, vi } from 'vitest' + import * as systemApi from '../../api/system' +import SystemStatus from '../SystemStatus' // Mock the API module vi.mock('../../api/system', () => ({ diff --git a/frontend/src/components/__tests__/WebSocketStatusCard.test.tsx b/frontend/src/components/__tests__/WebSocketStatusCard.test.tsx index 53bfbe5f..43117ff1 100644 --- a/frontend/src/components/__tests__/WebSocketStatusCard.test.tsx +++ b/frontend/src/components/__tests__/WebSocketStatusCard.test.tsx @@ -1,9 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { WebSocketStatusCard } from '../WebSocketStatusCard'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + import * as websocketApi from '../../api/websocket'; +import { WebSocketStatusCard } from '../WebSocketStatusCard'; // Mock the API functions vi.mock('../../api/websocket'); diff --git a/frontend/src/components/dialogs/ImportSuccessModal.tsx b/frontend/src/components/dialogs/ImportSuccessModal.tsx index 911b5e71..d24c3956 100644 --- a/frontend/src/components/dialogs/ImportSuccessModal.tsx +++ b/frontend/src/components/dialogs/ImportSuccessModal.tsx @@ -29,7 +29,7 @@ export default function ImportSuccessModal({ return (
-
+
{/* Header */}
diff --git a/frontend/src/components/dialogs/__tests__/ImportSuccessModal.test.tsx b/frontend/src/components/dialogs/__tests__/ImportSuccessModal.test.tsx index 8890dcab..aaff65d6 100644 --- a/frontend/src/components/dialogs/__tests__/ImportSuccessModal.test.tsx +++ b/frontend/src/components/dialogs/__tests__/ImportSuccessModal.test.tsx @@ -1,5 +1,6 @@ -import { describe, it, expect, vi } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' +import { describe, it, expect, vi } from 'vitest' + import ImportSuccessModal from '../ImportSuccessModal' describe('ImportSuccessModal', () => { diff --git a/frontend/src/components/dns-providers/ManualDNSChallenge.tsx b/frontend/src/components/dns-providers/ManualDNSChallenge.tsx index a6a2d019..1d742cf1 100644 --- a/frontend/src/components/dns-providers/ManualDNSChallenge.tsx +++ b/frontend/src/components/dns-providers/ManualDNSChallenge.tsx @@ -1,5 +1,3 @@ -import { useState, useEffect, useRef, useCallback } from 'react' -import { useTranslation } from 'react-i18next' import { Copy, Check, @@ -11,10 +9,15 @@ import { Loader2, Info, } from 'lucide-react' -import { Button, Card, CardHeader, CardContent, Progress, Alert } from '../ui' +import { useState, useEffect, useRef, useCallback } from 'react' +import { useTranslation } from 'react-i18next' + import { useChallengePoll, useManualChallengeMutations } from '../../hooks/useManualChallenge' -import type { ManualChallenge, ChallengeStatus } from '../../api/manualChallenge' import { toast } from '../../utils/toast' +import { Button, Card, CardHeader, CardContent, Progress, Alert } from '../ui' + +import type { ManualChallenge, ChallengeStatus } from '../../api/manualChallenge' + interface ManualDNSChallengeProps { /** The DNS provider ID */ diff --git a/frontend/src/components/layout/PageShell.tsx b/frontend/src/components/layout/PageShell.tsx index 8e8d9876..a4371ade 100644 --- a/frontend/src/components/layout/PageShell.tsx +++ b/frontend/src/components/layout/PageShell.tsx @@ -1,4 +1,5 @@ import * as React from 'react' + import { cn } from '../../utils/cn' export interface PageShellProps { diff --git a/frontend/src/components/ui/Alert.tsx b/frontend/src/components/ui/Alert.tsx index afe185a9..5954df75 100644 --- a/frontend/src/components/ui/Alert.tsx +++ b/frontend/src/components/ui/Alert.tsx @@ -1,6 +1,4 @@ -import * as React from 'react' import { cva, type VariantProps } from 'class-variance-authority' -import { cn } from '../../utils/cn' import { Info, CheckCircle, @@ -9,6 +7,10 @@ import { X, type LucideIcon, } from 'lucide-react' +import * as React from 'react' + +import { cn } from '../../utils/cn' + const alertVariants = cva( 'relative flex gap-3 p-4 rounded-lg border transition-all duration-normal', diff --git a/frontend/src/components/ui/Badge.tsx b/frontend/src/components/ui/Badge.tsx index e86e5515..6828d9fd 100644 --- a/frontend/src/components/ui/Badge.tsx +++ b/frontend/src/components/ui/Badge.tsx @@ -1,4 +1,5 @@ import { cva, type VariantProps } from 'class-variance-authority' + import { cn } from '../../utils/cn' const badgeVariants = cva( diff --git a/frontend/src/components/ui/Button.tsx b/frontend/src/components/ui/Button.tsx index 17e15c1a..502239b4 100644 --- a/frontend/src/components/ui/Button.tsx +++ b/frontend/src/components/ui/Button.tsx @@ -1,6 +1,7 @@ -import * as React from 'react' import { cva, type VariantProps } from 'class-variance-authority' import { Loader2, type LucideIcon } from 'lucide-react' +import * as React from 'react' + import { cn } from '../../utils/cn' const buttonVariants = cva( diff --git a/frontend/src/components/ui/Card.tsx b/frontend/src/components/ui/Card.tsx index f739f81d..c03011b0 100644 --- a/frontend/src/components/ui/Card.tsx +++ b/frontend/src/components/ui/Card.tsx @@ -1,5 +1,6 @@ -import * as React from 'react' import { cva, type VariantProps } from 'class-variance-authority' +import * as React from 'react' + import { cn } from '../../utils/cn' const cardVariants = cva( diff --git a/frontend/src/components/ui/Checkbox.tsx b/frontend/src/components/ui/Checkbox.tsx index 1bd50d75..f0503e81 100644 --- a/frontend/src/components/ui/Checkbox.tsx +++ b/frontend/src/components/ui/Checkbox.tsx @@ -1,6 +1,7 @@ -import * as React from 'react' import * as CheckboxPrimitive from '@radix-ui/react-checkbox' import { Check, Minus } from 'lucide-react' +import * as React from 'react' + import { cn } from '../../utils/cn' export interface CheckboxProps diff --git a/frontend/src/components/ui/DataTable.tsx b/frontend/src/components/ui/DataTable.tsx index 400aec96..ded9134c 100644 --- a/frontend/src/components/ui/DataTable.tsx +++ b/frontend/src/components/ui/DataTable.tsx @@ -1,7 +1,8 @@ -import * as React from 'react' import { ChevronUp, ChevronDown, ChevronsUpDown } from 'lucide-react' -import { cn } from '../../utils/cn' +import * as React from 'react' + import { Checkbox } from './Checkbox' +import { cn } from '../../utils/cn' export interface Column { key: string diff --git a/frontend/src/components/ui/Dialog.tsx b/frontend/src/components/ui/Dialog.tsx index e59e6aa7..e6ca7b14 100644 --- a/frontend/src/components/ui/Dialog.tsx +++ b/frontend/src/components/ui/Dialog.tsx @@ -1,6 +1,7 @@ -import * as React from 'react' import * as DialogPrimitive from '@radix-ui/react-dialog' import { X } from 'lucide-react' +import * as React from 'react' + import { cn } from '../../utils/cn' const Dialog = DialogPrimitive.Root diff --git a/frontend/src/components/ui/EmptyState.tsx b/frontend/src/components/ui/EmptyState.tsx index fdc2ed92..7104afdc 100644 --- a/frontend/src/components/ui/EmptyState.tsx +++ b/frontend/src/components/ui/EmptyState.tsx @@ -1,6 +1,7 @@ import * as React from 'react' -import { cn } from '../../utils/cn' + import { Button, type ButtonProps } from './Button' +import { cn } from '../../utils/cn' export interface EmptyStateAction { label: string diff --git a/frontend/src/components/ui/Input.tsx b/frontend/src/components/ui/Input.tsx index b33e3ee5..aa2eabb9 100644 --- a/frontend/src/components/ui/Input.tsx +++ b/frontend/src/components/ui/Input.tsx @@ -1,5 +1,6 @@ -import * as React from 'react' import { Eye, EyeOff, type LucideIcon } from 'lucide-react' +import * as React from 'react' + import { cn } from '../../utils/cn' export interface InputProps extends React.InputHTMLAttributes { diff --git a/frontend/src/components/ui/Label.tsx b/frontend/src/components/ui/Label.tsx index 75e4d60a..e5eb68f5 100644 --- a/frontend/src/components/ui/Label.tsx +++ b/frontend/src/components/ui/Label.tsx @@ -1,5 +1,6 @@ -import * as React from 'react' import { cva, type VariantProps } from 'class-variance-authority' +import * as React from 'react' + import { cn } from '../../utils/cn' const labelVariants = cva( diff --git a/frontend/src/components/ui/NativeSelect.tsx b/frontend/src/components/ui/NativeSelect.tsx index edb305ba..658b0b2c 100644 --- a/frontend/src/components/ui/NativeSelect.tsx +++ b/frontend/src/components/ui/NativeSelect.tsx @@ -1,4 +1,5 @@ import { forwardRef } from 'react'; + import { cn } from '../../utils/cn'; export interface NativeSelectProps extends React.SelectHTMLAttributes { diff --git a/frontend/src/components/ui/Progress.tsx b/frontend/src/components/ui/Progress.tsx index 935a640c..adf34d46 100644 --- a/frontend/src/components/ui/Progress.tsx +++ b/frontend/src/components/ui/Progress.tsx @@ -1,6 +1,7 @@ -import * as React from 'react' import * as ProgressPrimitive from '@radix-ui/react-progress' import { cva, type VariantProps } from 'class-variance-authority' +import * as React from 'react' + import { cn } from '../../utils/cn' const progressVariants = cva( diff --git a/frontend/src/components/ui/Select.tsx b/frontend/src/components/ui/Select.tsx index f3471eea..d84fb2dd 100644 --- a/frontend/src/components/ui/Select.tsx +++ b/frontend/src/components/ui/Select.tsx @@ -1,6 +1,7 @@ -import * as React from 'react' import * as SelectPrimitive from '@radix-ui/react-select' import { Check, ChevronDown, ChevronUp } from 'lucide-react' +import * as React from 'react' + import { cn } from '../../utils/cn' const Select = SelectPrimitive.Root @@ -83,7 +84,7 @@ const SelectContent = React.forwardRef< {children} @@ -139,7 +140,7 @@ const SelectItem = React.forwardRef< 'rounded-md py-2 pl-8 pr-2 text-sm', 'outline-none', 'focus:bg-surface-muted focus:text-content-primary', - 'data-disabled:pointer-events-none data-disabled:opacity-50', + 'data-[disabled]:pointer-events-none data-[disabled]:opacity-50', className )} {...props} diff --git a/frontend/src/components/ui/Skeleton.tsx b/frontend/src/components/ui/Skeleton.tsx index e44dc97c..d067dd5f 100644 --- a/frontend/src/components/ui/Skeleton.tsx +++ b/frontend/src/components/ui/Skeleton.tsx @@ -1,4 +1,5 @@ import { cva, type VariantProps } from 'class-variance-authority' + import { cn } from '../../utils/cn' const skeletonVariants = cva( diff --git a/frontend/src/components/ui/StatsCard.tsx b/frontend/src/components/ui/StatsCard.tsx index 54711ff3..03fe7413 100644 --- a/frontend/src/components/ui/StatsCard.tsx +++ b/frontend/src/components/ui/StatsCard.tsx @@ -1,5 +1,6 @@ -import * as React from 'react' import { TrendingUp, TrendingDown, Minus } from 'lucide-react' +import * as React from 'react' + import { cn } from '../../utils/cn' export interface StatsCardChange { diff --git a/frontend/src/components/ui/Switch.tsx b/frontend/src/components/ui/Switch.tsx index c9d626dd..89214db1 100644 --- a/frontend/src/components/ui/Switch.tsx +++ b/frontend/src/components/ui/Switch.tsx @@ -1,4 +1,5 @@ import * as React from 'react' + import { cn } from '../../utils/cn' interface SwitchProps extends React.InputHTMLAttributes { diff --git a/frontend/src/components/ui/Tabs.test.tsx b/frontend/src/components/ui/Tabs.test.tsx index 2774bb51..36168b11 100644 --- a/frontend/src/components/ui/Tabs.test.tsx +++ b/frontend/src/components/ui/Tabs.test.tsx @@ -1,7 +1,8 @@ import '@testing-library/jest-dom/vitest' import { render, screen } from '@testing-library/react' -import { describe, it, expect } from 'vitest' import userEvent from '@testing-library/user-event' +import { describe, it, expect } from 'vitest' + import { Tabs, TabsList, TabsTrigger, TabsContent } from './Tabs' describe('Tabs', () => { diff --git a/frontend/src/components/ui/Tabs.tsx b/frontend/src/components/ui/Tabs.tsx index 40882376..ed7b2b7d 100644 --- a/frontend/src/components/ui/Tabs.tsx +++ b/frontend/src/components/ui/Tabs.tsx @@ -1,5 +1,6 @@ -import * as React from 'react' import * as TabsPrimitive from '@radix-ui/react-tabs' +import * as React from 'react' + import { cn } from '../../utils/cn' const Tabs = TabsPrimitive.Root diff --git a/frontend/src/components/ui/Textarea.tsx b/frontend/src/components/ui/Textarea.tsx index 4dc8d6e9..764c04fa 100644 --- a/frontend/src/components/ui/Textarea.tsx +++ b/frontend/src/components/ui/Textarea.tsx @@ -1,4 +1,5 @@ import * as React from 'react' + import { cn } from '../../utils/cn' export interface TextareaProps diff --git a/frontend/src/components/ui/Tooltip.tsx b/frontend/src/components/ui/Tooltip.tsx index 5d799612..855a6de6 100644 --- a/frontend/src/components/ui/Tooltip.tsx +++ b/frontend/src/components/ui/Tooltip.tsx @@ -1,5 +1,6 @@ -import * as React from 'react' import * as TooltipPrimitive from '@radix-ui/react-tooltip' +import * as React from 'react' + import { cn } from '../../utils/cn' const TooltipProvider = TooltipPrimitive.Provider diff --git a/frontend/src/components/ui/__tests__/Alert.test.tsx b/frontend/src/components/ui/__tests__/Alert.test.tsx index a6cb9bea..254989aa 100644 --- a/frontend/src/components/ui/__tests__/Alert.test.tsx +++ b/frontend/src/components/ui/__tests__/Alert.test.tsx @@ -1,7 +1,8 @@ import '@testing-library/jest-dom/vitest' import { render, screen, fireEvent } from '@testing-library/react' -import { describe, it, expect, vi } from 'vitest' import { AlertCircle } from 'lucide-react' +import { describe, it, expect, vi } from 'vitest' + import { Alert, AlertTitle, AlertDescription } from '../Alert' describe('Alert', () => { diff --git a/frontend/src/components/ui/__tests__/DataTable.test.tsx b/frontend/src/components/ui/__tests__/DataTable.test.tsx index 3d96eeb0..beddcf71 100644 --- a/frontend/src/components/ui/__tests__/DataTable.test.tsx +++ b/frontend/src/components/ui/__tests__/DataTable.test.tsx @@ -1,5 +1,6 @@ import { render, screen, fireEvent } from '@testing-library/react' import { describe, it, expect, vi } from 'vitest' + import { DataTable, type Column } from '../DataTable' interface TestRow { diff --git a/frontend/src/components/ui/__tests__/Input.test.tsx b/frontend/src/components/ui/__tests__/Input.test.tsx index 3f956f44..d66074ba 100644 --- a/frontend/src/components/ui/__tests__/Input.test.tsx +++ b/frontend/src/components/ui/__tests__/Input.test.tsx @@ -1,8 +1,9 @@ import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { describe, it, expect, vi } from 'vitest' -import { createRef } from 'react' import { Search, Mail, Lock } from 'lucide-react' +import { createRef } from 'react' +import { describe, it, expect, vi } from 'vitest' + import { Input } from '../Input' describe('Input', () => { diff --git a/frontend/src/components/ui/__tests__/Skeleton.test.tsx b/frontend/src/components/ui/__tests__/Skeleton.test.tsx index 6abb0f79..41e5bf3b 100644 --- a/frontend/src/components/ui/__tests__/Skeleton.test.tsx +++ b/frontend/src/components/ui/__tests__/Skeleton.test.tsx @@ -1,5 +1,6 @@ import { render, screen } from '@testing-library/react' import { describe, it, expect } from 'vitest' + import { Skeleton, SkeletonCard, diff --git a/frontend/src/components/ui/__tests__/StatsCard.test.tsx b/frontend/src/components/ui/__tests__/StatsCard.test.tsx index 42befffc..13141052 100644 --- a/frontend/src/components/ui/__tests__/StatsCard.test.tsx +++ b/frontend/src/components/ui/__tests__/StatsCard.test.tsx @@ -1,6 +1,7 @@ import { render, screen } from '@testing-library/react' -import { describe, it, expect } from 'vitest' import { Users } from 'lucide-react' +import { describe, it, expect } from 'vitest' + import { StatsCard, type StatsCardChange } from '../StatsCard' describe('StatsCard', () => { diff --git a/frontend/src/context/AuthContext.tsx b/frontend/src/context/AuthContext.tsx index 44a9c333..4fd416bb 100644 --- a/frontend/src/context/AuthContext.tsx +++ b/frontend/src/context/AuthContext.tsx @@ -1,6 +1,7 @@ import { useState, useEffect, useCallback, useRef, type ReactNode, type FC } from 'react'; + +import { AuthContext, type User } from './AuthContextValue'; import client, { setAuthToken, setAuthErrorHandler } from '../api/client'; -import { AuthContext, User } from './AuthContextValue'; export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => { const [user, setUser] = useState(null); @@ -164,15 +165,15 @@ export const AuthProvider: FC<{ children: ReactNode }> = ({ children }) => { const events = ['mousedown', 'keydown', 'scroll', 'touchstart']; const handleActivity = () => resetTimer(); - events.forEach(event => { + for (const event of events) { window.addEventListener(event, handleActivity); - }); + } return () => { if (timeoutId) clearTimeout(timeoutId); - events.forEach(event => { + for (const event of events) { window.removeEventListener(event, handleActivity); - }); + } }; }, [user, logout]); diff --git a/frontend/src/context/LanguageContext.tsx b/frontend/src/context/LanguageContext.tsx index dcb7d6eb..590671df 100644 --- a/frontend/src/context/LanguageContext.tsx +++ b/frontend/src/context/LanguageContext.tsx @@ -1,6 +1,7 @@ -import { ReactNode, useState, useEffect } from 'react' +import { type ReactNode, useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { LanguageContext, Language } from './LanguageContextValue' + +import { LanguageContext, type Language } from './LanguageContextValue' export function LanguageProvider({ children }: { children: ReactNode }) { const { i18n } = useTranslation() diff --git a/frontend/src/context/ThemeContext.tsx b/frontend/src/context/ThemeContext.tsx index 98f0a3db..5e17057b 100644 --- a/frontend/src/context/ThemeContext.tsx +++ b/frontend/src/context/ThemeContext.tsx @@ -1,5 +1,6 @@ -import { useEffect, useState, ReactNode } from 'react' -import { ThemeContext, Theme } from './ThemeContextValue' +import { useEffect, useState, type ReactNode } from 'react' + +import { ThemeContext, type Theme } from './ThemeContextValue' export function ThemeProvider({ children }: { children: ReactNode }) { const [theme, setTheme] = useState(() => { diff --git a/frontend/src/data/__tests__/crowdsecPresets.test.ts b/frontend/src/data/__tests__/crowdsecPresets.test.ts index 6ca204c0..330fc3a1 100644 --- a/frontend/src/data/__tests__/crowdsecPresets.test.ts +++ b/frontend/src/data/__tests__/crowdsecPresets.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect } from 'vitest' + import { CROWDSEC_PRESETS, findCrowdsecPreset, type CrowdsecPreset } from '../crowdsecPresets' describe('crowdsecPresets', () => { @@ -13,35 +14,35 @@ describe('crowdsecPresets', () => { }) it('should have valid YAML content for each preset', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset.content).toContain('configs:') expect(preset.content).toMatch(/collections:|parsers:|scenarios:|postoverflows:/) - }) + } }) it('should have required metadata fields', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset).toHaveProperty('slug') expect(preset).toHaveProperty('title') expect(preset).toHaveProperty('description') expect(preset).toHaveProperty('content') expect(preset.slug).toMatch(/^[a-z0-9-]+$/) // Slug format validation - }) + } }) it('should have descriptive titles and descriptions', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset.title.length).toBeGreaterThan(5) expect(preset.description.length).toBeGreaterThan(10) - }) + } }) it('should have tags for each preset', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset.tags).toBeDefined() expect(Array.isArray(preset.tags)).toBe(true) expect(preset.tags!.length).toBeGreaterThan(0) - }) + } }) it('should have warnings for production-critical presets', () => { @@ -61,32 +62,32 @@ describe('crowdsecPresets', () => { describe('preset content integrity', () => { it('should have valid CrowdSec YAML structure', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { const lines = preset.content.split('\n') expect(lines[0]).toMatch(/^configs:/) - }) + } }) it('should reference valid CrowdSec hub items', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { // Extract collection references const collections = preset.content.match(/- crowdsecurity\/[\w-]+/g) || [] - collections.forEach((item) => { + for (const item of collections) { // Hub items can contain underscores (e.g., http-crawl-non_statics) expect(item).toMatch(/^- crowdsecurity\/[a-z0-9-_]+$/) - }) - }) + } + } }) it('should have proper YAML indentation', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { const lines = preset.content.split('\n') // Check that collection/parser/scenario items are indented with spaces const itemLines = lines.filter((line) => line.trim().startsWith('- crowdsecurity/')) - itemLines.forEach((line) => { + for (const line of itemLines) { expect(line).toMatch(/^\s{4,}- crowdsecurity\//) - }) - }) + } + } }) it('should reference known CrowdSec collections', () => { @@ -191,11 +192,11 @@ describe('crowdsecPresets', () => { describe('preset tag consistency', () => { it('should have consistent tag naming (lowercase, hyphenated)', () => { - CROWDSEC_PRESETS.forEach((preset) => { - preset.tags?.forEach((tag) => { + for (const preset of CROWDSEC_PRESETS) { + for (const tag of preset.tags ?? []) { expect(tag).toMatch(/^[a-z0-9-]+$/) - }) - }) + } + } }) it('should have descriptive tags', () => { @@ -207,100 +208,96 @@ describe('crowdsecPresets', () => { describe('slug format validation', () => { it('should use lowercase slugs', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset.slug).toBe(preset.slug.toLowerCase()) - }) + } }) it('should use hyphens as separators', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset.slug).not.toContain('_') expect(preset.slug).not.toContain(' ') - }) + } }) it('should not have leading or trailing hyphens', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset.slug).not.toMatch(/^-/) expect(preset.slug).not.toMatch(/-$/) - }) + } }) it('should not have consecutive hyphens', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset.slug).not.toContain('--') - }) + } }) }) describe('content safety', () => { it('should not contain executable code', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset.content).not.toContain(' { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset.content).not.toContain('DROP TABLE') expect(preset.content).not.toContain('DELETE FROM') - }) + } }) it('should not contain path traversal attempts', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset.content).not.toContain('../') expect(preset.content).not.toContain('..\\') - }) + } }) }) describe('TypeScript type safety', () => { it('should satisfy CrowdsecPreset interface', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { const typedPreset: CrowdsecPreset = preset expect(typedPreset.slug).toBeTruthy() expect(typedPreset.title).toBeTruthy() expect(typedPreset.description).toBeTruthy() expect(typedPreset.content).toBeTruthy() - }) + } }) it('should have optional tags and warning properties', () => { - CROWDSEC_PRESETS.forEach((preset) => { - if (preset.tags !== undefined) { - expect(Array.isArray(preset.tags)).toBe(true) - } - if (preset.warning !== undefined) { - expect(typeof preset.warning).toBe('string') - } - }) + for (const preset of CROWDSEC_PRESETS) { + expect(preset.tags === undefined || Array.isArray(preset.tags)).toBe(true) + expect(preset.warning === undefined || typeof preset.warning === 'string').toBe(true) + } }) }) describe('usability validations', () => { it('should have human-readable titles', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset.title).not.toMatch(/^[a-z-]+$/) // Not just slug format expect(preset.title).toMatch(/^[A-Z]/) // Starts with capital - }) + } }) it('should have actionable descriptions', () => { - CROWDSEC_PRESETS.forEach((preset) => { + for (const preset of CROWDSEC_PRESETS) { expect(preset.description.split(' ').length).toBeGreaterThan(5) - }) + } }) it('should have clear warnings when present', () => { - CROWDSEC_PRESETS.forEach((preset) => { - if (preset.warning) { - expect(preset.warning.length).toBeGreaterThan(10) - expect(preset.warning).toMatch(/[.!]$/) // Ends with punctuation + for (const preset of CROWDSEC_PRESETS) { + for (const warning of preset.warning ? [preset.warning] : []) { + expect(warning.length).toBeGreaterThan(10) + expect(warning).toMatch(/[.!]$/) // Ends with punctuation } - }) + } }) }) }) diff --git a/frontend/src/data/__tests__/securityPresets.test.ts b/frontend/src/data/__tests__/securityPresets.test.ts index a2d25da0..dba189c8 100644 --- a/frontend/src/data/__tests__/securityPresets.test.ts +++ b/frontend/src/data/__tests__/securityPresets.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect } from 'vitest'; + import { SECURITY_PRESETS, getPresetById, @@ -14,7 +15,7 @@ describe('securityPresets', () => { expect(SECURITY_PRESETS.length).toBeGreaterThan(0); // Verify preset structure - SECURITY_PRESETS.forEach((preset) => { + for (const preset of SECURITY_PRESETS) { expect(preset).toHaveProperty('id'); expect(preset).toHaveProperty('name'); expect(preset).toHaveProperty('description'); @@ -23,29 +24,29 @@ describe('securityPresets', () => { expect(preset).toHaveProperty('estimatedIPs'); expect(preset).toHaveProperty('dataSource'); expect(preset).toHaveProperty('dataSourceUrl'); - }); + } }); it('has valid categories', () => { const validCategories = ['security', 'advanced']; - SECURITY_PRESETS.forEach((preset) => { + for (const preset of SECURITY_PRESETS) { expect(validCategories).toContain(preset.category); - }); + } }); it('has valid types', () => { const validTypes = ['geo_blacklist', 'blacklist']; - SECURITY_PRESETS.forEach((preset) => { + for (const preset of SECURITY_PRESETS) { expect(validTypes).toContain(preset.type); - }); + } }); it('geo_blacklist presets have countryCodes', () => { const geoPresets = SECURITY_PRESETS.filter((p) => p.type === 'geo_blacklist'); - geoPresets.forEach((preset) => { + for (const preset of geoPresets) { expect(preset.countryCodes).toBeDefined(); expect(preset.countryCodes!.length).toBeGreaterThan(0); - }); + } }); it('no IP-based blacklist presets are included (CrowdSec handles dynamic IP threats)', () => { @@ -73,17 +74,17 @@ describe('securityPresets', () => { it('returns security category presets', () => { const securityPresets = getPresetsByCategory('security'); expect(securityPresets.length).toBeGreaterThan(0); - securityPresets.forEach((preset) => { + for (const preset of securityPresets) { expect(preset.category).toBe('security'); - }); + } }); it('returns advanced category presets (may be empty)', () => { const advancedPresets = getPresetsByCategory('advanced'); expect(Array.isArray(advancedPresets)).toBe(true); - advancedPresets.forEach((preset) => { + for (const preset of advancedPresets) { expect(preset.category).toBe('advanced'); - }); + } }); }); diff --git a/frontend/src/hooks/__tests__/useAccessLists.test.tsx b/frontend/src/hooks/__tests__/useAccessLists.test.tsx index 9659b1d1..f3df8d8b 100644 --- a/frontend/src/hooks/__tests__/useAccessLists.test.tsx +++ b/frontend/src/hooks/__tests__/useAccessLists.test.tsx @@ -1,9 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, waitFor } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +import { accessListsApi, type AccessList } from '../../api/accessLists'; import { useAccessLists, useAccessList, useCreateAccessList, useUpdateAccessList, useDeleteAccessList, useTestIP } from '../useAccessLists'; -import { accessListsApi } from '../../api/accessLists'; -import type { AccessList } from '../../api/accessLists'; + // Mock the API module vi.mock('../../api/accessLists'); @@ -147,7 +148,7 @@ describe('useAccessLists hooks', () => { describe('useDeleteAccessList', () => { it('should delete an access list', async () => { - vi.mocked(accessListsApi.delete).mockResolvedValueOnce(undefined); + vi.mocked(accessListsApi.delete).mockResolvedValueOnce(); const { result } = renderHook(() => useDeleteAccessList(), { wrapper: createWrapper(), diff --git a/frontend/src/hooks/__tests__/useAuditLogs.test.tsx b/frontend/src/hooks/__tests__/useAuditLogs.test.tsx index 19fb9b0d..a631b0bc 100644 --- a/frontend/src/hooks/__tests__/useAuditLogs.test.tsx +++ b/frontend/src/hooks/__tests__/useAuditLogs.test.tsx @@ -1,6 +1,8 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { renderHook, waitFor } from '@testing-library/react' import { describe, it, expect, vi, beforeEach } from 'vitest' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + +import { getAuditLogs, getAuditLog, getAuditLogsByProvider } from '../../api/auditLogs' import { useAuditLogs, useAuditLog, useAuditLogsByProvider } from '../useAuditLogs' // Mock the API module @@ -10,8 +12,6 @@ vi.mock('../../api/auditLogs', () => ({ getAuditLogsByProvider: vi.fn(), })) -import { getAuditLogs, getAuditLog, getAuditLogsByProvider } from '../../api/auditLogs' - const mockAuditLog = { id: 1, uuid: 'test-uuid-123', diff --git a/frontend/src/hooks/__tests__/useAuth.test.tsx b/frontend/src/hooks/__tests__/useAuth.test.tsx index e3eb5dd8..0ba2e1c2 100644 --- a/frontend/src/hooks/__tests__/useAuth.test.tsx +++ b/frontend/src/hooks/__tests__/useAuth.test.tsx @@ -1,5 +1,6 @@ import { render, screen } from '@testing-library/react' import { describe, it, expect } from 'vitest' + import { AuthContext } from '../../context/AuthContextValue' import { useAuth } from '../useAuth' diff --git a/frontend/src/hooks/__tests__/useConsoleEnrollment.test.tsx b/frontend/src/hooks/__tests__/useConsoleEnrollment.test.tsx index 112522cd..24e750b3 100644 --- a/frontend/src/hooks/__tests__/useConsoleEnrollment.test.tsx +++ b/frontend/src/hooks/__tests__/useConsoleEnrollment.test.tsx @@ -1,8 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { renderHook, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { useConsoleStatus, useEnrollConsole } from '../useConsoleEnrollment' +import { renderHook, waitFor } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as consoleEnrollmentApi from '../../api/consoleEnrollment' +import { useConsoleStatus, useEnrollConsole } from '../useConsoleEnrollment' + import type { ConsoleEnrollmentStatus, ConsoleEnrollPayload } from '../../api/consoleEnrollment' vi.mock('../../api/consoleEnrollment') diff --git a/frontend/src/hooks/__tests__/useCredentials.test.tsx b/frontend/src/hooks/__tests__/useCredentials.test.tsx index 5c970093..fa8ccee0 100644 --- a/frontend/src/hooks/__tests__/useCredentials.test.tsx +++ b/frontend/src/hooks/__tests__/useCredentials.test.tsx @@ -1,7 +1,9 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { renderHook, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { ReactNode } from 'react' +import { renderHook, waitFor } from '@testing-library/react' +import { type ReactNode } from 'react' +import { describe, it, expect, vi, beforeEach } from 'vitest' + +import * as credentialsApi from '../../api/credentials' import { useCredentials, useCredential, @@ -11,7 +13,6 @@ import { useTestCredential, useEnableMultiCredentials, } from '../useCredentials' -import * as credentialsApi from '../../api/credentials' vi.mock('../../api/credentials') diff --git a/frontend/src/hooks/__tests__/useDNSDetection.test.tsx b/frontend/src/hooks/__tests__/useDNSDetection.test.tsx index 0681af55..430bc6bb 100644 --- a/frontend/src/hooks/__tests__/useDNSDetection.test.tsx +++ b/frontend/src/hooks/__tests__/useDNSDetection.test.tsx @@ -1,8 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { renderHook, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { useDetectDNSProvider, useCachedDetectionResult, useDetectionPatterns } from '../useDNSDetection' +import { renderHook, waitFor } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as api from '../../api/dnsDetection' +import { useDetectDNSProvider, useCachedDetectionResult, useDetectionPatterns } from '../useDNSDetection' + import type { DetectionResult, NameserverPattern } from '../../api/dnsDetection' vi.mock('../../api/dnsDetection') @@ -14,10 +16,9 @@ const createWrapper = () => { mutations: { retry: false }, }, }) - const Wrapper = ({ children }: { children: React.ReactNode }) => ( + return ({ children }: { children: React.ReactNode }) => ( {children} ) - return Wrapper } describe('useDNSDetection hooks', () => { diff --git a/frontend/src/hooks/__tests__/useDNSProviders.test.tsx b/frontend/src/hooks/__tests__/useDNSProviders.test.tsx index 1befbdd7..7efdb828 100644 --- a/frontend/src/hooks/__tests__/useDNSProviders.test.tsx +++ b/frontend/src/hooks/__tests__/useDNSProviders.test.tsx @@ -1,14 +1,15 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { renderHook, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { renderHook, waitFor } from '@testing-library/react' import React from 'react' +import { describe, it, expect, vi, beforeEach } from 'vitest' + +import * as api from '../../api/dnsProviders' import { useDNSProviders, useDNSProvider, useDNSProviderTypes, useDNSProviderMutations, } from '../useDNSProviders' -import * as api from '../../api/dnsProviders' vi.mock('../../api/dnsProviders') @@ -420,7 +421,7 @@ describe('useDNSProviderMutations', () => { describe('deleteMutation', () => { it('deletes provider successfully', async () => { - vi.mocked(api.deleteDNSProvider).mockResolvedValue(undefined) + vi.mocked(api.deleteDNSProvider).mockResolvedValue() const { result } = renderHook(() => useDNSProviderMutations(), { wrapper: createWrapper(), @@ -436,7 +437,7 @@ describe('useDNSProviderMutations', () => { }) it('invalidates list query on success', async () => { - vi.mocked(api.deleteDNSProvider).mockResolvedValue(undefined) + vi.mocked(api.deleteDNSProvider).mockResolvedValue() const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } }, diff --git a/frontend/src/hooks/__tests__/useDocker.test.tsx b/frontend/src/hooks/__tests__/useDocker.test.tsx index 5ae6321d..46cd41f4 100644 --- a/frontend/src/hooks/__tests__/useDocker.test.tsx +++ b/frontend/src/hooks/__tests__/useDocker.test.tsx @@ -1,9 +1,10 @@ -import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { vi, describe, it, expect, beforeEach } from 'vitest'; -import { useDocker } from '../useDocker'; -import { dockerApi } from '../../api/docker'; +import { renderHook, waitFor } from '@testing-library/react'; import React from 'react'; +import { vi, describe, it, expect, beforeEach } from 'vitest'; + +import { dockerApi } from '../../api/docker'; +import { useDocker } from '../useDocker'; vi.mock('../../api/docker', () => ({ dockerApi: { @@ -82,7 +83,7 @@ describe('useDocker', () => { }); it('does not fetch when both host and serverId are undefined', async () => { - const { result } = renderHook(() => useDocker(undefined, undefined), { + const { result } = renderHook(() => useDocker(), { wrapper: createWrapper(), }); diff --git a/frontend/src/hooks/__tests__/useDomains.test.tsx b/frontend/src/hooks/__tests__/useDomains.test.tsx index fec4edcb..6d921fd3 100644 --- a/frontend/src/hooks/__tests__/useDomains.test.tsx +++ b/frontend/src/hooks/__tests__/useDomains.test.tsx @@ -1,9 +1,10 @@ -import { renderHook, waitFor, act } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { vi, describe, it, expect, beforeEach } from 'vitest'; -import { useDomains } from '../useDomains'; -import * as api from '../../api/domains'; +import { renderHook, waitFor, act } from '@testing-library/react'; import React from 'react'; +import { vi, describe, it, expect, beforeEach } from 'vitest'; + +import * as api from '../../api/domains'; +import { useDomains } from '../useDomains'; vi.mock('../../api/domains', () => ({ getDomains: vi.fn(), @@ -93,7 +94,7 @@ describe('useDomains', () => { it('deletes a domain', async () => { vi.mocked(api.getDomains).mockResolvedValue(mockDomains); - vi.mocked(api.deleteDomain).mockResolvedValue(undefined); + vi.mocked(api.deleteDomain).mockResolvedValue(); const { result } = renderHook(() => useDomains(), { wrapper: createWrapper(), diff --git a/frontend/src/hooks/__tests__/useImport.test.ts b/frontend/src/hooks/__tests__/useImport.test.ts index 44da4e49..795d3fb8 100644 --- a/frontend/src/hooks/__tests__/useImport.test.ts +++ b/frontend/src/hooks/__tests__/useImport.test.ts @@ -1,11 +1,12 @@ -import { renderHook, act, waitFor } from '@testing-library/react' -import { vi, describe, it, expect, beforeEach } from 'vitest' -import React from 'react' -import { createTestQueryClient } from '../../test/createTestQueryClient' import { QueryClientProvider } from '@tanstack/react-query' +import { renderHook, act, waitFor } from '@testing-library/react' +import React from 'react' +import { vi, describe, it, expect, beforeEach } from 'vitest' import * as api from '../../api/import' +import { createTestQueryClient } from '../../test/createTestQueryClient' import { useImport } from '../useImport' + import type { ImportSession, ImportPreview } from '../../api/import' vi.mock('../../api/import', () => ({ diff --git a/frontend/src/hooks/__tests__/useImport.test.tsx b/frontend/src/hooks/__tests__/useImport.test.tsx index 7d141ffb..6df912cd 100644 --- a/frontend/src/hooks/__tests__/useImport.test.tsx +++ b/frontend/src/hooks/__tests__/useImport.test.tsx @@ -1,9 +1,10 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -import { renderHook, waitFor, act } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { renderHook, waitFor, act } from '@testing-library/react' import React from 'react' -import { useImport, QUERY_KEY } from '../useImport' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + import * as api from '../../api/import' +import { useImport, QUERY_KEY } from '../useImport' // Mock the API vi.mock('../../api/import', () => ({ @@ -718,7 +719,7 @@ describe('useImport', () => { vi.mocked(api.uploadCaddyfile).mockResolvedValue(mockResponse) vi.mocked(api.getImportStatus).mockResolvedValue({ has_pending: true, session: mockSession }) vi.mocked(api.getImportPreview).mockResolvedValue(mockResponse) - vi.mocked(api.cancelImport).mockResolvedValue(undefined) + vi.mocked(api.cancelImport).mockResolvedValue() const { queryClient, wrapper } = createWrapper() const removeSpy = vi.spyOn(queryClient, 'removeQueries') diff --git a/frontend/src/hooks/__tests__/useJSONImport.test.tsx b/frontend/src/hooks/__tests__/useJSONImport.test.tsx index 445939a2..f05656e4 100644 --- a/frontend/src/hooks/__tests__/useJSONImport.test.tsx +++ b/frontend/src/hooks/__tests__/useJSONImport.test.tsx @@ -1,9 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { renderHook, act, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { renderHook, act, waitFor } from '@testing-library/react' import React from 'react' -import { useJSONImport } from '../useJSONImport' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as api from '../../api/jsonImport' +import { useJSONImport } from '../useJSONImport' vi.mock('../../api/jsonImport', () => ({ uploadJSONExport: vi.fn(), @@ -124,7 +125,7 @@ describe('useJSONImport', () => { }, conflict_details: {}, }) - vi.mocked(api.cancelJSONImport).mockResolvedValue(undefined) + vi.mocked(api.cancelJSONImport).mockResolvedValue() const { result } = renderHook(() => useJSONImport(), { wrapper: createWrapper() }) diff --git a/frontend/src/hooks/__tests__/useLanguage.test.tsx b/frontend/src/hooks/__tests__/useLanguage.test.tsx index ff776965..216a0f1e 100644 --- a/frontend/src/hooks/__tests__/useLanguage.test.tsx +++ b/frontend/src/hooks/__tests__/useLanguage.test.tsx @@ -1,8 +1,9 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' import { renderHook, act } from '@testing-library/react' -import { ReactNode } from 'react' -import { useLanguage } from '../useLanguage' +import { type ReactNode } from 'react' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import { LanguageProvider } from '../../context/LanguageContext' +import { useLanguage } from '../useLanguage' // Mock i18next vi.mock('react-i18next', () => ({ @@ -79,11 +80,11 @@ describe('useLanguage', () => { const languages = ['en', 'es', 'fr', 'de', 'zh'] as const - languages.forEach((lang) => { + for (const lang of languages) { act(() => { result.current.setLanguage(lang) }) expect(result.current.language).toBe(lang) - }) + } }) }) diff --git a/frontend/src/hooks/__tests__/useManualChallenge.test.tsx b/frontend/src/hooks/__tests__/useManualChallenge.test.tsx index ff354a92..a0f1c740 100644 --- a/frontend/src/hooks/__tests__/useManualChallenge.test.tsx +++ b/frontend/src/hooks/__tests__/useManualChallenge.test.tsx @@ -1,8 +1,9 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { renderHook, waitFor, act } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { useManualChallenge, useChallengePoll, useManualChallengeMutations } from '../useManualChallenge' +import { renderHook, waitFor, act } from '@testing-library/react' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as api from '../../api/manualChallenge' +import { useManualChallenge, useChallengePoll, useManualChallengeMutations } from '../useManualChallenge' vi.mock('../../api/manualChallenge') @@ -207,7 +208,7 @@ describe('useManualChallenge hooks', () => { describe('deleteMutation', () => { it('deletes a challenge', async () => { - vi.mocked(api.deleteChallenge).mockResolvedValueOnce(undefined) + vi.mocked(api.deleteChallenge).mockResolvedValueOnce() const { result } = renderHook( () => useManualChallengeMutations(), diff --git a/frontend/src/hooks/__tests__/useNPMImport.test.tsx b/frontend/src/hooks/__tests__/useNPMImport.test.tsx index ff9a330e..27d48695 100644 --- a/frontend/src/hooks/__tests__/useNPMImport.test.tsx +++ b/frontend/src/hooks/__tests__/useNPMImport.test.tsx @@ -1,9 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { renderHook, act, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { renderHook, act, waitFor } from '@testing-library/react' import React from 'react' -import { useNPMImport } from '../useNPMImport' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as api from '../../api/npmImport' +import { useNPMImport } from '../useNPMImport' vi.mock('../../api/npmImport', () => ({ uploadNPMExport: vi.fn(), @@ -124,7 +125,7 @@ describe('useNPMImport', () => { }, conflict_details: {}, }) - vi.mocked(api.cancelNPMImport).mockResolvedValue(undefined) + vi.mocked(api.cancelNPMImport).mockResolvedValue() const { result } = renderHook(() => useNPMImport(), { wrapper: createWrapper() }) diff --git a/frontend/src/hooks/__tests__/useNotifications.test.tsx b/frontend/src/hooks/__tests__/useNotifications.test.tsx index bee14720..61807f6a 100644 --- a/frontend/src/hooks/__tests__/useNotifications.test.tsx +++ b/frontend/src/hooks/__tests__/useNotifications.test.tsx @@ -1,12 +1,13 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { ReactNode } from 'react'; +import { renderHook, waitFor } from '@testing-library/react'; +import { type ReactNode } from 'react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + +import * as notificationsApi from '../../api/notifications'; import { useSecurityNotificationSettings, useUpdateSecurityNotificationSettings, } from '../useNotifications'; -import * as notificationsApi from '../../api/notifications'; // Mock the API vi.mock('../../api/notifications', async () => { diff --git a/frontend/src/hooks/__tests__/usePlugins.test.tsx b/frontend/src/hooks/__tests__/usePlugins.test.tsx index be23adb1..f21f4685 100644 --- a/frontend/src/hooks/__tests__/usePlugins.test.tsx +++ b/frontend/src/hooks/__tests__/usePlugins.test.tsx @@ -1,7 +1,9 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { renderHook, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { renderHook, waitFor } from '@testing-library/react' import React from 'react' +import { describe, it, expect, vi, beforeEach } from 'vitest' + +import * as api from '../../api/plugins' import { usePlugins, usePlugin, @@ -9,7 +11,6 @@ import { useDisablePlugin, useReloadPlugins, } from '../usePlugins' -import * as api from '../../api/plugins' vi.mock('../../api/plugins') diff --git a/frontend/src/hooks/__tests__/useProxyHosts-bulk.test.tsx b/frontend/src/hooks/__tests__/useProxyHosts-bulk.test.tsx index 5b6bb2f9..cdeacded 100644 --- a/frontend/src/hooks/__tests__/useProxyHosts-bulk.test.tsx +++ b/frontend/src/hooks/__tests__/useProxyHosts-bulk.test.tsx @@ -1,8 +1,9 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest'; -import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { useProxyHosts } from '../useProxyHosts'; +import { renderHook, waitFor } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; + import * as proxyHostsApi from '../../api/proxyHosts'; +import { useProxyHosts } from '../useProxyHosts'; // Mock the API module vi.mock('../../api/proxyHosts'); diff --git a/frontend/src/hooks/__tests__/useProxyHosts.test.tsx b/frontend/src/hooks/__tests__/useProxyHosts.test.tsx index a6a3ca28..d2f3254e 100644 --- a/frontend/src/hooks/__tests__/useProxyHosts.test.tsx +++ b/frontend/src/hooks/__tests__/useProxyHosts.test.tsx @@ -1,10 +1,11 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -import { renderHook, waitFor, act } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { renderHook, waitFor, act } from '@testing-library/react' import React from 'react' -import { useProxyHosts } from '../useProxyHosts' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + import * as api from '../../api/proxyHosts' import { createMockProxyHost } from '../../testUtils/createMockProxyHost' +import { useProxyHosts } from '../useProxyHosts' // Mock the API vi.mock('../../api/proxyHosts', () => ({ diff --git a/frontend/src/hooks/__tests__/useRemoteServers.test.tsx b/frontend/src/hooks/__tests__/useRemoteServers.test.tsx index 30257aa0..39ea60fb 100644 --- a/frontend/src/hooks/__tests__/useRemoteServers.test.tsx +++ b/frontend/src/hooks/__tests__/useRemoteServers.test.tsx @@ -1,9 +1,10 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' -import { renderHook, waitFor, act } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { renderHook, waitFor, act } from '@testing-library/react' import React from 'react' -import { useRemoteServers } from '../useRemoteServers' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + import * as api from '../../api/remoteServers' +import { useRemoteServers } from '../useRemoteServers' // Mock the API vi.mock('../../api/remoteServers', () => ({ diff --git a/frontend/src/hooks/__tests__/useSecurity.test.tsx b/frontend/src/hooks/__tests__/useSecurity.test.tsx index 297b9540..99f5c3f9 100644 --- a/frontend/src/hooks/__tests__/useSecurity.test.tsx +++ b/frontend/src/hooks/__tests__/useSecurity.test.tsx @@ -1,6 +1,9 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { renderHook, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { renderHook, waitFor } from '@testing-library/react' +import toast from 'react-hot-toast' +import { describe, it, expect, vi, beforeEach } from 'vitest' + +import * as securityApi from '../../api/security' import { useSecurityStatus, useSecurityConfig, @@ -14,8 +17,7 @@ import { useEnableCerberus, useDisableCerberus, } from '../useSecurity' -import * as securityApi from '../../api/security' -import toast from 'react-hot-toast' + vi.mock('../../api/security') vi.mock('react-hot-toast') @@ -101,7 +103,7 @@ describe('useSecurity hooks', () => { const { result } = renderHook(() => useGenerateBreakGlassToken(), { wrapper }) - result.current.mutate(undefined) + result.current.mutate() await waitFor(() => expect(result.current.isSuccess).toBe(true)) expect(result.current.data).toEqual(mockToken) diff --git a/frontend/src/hooks/__tests__/useSecurityHeaders.test.tsx b/frontend/src/hooks/__tests__/useSecurityHeaders.test.tsx index a40d421c..c3e4b39d 100644 --- a/frontend/src/hooks/__tests__/useSecurityHeaders.test.tsx +++ b/frontend/src/hooks/__tests__/useSecurityHeaders.test.tsx @@ -1,6 +1,14 @@ -import { renderHook, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { renderHook, waitFor } from '@testing-library/react'; +import toast from 'react-hot-toast'; import { describe, it, expect, vi, beforeEach } from 'vitest'; + +import { + securityHeadersApi, + type SecurityHeaderProfile, + type SecurityHeaderPreset, + type CreateProfileRequest, +} from '../../api/securityHeaders'; import { useSecurityHeaderProfiles, useSecurityHeaderProfile, @@ -13,13 +21,6 @@ import { useValidateCSP, useBuildCSP, } from '../useSecurityHeaders'; -import { - securityHeadersApi, - SecurityHeaderProfile, - SecurityHeaderPreset, - CreateProfileRequest, -} from '../../api/securityHeaders'; -import toast from 'react-hot-toast'; vi.mock('../../api/securityHeaders'); vi.mock('react-hot-toast'); @@ -170,7 +171,7 @@ describe('useSecurityHeaders', () => { describe('useDeleteSecurityHeaderProfile', () => { it('should delete a profile successfully', async () => { - vi.mocked(securityHeadersApi.deleteProfile).mockResolvedValue(undefined); + vi.mocked(securityHeadersApi.deleteProfile).mockResolvedValue(); const { result } = renderHook(() => useDeleteSecurityHeaderProfile(), { wrapper: createWrapper(), diff --git a/frontend/src/hooks/__tests__/useTheme.test.tsx b/frontend/src/hooks/__tests__/useTheme.test.tsx index a47f2159..c0e2bd08 100644 --- a/frontend/src/hooks/__tests__/useTheme.test.tsx +++ b/frontend/src/hooks/__tests__/useTheme.test.tsx @@ -1,5 +1,6 @@ -import { describe, it, expect } from 'vitest' import { renderHook } from '@testing-library/react' +import { describe, it, expect } from 'vitest' + import { useTheme } from '../useTheme' describe('useTheme', () => { diff --git a/frontend/src/hooks/useAccessLists.ts b/frontend/src/hooks/useAccessLists.ts index a7d80f44..b0dc4f31 100644 --- a/frontend/src/hooks/useAccessLists.ts +++ b/frontend/src/hooks/useAccessLists.ts @@ -1,7 +1,8 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; -import { accessListsApi, type CreateAccessListRequest } from '../api/accessLists'; import toast from 'react-hot-toast'; +import { accessListsApi, type CreateAccessListRequest } from '../api/accessLists'; + export function useAccessLists() { return useQuery({ queryKey: ['accessLists'], diff --git a/frontend/src/hooks/useAuditLogs.ts b/frontend/src/hooks/useAuditLogs.ts index f399ca69..e98ba17d 100644 --- a/frontend/src/hooks/useAuditLogs.ts +++ b/frontend/src/hooks/useAuditLogs.ts @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query' + import { getAuditLogs, getAuditLog, diff --git a/frontend/src/hooks/useAuth.ts b/frontend/src/hooks/useAuth.ts index 598921c5..23606fe4 100644 --- a/frontend/src/hooks/useAuth.ts +++ b/frontend/src/hooks/useAuth.ts @@ -1,4 +1,5 @@ import { useContext } from 'react'; + import { AuthContext } from '../context/AuthContextValue'; export const useAuth = () => { diff --git a/frontend/src/hooks/useCertificates.ts b/frontend/src/hooks/useCertificates.ts index 358f9bd3..228c3c7d 100644 --- a/frontend/src/hooks/useCertificates.ts +++ b/frontend/src/hooks/useCertificates.ts @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query' + import { getCertificates } from '../api/certificates' interface UseCertificatesOptions { diff --git a/frontend/src/hooks/useConsoleEnrollment.ts b/frontend/src/hooks/useConsoleEnrollment.ts index d8c62ac4..9dc199a8 100644 --- a/frontend/src/hooks/useConsoleEnrollment.ts +++ b/frontend/src/hooks/useConsoleEnrollment.ts @@ -1,4 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' + import { enrollConsole, getConsoleStatus, clearConsoleEnrollment, type ConsoleEnrollPayload, type ConsoleEnrollmentStatus } from '../api/consoleEnrollment' export function useConsoleStatus(enabled = true) { diff --git a/frontend/src/hooks/useCredentials.ts b/frontend/src/hooks/useCredentials.ts index 7851ec63..2c86b388 100644 --- a/frontend/src/hooks/useCredentials.ts +++ b/frontend/src/hooks/useCredentials.ts @@ -1,4 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' + import { getCredentials, getCredential, diff --git a/frontend/src/hooks/useDNSDetection.ts b/frontend/src/hooks/useDNSDetection.ts index d198386f..0cbcbd8c 100644 --- a/frontend/src/hooks/useDNSDetection.ts +++ b/frontend/src/hooks/useDNSDetection.ts @@ -1,4 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' + import { detectDNSProvider, getDetectionPatterns, diff --git a/frontend/src/hooks/useDNSProviders.ts b/frontend/src/hooks/useDNSProviders.ts index 0a1f25f1..1de73662 100644 --- a/frontend/src/hooks/useDNSProviders.ts +++ b/frontend/src/hooks/useDNSProviders.ts @@ -1,4 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' + import { getDNSProviders, getDNSProvider, diff --git a/frontend/src/hooks/useDocker.ts b/frontend/src/hooks/useDocker.ts index 06a9a006..a2206c69 100644 --- a/frontend/src/hooks/useDocker.ts +++ b/frontend/src/hooks/useDocker.ts @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query' + import { dockerApi } from '../api/docker' export function useDocker(host?: string | null, serverId?: string | null) { diff --git a/frontend/src/hooks/useDomains.ts b/frontend/src/hooks/useDomains.ts index a378f54f..8c8bcd0a 100644 --- a/frontend/src/hooks/useDomains.ts +++ b/frontend/src/hooks/useDomains.ts @@ -1,4 +1,5 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' + import * as api from '../api/domains' export function useDomains() { diff --git a/frontend/src/hooks/useEncryption.ts b/frontend/src/hooks/useEncryption.ts index 35cf69ca..85380473 100644 --- a/frontend/src/hooks/useEncryption.ts +++ b/frontend/src/hooks/useEncryption.ts @@ -1,4 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' + import { getEncryptionStatus, rotateEncryptionKey, diff --git a/frontend/src/hooks/useImport.ts b/frontend/src/hooks/useImport.ts index 36685fd8..5d078592 100644 --- a/frontend/src/hooks/useImport.ts +++ b/frontend/src/hooks/useImport.ts @@ -1,14 +1,15 @@ -import { useState } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { useState } from 'react'; + import { uploadCaddyfile, getImportPreview, commitImport, cancelImport, getImportStatus, - ImportSession, - ImportPreview, - ImportCommitResult, + type ImportSession, + type ImportPreview, + type ImportCommitResult, } from '../api/import'; export const QUERY_KEY = ['import-session']; diff --git a/frontend/src/hooks/useJSONImport.ts b/frontend/src/hooks/useJSONImport.ts index da8e28c1..a828225c 100644 --- a/frontend/src/hooks/useJSONImport.ts +++ b/frontend/src/hooks/useJSONImport.ts @@ -1,11 +1,12 @@ -import { useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useState } from 'react'; + import { uploadJSONExport, commitJSONImport, cancelJSONImport, - JSONImportPreview, - JSONImportCommitResult, + type JSONImportPreview, + type JSONImportCommitResult, } from '../api/jsonImport'; /** diff --git a/frontend/src/hooks/useLanguage.ts b/frontend/src/hooks/useLanguage.ts index 18cd8921..8a266f9d 100644 --- a/frontend/src/hooks/useLanguage.ts +++ b/frontend/src/hooks/useLanguage.ts @@ -1,5 +1,6 @@ import { useContext } from 'react' -import { LanguageContext, LanguageContextType } from '../context/LanguageContextValue' + +import { LanguageContext, type LanguageContextType } from '../context/LanguageContextValue' export function useLanguage(): LanguageContextType { const context = useContext(LanguageContext) diff --git a/frontend/src/hooks/useManualChallenge.ts b/frontend/src/hooks/useManualChallenge.ts index 6939bf11..4774b083 100644 --- a/frontend/src/hooks/useManualChallenge.ts +++ b/frontend/src/hooks/useManualChallenge.ts @@ -1,4 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' + import { getChallenge, createChallenge, diff --git a/frontend/src/hooks/useNPMImport.ts b/frontend/src/hooks/useNPMImport.ts index b6718463..85805adf 100644 --- a/frontend/src/hooks/useNPMImport.ts +++ b/frontend/src/hooks/useNPMImport.ts @@ -1,11 +1,12 @@ -import { useState } from 'react'; import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useState } from 'react'; + import { uploadNPMExport, commitNPMImport, cancelNPMImport, - NPMImportPreview, - NPMImportCommitResult, + type NPMImportPreview, + type NPMImportCommitResult, } from '../api/npmImport'; /** diff --git a/frontend/src/hooks/useNotifications.ts b/frontend/src/hooks/useNotifications.ts index c4acdfbf..ac3012cb 100644 --- a/frontend/src/hooks/useNotifications.ts +++ b/frontend/src/hooks/useNotifications.ts @@ -1,8 +1,9 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; + import { getSecurityNotificationSettings, updateSecurityNotificationSettings, - SecurityNotificationSettings, + type SecurityNotificationSettings, } from '../api/notifications'; import { toast } from '../utils/toast'; diff --git a/frontend/src/hooks/usePlugins.ts b/frontend/src/hooks/usePlugins.ts index 0bad4440..41989082 100644 --- a/frontend/src/hooks/usePlugins.ts +++ b/frontend/src/hooks/usePlugins.ts @@ -1,4 +1,5 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' + import { getPlugins, getPlugin, diff --git a/frontend/src/hooks/useProxyHosts.ts b/frontend/src/hooks/useProxyHosts.ts index e62d16cc..50fe1a2a 100644 --- a/frontend/src/hooks/useProxyHosts.ts +++ b/frontend/src/hooks/useProxyHosts.ts @@ -1,4 +1,5 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; + import { getProxyHosts, createProxyHost, @@ -6,7 +7,7 @@ import { deleteProxyHost, bulkUpdateACL, bulkUpdateSecurityHeaders, - ProxyHost + type ProxyHost } from '../api/proxyHosts'; export const QUERY_KEY = ['proxy-hosts']; diff --git a/frontend/src/hooks/useRemoteServers.ts b/frontend/src/hooks/useRemoteServers.ts index 9b158525..0e6ab20b 100644 --- a/frontend/src/hooks/useRemoteServers.ts +++ b/frontend/src/hooks/useRemoteServers.ts @@ -1,11 +1,12 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; + import { getRemoteServers, createRemoteServer, updateRemoteServer, deleteRemoteServer, testRemoteServerConnection, - RemoteServer + type RemoteServer } from '../api/remoteServers'; export const QUERY_KEY = ['remote-servers']; diff --git a/frontend/src/hooks/useSecurity.ts b/frontend/src/hooks/useSecurity.ts index 462600ff..d2bd2a33 100644 --- a/frontend/src/hooks/useSecurity.ts +++ b/frontend/src/hooks/useSecurity.ts @@ -1,4 +1,6 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' +import toast from 'react-hot-toast' + import { getSecurityStatus, getSecurityConfig, @@ -15,7 +17,6 @@ import { type SecurityConfigPayload, type CreateDecisionPayload, } from '../api/security' -import toast from 'react-hot-toast' export function useSecurityStatus() { return useQuery({ queryKey: ['securityStatus'], queryFn: getSecurityStatus }) diff --git a/frontend/src/hooks/useSecurityHeaders.ts b/frontend/src/hooks/useSecurityHeaders.ts index c91dc5c8..8363e21e 100644 --- a/frontend/src/hooks/useSecurityHeaders.ts +++ b/frontend/src/hooks/useSecurityHeaders.ts @@ -1,8 +1,9 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { securityHeadersApi } from '../api/securityHeaders'; -import type { CreateProfileRequest, ApplyPresetRequest } from '../api/securityHeaders'; import toast from 'react-hot-toast'; +import { securityHeadersApi, type CreateProfileRequest, type ApplyPresetRequest } from '../api/securityHeaders'; + + export function useSecurityHeaderProfiles() { return useQuery({ queryKey: ['securityHeaderProfiles'], diff --git a/frontend/src/hooks/useTheme.ts b/frontend/src/hooks/useTheme.ts index b049a7e1..aa82f51f 100644 --- a/frontend/src/hooks/useTheme.ts +++ b/frontend/src/hooks/useTheme.ts @@ -1,4 +1,5 @@ import { useContext } from 'react' + import { ThemeContext } from '../context/ThemeContextValue' export function useTheme() { diff --git a/frontend/src/hooks/useWebSocketStatus.ts b/frontend/src/hooks/useWebSocketStatus.ts index 574ef74f..37578440 100644 --- a/frontend/src/hooks/useWebSocketStatus.ts +++ b/frontend/src/hooks/useWebSocketStatus.ts @@ -1,4 +1,5 @@ import { useQuery } from '@tanstack/react-query'; + import { getWebSocketConnections, getWebSocketStats } from '../api/websocket'; /** diff --git a/frontend/src/i18n.ts b/frontend/src/i18n.ts index 121e938d..16292e1a 100644 --- a/frontend/src/i18n.ts +++ b/frontend/src/i18n.ts @@ -1,11 +1,11 @@ import i18n from 'i18next' -import { initReactI18next } from 'react-i18next' import LanguageDetector from 'i18next-browser-languagedetector' +import { initReactI18next } from 'react-i18next' +import deTranslation from './locales/de/translation.json' import enTranslation from './locales/en/translation.json' import esTranslation from './locales/es/translation.json' import frTranslation from './locales/fr/translation.json' -import deTranslation from './locales/de/translation.json' import zhTranslation from './locales/zh/translation.json' const resources = { diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index b115c85a..e8b6639e 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,9 +1,10 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import React from 'react' import ReactDOM from 'react-dom/client' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + import App from './App.tsx' -import { ThemeProvider } from './context/ThemeContext' import { LanguageProvider } from './context/LanguageContext' +import { ThemeProvider } from './context/ThemeContext' import i18n from './i18n' import './index.css' diff --git a/frontend/src/pages/AcceptInvite.tsx b/frontend/src/pages/AcceptInvite.tsx index 9d08899d..158a7308 100644 --- a/frontend/src/pages/AcceptInvite.tsx +++ b/frontend/src/pages/AcceptInvite.tsx @@ -1,14 +1,16 @@ -import { useState, useEffect } from 'react' -import { useSearchParams, useNavigate } from 'react-router-dom' import { useMutation, useQuery } from '@tanstack/react-query' -import { useTranslation } from 'react-i18next' -import { Card } from '../components/ui/Card' -import { Button } from '../components/ui/Button' -import { Input } from '../components/ui/Input' -import { PasswordStrengthMeter } from '../components/PasswordStrengthMeter' -import { toast } from '../utils/toast' -import { validateInvite, acceptInvite } from '../api/users' import { Loader2, CheckCircle2, XCircle, UserCheck } from 'lucide-react' +import { useState, useEffect } from 'react' +import { useTranslation } from 'react-i18next' +import { useSearchParams, useNavigate } from 'react-router-dom' + +import { validateInvite, acceptInvite } from '../api/users' +import { PasswordStrengthMeter } from '../components/PasswordStrengthMeter' +import { Button } from '../components/ui/Button' +import { Card } from '../components/ui/Card' +import { Input } from '../components/ui/Input' +import { toast } from '../utils/toast' + export default function AcceptInvite() { const { t } = useTranslation() diff --git a/frontend/src/pages/AccessLists.tsx b/frontend/src/pages/AccessLists.tsx index 07311dc0..a40d8a4b 100644 --- a/frontend/src/pages/AccessLists.tsx +++ b/frontend/src/pages/AccessLists.tsx @@ -1,17 +1,12 @@ -import { useState } from 'react'; -import { useTranslation } from 'react-i18next'; import { Plus, Pencil, Trash2, TestTube2, ExternalLink, Shield } from 'lucide-react'; -import { - useAccessLists, - useCreateAccessList, - useUpdateAccessList, - useDeleteAccessList, - useTestIP, -} from '../hooks/useAccessLists'; -import { AccessListForm, type AccessListFormData } from '../components/AccessListForm'; -import type { AccessList } from '../api/accessLists'; -import { createBackup } from '../api/backups'; +import { useState } from 'react'; import toast from 'react-hot-toast'; +import { useTranslation } from 'react-i18next'; + + + +import { createBackup } from '../api/backups'; +import { AccessListForm, type AccessListFormData } from '../components/AccessListForm'; import { PageShell } from '../components/layout/PageShell'; import { Badge, @@ -29,6 +24,15 @@ import { Card, type Column, } from '../components/ui'; +import { + useAccessLists, + useCreateAccessList, + useUpdateAccessList, + useDeleteAccessList, + useTestIP, +} from '../hooks/useAccessLists'; + +import type { AccessList } from '../api/accessLists'; export default function AccessLists() { const { t } = useTranslation(); diff --git a/frontend/src/pages/AuditLogs.tsx b/frontend/src/pages/AuditLogs.tsx index 255737cf..ea2f6fee 100644 --- a/frontend/src/pages/AuditLogs.tsx +++ b/frontend/src/pages/AuditLogs.tsx @@ -1,10 +1,9 @@ -import { useState } from 'react' import { format } from 'date-fns' import { Download, Filter, X } from 'lucide-react' -import { PageShell } from '../components/layout/PageShell' -import { useAuditLogs, type AuditLogFilters, type AuditLog } from '../hooks/useAuditLogs' +import { useState } from 'react' + import { exportAuditLogsCSV } from '../api/auditLogs' -import { DataTable, type Column } from '../components/ui/DataTable' +import { PageShell } from '../components/layout/PageShell' import { Card, CardHeader, @@ -14,6 +13,7 @@ import { Badge, Input, } from '../components/ui' +import { DataTable, type Column } from '../components/ui/DataTable' import { Dialog, DialogContent, @@ -28,6 +28,7 @@ import { SelectTrigger, SelectValue, } from '../components/ui/Select' +import { useAuditLogs, type AuditLogFilters, type AuditLog } from '../hooks/useAuditLogs' import { toast } from '../utils/toast' /** Audit log detail modal */ diff --git a/frontend/src/pages/Backups.tsx b/frontend/src/pages/Backups.tsx index 62a486b4..7fe1463b 100644 --- a/frontend/src/pages/Backups.tsx +++ b/frontend/src/pages/Backups.tsx @@ -1,11 +1,10 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { Download, RotateCcw, Plus, Archive, Trash2, Save } from 'lucide-react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' -import { toast } from '../utils/toast' -import { getBackups, createBackup, restoreBackup, deleteBackup, BackupFile } from '../api/backups' + +import { getBackups, createBackup, restoreBackup, deleteBackup, type BackupFile } from '../api/backups' import { getSettings, updateSetting } from '../api/settings' -import { Download, RotateCcw, Plus, Archive, Trash2, Save } from 'lucide-react' -import { useAuth } from '../hooks/useAuth' import { PageShell } from '../components/layout/PageShell' import { Button, @@ -25,6 +24,8 @@ import { DialogFooter, type Column, } from '../components/ui' +import { useAuth } from '../hooks/useAuth' +import { toast } from '../utils/toast' const formatSize = (bytes: number): string => { if (bytes < 1024) return `${bytes} B` diff --git a/frontend/src/pages/Certificates.tsx b/frontend/src/pages/Certificates.tsx index 92401f9d..87f2bed7 100644 --- a/frontend/src/pages/Certificates.tsx +++ b/frontend/src/pages/Certificates.tsx @@ -1,10 +1,10 @@ -import { useState } from 'react' -import { useTranslation } from 'react-i18next' import { useMutation, useQueryClient } from '@tanstack/react-query' import { Plus, ShieldCheck } from 'lucide-react' -import CertificateList from '../components/CertificateList' +import { useState } from 'react' +import { useTranslation } from 'react-i18next' + import { uploadCertificate } from '../api/certificates' -import { toast } from '../utils/toast' +import CertificateList from '../components/CertificateList' import { PageShell } from '../components/layout/PageShell' import { Button, @@ -17,6 +17,7 @@ import { DialogFooter, Label, } from '../components/ui' +import { toast } from '../utils/toast' export default function Certificates() { const { t } = useTranslation() diff --git a/frontend/src/pages/CrowdSecConfig.tsx b/frontend/src/pages/CrowdSecConfig.tsx index a0524c18..bca3888d 100644 --- a/frontend/src/pages/CrowdSecConfig.tsx +++ b/frontend/src/pages/CrowdSecConfig.tsx @@ -1,23 +1,24 @@ -import { useEffect, useMemo, useState } from 'react' +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { isAxiosError } from 'axios' -import { useNavigate, Link } from 'react-router-dom' +import { Shield, ShieldOff, Trash2, Search, AlertTriangle, ExternalLink } from 'lucide-react' +import { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' +import { useNavigate, Link } from 'react-router-dom' + +import { createBackup } from '../api/backups' +import { exportCrowdsecConfig, importCrowdsecConfig, listCrowdsecFiles, readCrowdsecFile, writeCrowdsecFile, listCrowdsecDecisions, banIP, unbanIP, type CrowdSecDecision, statusCrowdsec, type CrowdSecStatus, startCrowdsec } from '../api/crowdsec' +import { getFeatureFlags } from '../api/featureFlags' +import { listCrowdsecPresets, pullCrowdsecPreset, applyCrowdsecPreset, getCrowdsecPresetCache } from '../api/presets' +import { getSecurityStatus } from '../api/security' +import { CrowdSecBouncerKeyDisplay } from '../components/CrowdSecBouncerKeyDisplay' +import { ConfigReloadOverlay } from '../components/LoadingStates' import { Button } from '../components/ui/Button' import { Card } from '../components/ui/Card' import { Input } from '../components/ui/Input' -import { getSecurityStatus } from '../api/security' -import { getFeatureFlags } from '../api/featureFlags' -import { exportCrowdsecConfig, importCrowdsecConfig, listCrowdsecFiles, readCrowdsecFile, writeCrowdsecFile, listCrowdsecDecisions, banIP, unbanIP, CrowdSecDecision, statusCrowdsec, CrowdSecStatus, startCrowdsec } from '../api/crowdsec' -import { listCrowdsecPresets, pullCrowdsecPreset, applyCrowdsecPreset, getCrowdsecPresetCache } from '../api/presets' -import { createBackup } from '../api/backups' -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' -import { toast } from '../utils/toast' -import { ConfigReloadOverlay } from '../components/LoadingStates' -import { CrowdSecBouncerKeyDisplay } from '../components/CrowdSecBouncerKeyDisplay' -import { Shield, ShieldOff, Trash2, Search, AlertTriangle, ExternalLink } from 'lucide-react' -import { buildCrowdsecExportFilename, downloadCrowdsecExport, promptCrowdsecFilename } from '../utils/crowdsecExport' -import { CROWDSEC_PRESETS, CrowdsecPreset } from '../data/crowdsecPresets' +import { CROWDSEC_PRESETS, type CrowdsecPreset } from '../data/crowdsecPresets' import { useConsoleStatus, useEnrollConsole, useClearConsoleEnrollment } from '../hooks/useConsoleEnrollment' +import { buildCrowdsecExportFilename, downloadCrowdsecExport, promptCrowdsecFilename } from '../utils/crowdsecExport' +import { toast } from '../utils/toast' export default function CrowdSecConfig() { const { t } = useTranslation() @@ -1225,7 +1226,7 @@ export default function CrowdSecConfig() { {/* Layer 3: Form content (pointer-events-auto) */}
{ if (e.key === 'Escape') setShowBanModal(false) if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) banMutation.mutate() @@ -1319,7 +1320,7 @@ export default function CrowdSecConfig() { aria-label={t('common.close')} />
{ if (e.key === 'Escape') setConfirmUnban(null) if (e.key === 'Enter') unbanMutation.mutate(confirmUnban.ip) diff --git a/frontend/src/pages/DNS.tsx b/frontend/src/pages/DNS.tsx index 36735a55..8fb4ef5c 100644 --- a/frontend/src/pages/DNS.tsx +++ b/frontend/src/pages/DNS.tsx @@ -1,8 +1,10 @@ -import { Link, Outlet, useLocation } from 'react-router-dom' +import { Cloud, Puzzle } from 'lucide-react' import { useTranslation } from 'react-i18next' +import { Link, Outlet, useLocation } from 'react-router-dom' + import { PageShell } from '../components/layout/PageShell' import { cn } from '../utils/cn' -import { Cloud, Puzzle } from 'lucide-react' + export default function DNS() { const { t } = useTranslation() diff --git a/frontend/src/pages/DNSProviders.tsx b/frontend/src/pages/DNSProviders.tsx index 3d5c77b0..623286c9 100644 --- a/frontend/src/pages/DNSProviders.tsx +++ b/frontend/src/pages/DNSProviders.tsx @@ -1,12 +1,13 @@ +import { Plus, Cloud } from 'lucide-react' import { useCallback, useState } from 'react' import { useTranslation } from 'react-i18next' -import { Plus, Cloud } from 'lucide-react' -import { Button, Alert, EmptyState, Skeleton } from '../components/ui' + +import { getChallenge, type ManualChallenge } from '../api/manualChallenge' +import { ManualDNSChallenge } from '../components/dns-providers' import DNSProviderCard from '../components/DNSProviderCard' import DNSProviderForm from '../components/DNSProviderForm' -import { ManualDNSChallenge } from '../components/dns-providers' +import { Button, Alert, EmptyState, Skeleton } from '../components/ui' import { useDNSProviders, useDNSProviderMutations, type DNSProvider } from '../hooks/useDNSProviders' -import { getChallenge, type ManualChallenge } from '../api/manualChallenge' import { toast } from '../utils/toast' export default function DNSProviders() { diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index ee26f8a7..878fee0c 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -1,15 +1,16 @@ +import { useQuery, useQueryClient } from '@tanstack/react-query' +import { Globe, Server, FileKey, Activity, CheckCircle2, AlertTriangle } from 'lucide-react' import { useMemo, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { useProxyHosts } from '../hooks/useProxyHosts' -import { useRemoteServers } from '../hooks/useRemoteServers' -import { useCertificates } from '../hooks/useCertificates' -import { useAccessLists } from '../hooks/useAccessLists' -import { useQuery, useQueryClient } from '@tanstack/react-query' + import { checkHealth } from '../api/health' -import { Globe, Server, FileKey, Activity, CheckCircle2, AlertTriangle } from 'lucide-react' import { PageShell } from '../components/layout/PageShell' import { StatsCard, Skeleton } from '../components/ui' import UptimeWidget from '../components/UptimeWidget' +import { useAccessLists } from '../hooks/useAccessLists' +import { useCertificates } from '../hooks/useCertificates' +import { useProxyHosts } from '../hooks/useProxyHosts' +import { useRemoteServers } from '../hooks/useRemoteServers' function StatsCardSkeleton() { return ( @@ -41,14 +42,14 @@ export default function Dashboard() { // so we match by domain name instead const hasPendingCerts = useMemo(() => { const certifiedDomains = new Set() - certificates.forEach(cert => { + for (const cert of certificates) { // Handle missing or undefined domain field - if (!cert.domain) return - cert.domain.split(',').forEach(d => { + if (!cert.domain) continue + for (const d of cert.domain.split(',')) { const trimmed = d.trim().toLowerCase() if (trimmed) certifiedDomains.add(trimmed) - }) - }) + } + } // Check if any SSL host lacks a certificate const sslHosts = hosts.filter(h => h.ssl_forced && h.enabled) diff --git a/frontend/src/pages/Domains.tsx b/frontend/src/pages/Domains.tsx index 723ff2d4..44828e91 100644 --- a/frontend/src/pages/Domains.tsx +++ b/frontend/src/pages/Domains.tsx @@ -1,7 +1,8 @@ +import { Trash2, Plus, Globe, Loader2 } from 'lucide-react' import { useState } from 'react' import { useTranslation } from 'react-i18next' + import { useDomains } from '../hooks/useDomains' -import { Trash2, Plus, Globe, Loader2 } from 'lucide-react' export default function Domains() { const { t } = useTranslation() diff --git a/frontend/src/pages/EncryptionManagement.tsx b/frontend/src/pages/EncryptionManagement.tsx index 052a37fc..b953997f 100644 --- a/frontend/src/pages/EncryptionManagement.tsx +++ b/frontend/src/pages/EncryptionManagement.tsx @@ -1,14 +1,7 @@ +import { Key, Shield, AlertTriangle, CheckCircle, Clock, RefreshCw, AlertCircle } from 'lucide-react' import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { Key, Shield, AlertTriangle, CheckCircle, Clock, RefreshCw, AlertCircle } from 'lucide-react' -import { - useEncryptionStatus, - useRotateKey, - useRotationHistory, - useValidateKeys, - type RotationHistoryEntry, -} from '../hooks/useEncryption' -import { toast } from '../utils/toast' + import { PageShell } from '../components/layout/PageShell' import { Card, @@ -28,6 +21,14 @@ import { DialogFooter, Skeleton, } from '../components/ui' +import { + useEncryptionStatus, + useRotateKey, + useRotationHistory, + useValidateKeys, + type RotationHistoryEntry, +} from '../hooks/useEncryption' +import { toast } from '../utils/toast' // Skeleton loader for status cards function StatusCardSkeleton() { @@ -166,12 +167,12 @@ export default function EncryptionManagement() { if (result.valid) { toast.success(t('encryption.validationSuccess')) if (result.warnings && result.warnings.length > 0) { - result.warnings.forEach((warning) => toast.warning(warning)) + for (const warning of result.warnings) toast.warning(warning) } } else { toast.error(t('encryption.validationError')) if (result.errors && result.errors.length > 0) { - result.errors.forEach((error) => toast.error(error)) + for (const error of result.errors) toast.error(error) } } }, diff --git a/frontend/src/pages/ImportCaddy.tsx b/frontend/src/pages/ImportCaddy.tsx index 5d3ee10a..05bffaac 100644 --- a/frontend/src/pages/ImportCaddy.tsx +++ b/frontend/src/pages/ImportCaddy.tsx @@ -1,13 +1,14 @@ +import { type AxiosError } from 'axios' import { useState } from 'react' -import { useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' -import { AxiosError } from 'axios' +import { useNavigate } from 'react-router-dom' + import { createBackup } from '../api/backups' -import { useImport } from '../hooks/useImport' +import ImportSuccessModal from '../components/dialogs/ImportSuccessModal' import ImportBanner from '../components/ImportBanner' import ImportReviewTable from '../components/ImportReviewTable' import ImportSitesModal from '../components/ImportSitesModal' -import ImportSuccessModal from '../components/dialogs/ImportSuccessModal' +import { useImport } from '../hooks/useImport' /** Response data structure for import API errors containing warnings */ interface ImportErrorResponse { diff --git a/frontend/src/pages/ImportCrowdSec.tsx b/frontend/src/pages/ImportCrowdSec.tsx index 0f94a0a7..93d0cad7 100644 --- a/frontend/src/pages/ImportCrowdSec.tsx +++ b/frontend/src/pages/ImportCrowdSec.tsx @@ -1,11 +1,13 @@ -import { useState } from 'react' import { useMutation } from '@tanstack/react-query' +import { useState } from 'react' +import { toast } from 'react-hot-toast' import { useTranslation } from 'react-i18next' -import { importCrowdsecConfig } from '../api/crowdsec' + import { createBackup } from '../api/backups' +import { importCrowdsecConfig } from '../api/crowdsec' import { Button } from '../components/ui/Button' import { Card } from '../components/ui/Card' -import { toast } from 'react-hot-toast' + export default function ImportCrowdSec() { const { t } = useTranslation() diff --git a/frontend/src/pages/ImportJSON.tsx b/frontend/src/pages/ImportJSON.tsx index 94987319..d55a31c6 100644 --- a/frontend/src/pages/ImportJSON.tsx +++ b/frontend/src/pages/ImportJSON.tsx @@ -1,9 +1,10 @@ import { useState } from 'react' -import { useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' + import { createBackup } from '../api/backups' -import { useJSONImport } from '../hooks/useJSONImport' import ImportSuccessModal from '../components/dialogs/ImportSuccessModal' +import { useJSONImport } from '../hooks/useJSONImport' export default function ImportJSON() { const { t } = useTranslation() diff --git a/frontend/src/pages/ImportNPM.tsx b/frontend/src/pages/ImportNPM.tsx index 6f7ec6bc..fabf9c5c 100644 --- a/frontend/src/pages/ImportNPM.tsx +++ b/frontend/src/pages/ImportNPM.tsx @@ -1,9 +1,10 @@ import { useState } from 'react' -import { useNavigate } from 'react-router-dom' import { useTranslation } from 'react-i18next' +import { useNavigate } from 'react-router-dom' + import { createBackup } from '../api/backups' -import { useNPMImport } from '../hooks/useNPMImport' import ImportSuccessModal from '../components/dialogs/ImportSuccessModal' +import { useNPMImport } from '../hooks/useNPMImport' export default function ImportNPM() { const { t } = useTranslation() diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 14bcc4ed..d8a3c38d 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -1,15 +1,16 @@ -import { useState, useEffect } from 'react' -import { useNavigate } from 'react-router-dom' import { useQuery, useQueryClient } from '@tanstack/react-query' +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { Card } from '../components/ui/Card' -import { Input } from '../components/ui/Input' -import { Button } from '../components/ui/Button' -import { toast } from '../utils/toast' +import { useNavigate } from 'react-router-dom' + import client from '../api/client' -import { useAuth } from '../hooks/useAuth' import { getSetupStatus } from '../api/setup' import { ConfigReloadOverlay } from '../components/LoadingStates' +import { Button } from '../components/ui/Button' +import { Card } from '../components/ui/Card' +import { Input } from '../components/ui/Input' +import { useAuth } from '../hooks/useAuth' +import { toast } from '../utils/toast' export default function Login() { const { t } = useTranslation() diff --git a/frontend/src/pages/Logs.tsx b/frontend/src/pages/Logs.tsx index 963b2669..9c31ff0c 100644 --- a/frontend/src/pages/Logs.tsx +++ b/frontend/src/pages/Logs.tsx @@ -1,12 +1,13 @@ +import { useQuery } from '@tanstack/react-query'; +import { FileText, ChevronLeft, ChevronRight, ScrollText } from 'lucide-react'; import { useState, useEffect, type FC } from 'react'; import { useTranslation } from 'react-i18next'; -import { useQuery } from '@tanstack/react-query'; import { useSearchParams } from 'react-router-dom'; -import { getLogs, getLogContent, downloadLog, LogFilter } from '../api/logs'; -import { FileText, ChevronLeft, ChevronRight, ScrollText } from 'lucide-react'; -import { LogTable } from '../components/LogTable'; -import { LogFilters } from '../components/LogFilters'; + +import { getLogs, getLogContent, downloadLog, type LogFilter } from '../api/logs'; import { PageShell } from '../components/layout/PageShell'; +import { LogFilters } from '../components/LogFilters'; +import { LogTable } from '../components/LogTable'; import { Button, Card, diff --git a/frontend/src/pages/Notifications.tsx b/frontend/src/pages/Notifications.tsx index 7f623144..5f04b8de 100644 --- a/frontend/src/pages/Notifications.tsx +++ b/frontend/src/pages/Notifications.tsx @@ -1,11 +1,12 @@ -import { useEffect, useState, type FC } from 'react'; -import { useTranslation } from 'react-i18next'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { getProviders, createProvider, updateProvider, deleteProvider, testProvider, getTemplates, previewProvider, NotificationProvider, getExternalTemplates, previewExternalTemplate, ExternalTemplate, createExternalTemplate, updateExternalTemplate, deleteExternalTemplate, NotificationTemplate, SUPPORTED_NOTIFICATION_PROVIDER_TYPES, type SupportedNotificationProviderType } from '../api/notifications'; -import { Card } from '../components/ui/Card'; -import { Button } from '../components/ui/Button'; import { Bell, Plus, Trash2, Edit2, Send, Check, X, Loader2 } from 'lucide-react'; +import { useEffect, useState, type FC } from 'react'; import { useForm } from 'react-hook-form'; +import { useTranslation } from 'react-i18next'; + +import { getProviders, createProvider, updateProvider, deleteProvider, testProvider, getTemplates, previewProvider, type NotificationProvider, getExternalTemplates, previewExternalTemplate, type ExternalTemplate, createExternalTemplate, updateExternalTemplate, deleteExternalTemplate, type NotificationTemplate, SUPPORTED_NOTIFICATION_PROVIDER_TYPES, type SupportedNotificationProviderType } from '../api/notifications'; +import { Button } from '../components/ui/Button'; +import { Card } from '../components/ui/Card'; import { toast } from '../utils/toast'; const DISCORD_PROVIDER_TYPE: SupportedNotificationProviderType = 'discord'; @@ -130,7 +131,7 @@ const ProviderForm: FC<{ try { // If using an external saved template (id), call previewExternalTemplate with template_id if (formData.template && typeof formData.template === 'string' && formData.template.length === 36) { - const res = await previewExternalTemplate(formData.template, undefined, undefined); + const res = await previewExternalTemplate(formData.template); if (res.parsed) setPreviewContent(JSON.stringify(res.parsed, null, 2)); else setPreviewContent(res.rendered); } else { const res = await previewProvider({ ...formData, type: normalizeProviderType(formData.type) } as Partial); diff --git a/frontend/src/pages/PassthroughLanding.tsx b/frontend/src/pages/PassthroughLanding.tsx index fab15f6c..d4624da2 100644 --- a/frontend/src/pages/PassthroughLanding.tsx +++ b/frontend/src/pages/PassthroughLanding.tsx @@ -1,9 +1,10 @@ +import { Shield, LogOut } from 'lucide-react' import { useEffect, useRef } from 'react' import { useTranslation } from 'react-i18next' -import { useAuth } from '../hooks/useAuth' + import { Button } from '../components/ui/Button' import { Card } from '../components/ui/Card' -import { Shield, LogOut } from 'lucide-react' +import { useAuth } from '../hooks/useAuth' export default function PassthroughLanding() { const { t } = useTranslation() diff --git a/frontend/src/pages/Plugins.tsx b/frontend/src/pages/Plugins.tsx index f5374982..fcb9dd70 100644 --- a/frontend/src/pages/Plugins.tsx +++ b/frontend/src/pages/Plugins.tsx @@ -1,6 +1,7 @@ +import { RefreshCw, Package, AlertCircle, CheckCircle, XCircle, Info } from 'lucide-react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import { RefreshCw, Package, AlertCircle, CheckCircle, XCircle, Info } from 'lucide-react' + import { Button, Badge, diff --git a/frontend/src/pages/ProxyHosts.tsx b/frontend/src/pages/ProxyHosts.tsx index 1bcbdb38..869f9957 100644 --- a/frontend/src/pages/ProxyHosts.tsx +++ b/frontend/src/pages/ProxyHosts.tsx @@ -1,20 +1,17 @@ -import { useState, useMemo } from 'react' -import { useTranslation } from 'react-i18next' -import { Loader2, ExternalLink, AlertTriangle, Trash2, Globe, Settings } from 'lucide-react' import { useQuery } from '@tanstack/react-query' -import { useProxyHosts } from '../hooks/useProxyHosts' -import { getMonitors, type UptimeMonitor } from '../api/uptime' -import { useCertificates } from '../hooks/useCertificates' -import { useAccessLists } from '../hooks/useAccessLists' -import { useSecurityHeaderProfiles } from '../hooks/useSecurityHeaders' -import { getSettings } from '../api/settings' +import { Loader2, ExternalLink, AlertTriangle, Trash2, Globe, Settings } from 'lucide-react' +import { useState, useMemo } from 'react' +import { toast } from 'react-hot-toast' +import { useTranslation } from 'react-i18next' + import { createBackup } from '../api/backups' import { deleteCertificate } from '../api/certificates' -import type { ProxyHost } from '../api/proxyHosts' -import compareHosts from '../utils/compareHosts' -import type { AccessList } from '../api/accessLists' -import ProxyHostForm from '../components/ProxyHostForm' +import { getSettings } from '../api/settings' +import { getMonitors, type UptimeMonitor } from '../api/uptime' +import CertificateCleanupDialog from '../components/dialogs/CertificateCleanupDialog' import { PageShell } from '../components/layout/PageShell' +import { ConfigReloadOverlay } from '../components/LoadingStates' +import ProxyHostForm from '../components/ProxyHostForm' import { Badge, Alert, @@ -32,10 +29,16 @@ import { DialogDescription, type Column, } from '../components/ui' -import { toast } from 'react-hot-toast' +import { useAccessLists } from '../hooks/useAccessLists' +import { useCertificates } from '../hooks/useCertificates' +import { useProxyHosts } from '../hooks/useProxyHosts' +import { useSecurityHeaderProfiles } from '../hooks/useSecurityHeaders' +import compareHosts from '../utils/compareHosts' import { formatSettingLabel, settingHelpText, applyBulkSettingsToHosts } from '../utils/proxyHostsHelpers' -import { ConfigReloadOverlay } from '../components/LoadingStates' -import CertificateCleanupDialog from '../components/dialogs/CertificateCleanupDialog' + +import type { AccessList } from '../api/accessLists' +import type { ProxyHost } from '../api/proxyHosts' + export default function ProxyHosts() { const { t } = useTranslation() @@ -99,14 +102,14 @@ export default function ProxyHosts() { // Create a map of domain -> certificate status for quick lookup const certStatusByDomain = useMemo(() => { const map: Record = {} - certificates.forEach(cert => { + for (const cert of certificates) { const domains = cert.domain.split(',').map(d => d.trim().toLowerCase()) - domains.forEach(domain => { + for (const domain of domains) { if (!map[domain]) { map[domain] = { status: cert.status, provider: cert.provider } } - }) - }) + } + } return map }, [certificates]) @@ -131,11 +134,7 @@ export default function ProxyHosts() { } const handleSubmit = async (data: Partial) => { - if (editingHost) { - await updateHost(editingHost.uuid, data) - } else { - await createHost(data) - } + await (editingHost ? updateHost(editingHost.uuid, data) : createHost(data)); setShowForm(false) setEditingHost(undefined) } @@ -332,7 +331,7 @@ export default function ProxyHosts() { // Collect certificates to potentially delete const certsToConsider: Map = new Map() - hostUUIDs.forEach(uuid => { + for (const uuid of hostUUIDs) { const host = hosts.find(h => h.uuid === uuid) if (host?.certificate_id && host.certificate) { const cert = host.certificate @@ -353,7 +352,7 @@ export default function ProxyHosts() { } } } - }) + } // If there are orphaned certificates, show cleanup dialog if (certsToConsider.size > 0) { @@ -757,7 +756,7 @@ export default function ProxyHosts() { className="w-full bg-surface-muted border border-border rounded-lg px-4 py-2 text-content-primary focus:outline-none focus:ring-2 focus:ring-brand-500" > - {securityProfiles && securityProfiles.filter(p => p.is_preset).length > 0 && ( + {securityProfiles && securityProfiles.some(p => p.is_preset) && ( {securityProfiles .filter(p => p.is_preset) @@ -769,7 +768,7 @@ export default function ProxyHosts() { ))} )} - {securityProfiles && securityProfiles.filter(p => !p.is_preset).length > 0 && ( + {securityProfiles && securityProfiles.some(p => !p.is_preset) && ( {securityProfiles .filter(p => !p.is_preset) diff --git a/frontend/src/pages/RateLimiting.tsx b/frontend/src/pages/RateLimiting.tsx index aa65d6c2..4ea80e24 100644 --- a/frontend/src/pages/RateLimiting.tsx +++ b/frontend/src/pages/RateLimiting.tsx @@ -1,14 +1,15 @@ +import { useMutation, useQueryClient } from '@tanstack/react-query' +import { Gauge, Info } from 'lucide-react' import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { Gauge, Info } from 'lucide-react' -import { Button } from '../components/ui/Button' -import { Input } from '../components/ui/Input' -import { Card } from '../components/ui/Card' -import { useSecurityStatus, useSecurityConfig, useUpdateSecurityConfig } from '../hooks/useSecurity' + import { updateSetting } from '../api/settings' -import { useMutation, useQueryClient } from '@tanstack/react-query' -import { toast } from '../utils/toast' import { ConfigReloadOverlay } from '../components/LoadingStates' +import { Button } from '../components/ui/Button' +import { Card } from '../components/ui/Card' +import { Input } from '../components/ui/Input' +import { useSecurityStatus, useSecurityConfig, useUpdateSecurityConfig } from '../hooks/useSecurity' +import { toast } from '../utils/toast' export default function RateLimiting() { const { t } = useTranslation() diff --git a/frontend/src/pages/RemoteServers.tsx b/frontend/src/pages/RemoteServers.tsx index e867a97a..a9c34efb 100644 --- a/frontend/src/pages/RemoteServers.tsx +++ b/frontend/src/pages/RemoteServers.tsx @@ -1,10 +1,9 @@ +import { Plus, Pencil, Trash2, Server, LayoutGrid, LayoutList } from 'lucide-react' import { useState } from 'react' import { useTranslation } from 'react-i18next' -import { Plus, Pencil, Trash2, Server, LayoutGrid, LayoutList } from 'lucide-react' -import { useRemoteServers } from '../hooks/useRemoteServers' -import type { RemoteServer } from '../api/remoteServers' -import RemoteServerForm from '../components/RemoteServerForm' + import { PageShell } from '../components/layout/PageShell' +import RemoteServerForm from '../components/RemoteServerForm' import { Badge, Button, @@ -21,6 +20,10 @@ import { Card, type Column, } from '../components/ui' +import { useRemoteServers } from '../hooks/useRemoteServers' + +import type { RemoteServer } from '../api/remoteServers' + export default function RemoteServers() { const { t } = useTranslation() @@ -42,11 +45,7 @@ export default function RemoteServers() { } const handleSubmit = async (data: Partial) => { - if (editingServer) { - await updateServer(editingServer.uuid, data) - } else { - await createServer(data) - } + await (editingServer ? updateServer(editingServer.uuid, data) : createServer(data)); setShowForm(false) setEditingServer(undefined) } diff --git a/frontend/src/pages/SMTPSettings.tsx b/frontend/src/pages/SMTPSettings.tsx index b580a10f..455b0626 100644 --- a/frontend/src/pages/SMTPSettings.tsx +++ b/frontend/src/pages/SMTPSettings.tsx @@ -1,18 +1,20 @@ -import { useState, useEffect } from 'react' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { Mail, Send, CheckCircle2, XCircle } from 'lucide-react' +import { useState, useEffect } from 'react' import { useTranslation } from 'react-i18next' -import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '../components/ui/Card' -import { Button } from '../components/ui/Button' -import { Input } from '../components/ui/Input' -import { Label } from '../components/ui/Label' + +import { getSMTPConfig, updateSMTPConfig, testSMTPConnection, sendTestEmail, type SMTPConfigRequest } from '../api/smtp' import { Alert } from '../components/ui/Alert' import { Badge } from '../components/ui/Badge' -import { Skeleton } from '../components/ui/Skeleton' +import { Button } from '../components/ui/Button' +import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '../components/ui/Card' +import { Input } from '../components/ui/Input' +import { Label } from '../components/ui/Label' import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '../components/ui/Select' +import { Skeleton } from '../components/ui/Skeleton' import { toast } from '../utils/toast' -import { getSMTPConfig, updateSMTPConfig, testSMTPConnection, sendTestEmail } from '../api/smtp' -import type { SMTPConfigRequest } from '../api/smtp' -import { Mail, Send, CheckCircle2, XCircle } from 'lucide-react' + + export default function SMTPSettings() { const { t } = useTranslation() diff --git a/frontend/src/pages/Security.tsx b/frontend/src/pages/Security.tsx index eff34616..e7b7e09a 100644 --- a/frontend/src/pages/Security.tsx +++ b/frontend/src/pages/Security.tsx @@ -1,17 +1,16 @@ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' -import { useState, useEffect, useMemo } from 'react' -import { useNavigate, Outlet } from 'react-router-dom' -import { useTranslation } from 'react-i18next' import { Shield, ShieldAlert, ShieldCheck, Lock, Activity, ExternalLink, Bell } from 'lucide-react' -import { getSecurityStatus, type SecurityStatus } from '../api/security' -import { useSecurityConfig, useUpdateSecurityConfig, useGenerateBreakGlassToken } from '../hooks/useSecurity' +import { useState, useEffect, useMemo } from 'react' +import { useTranslation } from 'react-i18next' +import { useNavigate, Outlet } from 'react-router-dom' + import { startCrowdsec, stopCrowdsec, statusCrowdsec } from '../api/crowdsec' +import { getSecurityStatus, type SecurityStatus } from '../api/security' import { updateSetting } from '../api/settings' -import { toast } from '../utils/toast' -import { ConfigReloadOverlay } from '../components/LoadingStates' -import { LiveLogViewer } from '../components/LiveLogViewer' import { CrowdSecKeyWarning } from '../components/CrowdSecKeyWarning' import { PageShell } from '../components/layout/PageShell' +import { LiveLogViewer } from '../components/LiveLogViewer' +import { ConfigReloadOverlay } from '../components/LoadingStates' import { Card, CardHeader, @@ -29,6 +28,8 @@ import { TooltipContent, TooltipProvider, } from '../components/ui' +import { useSecurityConfig, useUpdateSecurityConfig, useGenerateBreakGlassToken } from '../hooks/useSecurity' +import { toast } from '../utils/toast' // Skeleton loader for security layer cards function SecurityCardSkeleton() { diff --git a/frontend/src/pages/SecurityHeaders.tsx b/frontend/src/pages/SecurityHeaders.tsx index edfe816b..fa8bb9a1 100644 --- a/frontend/src/pages/SecurityHeaders.tsx +++ b/frontend/src/pages/SecurityHeaders.tsx @@ -1,18 +1,14 @@ -import { useState } from 'react'; -import { useTranslation } from 'react-i18next'; import { Plus, Pencil, Trash2, Shield, Copy, Eye, Info } from 'lucide-react'; -import { - useSecurityHeaderProfiles, - useCreateSecurityHeaderProfile, - useUpdateSecurityHeaderProfile, - useDeleteSecurityHeaderProfile, -} from '../hooks/useSecurityHeaders'; +import { useState } from 'react'; +import toast from 'react-hot-toast'; +import { useTranslation } from 'react-i18next'; + + + +import { createBackup } from '../api/backups'; +import { PageShell } from '../components/layout/PageShell'; import { SecurityHeaderProfileForm } from '../components/SecurityHeaderProfileForm'; import { SecurityScoreDisplay } from '../components/SecurityScoreDisplay'; -import type { SecurityHeaderProfile, CreateProfileRequest } from '../api/securityHeaders'; -import { createBackup } from '../api/backups'; -import toast from 'react-hot-toast'; -import { PageShell } from '../components/layout/PageShell'; import { Button, Alert, @@ -29,6 +25,14 @@ import { TooltipContent, TooltipProvider, } from '../components/ui'; +import { + useSecurityHeaderProfiles, + useCreateSecurityHeaderProfile, + useUpdateSecurityHeaderProfile, + useDeleteSecurityHeaderProfile, +} from '../hooks/useSecurityHeaders'; + +import type { SecurityHeaderProfile, CreateProfileRequest } from '../api/securityHeaders'; export default function SecurityHeaders() { const { t } = useTranslation(); diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index 2b591b4e..45ad5a96 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -1,9 +1,10 @@ -import { Link, Outlet, useLocation } from 'react-router-dom' -import { useTranslation } from 'react-i18next' -import { PageShell } from '../components/layout/PageShell' -import { cn } from '../utils/cn' import { Settings as SettingsIcon, Server, Mail, Bell, Users } from 'lucide-react' +import { useTranslation } from 'react-i18next' +import { Link, Outlet, useLocation } from 'react-router-dom' + +import { PageShell } from '../components/layout/PageShell' import { useAuth } from '../hooks/useAuth' +import { cn } from '../utils/cn' export default function Settings() { const { t } = useTranslation() diff --git a/frontend/src/pages/Setup.tsx b/frontend/src/pages/Setup.tsx index 6726338c..47f7bc67 100644 --- a/frontend/src/pages/Setup.tsx +++ b/frontend/src/pages/Setup.tsx @@ -1,13 +1,14 @@ -import { useState, useEffect, type FormEvent, type FC } from 'react'; -import { useNavigate } from 'react-router-dom'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useState, useEffect, type FormEvent, type FC } from 'react'; import { useTranslation } from 'react-i18next'; -import { getSetupStatus, performSetup, SetupRequest } from '../api/setup'; +import { useNavigate } from 'react-router-dom'; + import client from '../api/client'; -import { useAuth } from '../hooks/useAuth'; -import { Input } from '../components/ui/Input'; -import { Button } from '../components/ui/Button'; +import { getSetupStatus, performSetup, type SetupRequest } from '../api/setup'; import { PasswordStrengthMeter } from '../components/PasswordStrengthMeter'; +import { Button } from '../components/ui/Button'; +import { Input } from '../components/ui/Input'; +import { useAuth } from '../hooks/useAuth'; import { isValidEmail } from '../utils/validation'; const Setup: FC = () => { diff --git a/frontend/src/pages/SystemSettings.tsx b/frontend/src/pages/SystemSettings.tsx index f9759ec3..45e90b0d 100644 --- a/frontend/src/pages/SystemSettings.tsx +++ b/frontend/src/pages/SystemSettings.tsx @@ -1,25 +1,26 @@ +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' +import { Server, RefreshCw, Save, Activity, Info, ExternalLink, CheckCircle2, XCircle, AlertTriangle } from 'lucide-react' import { useState, useEffect, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' -import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '../components/ui/Card' -import { Button } from '../components/ui/Button' -import { Input } from '../components/ui/Input' -import { Switch } from '../components/ui/Switch' -import { Label } from '../components/ui/Label' + +import client from '../api/client' +import { getFeatureFlags, updateFeatureFlags } from '../api/featureFlags' +import { getSettings, updateSetting, testPublicURL } from '../api/settings' +import { LanguageSelector } from '../components/LanguageSelector' +import { ConfigReloadOverlay } from '../components/LoadingStates' import { Alert, AlertDescription } from '../components/ui/Alert' import { Badge } from '../components/ui/Badge' -import { Skeleton } from '../components/ui/Skeleton' +import { Button } from '../components/ui/Button' +import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '../components/ui/Card' +import { Input } from '../components/ui/Input' +import { Label } from '../components/ui/Label' import { Select, SelectTrigger, SelectContent, SelectItem, SelectValue } from '../components/ui/Select' +import { Skeleton } from '../components/ui/Skeleton' +import { Switch } from '../components/ui/Switch' import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '../components/ui/Tooltip' -import { toast } from '../utils/toast' -import { getSettings, updateSetting, testPublicURL } from '../api/settings' -import { getFeatureFlags, updateFeatureFlags } from '../api/featureFlags' -import client from '../api/client' -import { Server, RefreshCw, Save, Activity, Info, ExternalLink, CheckCircle2, XCircle, AlertTriangle } from 'lucide-react' -import { ConfigReloadOverlay } from '../components/LoadingStates' import { WebSocketStatusCard } from '../components/WebSocketStatusCard' -import { LanguageSelector } from '../components/LanguageSelector' import { cn } from '../utils/cn' +import { toast } from '../utils/toast' interface HealthResponse { status: string @@ -57,13 +58,13 @@ export default function SystemSettings() { : undefined const keepaliveCountError = (() => { if (!keepaliveCountTrimmed) { - return undefined + return } const parsed = Number.parseInt(keepaliveCountTrimmed, 10) if (!Number.isInteger(parsed) || parsed < 1 || parsed > 1000) { return t('systemSettings.general.keepaliveCountError') } - return undefined + return })() const hasKeepaliveValidationError = Boolean(keepaliveIdleError || keepaliveCountError) diff --git a/frontend/src/pages/Tasks.tsx b/frontend/src/pages/Tasks.tsx index b34b0b54..cf649636 100644 --- a/frontend/src/pages/Tasks.tsx +++ b/frontend/src/pages/Tasks.tsx @@ -1,5 +1,5 @@ -import { Link, Outlet, useLocation } from 'react-router-dom' import { useTranslation } from 'react-i18next' +import { Link, Outlet, useLocation } from 'react-router-dom' export default function Tasks() { const { t } = useTranslation() diff --git a/frontend/src/pages/Uptime.tsx b/frontend/src/pages/Uptime.tsx index 8bbcfada..8577d998 100644 --- a/frontend/src/pages/Uptime.tsx +++ b/frontend/src/pages/Uptime.tsx @@ -1,10 +1,12 @@ -import { useMemo, useState, type FC, type FormEvent } from 'react'; -import { useTranslation } from 'react-i18next'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { getMonitors, getMonitorHistory, updateMonitor, deleteMonitor, checkMonitor, createMonitor, syncMonitors, UptimeMonitor } from '../api/uptime'; -import { Activity, ArrowUp, ArrowDown, Settings, X, Pause, RefreshCw, Plus, Loader } from 'lucide-react'; -import { toast } from 'react-hot-toast' import { formatDistanceToNow } from 'date-fns'; +import { Activity, ArrowUp, ArrowDown, Settings, X, Pause, RefreshCw, Plus, Loader } from 'lucide-react'; +import { useMemo, useState, type FC, type FormEvent } from 'react'; +import { toast } from 'react-hot-toast' +import { useTranslation } from 'react-i18next'; + +import { getMonitors, getMonitorHistory, updateMonitor, deleteMonitor, checkMonitor, createMonitor, syncMonitors, type UptimeMonitor } from '../api/uptime'; + type BaseMonitorStatus = 'up' | 'down' | 'pending'; type EffectiveMonitorStatus = BaseMonitorStatus | 'paused'; @@ -92,7 +94,7 @@ const MonitorCard: FC<{ monitor: UptimeMonitor; onEdit: (monitor: UptimeMonitor)

{monitor.name}

-
({ diff --git a/frontend/src/pages/__tests__/AccessLists.test.tsx b/frontend/src/pages/__tests__/AccessLists.test.tsx index f03f18d7..ab341dc2 100644 --- a/frontend/src/pages/__tests__/AccessLists.test.tsx +++ b/frontend/src/pages/__tests__/AccessLists.test.tsx @@ -1,11 +1,7 @@ import { screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { UseMutationResult, UseQueryResult } from '@tanstack/react-query' -import AccessLists from '../AccessLists' -import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' -import type { AccessList, CreateAccessListRequest, TestIPResponse } from '../../api/accessLists' -import type { AccessListFormData } from '../../components/AccessListForm' + import { createBackup } from '../../api/backups' import { useAccessLists, @@ -14,6 +10,12 @@ import { useTestIP, useUpdateAccessList, } from '../../hooks/useAccessLists' +import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' +import AccessLists from '../AccessLists' + +import type { AccessList, CreateAccessListRequest, TestIPResponse } from '../../api/accessLists' +import type { AccessListFormData } from '../../components/AccessListForm' +import type { UseMutationResult, UseQueryResult } from '@tanstack/react-query' const translations: Record = { 'accessLists.noAccessLists': 'No Access Lists', @@ -203,7 +205,7 @@ describe('AccessLists', () => { createMutationResult }>() const deleteMutationMock = (): ReturnType => - createMutationResult({}, (_id, options) => options?.onSuccess?.(undefined)) + createMutationResult({}, (_id, options) => options?.onSuccess?.()) const testIPMutationMock = (): ReturnType => createMutationResult({}, (_payload, options) => diff --git a/frontend/src/pages/__tests__/AuditLogs.test.tsx b/frontend/src/pages/__tests__/AuditLogs.test.tsx index 180a8f4c..1cb78bde 100644 --- a/frontend/src/pages/__tests__/AuditLogs.test.tsx +++ b/frontend/src/pages/__tests__/AuditLogs.test.tsx @@ -1,10 +1,11 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { render, screen, fireEvent, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { render, screen, fireEvent, waitFor } from '@testing-library/react' import { MemoryRouter } from 'react-router-dom' -import AuditLogs from '../AuditLogs' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as auditLogsApi from '../../api/auditLogs' import { toast } from '../../utils/toast' +import AuditLogs from '../AuditLogs' vi.mock('../../api/auditLogs') vi.mock('react-i18next', () => ({ diff --git a/frontend/src/pages/__tests__/Certificates.test.tsx b/frontend/src/pages/__tests__/Certificates.test.tsx index a306bd0e..c211726b 100644 --- a/frontend/src/pages/__tests__/Certificates.test.tsx +++ b/frontend/src/pages/__tests__/Certificates.test.tsx @@ -1,11 +1,13 @@ import { fireEvent, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { beforeEach, describe, expect, it, vi } from 'vitest' -import Certificates from '../Certificates' + +import { uploadCertificate, type Certificate } from '../../api/certificates' import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' -import type { Certificate } from '../../api/certificates' -import { uploadCertificate } from '../../api/certificates' import { toast } from '../../utils/toast' +import Certificates from '../Certificates' + + const translations: Record = { 'certificates.addCertificate': 'Add Certificate', diff --git a/frontend/src/pages/__tests__/CrowdSecConfig.coverage.test.tsx b/frontend/src/pages/__tests__/CrowdSecConfig.coverage.test.tsx index cd24415e..285cf562 100644 --- a/frontend/src/pages/__tests__/CrowdSecConfig.coverage.test.tsx +++ b/frontend/src/pages/__tests__/CrowdSecConfig.coverage.test.tsx @@ -1,19 +1,20 @@ -import { AxiosError } from 'axios' +import { type QueryClient } from '@tanstack/react-query' import { screen, waitFor, act, cleanup, within, fireEvent } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient } from '@tanstack/react-query' +import { AxiosError } from 'axios' import { describe, it, expect, vi, beforeEach } from 'vitest' -import CrowdSecConfig from '../CrowdSecConfig' -import * as securityApi from '../../api/security' -import * as crowdsecApi from '../../api/crowdsec' -import * as presetsApi from '../../api/presets' + import * as backupsApi from '../../api/backups' -import * as settingsApi from '../../api/settings' +import * as crowdsecApi from '../../api/crowdsec' import * as featureFlagsApi from '../../api/featureFlags' +import * as presetsApi from '../../api/presets' +import * as securityApi from '../../api/security' +import * as settingsApi from '../../api/settings' import { CROWDSEC_PRESETS } from '../../data/crowdsecPresets' import { renderWithQueryClient, createTestQueryClient } from '../../test-utils/renderWithQueryClient' -import { toast } from '../../utils/toast' import * as exportUtils from '../../utils/crowdsecExport' +import { toast } from '../../utils/toast' +import CrowdSecConfig from '../CrowdSecConfig' vi.mock('../../api/security') vi.mock('../../api/crowdsec') @@ -244,7 +245,7 @@ describe('CrowdSecConfig coverage', () => { vi.mocked(presetsApi.pullCrowdsecPreset).mockRejectedValueOnce(axiosError(503, 'hub down', { error: 'hub down' })) await userEvent.click(screen.getByText('Pull Preview')) - await waitFor(() => expect(screen.getByTestId('preset-hub-unavailable')).toBeInTheDocument()) + expect(await screen.findByTestId('preset-hub-unavailable')).toBeInTheDocument() vi.mocked(presetsApi.pullCrowdsecPreset).mockRejectedValueOnce(axiosError(500, 'boom', { error: 'boom' })) await userEvent.click(screen.getByText('Pull Preview')) @@ -342,7 +343,7 @@ describe('CrowdSecConfig coverage', () => { vi.mocked(presetsApi.applyCrowdsecPreset).mockRejectedValueOnce(axiosError(503, 'hub')) await userEvent.click(applyBtn) - await waitFor(() => expect(screen.getByTestId('preset-hub-unavailable')).toBeInTheDocument()) + expect(await screen.findByTestId('preset-hub-unavailable')).toBeInTheDocument() vi.mocked(presetsApi.applyCrowdsecPreset).mockRejectedValueOnce(axiosError(500, 'not cached', { error: 'Pull the preset first' })) await userEvent.click(applyBtn) @@ -381,7 +382,7 @@ describe('CrowdSecConfig coverage', () => { }) vi.mocked(presetsApi.pullCrowdsecPreset).mockRejectedValueOnce(axiosError(503, 'hub')) await renderPage() - await waitFor(() => expect(screen.getByTestId('preset-hub-unavailable')).toBeInTheDocument()) + expect(await screen.findByTestId('preset-hub-unavailable')).toBeInTheDocument() expect((screen.getByTestId('apply-preset-btn') as HTMLButtonElement).disabled).toBe(true) }) @@ -661,7 +662,7 @@ describe('CrowdSecConfig coverage', () => { }), ) const { queryClient } = await renderPage(createTestQueryClient()) - await waitFor(() => expect(screen.getByTestId('preset-preview')).toBeInTheDocument()) + expect(await screen.findByTestId('preset-preview')).toBeInTheDocument() const fileInput = screen.getByTestId('import-file') as HTMLInputElement await userEvent.upload(fileInput, new File(['data'], 'cfg.tar.gz')) await userEvent.click(screen.getByTestId('import-btn')) @@ -680,7 +681,7 @@ describe('CrowdSecConfig coverage', () => { }), ) await renderPage() - await waitFor(() => expect(screen.getByTestId('preset-preview')).toBeInTheDocument()) + expect(await screen.findByTestId('preset-preview')).toBeInTheDocument() await userEvent.selectOptions(screen.getByTestId('crowdsec-file-select'), 'acquis.yaml') // Use getAllByRole and filter for textarea (not the search input) const textareas = screen.getAllByRole('textbox') diff --git a/frontend/src/pages/__tests__/CrowdSecConfig.spec.tsx b/frontend/src/pages/__tests__/CrowdSecConfig.spec.tsx index 853cc12c..ac330b2b 100644 --- a/frontend/src/pages/__tests__/CrowdSecConfig.spec.tsx +++ b/frontend/src/pages/__tests__/CrowdSecConfig.spec.tsx @@ -1,16 +1,18 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { AxiosError, AxiosResponse } from 'axios' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { AxiosError, type AxiosResponse } from 'axios' import { BrowserRouter } from 'react-router-dom' -import CrowdSecConfig from '../CrowdSecConfig' -import * as api from '../../api/security' -import * as crowdsecApi from '../../api/crowdsec' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as backupsApi from '../../api/backups' -import * as presetsApi from '../../api/presets' +import * as crowdsecApi from '../../api/crowdsec' import * as featureFlagsApi from '../../api/featureFlags' +import * as presetsApi from '../../api/presets' +import * as api from '../../api/security' import { CROWDSEC_PRESETS } from '../../data/crowdsecPresets' +import CrowdSecConfig from '../CrowdSecConfig' + import type { ConsoleEnrollmentStatus } from '../../api/consoleEnrollment' vi.mock('../../api/security') @@ -102,7 +104,7 @@ describe('CrowdSecConfig', () => { vi.mocked(crowdsecApi.exportCrowdsecConfig).mockResolvedValue(blob) vi.spyOn(window, 'prompt').mockReturnValue('crowdsec-export') renderWithProviders() - await waitFor(() => expect(screen.getByText('CrowdSec Configuration')).toBeInTheDocument()) + expect(await screen.findByText('CrowdSec Configuration')).toBeInTheDocument() const exportBtn = screen.getByText('Export') await userEvent.click(exportBtn) await waitFor(() => expect(crowdsecApi.exportCrowdsecConfig).toHaveBeenCalled()) @@ -114,7 +116,7 @@ describe('CrowdSecConfig', () => { vi.mocked(crowdsecApi.listCrowdsecFiles).mockResolvedValue({ files: [] }) vi.mocked(crowdsecApi.importCrowdsecConfig).mockResolvedValue({ status: 'imported' }) renderWithProviders() - await waitFor(() => expect(screen.getByText('CrowdSec Configuration')).toBeInTheDocument()) + expect(await screen.findByText('CrowdSec Configuration')).toBeInTheDocument() const input = screen.getByTestId('import-file') as HTMLInputElement const file = new File(['dummy'], 'cfg.tar.gz') await userEvent.upload(input, file) @@ -130,7 +132,7 @@ describe('CrowdSecConfig', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText('CrowdSec Configuration')).toBeInTheDocument()) + expect(await screen.findByText('CrowdSec Configuration')).toBeInTheDocument() expect(screen.queryByTestId('console-enrollment-card')).not.toBeInTheDocument() }) @@ -141,7 +143,7 @@ describe('CrowdSecConfig', () => { renderWithProviders() - await waitFor(() => expect(screen.getByTestId('console-enrollment-card')).toBeInTheDocument()) + expect(await screen.findByTestId('console-enrollment-card')).toBeInTheDocument() expect(screen.getByTestId('console-enrollment-token')).toBeInTheDocument() }) @@ -178,7 +180,7 @@ describe('CrowdSecConfig', () => { renderWithProviders() - await waitFor(() => expect(screen.getByTestId('console-enrollment-card')).toBeInTheDocument()) + expect(await screen.findByTestId('console-enrollment-card')).toBeInTheDocument() await userEvent.type(screen.getByTestId('console-enrollment-token'), 'secret-1234567890') await userEvent.clear(screen.getByTestId('console-agent-name')) await userEvent.type(screen.getByTestId('console-agent-name'), 'agent-one') @@ -215,7 +217,7 @@ describe('CrowdSecConfig', () => { renderWithProviders() - await waitFor(() => expect(screen.getByTestId('console-ack-checkbox')).toBeInTheDocument()) + expect(await screen.findByTestId('console-ack-checkbox')).toBeInTheDocument() await userEvent.type(screen.getByTestId('console-enrollment-token'), 'another-secret-123456') await userEvent.click(screen.getByTestId('console-ack-checkbox')) await userEvent.click(screen.getByTestId('console-retry-btn')) @@ -239,9 +241,9 @@ describe('CrowdSecConfig', () => { vi.mocked(crowdsecApi.writeCrowdsecFile).mockResolvedValue({ status: 'written' }) renderWithProviders() - await waitFor(() => expect(screen.getByText('CrowdSec Configuration')).toBeInTheDocument()) + expect(await screen.findByText('CrowdSec Configuration')).toBeInTheDocument() // wait for file list - await waitFor(() => expect(screen.getByText('conf.d/a.conf')).toBeInTheDocument()) + expect(await screen.findByText('conf.d/a.conf')).toBeInTheDocument() const select = screen.getByTestId('crowdsec-file-select') await userEvent.selectOptions(select, 'conf.d/a.conf') await waitFor(() => expect(crowdsecApi.readCrowdsecFile).toHaveBeenCalledWith('conf.d/a.conf')) @@ -264,7 +266,7 @@ describe('CrowdSecConfig', () => { vi.mocked(crowdsecApi.listCrowdsecFiles).mockResolvedValue({ files: [] }) renderWithProviders() - await waitFor(() => expect(screen.getByText('CrowdSec Configuration')).toBeInTheDocument()) + expect(await screen.findByText('CrowdSec Configuration')).toBeInTheDocument() expect(screen.getByText(/CrowdSec is controlled via the toggle on the/i)).toBeInTheDocument() expect(screen.getByRole('link', { name: /Security/i })).toHaveAttribute('href', '/security') }) @@ -287,7 +289,7 @@ describe('CrowdSecConfig', () => { vi.mocked(presetsApi.applyCrowdsecPreset).mockRejectedValue(axiosError) renderWithProviders() - await waitFor(() => expect(screen.getByText('CrowdSec Configuration')).toBeInTheDocument()) + expect(await screen.findByText('CrowdSec Configuration')).toBeInTheDocument() await waitFor(() => expect(screen.getByTestId('preset-preview')).toHaveTextContent('configs:')) const fileSelect = screen.getByTestId('crowdsec-file-select') await userEvent.selectOptions(fileSelect, 'acquis.yaml') @@ -352,7 +354,7 @@ describe('CrowdSecConfig', () => { const presetCard = await screen.findByText('Hub Only') await userEvent.click(presetCard) - await waitFor(() => expect(screen.getByTestId('preset-hub-unavailable')).toBeInTheDocument()) + expect(await screen.findByTestId('preset-hub-unavailable')).toBeInTheDocument() const applyBtn = screen.getByTestId('apply-preset-btn') as HTMLButtonElement expect(applyBtn.disabled).toBe(true) @@ -405,7 +407,7 @@ describe('CrowdSecConfig', () => { const applyBtn = await screen.findByTestId('apply-preset-btn') await userEvent.click(applyBtn) - await waitFor(() => expect(screen.getByTestId('preset-validation-error')).toBeInTheDocument()) + expect(await screen.findByTestId('preset-validation-error')).toBeInTheDocument() expect(screen.getByTestId('preset-validation-error')).toHaveTextContent('Preset must be pulled before applying') }) }) diff --git a/frontend/src/pages/__tests__/CrowdSecConfig.test.tsx b/frontend/src/pages/__tests__/CrowdSecConfig.test.tsx index a6f1483b..1dfef4b4 100644 --- a/frontend/src/pages/__tests__/CrowdSecConfig.test.tsx +++ b/frontend/src/pages/__tests__/CrowdSecConfig.test.tsx @@ -1,15 +1,16 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { MemoryRouter } from 'react-router-dom' -import CrowdSecConfig from '../CrowdSecConfig' -import * as securityApi from '../../api/security' -import * as crowdsecApi from '../../api/crowdsec' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as backupsApi from '../../api/backups' -import * as presetsApi from '../../api/presets' +import * as crowdsecApi from '../../api/crowdsec' import * as featureFlagsApi from '../../api/featureFlags' +import * as presetsApi from '../../api/presets' +import * as securityApi from '../../api/security' import { toast } from '../../utils/toast' +import CrowdSecConfig from '../CrowdSecConfig' vi.mock('../../api/security') vi.mock('../../api/crowdsec') @@ -101,8 +102,8 @@ describe('CrowdSecConfig', () => { }) vi.mocked(crowdsecApi.exportCrowdsecConfig).mockResolvedValue(new Blob(['data'])) vi.mocked(crowdsecApi.importCrowdsecConfig).mockResolvedValue({}) - vi.mocked(crowdsecApi.banIP).mockResolvedValue(undefined) - vi.mocked(crowdsecApi.unbanIP).mockResolvedValue(undefined) + vi.mocked(crowdsecApi.banIP).mockResolvedValue() + vi.mocked(crowdsecApi.unbanIP).mockResolvedValue() vi.mocked(crowdsecApi.statusCrowdsec).mockResolvedValue({ running: true, pid: 123, diff --git a/frontend/src/pages/__tests__/DNS.test.tsx b/frontend/src/pages/__tests__/DNS.test.tsx index 85484d82..72e077a3 100644 --- a/frontend/src/pages/__tests__/DNS.test.tsx +++ b/frontend/src/pages/__tests__/DNS.test.tsx @@ -1,7 +1,8 @@ -import { describe, it, expect, vi } from 'vitest' import { screen, within } from '@testing-library/react' -import DNS from '../DNS' +import { describe, it, expect, vi } from 'vitest' + import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' +import DNS from '../DNS' vi.mock('react-i18next', () => ({ useTranslation: () => ({ diff --git a/frontend/src/pages/__tests__/DNSProviders.test.tsx b/frontend/src/pages/__tests__/DNSProviders.test.tsx index 1e38b9aa..259d812e 100644 --- a/frontend/src/pages/__tests__/DNSProviders.test.tsx +++ b/frontend/src/pages/__tests__/DNSProviders.test.tsx @@ -1,12 +1,15 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import type { ReactNode } from 'react' -import DNSProviders from '../DNSProviders' -import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' -import { useDNSProviders, useDNSProviderMutations, type DNSProvider } from '../../hooks/useDNSProviders' +import { describe, it, expect, vi, beforeEach } from 'vitest' + + import { getChallenge } from '../../api/manualChallenge' +import { useDNSProviders, useDNSProviderMutations, type DNSProvider } from '../../hooks/useDNSProviders' +import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' import { toast } from '../../utils/toast' +import DNSProviders from '../DNSProviders' + +import type { ReactNode } from 'react' vi.mock('react-i18next', () => ({ useTranslation: () => ({ diff --git a/frontend/src/pages/__tests__/Dashboard.test.tsx b/frontend/src/pages/__tests__/Dashboard.test.tsx index cc2d5c7c..91445bf7 100644 --- a/frontend/src/pages/__tests__/Dashboard.test.tsx +++ b/frontend/src/pages/__tests__/Dashboard.test.tsx @@ -1,7 +1,8 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' import { screen } from '@testing-library/react' -import Dashboard from '../Dashboard' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' +import Dashboard from '../Dashboard' vi.mock('../../hooks/useProxyHosts', () => ({ useProxyHosts: () => ({ diff --git a/frontend/src/pages/__tests__/EncryptionManagement.test.tsx b/frontend/src/pages/__tests__/EncryptionManagement.test.tsx index e3739273..e47fc0fc 100644 --- a/frontend/src/pages/__tests__/EncryptionManagement.test.tsx +++ b/frontend/src/pages/__tests__/EncryptionManagement.test.tsx @@ -1,10 +1,12 @@ -import { render, screen, waitFor } from '@testing-library/react' -import { describe, it, expect, vi, beforeEach } from 'vitest' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { BrowserRouter } from 'react-router-dom' -import EncryptionManagement from '../EncryptionManagement' -import * as encryptionApi from '../../api/encryption' +import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' +import { BrowserRouter } from 'react-router-dom' +import { describe, it, expect, vi, beforeEach } from 'vitest' + +import * as encryptionApi from '../../api/encryption' +import EncryptionManagement from '../EncryptionManagement' + // Mock the API module vi.mock('../../api/encryption') diff --git a/frontend/src/pages/__tests__/ImportCaddy-handlers.test.tsx b/frontend/src/pages/__tests__/ImportCaddy-handlers.test.tsx index bbc9ad9b..6167af0a 100644 --- a/frontend/src/pages/__tests__/ImportCaddy-handlers.test.tsx +++ b/frontend/src/pages/__tests__/ImportCaddy-handlers.test.tsx @@ -1,10 +1,11 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen, fireEvent, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { BrowserRouter } from 'react-router-dom' -import ImportCaddy from '../ImportCaddy' -import { useImport } from '../../hooks/useImport' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import { createBackup } from '../../api/backups' +import { useImport } from '../../hooks/useImport' +import ImportCaddy from '../ImportCaddy' // Mock the hooks and API calls vi.mock('../../hooks/useImport') diff --git a/frontend/src/pages/__tests__/ImportCaddy-imports.test.tsx b/frontend/src/pages/__tests__/ImportCaddy-imports.test.tsx index 631a306b..f866f5a6 100644 --- a/frontend/src/pages/__tests__/ImportCaddy-imports.test.tsx +++ b/frontend/src/pages/__tests__/ImportCaddy-imports.test.tsx @@ -1,7 +1,8 @@ -import { describe, it, expect, vi } from 'vitest' -import { render, screen } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { render, screen } from '@testing-library/react' import { BrowserRouter } from 'react-router-dom' +import { describe, it, expect, vi } from 'vitest' + import ImportCaddy from '../ImportCaddy' // Create a simple mock for useImport that returns the error state diff --git a/frontend/src/pages/__tests__/ImportCaddy-multifile-modal.test.tsx b/frontend/src/pages/__tests__/ImportCaddy-multifile-modal.test.tsx index 600d5a29..2acb04e5 100644 --- a/frontend/src/pages/__tests__/ImportCaddy-multifile-modal.test.tsx +++ b/frontend/src/pages/__tests__/ImportCaddy-multifile-modal.test.tsx @@ -1,9 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' import { render, screen, waitFor } from '@testing-library/react' -import { BrowserRouter } from 'react-router-dom' import userEvent from '@testing-library/user-event' -import ImportCaddy from '../ImportCaddy' +import { BrowserRouter } from 'react-router-dom' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import { useImport } from '../../hooks/useImport' +import ImportCaddy from '../ImportCaddy' // Mock the hooks and API calls vi.mock('../../hooks/useImport') @@ -92,13 +93,14 @@ describe('ImportCaddy - Multi-File Modal', () => { const button = screen.getByTestId('multi-file-import-button') await user.click(button) + let modal!: HTMLElement await waitFor(() => { - const modal = screen.getByRole('dialog') + modal = screen.getByRole('dialog') expect(modal).toBeInTheDocument() - expect(modal).toHaveAttribute('aria-modal', 'true') - expect(modal).toHaveAttribute('aria-labelledby', 'multi-site-modal-title') - expect(modal).toHaveAttribute('data-testid', 'multi-site-modal') }) + expect(modal).toHaveAttribute('data-testid', 'multi-site-modal') + expect(modal).toHaveAttribute('aria-labelledby', 'multi-site-modal-title') + expect(modal).toHaveAttribute('aria-modal', 'true') }) it('modal contains correct title for screen readers', async () => { @@ -113,12 +115,13 @@ describe('ImportCaddy - Multi-File Modal', () => { const button = screen.getByTestId('multi-file-import-button') await user.click(button) + let title!: HTMLElement await waitFor(() => { // Use heading role to specifically target the modal title, not the button - const title = screen.getByRole('heading', { name: 'Multi-site Import' }) + title = screen.getByRole('heading', { name: 'Multi-site Import' }) expect(title).toBeInTheDocument() - expect(title).toHaveAttribute('id', 'multi-site-modal-title') }) + expect(title).toHaveAttribute('id', 'multi-site-modal-title') }) it('closes modal when clicking outside overlay', async () => { @@ -143,14 +146,12 @@ describe('ImportCaddy - Multi-File Modal', () => { const overlay = screen.getByRole('dialog').querySelector('.bg-black\\/60') expect(overlay).toBeInTheDocument() - if (overlay) { - await user.click(overlay) + await user.click(overlay!) - // Modal should close - await waitFor(() => { - expect(screen.queryByRole('dialog')).not.toBeInTheDocument() - }) - } + // Modal should close + await waitFor(() => { + expect(screen.queryByRole('dialog')).not.toBeInTheDocument() + }) }) it('opens modal and shows it correctly', async () => { diff --git a/frontend/src/pages/__tests__/ImportCaddy-warnings.test.tsx b/frontend/src/pages/__tests__/ImportCaddy-warnings.test.tsx index cda3f7d4..c5378778 100644 --- a/frontend/src/pages/__tests__/ImportCaddy-warnings.test.tsx +++ b/frontend/src/pages/__tests__/ImportCaddy-warnings.test.tsx @@ -1,7 +1,8 @@ -import { describe, it, expect, vi } from 'vitest' -import { render, screen } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { render, screen } from '@testing-library/react' import { BrowserRouter } from 'react-router-dom' +import { describe, it, expect, vi } from 'vitest' + import ImportCaddy from '../ImportCaddy' // Create a simple mock for useImport that returns the preview state diff --git a/frontend/src/pages/__tests__/ImportCrowdSec.spec.tsx b/frontend/src/pages/__tests__/ImportCrowdSec.spec.tsx index e2c81466..ca5e8571 100644 --- a/frontend/src/pages/__tests__/ImportCrowdSec.spec.tsx +++ b/frontend/src/pages/__tests__/ImportCrowdSec.spec.tsx @@ -1,12 +1,13 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { render, screen, waitFor, fireEvent } from '@testing-library/react' -import { BrowserRouter } from 'react-router-dom' import { QueryClientProvider } from '@tanstack/react-query' +import { render, screen, waitFor, fireEvent } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import ImportCrowdSec from '../ImportCrowdSec' -import * as api from '../../api/crowdsec' +import { BrowserRouter } from 'react-router-dom' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as backups from '../../api/backups' +import * as api from '../../api/crowdsec' import { createTestQueryClient } from '../../test/createTestQueryClient' +import ImportCrowdSec from '../ImportCrowdSec' vi.mock('../../api/crowdsec') vi.mock('../../api/backups') diff --git a/frontend/src/pages/__tests__/ImportCrowdSec.test.tsx b/frontend/src/pages/__tests__/ImportCrowdSec.test.tsx index 53e457d6..33ba3949 100644 --- a/frontend/src/pages/__tests__/ImportCrowdSec.test.tsx +++ b/frontend/src/pages/__tests__/ImportCrowdSec.test.tsx @@ -1,13 +1,14 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { MemoryRouter } from 'react-router-dom' -import { QueryClientProvider } from '@tanstack/react-query' -import ImportCrowdSec from '../ImportCrowdSec' -import * as crowdsecApi from '../../api/crowdsec' -import * as backupsApi from '../../api/backups' import { toast } from 'react-hot-toast' +import { MemoryRouter } from 'react-router-dom' +import { describe, it, expect, vi, beforeEach } from 'vitest' + +import * as backupsApi from '../../api/backups' +import * as crowdsecApi from '../../api/crowdsec' import { createTestQueryClient } from '../../test/createTestQueryClient' +import ImportCrowdSec from '../ImportCrowdSec' vi.mock('../../api/crowdsec') vi.mock('../../api/backups') diff --git a/frontend/src/pages/__tests__/Login.overlay.audit.test.tsx b/frontend/src/pages/__tests__/Login.overlay.audit.test.tsx index d78e116b..283b909c 100644 --- a/frontend/src/pages/__tests__/Login.overlay.audit.test.tsx +++ b/frontend/src/pages/__tests__/Login.overlay.audit.test.tsx @@ -1,12 +1,13 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { MemoryRouter } from 'react-router-dom' -import Login from '../Login' -import * as authHook from '../../hooks/useAuth' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import client from '../../api/client' import * as setupApi from '../../api/setup' +import * as authHook from '../../hooks/useAuth' +import Login from '../Login' // Mock modules vi.mock('../../api/client') diff --git a/frontend/src/pages/__tests__/Login.test.tsx b/frontend/src/pages/__tests__/Login.test.tsx index f5a39066..5a31f29b 100644 --- a/frontend/src/pages/__tests__/Login.test.tsx +++ b/frontend/src/pages/__tests__/Login.test.tsx @@ -1,4 +1,16 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { MemoryRouter } from 'react-router-dom' import { describe, it, expect, vi, beforeEach } from 'vitest' + +import client from '../../api/client' +import * as setupApi from '../../api/setup' +import * as authHook from '../../hooks/useAuth' +import { toast } from '../../utils/toast' +import Login from '../Login' + +import type { AuthContextType } from '../../context/AuthContextValue' + // Mock react-router-dom useNavigate at module level const mockNavigate = vi.fn() vi.mock('react-router-dom', async () => { @@ -9,16 +21,6 @@ vi.mock('react-router-dom', async () => { } }) -import { render, screen, fireEvent, waitFor } from '@testing-library/react' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import Login from '../Login' -import * as setupApi from '../../api/setup' -import client from '../../api/client' -import * as authHook from '../../hooks/useAuth' -import type { AuthContextType } from '../../context/AuthContextValue' -import { toast } from '../../utils/toast' -import { MemoryRouter } from 'react-router-dom' - vi.mock('../../api/setup') vi.mock('../../hooks/useAuth') diff --git a/frontend/src/pages/__tests__/Notifications.test.tsx b/frontend/src/pages/__tests__/Notifications.test.tsx index 64a7854f..e0fec460 100644 --- a/frontend/src/pages/__tests__/Notifications.test.tsx +++ b/frontend/src/pages/__tests__/Notifications.test.tsx @@ -1,10 +1,12 @@ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' import { screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import Notifications from '../Notifications' -import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + import * as notificationsApi from '../../api/notifications' +import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' import { toast } from '../../utils/toast' +import Notifications from '../Notifications' + import type { NotificationProvider } from '../../api/notifications' vi.mock('react-i18next', () => ({ @@ -290,7 +292,7 @@ describe('Notifications', () => { } vi.mocked(notificationsApi.getExternalTemplates).mockResolvedValue([template]) - vi.mocked(notificationsApi.deleteExternalTemplate).mockResolvedValue(undefined) + vi.mocked(notificationsApi.deleteExternalTemplate).mockResolvedValue() const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true) const user = userEvent.setup() @@ -397,7 +399,7 @@ describe('Notifications', () => { } vi.mocked(notificationsApi.getExternalTemplates).mockResolvedValue([template]) - vi.mocked(notificationsApi.deleteExternalTemplate).mockResolvedValue(undefined) + vi.mocked(notificationsApi.deleteExternalTemplate).mockResolvedValue() const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false) const user = userEvent.setup() @@ -443,7 +445,7 @@ describe('Notifications', () => { }) it('submits provider test action from form using normalized discord type', async () => { - vi.mocked(notificationsApi.testProvider).mockResolvedValue(undefined) + vi.mocked(notificationsApi.testProvider).mockResolvedValue() const user = userEvent.setup() renderWithQueryClient() @@ -500,7 +502,7 @@ describe('Notifications', () => { it('triggers row-level send test action with discord payload', async () => { setupMocks([baseProvider]) - vi.mocked(notificationsApi.testProvider).mockResolvedValue(undefined) + vi.mocked(notificationsApi.testProvider).mockResolvedValue() const user = userEvent.setup() renderWithQueryClient() diff --git a/frontend/src/pages/__tests__/Plugins.test.tsx b/frontend/src/pages/__tests__/Plugins.test.tsx index 470d7d1a..31395203 100644 --- a/frontend/src/pages/__tests__/Plugins.test.tsx +++ b/frontend/src/pages/__tests__/Plugins.test.tsx @@ -1,8 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import Plugins from '../Plugins' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' +import Plugins from '../Plugins' + import type { PluginInfo } from '../../api/plugins' // Mock i18n diff --git a/frontend/src/pages/__tests__/ProxyHosts-bulk-acl.test.tsx b/frontend/src/pages/__tests__/ProxyHosts-bulk-acl.test.tsx index 1c5151c1..9660716b 100644 --- a/frontend/src/pages/__tests__/ProxyHosts-bulk-acl.test.tsx +++ b/frontend/src/pages/__tests__/ProxyHosts-bulk-acl.test.tsx @@ -1,15 +1,17 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { toast } from 'react-hot-toast'; import { MemoryRouter } from 'react-router-dom'; import { vi, describe, it, expect, beforeEach } from 'vitest'; + +import * as accessListsApi from '../../api/accessLists'; +import * as certificatesApi from '../../api/certificates'; +import * as proxyHostsApi from '../../api/proxyHosts'; +import * as settingsApi from '../../api/settings'; import { createMockProxyHost } from '../../testUtils/createMockProxyHost'; import ProxyHosts from '../ProxyHosts'; -import * as proxyHostsApi from '../../api/proxyHosts'; -import * as certificatesApi from '../../api/certificates'; -import * as accessListsApi from '../../api/accessLists'; -import * as settingsApi from '../../api/settings'; -import { toast } from 'react-hot-toast'; + // Mock toast vi.mock('react-hot-toast', () => ({ @@ -284,8 +286,7 @@ describe('ProxyHosts - Bulk ACL Modal', () => { const applyButton = buttons.find(btn => { const text = btn.textContent?.trim() || ''; // Match "Apply" exactly but not "Apply ACL" (which is the toggle) - const isApplyAction = text === 'Apply' || /^Apply \(\d+\)$/.test(text); - return isApplyAction; + return text === 'Apply' || /^Apply \(\d+\)$/.test(text); }); expect(applyButton).toBeTruthy(); expect((applyButton as HTMLButtonElement)?.disabled).toBe(true); @@ -324,11 +325,12 @@ describe('ProxyHosts - Bulk ACL Modal', () => { } // Apply button should be enabled and show count + let applyButton!: HTMLElement await waitFor(() => { - const applyButton = screen.getByRole('button', { name: /Apply \(1\)/ }); + applyButton = screen.getByRole('button', { name: /Apply \(1\)/ }); expect(applyButton).toBeTruthy(); - expect(applyButton).toHaveProperty('disabled', false); - }); + }) + expect(applyButton).toHaveProperty('disabled', false);; }); it('can select multiple ACLs', async () => { diff --git a/frontend/src/pages/__tests__/ProxyHosts-bulk-apply-all-settings.test.tsx b/frontend/src/pages/__tests__/ProxyHosts-bulk-apply-all-settings.test.tsx index 80e7b2f4..1b0a6660 100644 --- a/frontend/src/pages/__tests__/ProxyHosts-bulk-apply-all-settings.test.tsx +++ b/frontend/src/pages/__tests__/ProxyHosts-bulk-apply-all-settings.test.tsx @@ -1,17 +1,21 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; import { vi, describe, it, expect, beforeEach } from 'vitest'; -import ProxyHosts from '../ProxyHosts'; -import * as proxyHostsApi from '../../api/proxyHosts'; -import * as certificatesApi from '../../api/certificates'; -import type { ProxyHost } from '../../api/proxyHosts' -import type { Certificate } from '../../api/certificates' + import * as accessListsApi from '../../api/accessLists'; -import type { AccessList } from '../../api/accessLists' +import * as certificatesApi from '../../api/certificates'; +import * as proxyHostsApi from '../../api/proxyHosts'; import * as settingsApi from '../../api/settings'; import { createMockProxyHost } from '../../testUtils/createMockProxyHost'; +import ProxyHosts from '../ProxyHosts'; + +import type { AccessList } from '../../api/accessLists' +import type { Certificate } from '../../api/certificates' +import type { ProxyHost } from '../../api/proxyHosts' + + vi.mock('react-hot-toast', () => ({ toast: { success: vi.fn(), error: vi.fn(), loading: vi.fn(), dismiss: vi.fn() } })); vi.mock('../../api/proxyHosts', () => ({ getProxyHosts: vi.fn(), createProxyHost: vi.fn(), updateProxyHost: vi.fn(), deleteProxyHost: vi.fn(), bulkUpdateACL: vi.fn(), testProxyHostConnection: vi.fn() })); @@ -49,16 +53,16 @@ describe('ProxyHosts - Bulk Apply all settings coverage', () => { it('renders all bulk apply setting labels and allows toggling', async () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('Host 1')).toBeTruthy()); + expect(await screen.findByText('Host 1')).toBeTruthy(); // select all const headerCheckbox = screen.getByLabelText('Select all rows'); await userEvent.click(headerCheckbox); // open Bulk Apply - await waitFor(() => expect(screen.getByText('Bulk Apply')).toBeTruthy()); + expect(await screen.findByText('Bulk Apply')).toBeTruthy(); await userEvent.click(screen.getByText('Bulk Apply')); - await waitFor(() => expect(screen.getByText('Bulk Apply Settings')).toBeTruthy()); + expect(await screen.findByText('Bulk Apply Settings')).toBeTruthy(); const labels = [ 'Force SSL', diff --git a/frontend/src/pages/__tests__/ProxyHosts-bulk-apply-progress.test.tsx b/frontend/src/pages/__tests__/ProxyHosts-bulk-apply-progress.test.tsx index 187a3733..bc3230af 100644 --- a/frontend/src/pages/__tests__/ProxyHosts-bulk-apply-progress.test.tsx +++ b/frontend/src/pages/__tests__/ProxyHosts-bulk-apply-progress.test.tsx @@ -1,16 +1,18 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { MemoryRouter } from 'react-router-dom' import { vi, describe, it, expect, beforeEach } from 'vitest' -import ProxyHosts from '../ProxyHosts' -import * as proxyHostsApi from '../../api/proxyHosts' -import * as certificatesApi from '../../api/certificates' + import * as accessListsApi from '../../api/accessLists' +import * as certificatesApi from '../../api/certificates' +import * as proxyHostsApi from '../../api/proxyHosts' import * as settingsApi from '../../api/settings' -import type { Certificate } from '../../api/certificates' -import type { AccessList } from '../../api/accessLists' import { createMockProxyHost } from '../../testUtils/createMockProxyHost' +import ProxyHosts from '../ProxyHosts' + +import type { AccessList } from '../../api/accessLists' +import type { Certificate } from '../../api/certificates' import type { ProxyHost } from '../../api/proxyHosts' vi.mock('react-hot-toast', () => ({ toast: { success: vi.fn(), error: vi.fn(), loading: vi.fn(), dismiss: vi.fn() } })) @@ -52,7 +54,7 @@ describe('ProxyHosts - Bulk Apply progress UI', () => { const resolvers: Array<(v: ProxyHost) => void> = [] updateMock.mockImplementation(() => new Promise((res: (v: ProxyHost) => void) => { resolvers.push(res) })) renderWithProviders() - await waitFor(() => expect(screen.getByText('Progress 1')).toBeTruthy()) + expect(await screen.findByText('Progress 1')).toBeTruthy() // Select all const selectAll = screen.getByLabelText('Select all rows') @@ -60,7 +62,7 @@ describe('ProxyHosts - Bulk Apply progress UI', () => { // Open Bulk Apply await userEvent.click(screen.getByText('Bulk Apply')) - await waitFor(() => expect(screen.getByText('Bulk Apply Settings')).toBeTruthy()) + expect(await screen.findByText('Bulk Apply Settings')).toBeTruthy() // Enable one setting (Force SSL) - use Radix Checkbox (role="checkbox") in the row const forceLabel = screen.getByText(/Force SSL/i) as HTMLElement @@ -78,7 +80,7 @@ describe('ProxyHosts - Bulk Apply progress UI', () => { await waitFor(() => expect(screen.getAllByText(/Applying settings/i).length).toBeGreaterThan(0)) // Resolve both pending update promises to finish the operation - resolvers.forEach(r => r(hosts[0])) + for (const r of resolvers) r(hosts[0]) // Ensure subsequent tests aren't blocked by the special mock: make updateProxyHost resolve normally updateMock.mockImplementation(() => Promise.resolve(hosts[0] as ProxyHost)) diff --git a/frontend/src/pages/__tests__/ProxyHosts-bulk-apply.test.tsx b/frontend/src/pages/__tests__/ProxyHosts-bulk-apply.test.tsx index 7cf8c416..8d573ac0 100644 --- a/frontend/src/pages/__tests__/ProxyHosts-bulk-apply.test.tsx +++ b/frontend/src/pages/__tests__/ProxyHosts-bulk-apply.test.tsx @@ -1,17 +1,20 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; import { vi, describe, it, expect, beforeEach } from 'vitest'; -import ProxyHosts from '../ProxyHosts'; -import * as proxyHostsApi from '../../api/proxyHosts'; -import * as certificatesApi from '../../api/certificates'; -import type { Certificate } from '../../api/certificates' -import type { ProxyHost } from '../../api/proxyHosts' + import * as accessListsApi from '../../api/accessLists'; -import type { AccessList } from '../../api/accessLists' +import * as certificatesApi from '../../api/certificates'; +import * as proxyHostsApi from '../../api/proxyHosts'; import * as settingsApi from '../../api/settings'; import { createMockProxyHost } from '../../testUtils/createMockProxyHost'; +import ProxyHosts from '../ProxyHosts'; + +import type { AccessList } from '../../api/accessLists' +import type { Certificate } from '../../api/certificates' +import type { ProxyHost } from '../../api/proxyHosts' + // Mock toast vi.mock('react-hot-toast', () => ({ @@ -62,18 +65,18 @@ describe('ProxyHosts - Bulk Apply Settings', () => { it('shows Bulk Apply button when hosts selected and opens modal', async () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('Test Host 1')).toBeTruthy()); + expect(await screen.findByText('Test Host 1')).toBeTruthy(); // Select first host using select-all checkbox const selectAll = screen.getAllByRole('checkbox')[0]; await userEvent.click(selectAll); // Bulk Apply button should appear - await waitFor(() => expect(screen.getByText('Bulk Apply')).toBeTruthy()); + expect(await screen.findByText('Bulk Apply')).toBeTruthy(); // Open modal await userEvent.click(screen.getByText('Bulk Apply')); - await waitFor(() => expect(screen.getByText('Bulk Apply Settings')).toBeTruthy()); + expect(await screen.findByText('Bulk Apply Settings')).toBeTruthy(); }); it('applies selected settings to all selected hosts by calling updateProxyHost merged payload', async () => { @@ -81,16 +84,16 @@ describe('ProxyHosts - Bulk Apply Settings', () => { updateMock.mockResolvedValue(mockProxyHosts[0] as ProxyHost); renderWithProviders(); - await waitFor(() => expect(screen.getByText('Test Host 1')).toBeTruthy()); + expect(await screen.findByText('Test Host 1')).toBeTruthy(); // Select hosts const selectAll = screen.getByLabelText('Select all rows'); await userEvent.click(selectAll); - await waitFor(() => expect(screen.getByText('Bulk Apply')).toBeTruthy()); + expect(await screen.findByText('Bulk Apply')).toBeTruthy(); // Open Bulk Apply modal await userEvent.click(screen.getByText('Bulk Apply')); - await waitFor(() => expect(screen.getByText('Bulk Apply Settings')).toBeTruthy()); + expect(await screen.findByText('Bulk Apply Settings')).toBeTruthy(); // Enable first setting checkbox (Force SSL) - find the row by text and then get the Radix Checkbox (role="checkbox") const forceLabel = screen.getByText(/Force SSL/i) as HTMLElement; @@ -117,12 +120,12 @@ describe('ProxyHosts - Bulk Apply Settings', () => { it('cancels bulk apply modal when Cancel clicked', async () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('Test Host 1')).toBeTruthy()); + expect(await screen.findByText('Test Host 1')).toBeTruthy(); const selectAll = screen.getAllByRole('checkbox')[0]; await userEvent.click(selectAll); - await waitFor(() => expect(screen.getByText('Bulk Apply')).toBeTruthy()); + expect(await screen.findByText('Bulk Apply')).toBeTruthy(); await userEvent.click(screen.getByText('Bulk Apply')); - await waitFor(() => expect(screen.getByText('Bulk Apply Settings')).toBeTruthy()); + expect(await screen.findByText('Bulk Apply Settings')).toBeTruthy(); await userEvent.click(screen.getByRole('button', { name: 'Cancel' })); await waitFor(() => expect(screen.queryByText('Bulk Apply Settings')).toBeNull()); diff --git a/frontend/src/pages/__tests__/ProxyHosts-bulk-delete.test.tsx b/frontend/src/pages/__tests__/ProxyHosts-bulk-delete.test.tsx index 8609bd6f..967ba223 100644 --- a/frontend/src/pages/__tests__/ProxyHosts-bulk-delete.test.tsx +++ b/frontend/src/pages/__tests__/ProxyHosts-bulk-delete.test.tsx @@ -1,16 +1,18 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { toast } from 'react-hot-toast'; import { MemoryRouter } from 'react-router-dom'; import { vi, describe, it, expect, beforeEach } from 'vitest'; -import { createMockProxyHost } from '../../testUtils/createMockProxyHost'; -import ProxyHosts from '../ProxyHosts'; -import * as proxyHostsApi from '../../api/proxyHosts'; + +import * as accessListsApi from '../../api/accessLists'; import * as backupsApi from '../../api/backups'; import * as certificatesApi from '../../api/certificates'; -import * as accessListsApi from '../../api/accessLists'; +import * as proxyHostsApi from '../../api/proxyHosts'; import * as settingsApi from '../../api/settings'; -import { toast } from 'react-hot-toast'; +import { createMockProxyHost } from '../../testUtils/createMockProxyHost'; +import ProxyHosts from '../ProxyHosts'; + // Mock toast vi.mock('react-hot-toast', () => ({ @@ -243,9 +245,9 @@ describe('ProxyHosts - Bulk Delete with Backup', () => { // Should delete all selected hosts await waitFor(() => { expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('host-1'); - expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('host-2'); - expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('host-3'); - }); + }) + expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('host-3'); + expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('host-2');; // Should show success message await waitFor(() => { diff --git a/frontend/src/pages/__tests__/ProxyHosts-cert-cleanup.test.tsx b/frontend/src/pages/__tests__/ProxyHosts-cert-cleanup.test.tsx index c3b1b753..6e869fc1 100644 --- a/frontend/src/pages/__tests__/ProxyHosts-cert-cleanup.test.tsx +++ b/frontend/src/pages/__tests__/ProxyHosts-cert-cleanup.test.tsx @@ -1,17 +1,19 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { MemoryRouter } from 'react-router-dom' import { vi, describe, it, expect, beforeEach } from 'vitest' -import type { ProxyHost, Certificate } from '../../api/proxyHosts' -import ProxyHosts from '../ProxyHosts' -import * as proxyHostsApi from '../../api/proxyHosts' -import * as certificatesApi from '../../api/certificates' + import * as accessListsApi from '../../api/accessLists' +import * as backupsApi from '../../api/backups' +import * as certificatesApi from '../../api/certificates' +import * as proxyHostsApi from '../../api/proxyHosts' import * as settingsApi from '../../api/settings' import * as uptimeApi from '../../api/uptime' -import * as backupsApi from '../../api/backups' import { createMockProxyHost } from '../../testUtils/createMockProxyHost' +import ProxyHosts from '../ProxyHosts' + +import type { ProxyHost, Certificate } from '../../api/proxyHosts' vi.mock('react-hot-toast', () => ({ toast: { @@ -92,7 +94,7 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { vi.mocked(certificatesApi.deleteCertificate).mockResolvedValue() renderWithProviders() - await waitFor(() => expect(screen.getByText('Host1')).toBeTruthy()) + expect(await screen.findByText('Host1')).toBeTruthy() // Click row delete button const deleteBtn = screen.getByRole('button', { name: /delete/i }) @@ -148,7 +150,7 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { vi.mocked(proxyHostsApi.deleteProxyHost).mockResolvedValue() renderWithProviders() - await waitFor(() => expect(screen.getByText('Host1')).toBeTruthy()) + expect(await screen.findByText('Host1')).toBeTruthy() const deleteButtons = screen.getAllByRole('button', { name: /delete/i }) await userEvent.click(deleteButtons[0]) @@ -187,7 +189,7 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { vi.mocked(proxyHostsApi.deleteProxyHost).mockResolvedValue() renderWithProviders() - await waitFor(() => expect(screen.getByText('Host1')).toBeTruthy()) + expect(await screen.findByText('Host1')).toBeTruthy() const deleteBtn = screen.getByRole('button', { name: /delete/i }) await userEvent.click(deleteBtn) @@ -226,7 +228,7 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { vi.mocked(proxyHostsApi.deleteProxyHost).mockResolvedValue() renderWithProviders() - await waitFor(() => expect(screen.getByText('Host1')).toBeTruthy()) + expect(await screen.findByText('Host1')).toBeTruthy() // Click row delete button const deleteBtn = screen.getByRole('button', { name: /delete/i }) @@ -275,21 +277,21 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { ) renderWithProviders() - await waitFor(() => expect(screen.getByText('Host1')).toBeTruthy()) + expect(await screen.findByText('Host1')).toBeTruthy() // Click row delete button const deleteBtn = screen.getByRole('button', { name: /delete/i }) await userEvent.click(deleteBtn) // First dialog appears - await waitFor(() => expect(screen.getByText('Delete Proxy Host?')).toBeTruthy()) + expect(await screen.findByText('Delete Proxy Host?')).toBeTruthy() // Click "Delete" in the confirmation dialog const confirmDelete = screen.getAllByRole('button', { name: 'Delete' }) await userEvent.click(confirmDelete[confirmDelete.length - 1]) // Certificate cleanup dialog should appear - await waitFor(() => expect(screen.getByText(/orphaned certificate/i)).toBeTruthy()) + expect(await screen.findByText(/orphaned certificate/i)).toBeTruthy() // Check the certificate deletion checkbox const checkbox = document.getElementById('delete_certs') as HTMLInputElement @@ -331,20 +333,20 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { vi.mocked(certificatesApi.deleteCertificate).mockResolvedValue() renderWithProviders() - await waitFor(() => expect(screen.getByText('Host1')).toBeTruthy()) + expect(await screen.findByText('Host1')).toBeTruthy() // Select all hosts const selectAllCheckbox = screen.getByLabelText('Select all rows') await userEvent.click(selectAllCheckbox) // Click bulk delete button (the delete button in the toolbar, after Manage ACL) - await waitFor(() => expect(screen.getByText('Manage ACL')).toBeTruthy()) + expect(await screen.findByText('Manage ACL')).toBeTruthy() const manageACLButton = screen.getByText('Manage ACL') const bulkDeleteButton = manageACLButton.parentElement?.querySelector('button:last-child') as HTMLButtonElement await userEvent.click(bulkDeleteButton) // Confirm in bulk delete modal - text uses pluralized form "Proxy Host(s)" - await waitFor(() => expect(screen.getByText(/Delete 2 Proxy Host/)).toBeTruthy()) + expect(await screen.findByText(/Delete 2 Proxy Host/)).toBeTruthy() const deletePermBtn = screen.getByRole('button', { name: /Delete Permanently/i }) await userEvent.click(deletePermBtn) @@ -364,9 +366,9 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { await waitFor(() => { expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('h1') - expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('h2') expect(certificatesApi.deleteCertificate).toHaveBeenCalledWith(1) }) + expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('h2') }) it('bulk delete does NOT prompt when certificate is still used by other hosts', async () => { @@ -387,7 +389,7 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { vi.mocked(proxyHostsApi.deleteProxyHost).mockResolvedValue() renderWithProviders() - await waitFor(() => expect(screen.getByText('Host1')).toBeTruthy()) + expect(await screen.findByText('Host1')).toBeTruthy() // Select only host1 and host2 (host3 still uses the cert) const host1Row = screen.getByText('Host1').closest('tr') as HTMLTableRowElement @@ -400,7 +402,7 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { await userEvent.click(host2Checkbox) // Wait for bulk operations to be available - await waitFor(() => expect(screen.getByText('Bulk Apply')).toBeTruthy()) + expect(await screen.findByText('Bulk Apply')).toBeTruthy() // Click bulk delete - find the delete button in the toolbar (after Manage ACL) const manageACLButton = screen.getByText('Manage ACL') @@ -408,7 +410,7 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { await userEvent.click(bulkDeleteButton) // Confirm in modal - text uses pluralized form "Proxy Host(s)" - await waitFor(() => expect(screen.getByText(/Delete 2 Proxy Host/)).toBeTruthy()) + expect(await screen.findByText(/Delete 2 Proxy Host/)).toBeTruthy() const deletePermBtn = screen.getByRole('button', { name: /Delete Permanently/i }) await userEvent.click(deletePermBtn) @@ -416,9 +418,9 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { // It will directly delete without showing the orphaned cert dialog await waitFor(() => { expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('h1') - expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('h2') expect(certificatesApi.deleteCertificate).not.toHaveBeenCalled() }) + expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('h2') }) it('allows cancelling certificate cleanup dialog', async () => { @@ -436,13 +438,13 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { vi.mocked(certificatesApi.getCertificates).mockResolvedValue([]) renderWithProviders() - await waitFor(() => expect(screen.getByText('Host1')).toBeTruthy()) + expect(await screen.findByText('Host1')).toBeTruthy() const deleteBtn = screen.getByRole('button', { name: /delete/i }) await userEvent.click(deleteBtn) // Certificate cleanup dialog appears - await waitFor(() => expect(screen.getByText('Delete Proxy Host?')).toBeTruthy()) + expect(await screen.findByText('Delete Proxy Host?')).toBeTruthy() // Click Cancel const cancelBtn = screen.getByRole('button', { name: 'Cancel' }) @@ -472,21 +474,21 @@ describe('ProxyHosts - Certificate Cleanup Prompts', () => { vi.mocked(proxyHostsApi.deleteProxyHost).mockResolvedValue() renderWithProviders() - await waitFor(() => expect(screen.getByText('Host1')).toBeTruthy()) + expect(await screen.findByText('Host1')).toBeTruthy() // Click row delete button const deleteBtn = screen.getByRole('button', { name: /delete/i }) await userEvent.click(deleteBtn) // First dialog appears - await waitFor(() => expect(screen.getByText('Delete Proxy Host?')).toBeTruthy()) + expect(await screen.findByText('Delete Proxy Host?')).toBeTruthy() // Click "Delete" in the confirmation dialog const confirmDelete = screen.getAllByRole('button', { name: 'Delete' }) await userEvent.click(confirmDelete[confirmDelete.length - 1]) // Certificate cleanup dialog should appear - await waitFor(() => expect(screen.getByText(/orphaned certificate/i)).toBeTruthy()) + expect(await screen.findByText(/orphaned certificate/i)).toBeTruthy() // Checkbox should be unchecked by default const checkbox = document.getElementById('delete_certs') as HTMLInputElement diff --git a/frontend/src/pages/__tests__/ProxyHosts-coverage-isolated.test.tsx b/frontend/src/pages/__tests__/ProxyHosts-coverage-isolated.test.tsx index 4ff22dd9..d9c4641d 100644 --- a/frontend/src/pages/__tests__/ProxyHosts-coverage-isolated.test.tsx +++ b/frontend/src/pages/__tests__/ProxyHosts-coverage-isolated.test.tsx @@ -1,9 +1,11 @@ -import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { act } from 'react' +import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest' + import type { ProxyHost } from '../../api/proxyHosts' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' + // We'll use per-test module mocks via `vi.doMock` and dynamic imports to avoid // leaking mocks into other tests. Each test creates its own QueryClient. @@ -105,7 +107,7 @@ describe('ProxyHosts page - coverage targets (isolated)', () => { ) - await waitFor(() => expect(screen.getByText('StagingHost')).toBeInTheDocument()) + expect(await screen.findByText('StagingHost')).toBeInTheDocument() // Staging badge shows "Staging" text expect(screen.getByText('Staging')).toBeInTheDocument() @@ -126,7 +128,7 @@ describe('ProxyHosts page - coverage targets (isolated)', () => { ) - await waitFor(() => expect(screen.getByText('staging.example.com')).toBeInTheDocument()) + expect(await screen.findByText('staging.example.com')).toBeInTheDocument() const link = screen.getByText('staging.example.com').closest('a') as HTMLAnchorElement await act(async () => { await userEvent.click(link!) @@ -145,7 +147,7 @@ describe('ProxyHosts page - coverage targets (isolated)', () => { ) - await waitFor(() => expect(screen.getByText('StagingHost')).toBeInTheDocument()) + expect(await screen.findByText('StagingHost')).toBeInTheDocument() // Select hosts by finding rows and clicking first checkbox (selection) const row1 = screen.getByText('StagingHost').closest('tr') as HTMLTableRowElement @@ -157,7 +159,7 @@ describe('ProxyHosts page - coverage targets (isolated)', () => { await userEvent.click(bulkBtn) // Find the modal dialog - await waitFor(() => expect(screen.getByText('Bulk Apply Settings')).toBeInTheDocument()) + expect(await screen.findByText('Bulk Apply Settings')).toBeInTheDocument() // The bulk apply modal has checkboxes for each setting - find them by role const modalCheckboxes = screen.getAllByRole('checkbox').filter( diff --git a/frontend/src/pages/__tests__/ProxyHosts-coverage.test.tsx b/frontend/src/pages/__tests__/ProxyHosts-coverage.test.tsx index 343f73b0..93647bb0 100644 --- a/frontend/src/pages/__tests__/ProxyHosts-coverage.test.tsx +++ b/frontend/src/pages/__tests__/ProxyHosts-coverage.test.tsx @@ -1,21 +1,21 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { act } from 'react' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { MemoryRouter } from 'react-router-dom' import { vi, describe, it, expect, beforeEach } from 'vitest' -import type { ProxyHost } from '../../api/proxyHosts' -import ProxyHosts from '../ProxyHosts' -import { formatSettingLabel, settingHelpText, settingKeyToField, applyBulkSettingsToHosts } from '../../utils/proxyHostsHelpers' -import * as proxyHostsApi from '../../api/proxyHosts' -import * as certificatesApi from '../../api/certificates' + import * as accessListsApi from '../../api/accessLists' -import type { AccessList } from '../../api/accessLists' +import * as certificatesApi from '../../api/certificates' +import * as proxyHostsApi from '../../api/proxyHosts' import * as settingsApi from '../../api/settings' import * as uptimeApi from '../../api/uptime' -// Certificate type not required in this spec -import type { UptimeMonitor } from '../../api/uptime' -// toast is mocked in other tests; not used here +import { createMockProxyHost } from '../../testUtils/createMockProxyHost' +import { formatSettingLabel, settingHelpText, settingKeyToField, applyBulkSettingsToHosts } from '../../utils/proxyHostsHelpers' +import ProxyHosts from '../ProxyHosts' + +import type { AccessList } from '../../api/accessLists' +import type { ProxyHost } from '../../api/proxyHosts' vi.mock('react-hot-toast', () => ({ toast: { success: vi.fn(), error: vi.fn(), loading: vi.fn(), dismiss: vi.fn() } })) @@ -48,8 +48,6 @@ const renderWithProviders = (ui: React.ReactNode) => { ) } -import { createMockProxyHost } from '../../testUtils/createMockProxyHost' - const baseHost = (overrides: Partial = {}) => createMockProxyHost(overrides) describe('ProxyHosts - Coverage enhancements', () => { @@ -63,7 +61,7 @@ describe('ProxyHosts - Coverage enhancements', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText(/Create your first proxy host/)).toBeTruthy()) + expect(await screen.findByText(/Create your first proxy host/)).toBeTruthy() }) it('creates a proxy host via Add Host form submit', async () => { @@ -93,11 +91,11 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(settingsApi.getSettings).mockResolvedValue({}) renderWithProviders() - await waitFor(() => expect(screen.getByText(/Create your first proxy host/)).toBeTruthy()) + expect(await screen.findByText(/Create your first proxy host/)).toBeTruthy() const user = userEvent.setup() // Click the first Add Proxy Host button (in empty state) await user.click(screen.getAllByRole('button', { name: 'Add Proxy Host' })[0]) - await waitFor(() => expect(screen.getByRole('heading', { name: 'Add Proxy Host' })).toBeTruthy()) + expect(await screen.findByRole('heading', { name: 'Add Proxy Host' })).toBeTruthy() // Fill name const nameInput = screen.getByLabelText('Name *') as HTMLInputElement await user.clear(nameInput) @@ -143,13 +141,13 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(settingsApi.getSettings).mockResolvedValue({}) renderWithProviders() - await waitFor(() => expect(screen.getByText('S1')).toBeTruthy()) + expect(await screen.findByText('S1')).toBeTruthy() // Click select all header checkbox (has aria-label="Select all rows") const user = userEvent.setup() const selectAllBtn = screen.getByLabelText('Select all rows') await user.click(selectAllBtn) // Wait for selection UI to appear - text format includes "2 host(s) selected (all)" - await waitFor(() => expect(screen.getByText(/host\(s\) selected/)).toBeTruthy()) + expect(await screen.findByText(/host\(s\) selected/)).toBeTruthy() // Also check for "(all)" indicator expect(screen.getByText(/\(all\)/)).toBeTruthy() // Click again to deselect @@ -168,12 +166,12 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(proxyHostsApi.bulkUpdateACL).mockRejectedValue(new Error('Bad things')) renderWithProviders() - await waitFor(() => expect(screen.getByText('BHost')).toBeTruthy()) + expect(await screen.findByText('BHost')).toBeTruthy() const chk = screen.getAllByRole('checkbox')[0] const user = userEvent.setup() await user.click(chk) await user.click(screen.getByText('Manage ACL')) - await waitFor(() => expect(screen.getByText('List1')).toBeTruthy()) + expect(await screen.findByText('List1')).toBeTruthy() const label = screen.getByText('List1').closest('label') as HTMLElement // Radix Checkbox - query by role, not native input const checkbox = within(label).getByRole('checkbox') @@ -195,7 +193,7 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(proxyHostsApi.updateProxyHost).mockResolvedValue({ ...host, enabled: true }) renderWithProviders() - await waitFor(() => expect(screen.getByText('SwitchHost')).toBeTruthy()) + expect(await screen.findByText('SwitchHost')).toBeTruthy() const row = screen.getByText('SwitchHost').closest('tr') as HTMLTableRowElement // Switch component uses a label wrapping a hidden checkbox - find the label and click it const switchLabel = row.querySelector('label.cursor-pointer') as HTMLElement @@ -214,7 +212,7 @@ describe('ProxyHosts - Coverage enhancements', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText('aaa')).toBeTruthy()) + expect(await screen.findByText('aaa')).toBeTruthy() // Check both hosts are rendered expect(screen.getByText('aaa')).toBeTruthy() @@ -248,7 +246,7 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(settingsApi.getSettings).mockResolvedValue({}) renderWithProviders() - await waitFor(() => expect(screen.getByText('S1')).toBeTruthy()) + expect(await screen.findByText('S1')).toBeTruthy() const row = screen.getByText('S1').closest('tr') as HTMLTableRowElement const selectBtn = within(row).getAllByRole('checkbox')[0] @@ -272,12 +270,12 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(settingsApi.getSettings).mockResolvedValue({}) renderWithProviders() - await waitFor(() => expect(screen.getByText('S1')).toBeTruthy()) + expect(await screen.findByText('S1')).toBeTruthy() const headerCheckbox = screen.getAllByRole('checkbox')[0] const user = userEvent.setup() await user.click(headerCheckbox) await user.click(screen.getByText('Manage ACL')) - await waitFor(() => expect(screen.getByText('Apply Access List')).toBeTruthy()) + expect(await screen.findByText('Apply Access List')).toBeTruthy() // click backdrop (outer overlay) to close const overlay = document.querySelector('.fixed.inset-0') @@ -296,12 +294,12 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(settingsApi.getSettings).mockResolvedValue({}) renderWithProviders() - await waitFor(() => expect(screen.getByText('S1')).toBeTruthy()) + expect(await screen.findByText('S1')).toBeTruthy() const headerCheckbox = screen.getAllByRole('checkbox')[0] const user = userEvent.setup() await user.click(headerCheckbox) await user.click(screen.getByText('Manage ACL')) - await waitFor(() => expect(screen.getByText('List1')).toBeTruthy()) + expect(await screen.findByText('List1')).toBeTruthy() const label = screen.getByText('List1').closest('label') as HTMLLabelElement // Radix Checkbox - query by role, not native input const checkbox = within(label).getByRole('checkbox') @@ -326,12 +324,12 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(proxyHostsApi.bulkUpdateACL).mockResolvedValue({ updated: 2, errors: [] }) renderWithProviders() - await waitFor(() => expect(screen.getByText('S1')).toBeTruthy()) + expect(await screen.findByText('S1')).toBeTruthy() const chk = screen.getAllByRole('checkbox')[0] const user = userEvent.setup() await user.click(chk) await user.click(screen.getByText('Manage ACL')) - await waitFor(() => expect(screen.getByText('List1')).toBeTruthy()) + expect(await screen.findByText('List1')).toBeTruthy() // Toggle to Remove ACL await user.click(screen.getByText('Remove ACL')) // Click the action button (Remove ACL) - it's the primary action (bg-red) @@ -353,12 +351,12 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(settingsApi.getSettings).mockResolvedValue({}) renderWithProviders() - await waitFor(() => expect(screen.getByText('S1')).toBeTruthy()) + expect(await screen.findByText('S1')).toBeTruthy() const chk = screen.getAllByRole('checkbox')[0] const user = userEvent.setup() await user.click(chk) await user.click(screen.getByText('Manage ACL')) - await waitFor(() => expect(screen.getByText('Apply ACL')).toBeTruthy()) + expect(await screen.findByText('Apply ACL')).toBeTruthy() // Click Remove, then Apply to hit setBulkACLAction('apply') // Toggle Remove (header toggle) and back to Apply (header toggle) const headerToggles = screen.getAllByRole('button') @@ -383,12 +381,12 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(proxyHostsApi.bulkUpdateACL).mockResolvedValue({ updated: 1, errors: [{ uuid: 's2', error: 'Bad' }] }) renderWithProviders() - await waitFor(() => expect(screen.getByText('S1')).toBeTruthy()) + expect(await screen.findByText('S1')).toBeTruthy() const chk = screen.getAllByRole('checkbox')[0] const user = userEvent.setup() await user.click(chk) await user.click(screen.getByText('Manage ACL')) - await waitFor(() => expect(screen.getByText('List1')).toBeTruthy()) + expect(await screen.findByText('List1')).toBeTruthy() await userEvent.click(screen.getByText('Remove ACL')) const actionBtn = screen.getAllByRole('button', { name: 'Remove ACL' }).pop() if (actionBtn) await userEvent.click(actionBtn) @@ -409,11 +407,11 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(proxyHostsApi.bulkUpdateACL).mockRejectedValue(new Error('Bulk fail')) renderWithProviders() - await waitFor(() => expect(screen.getByText('S1')).toBeTruthy()) + expect(await screen.findByText('S1')).toBeTruthy() const chk = screen.getAllByRole('checkbox')[0] await userEvent.click(chk) await userEvent.click(screen.getByText('Manage ACL')) - await waitFor(() => expect(screen.getByText('List1')).toBeTruthy()) + expect(await screen.findByText('List1')).toBeTruthy() // Toggle Remove mode await userEvent.click(screen.getByText('Remove ACL')) const actionBtn = screen.getAllByRole('button', { name: 'Remove ACL' }).pop() @@ -432,17 +430,17 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(settingsApi.getSettings).mockResolvedValue({}) renderWithProviders() - await waitFor(() => expect(screen.getByText('S1')).toBeTruthy()) + expect(await screen.findByText('S1')).toBeTruthy() const headerCheckbox = screen.getByLabelText('Select all rows') await userEvent.click(headerCheckbox) // Wait for selection bar to appear and find the delete button - text format is "host(s) selected" - await waitFor(() => expect(screen.getByText(/host\(s\) selected/)).toBeTruthy()) + expect(await screen.findByText(/host\(s\) selected/)).toBeTruthy() // Click the bulk Delete button (with bg-error class) - there are multiple Delete buttons, get the one in selection bar const deleteButtons = screen.getAllByRole('button', { name: /Delete/ }) // The bulk delete button has bg-error class const bulkDeleteBtn = deleteButtons.find(btn => btn.classList.contains('bg-error')) await userEvent.click(bulkDeleteBtn!) - await waitFor(() => expect(screen.getByText(/Delete 2 Proxy Hosts?/i)).toBeTruthy()) + expect(await screen.findByText(/Delete 2 Proxy Hosts?/i)).toBeTruthy() const overlay = document.querySelector('.fixed.inset-0') if (overlay) await userEvent.click(overlay) await waitFor(() => expect(screen.queryByText(/Delete 2 Proxy Hosts?/i)).toBeNull()) @@ -458,7 +456,7 @@ describe('ProxyHosts - Coverage enhancements', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText('One')).toBeTruthy()) + expect(await screen.findByText('One')).toBeTruthy() const anchor = screen.getByRole('link', { name: /(test1\.example\.com|example\.com|One)/i }) await userEvent.click(anchor) expect(openSpy).toHaveBeenCalled() @@ -473,7 +471,7 @@ describe('ProxyHosts - Coverage enhancements', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText('One')).toBeTruthy()) + expect(await screen.findByText('One')).toBeTruthy() const anchor = screen.getByRole('link', { name: /(example\.com|One)/i }) // Anchor should render with target _self when same_tab expect(anchor.getAttribute('target')).toBe('_self') @@ -495,7 +493,7 @@ describe('ProxyHosts - Coverage enhancements', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText('CustomHost')).toBeTruthy()) + expect(await screen.findByText('CustomHost')).toBeTruthy() // Custom Cert - just verify the host renders expect(screen.getByText('CustomHost')).toBeTruthy() @@ -520,7 +518,7 @@ describe('ProxyHosts - Coverage enhancements', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText('Multi')).toBeTruthy()) + expect(await screen.findByText('Multi')).toBeTruthy() // Check multiple domain anchors; parse anchor hrefs instead of substring checks const anchors = screen.getAllByRole('link') const anchorHasHost = (el: Element | null, host: string) => { @@ -551,14 +549,14 @@ describe('ProxyHosts - Coverage enhancements', () => { const confirmSpy = vi.spyOn(window, 'confirm').mockImplementation(() => true) renderWithProviders() - await waitFor(() => expect(screen.getByText('Del')).toBeTruthy()) + expect(await screen.findByText('Del')).toBeTruthy() // Click Delete button in the row const editButton = screen.getByText('Edit') const row = editButton.closest('tr') as HTMLTableRowElement const delButton = within(row).getByText('Delete') await userEvent.click(delButton) // Confirm in dialog - await waitFor(() => expect(screen.getByRole('heading', { name: /Delete Proxy Host/i })).toBeTruthy()) + expect(await screen.findByRole('heading', { name: /Delete Proxy Host/i })).toBeTruthy() const confirmBtn = screen.getAllByRole('button', { name: 'Delete' }).pop()! await userEvent.click(confirmBtn) await waitFor(() => expect(proxyHostsApi.deleteProxyHost).toHaveBeenCalledWith('del1')) @@ -573,16 +571,16 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(accessListsApi.accessListsApi.list).mockResolvedValue([]) vi.mocked(settingsApi.getSettings).mockResolvedValue({}) // uptime monitors associated with host - vi.mocked(uptimeApi.getMonitors).mockResolvedValue([{ id: 'm1', name: 'm1', url: 'http://example', type: 'http', interval: 60, enabled: true, status: 'up', last_check: new Date().toISOString(), latency: 10, max_retries: 3, upstream_host: '127.0.0.5' } as UptimeMonitor]) + vi.mocked(uptimeApi.getMonitors).mockResolvedValue([{ id: 'm1', name: 'm1', url: 'http://example', type: 'http', interval: 60, enabled: true, status: 'up', last_check: new Date().toISOString(), latency: 10, max_retries: 3, upstream_host: '127.0.0.5' } as uptimeApi.UptimeMonitor]) const confirmSpy = vi.spyOn(window, 'confirm').mockImplementation(() => true) renderWithProviders() - await waitFor(() => expect(screen.getByText('Del2')).toBeTruthy()) + expect(await screen.findByText('Del2')).toBeTruthy() const row = screen.getByText('Del2').closest('tr') as HTMLTableRowElement const delButton = within(row).getByText('Delete') await userEvent.click(delButton) // Confirm in dialog - await waitFor(() => expect(screen.getByRole('heading', { name: /Delete Proxy Host/i })).toBeTruthy()) + expect(await screen.findByRole('heading', { name: /Delete Proxy Host/i })).toBeTruthy() const confirmBtn = screen.getAllByRole('button', { name: 'Delete' }).pop()! await userEvent.click(confirmBtn) // Should call delete with deleteUptime true @@ -602,12 +600,12 @@ describe('ProxyHosts - Coverage enhancements', () => { const confirmSpy = vi.spyOn(window, 'confirm').mockImplementation(() => true) renderWithProviders() - await waitFor(() => expect(screen.getByText('Del3')).toBeTruthy()) + expect(await screen.findByText('Del3')).toBeTruthy() const row = screen.getByText('Del3').closest('tr') as HTMLTableRowElement const delButton = within(row).getByText('Delete') await userEvent.click(delButton) // Confirm in dialog - await waitFor(() => expect(screen.getByRole('heading', { name: /Delete Proxy Host/i })).toBeTruthy()) + expect(await screen.findByRole('heading', { name: /Delete Proxy Host/i })).toBeTruthy() const confirmBtn = screen.getAllByRole('button', { name: 'Delete' }).pop()! await userEvent.click(confirmBtn) // Should call delete without second param @@ -626,7 +624,7 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(proxyHostsApi.updateProxyHost).mockResolvedValue({} as ProxyHost) renderWithProviders() - await waitFor(() => expect(screen.getByText('H1')).toBeTruthy()) + expect(await screen.findByText('H1')).toBeTruthy() // Select both hosts const headerCheckbox = screen.getAllByRole('checkbox')[0] @@ -634,7 +632,7 @@ describe('ProxyHosts - Coverage enhancements', () => { // Open Bulk Apply modal await userEvent.click(screen.getByText('Bulk Apply')) - await waitFor(() => expect(screen.getByText('Bulk Apply Settings')).toBeTruthy()) + expect(await screen.findByText('Bulk Apply Settings')).toBeTruthy() // In the modal, find Force SSL row and enable apply and set value true const forceLabel = screen.getByText('Force SSL') @@ -665,7 +663,7 @@ describe('ProxyHosts - Coverage enhancements', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText('Unnamed')).toBeTruthy()) + expect(await screen.findByText('Unnamed')).toBeTruthy() }) it('toggles host enable state via Switch', async () => { @@ -677,7 +675,7 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(proxyHostsApi.updateProxyHost).mockResolvedValue(baseHost({ uuid: 't1', name: 'Toggle', enabled: true })) renderWithProviders() - await waitFor(() => expect(screen.getByText('Toggle')).toBeTruthy()) + expect(await screen.findByText('Toggle')).toBeTruthy() // Locate the row and toggle the enabled switch - it's inside a label with cursor-pointer class const row = screen.getByText('Toggle').closest('tr') as HTMLTableRowElement // Switch component uses a label wrapping a hidden checkbox @@ -695,11 +693,11 @@ describe('ProxyHosts - Coverage enhancements', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText(/Create your first proxy host/)).toBeTruthy()) + expect(await screen.findByText(/Create your first proxy host/)).toBeTruthy() // Click the first Add Proxy Host button (in empty state) await userEvent.click(screen.getAllByRole('button', { name: 'Add Proxy Host' })[0]) // Form should open with Add Proxy Host header - await waitFor(() => expect(screen.getByRole('heading', { name: 'Add Proxy Host' })).toBeTruthy()) + expect(await screen.findByRole('heading', { name: 'Add Proxy Host' })).toBeTruthy() // Click Cancel should close the form const cancelButton = screen.getByText('Cancel') await userEvent.click(cancelButton) @@ -716,12 +714,12 @@ describe('ProxyHosts - Coverage enhancements', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText('EditMe')).toBeTruthy()) + expect(await screen.findByText('EditMe')).toBeTruthy() const editBtn = screen.getByText('Edit') await userEvent.click(editBtn) // Form header should show Edit Proxy Host - await waitFor(() => expect(screen.getByText('Edit Proxy Host')).toBeTruthy()) + expect(await screen.findByText('Edit Proxy Host')).toBeTruthy() // Change name and click Save const nameInput = screen.getByLabelText('Name *') as HTMLInputElement await userEvent.clear(nameInput) @@ -742,12 +740,12 @@ describe('ProxyHosts - Coverage enhancements', () => { const confirmSpy = vi.spyOn(window, 'confirm').mockImplementation(() => true) renderWithProviders() - await waitFor(() => expect(screen.getByText('DelErr')).toBeTruthy()) + expect(await screen.findByText('DelErr')).toBeTruthy() const row = screen.getByText('DelErr').closest('tr') as HTMLTableRowElement const delButton = within(row).getByText('Delete') await userEvent.click(delButton) // Confirm in dialog - await waitFor(() => expect(screen.getByRole('heading', { name: /Delete Proxy Host/i })).toBeTruthy()) + expect(await screen.findByRole('heading', { name: /Delete Proxy Host/i })).toBeTruthy() const confirmBtn = screen.getAllByRole('button', { name: 'Delete' }).pop()! await userEvent.click(confirmBtn) @@ -766,15 +764,15 @@ describe('ProxyHosts - Coverage enhancements', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText('A')).toBeTruthy()) + expect(await screen.findByText('A')).toBeTruthy() // Domain sort await userEvent.click(screen.getByText('Domain')) - await waitFor(() => expect(screen.getByText('B')).toBeTruthy()) // domain 'a.com' should appear first + expect(await screen.findByText('B')).toBeTruthy() // domain 'a.com' should appear first // Forward sort: toggle to change order await userEvent.click(screen.getByText('Forward To')) - await waitFor(() => expect(screen.getByText('A')).toBeTruthy()) + expect(await screen.findByText('A')).toBeTruthy() }) it('applies multiple ACLs sequentially with progress', async () => { @@ -791,7 +789,7 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(proxyHostsApi.bulkUpdateACL).mockResolvedValue({ updated: 2, errors: [] }) renderWithProviders() - await waitFor(() => expect(screen.getByText('H1')).toBeTruthy()) + expect(await screen.findByText('H1')).toBeTruthy() // Select all hosts const checkboxes = screen.getAllByRole('checkbox') @@ -799,7 +797,7 @@ describe('ProxyHosts - Coverage enhancements', () => { // Open Manage ACL await userEvent.click(screen.getByText('Manage ACL')) - await waitFor(() => expect(screen.getByText('A1')).toBeTruthy()) + expect(await screen.findByText('A1')).toBeTruthy() // Select both ACLs const aclCheckboxes = screen.getAllByRole('checkbox') @@ -829,11 +827,11 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(settingsApi.getSettings).mockResolvedValue({}) renderWithProviders() - await waitFor(() => expect(screen.getByText('S1')).toBeTruthy()) + expect(await screen.findByText('S1')).toBeTruthy() const checkboxes = screen.getAllByRole('checkbox') await userEvent.click(checkboxes[0]) - await waitFor(() => expect(screen.getByText('Manage ACL')).toBeTruthy()) + expect(await screen.findByText('Manage ACL')).toBeTruthy() await userEvent.click(screen.getByText('Manage ACL')) // Click Select All in modal @@ -866,15 +864,15 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(settingsApi.getSettings).mockResolvedValue({}) renderWithProviders() - await waitFor(() => expect(screen.getByText('S1')).toBeTruthy()) + expect(await screen.findByText('S1')).toBeTruthy() const checkboxes = screen.getAllByRole('checkbox') await userEvent.click(checkboxes[0]) - await waitFor(() => expect(screen.getByText('Manage ACL')).toBeTruthy()) + expect(await screen.findByText('Manage ACL')).toBeTruthy() await userEvent.click(screen.getByText('Manage ACL')) // Should show the 'No enabled access lists available' message - await waitFor(() => expect(screen.getByText('No enabled access lists available')).toBeTruthy()) + expect(await screen.findByText('No enabled access lists available')).toBeTruthy() }) it('formatSettingLabel, settingHelpText and settingKeyToField return expected values and defaults', () => { @@ -912,12 +910,12 @@ describe('ProxyHosts - Coverage enhancements', () => { vi.mocked(settingsApi.getSettings).mockResolvedValue({}) renderWithProviders() - await waitFor(() => expect(screen.getByText('S1')).toBeTruthy()) + expect(await screen.findByText('S1')).toBeTruthy() const headerCheckbox = screen.getAllByRole('checkbox')[0] const user = userEvent.setup() await user.click(headerCheckbox) await user.click(screen.getByText('Bulk Apply')) - await waitFor(() => expect(screen.getByText('Bulk Apply Settings')).toBeTruthy()) + expect(await screen.findByText('Bulk Apply Settings')).toBeTruthy() // click backdrop const overlay = document.querySelector('.fixed.inset-0') if (overlay) await user.click(overlay) @@ -934,19 +932,18 @@ describe('ProxyHosts - Coverage enhancements', () => { // mock updateProxyHost to fail for host-2 vi.mocked(proxyHostsApi.updateProxyHost).mockImplementation(async (uuid: string) => { if (uuid === 'host-2') throw new Error('update fail') - const result = baseHost({ uuid }) - return result + return baseHost({ uuid }) }) renderWithProviders() - await waitFor(() => expect(screen.getByText('H1')).toBeTruthy()) + expect(await screen.findByText('H1')).toBeTruthy() // select both const headerCheckbox = screen.getAllByRole('checkbox')[0] const user = userEvent.setup() await user.click(headerCheckbox) // Open Bulk Apply await user.click(screen.getByText('Bulk Apply')) - await waitFor(() => expect(screen.getByText('Bulk Apply Settings')).toBeTruthy()) + expect(await screen.findByText('Bulk Apply Settings')).toBeTruthy() // enable Force SSL apply + set switch const forceLabel = screen.getByText('Force SSL') // The row has class p-3 not p-2, and we need to get the parent flex container diff --git a/frontend/src/pages/__tests__/ProxyHosts-extra.test.tsx b/frontend/src/pages/__tests__/ProxyHosts-extra.test.tsx index b9357974..8337fbc2 100644 --- a/frontend/src/pages/__tests__/ProxyHosts-extra.test.tsx +++ b/frontend/src/pages/__tests__/ProxyHosts-extra.test.tsx @@ -1,18 +1,19 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { render, screen, waitFor, within } from '@testing-library/react' -import '@testing-library/jest-dom/vitest' -import userEvent from '@testing-library/user-event' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import type { ProxyHost } from '../../api/proxyHosts' -import type { UptimeMonitor } from '../../api/uptime' -import ProxyHosts from '../ProxyHosts' -import { useProxyHosts } from '../../hooks/useProxyHosts' -import { useCertificates } from '../../hooks/useCertificates' -import { useAccessLists } from '../../hooks/useAccessLists' -import { getSettings } from '../../api/settings' -import { getMonitors } from '../../api/uptime' -import { createBackup } from '../../api/backups' +import '@testing-library/jest-dom/vitest' +import { render, screen, waitFor, within } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { toast } from 'react-hot-toast' +import { describe, it, expect, vi, beforeEach } from 'vitest' + +import { createBackup } from '../../api/backups' +import { getSettings } from '../../api/settings' +import { getMonitors, type UptimeMonitor } from '../../api/uptime' +import { useAccessLists } from '../../hooks/useAccessLists' +import { useCertificates } from '../../hooks/useCertificates' +import { useProxyHosts } from '../../hooks/useProxyHosts' +import ProxyHosts from '../ProxyHosts' + +import type { ProxyHost } from '../../api/proxyHosts' vi.mock('../../hooks/useProxyHosts', () => ({ useProxyHosts: vi.fn() })) vi.mock('../../hooks/useCertificates', () => ({ useCertificates: vi.fn() })) @@ -121,7 +122,7 @@ describe('ProxyHosts page extra tests', () => { renderWithProviders() // hosts are sorted by name by default (Alpha before Beta) by the component - await waitFor(() => expect(screen.getByText('Alpha')).toBeInTheDocument()) + expect(await screen.findByText('Alpha')).toBeInTheDocument() const table = screen.getAllByRole('table')[0] const nameHeader = within(table).getAllByRole('button', { name: 'Name' })[0] @@ -163,12 +164,12 @@ describe('ProxyHosts page extra tests', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText('DelHost')).toBeInTheDocument()) + expect(await screen.findByText('DelHost')).toBeInTheDocument() const deleteBtn = screen.getByRole('button', { name: 'Delete proxy host DelHost' }) await userEvent.click(deleteBtn) // Confirm deletion in the dialog - await waitFor(() => expect(screen.getByRole('heading', { name: /Delete Proxy Host/i })).toBeInTheDocument()) + expect(await screen.findByRole('heading', { name: /Delete Proxy Host/i })).toBeInTheDocument() const confirmDeleteBtn = screen.getByRole('button', { name: /^Delete$/ }) await userEvent.click(confirmDeleteBtn) @@ -201,7 +202,7 @@ describe('ProxyHosts page extra tests', () => { ) renderWithProviders() - await waitFor(() => expect(screen.getByText('ValidHost')).toBeInTheDocument()) + expect(await screen.findByText('ValidHost')).toBeInTheDocument() // Check that SSL badges are rendered (text removed for better spacing) const sslBadges = screen.getAllByText('SSL') expect(sslBadges.length).toBeGreaterThan(0) @@ -211,7 +212,7 @@ describe('ProxyHosts page extra tests', () => { vi.mocked(useProxyHosts).mockReturnValue(createProxyHostsHookValue({ error: 'Failed to load' })) renderWithProviders() - await waitFor(() => expect(screen.getByText('Failed to load')).toBeInTheDocument()) + expect(await screen.findByText('Failed to load')).toBeInTheDocument() }) it('select all shows (all) selected in summary', async () => { @@ -221,18 +222,14 @@ describe('ProxyHosts page extra tests', () => { vi.mocked(useProxyHosts).mockReturnValue(createProxyHostsHookValue({ hosts: [h1, h2] })) renderWithProviders() - await waitFor(() => expect(screen.getByText('XHost')).toBeInTheDocument()) + expect(await screen.findByText('XHost')).toBeInTheDocument() const selectAllBtn = screen.getByRole('checkbox', { name: /Select all/i }) // fallback, find by title - if (!selectAllBtn) { - await userEvent.click(screen.getByTitle('Select all')) - } else { - await userEvent.click(selectAllBtn) - } + await (!selectAllBtn ? userEvent.click(screen.getByTitle('Select all')) : userEvent.click(selectAllBtn)); // Text is split across elements: "2 host(s) selected (all)" // Check for presence of both parts separately - await waitFor(() => expect(screen.getByText(/host\(s\) selected/)).toBeInTheDocument()) + expect(await screen.findByText(/host\(s\) selected/)).toBeInTheDocument() expect(screen.getByText(/\(all\)/)).toBeInTheDocument() }) @@ -251,7 +248,7 @@ describe('ProxyHosts page extra tests', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText('link.example.com')).toBeInTheDocument()) + expect(await screen.findByText('link.example.com')).toBeInTheDocument() // Use exact string match to avoid incomplete hostname regex (CodeQL js/incomplete-hostname-regexp) const link = screen.getByRole('link', { name: 'link.example.com' }) await userEvent.click(link) @@ -264,7 +261,7 @@ describe('ProxyHosts page extra tests', () => { vi.mocked(useProxyHosts).mockReturnValue(createProxyHostsHookValue({ hosts: [host] })) renderWithProviders() - await waitFor(() => expect(screen.getByText('XHost2')).toBeInTheDocument()) + expect(await screen.findByText('XHost2')).toBeInTheDocument() expect(screen.getByText('WS')).toBeInTheDocument() expect(screen.getByText('ACL')).toBeInTheDocument() }) @@ -282,7 +279,7 @@ describe('ProxyHosts page extra tests', () => { vi.mocked(useAccessLists).mockReturnValue(createAccessListsHookValue([acl])) renderWithProviders() - await waitFor(() => expect(screen.getByText('AclHost')).toBeInTheDocument()) + expect(await screen.findByText('AclHost')).toBeInTheDocument() // Select host using checkbox - find row first, then first checkbox (selection) within const row = screen.getByText('AclHost').closest('tr') as HTMLTableRowElement const selectBtn = within(row).getAllByRole('checkbox')[0] @@ -296,14 +293,14 @@ describe('ProxyHosts page extra tests', () => { const removeBtn = screen.getByText('Remove ACL') await userEvent.click(removeBtn) - await waitFor(() => expect(screen.getByText(/This will remove the access list from all 1 selected host/i)).toBeInTheDocument()) + expect(await screen.findByText(/This will remove the access list from all 1 selected host/i)).toBeInTheDocument() // Switch back to Apply ACL and select the ACL const applyBtn = screen.getByText('Apply ACL') await userEvent.click(applyBtn) const selectAll = screen.getByText('Select All') await userEvent.click(selectAll) - await waitFor(() => expect(screen.getByText('Apply (1)')).toBeInTheDocument()) + expect(await screen.findByText('Apply (1)')).toBeInTheDocument() }) it('bulk ACL remove action calls bulkUpdateACL with null and shows removed toast', async () => { @@ -314,7 +311,7 @@ describe('ProxyHosts page extra tests', () => { vi.mocked(useAccessLists).mockReturnValue(createAccessListsHookValue([{ id: 1, name: 'MyACL', enabled: true }])) renderWithProviders() - await waitFor(() => expect(screen.getByText('AclHost2')).toBeInTheDocument()) + expect(await screen.findByText('AclHost2')).toBeInTheDocument() const row = screen.getByText('AclHost2').closest('tr') as HTMLTableRowElement await userEvent.click(within(row).getAllByRole('checkbox')[0]) await userEvent.click(screen.getByText('Manage ACL')) @@ -332,12 +329,12 @@ describe('ProxyHosts page extra tests', () => { vi.mocked(useProxyHosts).mockReturnValue(createProxyHostsHookValue({ hosts: [host] })) renderWithProviders() - await waitFor(() => expect(screen.getByText('AclHost3')).toBeInTheDocument()) + expect(await screen.findByText('AclHost3')).toBeInTheDocument() const row = screen.getByText('AclHost3').closest('tr') as HTMLTableRowElement await userEvent.click(within(row).getAllByRole('checkbox')[0]) await userEvent.click(screen.getByText('Manage ACL')) - await waitFor(() => expect(screen.getByText('No enabled access lists available')).toBeInTheDocument()) + expect(await screen.findByText('No enabled access lists available')).toBeInTheDocument() }) it('bulk delete modal lists hosts to be deleted', async () => { @@ -347,7 +344,7 @@ describe('ProxyHosts page extra tests', () => { const confirmMock = vi.spyOn(window, 'confirm').mockImplementation(() => true) renderWithProviders() - await waitFor(() => expect(screen.getByText('DeleteMe2')).toBeInTheDocument()) + expect(await screen.findByText('DeleteMe2')).toBeInTheDocument() const row = screen.getByText('DeleteMe2').closest('tr') as HTMLTableRowElement await userEvent.click(within(row).getAllByRole('checkbox')[0]) const deleteButtons = screen.getAllByText('Delete') @@ -355,16 +352,14 @@ describe('ProxyHosts page extra tests', () => { if (!toolbarBtn) throw new Error('Toolbar delete button not found') await userEvent.click(toolbarBtn) - await waitFor(() => expect(screen.getByRole('heading', { name: /Delete 1 Proxy Host/i })).toBeInTheDocument()) + expect(await screen.findByRole('heading', { name: /Delete 1 Proxy Host/i })).toBeInTheDocument() // Ensure the modal lists the host by scoping to the modal content const listHeader = screen.getByText('Hosts to be deleted:') const modalRoot = listHeader.closest('div') expect(modalRoot).toBeTruthy() - if (modalRoot) { - const { getByText: getByTextWithin } = within(modalRoot) - expect(getByTextWithin('DeleteMe2')).toBeInTheDocument() - expect(getByTextWithin('(a.example.com)')).toBeInTheDocument() - } + const { getByText: getByTextWithin } = within(modalRoot!) + expect(getByTextWithin('DeleteMe2')).toBeInTheDocument() + expect(getByTextWithin('(a.example.com)')).toBeInTheDocument() // Confirm delete await userEvent.click(screen.getByRole('button', { name: /Delete Permanently/i })) await waitFor(() => expect(vi.mocked(toast.success)).toHaveBeenCalledWith(expect.stringContaining('Backup created'))) @@ -378,7 +373,7 @@ describe('ProxyHosts page extra tests', () => { vi.mocked(useProxyHosts).mockReturnValue(createProxyHostsHookValue({ hosts: [host], updateHost })) renderWithProviders() - await waitFor(() => expect(screen.getByText('BlankHost')).toBeInTheDocument()) + expect(await screen.findByText('BlankHost')).toBeInTheDocument() // Select host const row = screen.getByText('BlankHost').closest('tr') as HTMLTableRowElement await userEvent.click(within(row).getAllByRole('checkbox')[0]) @@ -405,7 +400,7 @@ describe('ProxyHosts page extra tests', () => { renderWithProviders() - await waitFor(() => expect(screen.getByText('DeleteMe')).toBeInTheDocument()) + expect(await screen.findByText('DeleteMe')).toBeInTheDocument() // Select host const row = screen.getByText('DeleteMe').closest('tr') as HTMLTableRowElement const selectBtn = within(row).getAllByRole('checkbox')[0] diff --git a/frontend/src/pages/__tests__/ProxyHosts-progress.test.tsx b/frontend/src/pages/__tests__/ProxyHosts-progress.test.tsx index e68889e3..d166c77c 100644 --- a/frontend/src/pages/__tests__/ProxyHosts-progress.test.tsx +++ b/frontend/src/pages/__tests__/ProxyHosts-progress.test.tsx @@ -1,15 +1,18 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' +import { toast } from 'react-hot-toast' import { MemoryRouter } from 'react-router-dom' import { vi, describe, it, expect, beforeEach } from 'vitest' -import type { ProxyHost, BulkUpdateACLResponse } from '../../api/proxyHosts' -import ProxyHosts from '../ProxyHosts' -import * as proxyHostsApi from '../../api/proxyHosts' -import * as certificatesApi from '../../api/certificates' + import * as accessListsApi from '../../api/accessLists' +import * as certificatesApi from '../../api/certificates' +import * as proxyHostsApi from '../../api/proxyHosts' import * as settingsApi from '../../api/settings' -import { toast } from 'react-hot-toast' +import ProxyHosts from '../ProxyHosts' + +import type { ProxyHost, BulkUpdateACLResponse } from '../../api/proxyHosts' + vi.mock('react-hot-toast', () => ({ toast: { success: vi.fn(), error: vi.fn(), loading: vi.fn(), dismiss: vi.fn() }, @@ -90,18 +93,18 @@ describe('ProxyHosts progress apply', () => { }) renderWithProviders() - await waitFor(() => expect(screen.getByText('H1')).toBeTruthy()) + expect(await screen.findByText('H1')).toBeTruthy() // Select both hosts via select-all const checkboxes = screen.getAllByRole('checkbox') await userEvent.click(checkboxes[0]) // Open bulk ACL modal - await waitFor(() => expect(screen.getByText('Manage ACL')).toBeTruthy()) + expect(await screen.findByText('Manage ACL')).toBeTruthy() await userEvent.click(screen.getByText('Manage ACL')) // Wait for ACL list - await waitFor(() => expect(screen.getByText('ACL1')).toBeTruthy()) + expect(await screen.findByText('ACL1')).toBeTruthy() // Select both ACLs const aclCheckboxes = screen.getAllByRole('checkbox') @@ -115,7 +118,7 @@ describe('ProxyHosts progress apply', () => { await userEvent.click(applyBtn) // Progress indicator should appear - await waitFor(() => expect(screen.getByText(/Applying ACLs/)).toBeTruthy()) + expect(await screen.findByText(/Applying ACLs/)).toBeTruthy() // After the first bulk operation starts, we should have a resolver await waitFor(() => expect(resolvers.length).toBeGreaterThanOrEqual(1)) @@ -137,7 +140,7 @@ describe('ProxyHosts progress apply', () => { vi.mocked(settingsApi.getSettings).mockResolvedValue({ 'ui.domain_link_behavior': 'same_tab' }) renderWithProviders() - await waitFor(() => expect(screen.getByText('One')).toBeTruthy()) + expect(await screen.findByText('One')).toBeTruthy() const anchor = screen.getByRole('link', { name: /^example\.com$/i }) expect(anchor.getAttribute('target')).toBe('_self') }) diff --git a/frontend/src/pages/__tests__/ProxyHosts.bulkApplyHeaders.test.tsx b/frontend/src/pages/__tests__/ProxyHosts.bulkApplyHeaders.test.tsx index 6da70743..8cd7e7d7 100644 --- a/frontend/src/pages/__tests__/ProxyHosts.bulkApplyHeaders.test.tsx +++ b/frontend/src/pages/__tests__/ProxyHosts.bulkApplyHeaders.test.tsx @@ -1,19 +1,22 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; import { vi, describe, it, expect, beforeEach } from 'vitest'; -import ProxyHosts from '../ProxyHosts'; -import * as proxyHostsApi from '../../api/proxyHosts'; + +import * as accessListsApi from '../../api/accessLists'; import * as certificatesApi from '../../api/certificates'; +import * as proxyHostsApi from '../../api/proxyHosts'; +import * as securityHeadersApi from '../../api/securityHeaders'; +import * as settingsApi from '../../api/settings'; +import { createMockProxyHost } from '../../testUtils/createMockProxyHost'; +import ProxyHosts from '../ProxyHosts'; + +import type { AccessList } from '../../api/accessLists'; import type { Certificate } from '../../api/certificates'; import type { ProxyHost } from '../../api/proxyHosts'; -import * as accessListsApi from '../../api/accessLists'; -import type { AccessList } from '../../api/accessLists'; -import * as settingsApi from '../../api/settings'; -import * as securityHeadersApi from '../../api/securityHeaders'; import type { SecurityHeaderProfile } from '../../api/securityHeaders'; -import { createMockProxyHost } from '../../testUtils/createMockProxyHost'; + // Mock toast vi.mock('react-hot-toast', () => ({ @@ -170,7 +173,7 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { it('shows security header profile option in bulk apply modal', async () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('Test Host 1')).toBeTruthy()); + expect(await screen.findByText('Test Host 1')).toBeTruthy(); // Select hosts const selectAll = screen.getByLabelText('Select all rows'); @@ -192,14 +195,14 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { it('enables profile selection when checkbox is checked', async () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('Test Host 1')).toBeTruthy()); + expect(await screen.findByText('Test Host 1')).toBeTruthy(); // Select hosts and open modal const selectAll = screen.getByLabelText('Select all rows'); await userEvent.click(selectAll); await userEvent.click(screen.getByText('Bulk Apply')); - await waitFor(() => expect(screen.getByText('Bulk Apply Settings')).toBeTruthy()); + expect(await screen.findByText('Bulk Apply Settings')).toBeTruthy(); // Find security header checkbox const securityHeaderLabel = screen.getByText('Security Header Profile'); @@ -221,7 +224,7 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { it('lists all available profiles in dropdown grouped correctly', async () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('Test Host 1')).toBeTruthy()); + expect(await screen.findByText('Test Host 1')).toBeTruthy(); // Select hosts and open modal const selectAll = screen.getByLabelText('Select all rows'); @@ -258,7 +261,7 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('Test Host 1')).toBeTruthy()); + expect(await screen.findByText('Test Host 1')).toBeTruthy(); // Select hosts and open modal const selectAll = screen.getByLabelText('Select all rows'); @@ -272,7 +275,7 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { await userEvent.click(securityHeaderCheckbox); // Select a profile - await waitFor(() => expect(screen.getByRole('combobox')).toBeTruthy()); + expect(await screen.findByRole('combobox')).toBeTruthy(); const dropdown = screen.getByRole('combobox') as HTMLSelectElement; await userEvent.selectOptions(dropdown, '1'); // Select profile ID 1 @@ -293,7 +296,7 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('Test Host 1')).toBeTruthy()); + expect(await screen.findByText('Test Host 1')).toBeTruthy(); // Select hosts and open modal const selectAll = screen.getByLabelText('Select all rows'); @@ -307,7 +310,7 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { await userEvent.click(securityHeaderCheckbox); // Select "None" (value 0) - await waitFor(() => expect(screen.getByRole('combobox')).toBeTruthy()); + expect(await screen.findByRole('combobox')).toBeTruthy(); const dropdown = screen.getByRole('combobox') as HTMLSelectElement; await userEvent.selectOptions(dropdown, '0'); @@ -334,14 +337,14 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { it('disables Apply button when no options selected', async () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('Test Host 1')).toBeTruthy()); + expect(await screen.findByText('Test Host 1')).toBeTruthy(); // Select hosts and open modal const selectAll = screen.getByLabelText('Select all rows'); await userEvent.click(selectAll); await userEvent.click(screen.getByText('Bulk Apply')); - await waitFor(() => expect(screen.getByText('Bulk Apply Settings')).toBeTruthy()); + expect(await screen.findByText('Bulk Apply Settings')).toBeTruthy(); // Apply button should be disabled when nothing is selected const dialog = screen.getByRole('dialog'); @@ -360,7 +363,7 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('Test Host 1')).toBeTruthy()); + expect(await screen.findByText('Test Host 1')).toBeTruthy(); // Select hosts and open modal const selectAll = screen.getByLabelText('Select all rows'); @@ -373,7 +376,7 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { const securityHeaderCheckbox = within(securityHeaderRow).getByRole('checkbox'); await userEvent.click(securityHeaderCheckbox); - await waitFor(() => expect(screen.getByRole('combobox')).toBeTruthy()); + expect(await screen.findByRole('combobox')).toBeTruthy(); const dropdown = screen.getByRole('combobox') as HTMLSelectElement; await userEvent.selectOptions(dropdown, '1'); @@ -391,7 +394,7 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { it('resets state on modal close', async () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('Test Host 1')).toBeTruthy()); + expect(await screen.findByText('Test Host 1')).toBeTruthy(); // Select hosts and open modal const selectAll = screen.getByLabelText('Select all rows'); @@ -404,7 +407,7 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { const securityHeaderCheckbox = within(securityHeaderRow).getByRole('checkbox'); await userEvent.click(securityHeaderCheckbox); - await waitFor(() => expect(screen.getByRole('combobox')).toBeTruthy()); + expect(await screen.findByRole('combobox')).toBeTruthy(); const dropdown = screen.getByRole('combobox') as HTMLSelectElement; await userEvent.selectOptions(dropdown, '1'); @@ -429,7 +432,7 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { it('shows profile description when profile is selected', async () => { renderWithProviders(); - await waitFor(() => expect(screen.getByText('Test Host 1')).toBeTruthy()); + expect(await screen.findByText('Test Host 1')).toBeTruthy(); // Select hosts and open modal const selectAll = screen.getByLabelText('Select all rows'); @@ -443,7 +446,7 @@ describe('ProxyHosts - Bulk Apply Security Headers', () => { await userEvent.click(securityHeaderCheckbox); // Select a profile - await waitFor(() => expect(screen.getByRole('combobox')).toBeTruthy()); + expect(await screen.findByRole('combobox')).toBeTruthy(); const dropdown = screen.getByRole('combobox') as HTMLSelectElement; await userEvent.selectOptions(dropdown, '1'); // Strict Security diff --git a/frontend/src/pages/__tests__/RateLimiting.spec.tsx b/frontend/src/pages/__tests__/RateLimiting.spec.tsx index 94457d5c..9f6bdefa 100644 --- a/frontend/src/pages/__tests__/RateLimiting.spec.tsx +++ b/frontend/src/pages/__tests__/RateLimiting.spec.tsx @@ -1,11 +1,13 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { BrowserRouter } from 'react-router-dom' -import RateLimiting from '../RateLimiting' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as securityApi from '../../api/security' import * as settingsApi from '../../api/settings' +import RateLimiting from '../RateLimiting' + import type { SecurityStatus } from '../../api/security' vi.mock('../../api/security') diff --git a/frontend/src/pages/__tests__/SMTPSettings.test.tsx b/frontend/src/pages/__tests__/SMTPSettings.test.tsx index dc4f8ac9..5e8b3bee 100644 --- a/frontend/src/pages/__tests__/SMTPSettings.test.tsx +++ b/frontend/src/pages/__tests__/SMTPSettings.test.tsx @@ -1,10 +1,11 @@ import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { vi, describe, it, expect, beforeEach } from 'vitest' -import SMTPSettings from '../SMTPSettings' + import * as smtpApi from '../../api/smtp' -import { toast } from '../../utils/toast' import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' +import { toast } from '../../utils/toast' +import SMTPSettings from '../SMTPSettings' const translations: Record = { 'smtp.configured': 'SMTP Configured', @@ -224,7 +225,7 @@ describe('SMTPSettings', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByPlaceholderText('smtp.gmail.com')).toBeInTheDocument()) + expect(await screen.findByPlaceholderText('smtp.gmail.com')).toBeInTheDocument() await user.type(screen.getByPlaceholderText('smtp.gmail.com'), 'bad.host') await user.type(screen.getByPlaceholderText('Charon '), 'ops@example.com') @@ -250,7 +251,7 @@ describe('SMTPSettings', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText(t('smtp.testConnection'))).toBeInTheDocument()) + expect(await screen.findByText(t('smtp.testConnection'))).toBeInTheDocument() // Button should start disabled until host and from address are provided const testButton = screen.getByRole('button', { name: t('smtp.testConnection') }) @@ -282,7 +283,7 @@ describe('SMTPSettings', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText(t('smtp.sendTestEmail'))).toBeInTheDocument()) + expect(await screen.findByText(t('smtp.sendTestEmail'))).toBeInTheDocument() const input = screen.getByPlaceholderText('recipient@example.com') as HTMLInputElement await user.type(input, 'keepme@example.com') diff --git a/frontend/src/pages/__tests__/Security.audit.test.tsx b/frontend/src/pages/__tests__/Security.audit.test.tsx index 1f0a1a96..e0ed2799 100644 --- a/frontend/src/pages/__tests__/Security.audit.test.tsx +++ b/frontend/src/pages/__tests__/Security.audit.test.tsx @@ -4,16 +4,19 @@ * Tests edge cases, input validation, error states, and security concerns * for the Cerberus Dashboard implementation. */ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { BrowserRouter } from 'react-router-dom' -import Security from '../Security' -import * as securityApi from '../../api/security' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as crowdsecApi from '../../api/crowdsec' +import * as securityApi from '../../api/security' import * as settingsApi from '../../api/settings' import { toast } from '../../utils/toast' +import Security from '../Security' + +import type * as useSecurity from '../../hooks/useSecurity' const mockSecurityStatus = { cerberus: { enabled: true }, @@ -35,7 +38,7 @@ vi.mock('../../utils/toast', () => ({ }, })) vi.mock('../../hooks/useSecurity', async (importOriginal) => { - const actual = await importOriginal() + const actual = await importOriginal() return { ...actual, useSecurityConfig: vi.fn(() => ({ data: { config: { admin_whitelist: '' } } })), @@ -173,7 +176,7 @@ describe.skip('Security Page - QA Security Audit', () => { await renderSecurityPage() // Page should still render even if status check fails - await waitFor(() => expect(screen.getByText(/Cerberus Dashboard/i)).toBeInTheDocument()) + expect(await screen.findByText(/Cerberus Dashboard/i)).toBeInTheDocument() }) }) @@ -191,7 +194,7 @@ describe.skip('Security Page - QA Security Audit', () => { await user.click(toggle) // Overlay should appear indicating operation in progress - await waitFor(() => expect(screen.getByText(/Three heads turn/i)).toBeInTheDocument()) + expect(await screen.findByText(/Three heads turn/i)).toBeInTheDocument() }) it('prevents double toggle when starting CrowdSec', async () => { @@ -400,7 +403,7 @@ describe.skip('Security Page - QA Security Audit', () => { } // Page should still be functional - await waitFor(() => expect(screen.getByText(/Cerberus Dashboard/i)).toBeInTheDocument()) + expect(await screen.findByText(/Cerberus Dashboard/i)).toBeInTheDocument() }) it('handles undefined crowdsec status gracefully', async () => { @@ -410,7 +413,7 @@ describe.skip('Security Page - QA Security Audit', () => { await renderSecurityPage() // Should not crash - await waitFor(() => expect(screen.getByText(/Cerberus Dashboard/i)).toBeInTheDocument()) + expect(await screen.findByText(/Cerberus Dashboard/i)).toBeInTheDocument() }) }) }) diff --git a/frontend/src/pages/__tests__/Security.dashboard.test.tsx b/frontend/src/pages/__tests__/Security.dashboard.test.tsx index 620d9c41..a61c7ef4 100644 --- a/frontend/src/pages/__tests__/Security.dashboard.test.tsx +++ b/frontend/src/pages/__tests__/Security.dashboard.test.tsx @@ -5,21 +5,24 @@ * Tests all 4 security cards display correct status, Cerberus disabled banner, * and toggle switches disabled when Cerberus is off. */ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { BrowserRouter } from 'react-router-dom' -import Security from '../Security' -import * as securityApi from '../../api/security' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as crowdsecApi from '../../api/crowdsec' +import * as securityApi from '../../api/security' import * as settingsApi from '../../api/settings' +import Security from '../Security' + +import type * as useSecurity from '../../hooks/useSecurity' vi.mock('../../api/security') vi.mock('../../api/crowdsec') vi.mock('../../api/settings') vi.mock('../../hooks/useSecurity', async (importOriginal) => { - const actual = await importOriginal() + const actual = await importOriginal() return { ...actual, useSecurityConfig: vi.fn(() => ({ data: { config: { admin_whitelist: '10.0.0.0/8' } } })), diff --git a/frontend/src/pages/__tests__/Security.errors.test.tsx b/frontend/src/pages/__tests__/Security.errors.test.tsx index 96fc95b3..d677391f 100644 --- a/frontend/src/pages/__tests__/Security.errors.test.tsx +++ b/frontend/src/pages/__tests__/Security.errors.test.tsx @@ -5,16 +5,19 @@ * Tests error messages on API failures, toast notifications on mutation errors, * and optimistic update rollback. */ -import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { BrowserRouter } from 'react-router-dom' -import Security from '../Security' -import * as securityApi from '../../api/security' +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + import * as crowdsecApi from '../../api/crowdsec' +import * as securityApi from '../../api/security' import * as settingsApi from '../../api/settings' import { toast } from '../../utils/toast' +import Security from '../Security' + +import type * as useSecurity from '../../hooks/useSecurity' vi.mock('../../api/security') vi.mock('../../api/crowdsec') @@ -28,7 +31,7 @@ vi.mock('../../utils/toast', () => ({ }, })) vi.mock('../../hooks/useSecurity', async (importOriginal) => { - const actual = await importOriginal() + const actual = await importOriginal() return { ...actual, useSecurityConfig: vi.fn(() => ({ data: { config: { admin_whitelist: '10.0.0.0/8' } } })), @@ -140,8 +143,8 @@ describe.skip('Security Error Handling Tests', () => { await waitFor(() => { expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('Failed to start CrowdSec')) - expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('Service unavailable')) }) + expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('Service unavailable')) }) }) @@ -160,8 +163,8 @@ describe.skip('Security Error Handling Tests', () => { await waitFor(() => { expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('Failed to stop CrowdSec')) - expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('Process locked')) }) + expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('Process locked')) }) }) @@ -244,8 +247,8 @@ describe.skip('Security Error Handling Tests', () => { await waitFor(() => { expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('Failed to update setting')) - expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('ACL update failed')) }) + expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('ACL update failed')) }) }) diff --git a/frontend/src/pages/__tests__/Security.functional.test.tsx b/frontend/src/pages/__tests__/Security.functional.test.tsx index afb7d543..bb385492 100644 --- a/frontend/src/pages/__tests__/Security.functional.test.tsx +++ b/frontend/src/pages/__tests__/Security.functional.test.tsx @@ -4,20 +4,23 @@ * These tests mock the LiveLogViewer component to avoid WebSocket issues * and focus on testing Security.tsx core functionality. */ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { BrowserRouter } from 'react-router-dom' -import Security from '../Security' -import * as securityApi from '../../api/security' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as crowdsecApi from '../../api/crowdsec' +import * as securityApi from '../../api/security' import * as settingsApi from '../../api/settings' +import Security from '../Security' + +import type * as ReactRouterDom from 'react-router-dom' const mockNavigate = vi.hoisted(() => vi.fn()) vi.mock('react-router-dom', async () => { - const actual = await vi.importActual('react-router-dom') + const actual = await vi.importActual('react-router-dom') return { ...actual, useNavigate: () => mockNavigate, diff --git a/frontend/src/pages/__tests__/Security.loading.test.tsx b/frontend/src/pages/__tests__/Security.loading.test.tsx index 1d5a4f7c..0ba3ad1a 100644 --- a/frontend/src/pages/__tests__/Security.loading.test.tsx +++ b/frontend/src/pages/__tests__/Security.loading.test.tsx @@ -5,21 +5,24 @@ * Tests ConfigReloadOverlay appears during operations, specific loading messages, * and overlay blocks interactions. */ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { BrowserRouter } from 'react-router-dom' -import Security from '../Security' -import * as securityApi from '../../api/security' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as crowdsecApi from '../../api/crowdsec' +import * as securityApi from '../../api/security' import * as settingsApi from '../../api/settings' +import Security from '../Security' + +import type * as useSecurity from '../../hooks/useSecurity' vi.mock('../../api/security') vi.mock('../../api/crowdsec') vi.mock('../../api/settings') vi.mock('../../hooks/useSecurity', async (importOriginal) => { - const actual = await importOriginal() + const actual = await importOriginal() return { ...actual, useSecurityConfig: vi.fn(() => ({ data: { config: { admin_whitelist: '10.0.0.0/8' } } })), diff --git a/frontend/src/pages/__tests__/Security.spec.tsx b/frontend/src/pages/__tests__/Security.spec.tsx index 7dc2759b..05d91d16 100644 --- a/frontend/src/pages/__tests__/Security.spec.tsx +++ b/frontend/src/pages/__tests__/Security.spec.tsx @@ -1,21 +1,23 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' -import { cleanup } from '@testing-library/react' -import { render, screen, waitFor } from '@testing-library/react' -import userEvent from '@testing-library/user-event' import { QueryClientProvider } from '@tanstack/react-query' +import { cleanup, render, screen, waitFor } from '@testing-library/react' +import userEvent from '@testing-library/user-event' import { BrowserRouter } from 'react-router-dom' -import Security from '../Security' -import * as api from '../../api/security' -import type { SecurityStatus, RuleSetsResponse } from '../../api/security' -import * as settingsApi from '../../api/settings' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as crowdsecApi from '../../api/crowdsec' -import { createTestQueryClient } from '../../test/createTestQueryClient' import * as logsApi from '../../api/logs' +import * as api from '../../api/security' +import * as settingsApi from '../../api/settings' +import { createTestQueryClient } from '../../test/createTestQueryClient' +import Security from '../Security' + +import type { SecurityStatus, RuleSetsResponse } from '../../api/security' +import type * as ReactRouterDom from 'react-router-dom' const mockNavigate = vi.fn() vi.mock('react-router-dom', async () => { - const actual = await vi.importActual('react-router-dom') + const actual = await vi.importActual('react-router-dom') return { ...actual, useNavigate: () => mockNavigate } }) @@ -148,10 +150,10 @@ describe('Security page', () => { acl: { enabled: false }, } vi.mocked(api.getSecurityStatus).mockResolvedValue(status as SecurityStatus) - vi.mocked(settingsApi.updateSetting).mockResolvedValue(undefined) + vi.mocked(settingsApi.updateSetting).mockResolvedValue() renderWithProviders() - await waitFor(() => expect(screen.getByText('Cerberus Dashboard')).toBeInTheDocument()) + expect(await screen.findByText('Cerberus Dashboard')).toBeInTheDocument() const crowdsecToggle = screen.getByTestId('toggle-crowdsec') as HTMLInputElement expect(crowdsecToggle.disabled).toBe(false) // Ensure enable-all controls were removed @@ -169,7 +171,7 @@ describe('Security page', () => { vi.mocked(api.getSecurityStatus).mockResolvedValue(status as SecurityStatus) const updateSpy = vi.mocked(settingsApi.updateSetting) renderWithProviders() - await waitFor(() => expect(screen.getByText('Cerberus Dashboard')).toBeInTheDocument()) + expect(await screen.findByText('Cerberus Dashboard')).toBeInTheDocument() const aclToggle = screen.getByTestId('toggle-acl') await userEvent.click(aclToggle) await waitFor(() => expect(updateSpy).toHaveBeenCalledWith('security.acl.enabled', 'true', 'security', 'bool')) @@ -190,10 +192,10 @@ describe('Security page', () => { vi.mocked(api.getSecurityStatus).mockResolvedValue(baseStatus as SecurityStatus) vi.mocked(crowdsecApi.statusCrowdsec).mockResolvedValue({ running: false, pid: 0, lapi_ready: false }) vi.mocked(crowdsecApi.startCrowdsec).mockResolvedValue({ status: 'started', pid: 123, lapi_ready: true }) - vi.mocked(settingsApi.updateSetting).mockResolvedValue(undefined) + vi.mocked(settingsApi.updateSetting).mockResolvedValue() renderWithProviders() - await waitFor(() => expect(screen.getByText('Cerberus Dashboard')).toBeInTheDocument()) + expect(await screen.findByText('Cerberus Dashboard')).toBeInTheDocument() const toggle = screen.getByTestId('toggle-crowdsec') await user.click(toggle) await waitFor(() => expect(crowdsecApi.startCrowdsec).toHaveBeenCalled()) @@ -206,7 +208,7 @@ describe('Security page', () => { vi.mocked(crowdsecApi.stopCrowdsec).mockResolvedValue(undefined) renderWithProviders() - await waitFor(() => expect(screen.getByText('Cerberus Dashboard')).toBeInTheDocument()) + expect(await screen.findByText('Cerberus Dashboard')).toBeInTheDocument() const stopToggle = screen.getByTestId('toggle-crowdsec') await user.click(stopToggle) await waitFor(() => expect(crowdsecApi.stopCrowdsec).toHaveBeenCalled()) @@ -222,7 +224,7 @@ describe('Security page', () => { } vi.mocked(api.getSecurityStatus).mockResolvedValue(status as SecurityStatus) renderWithProviders() - await waitFor(() => expect(screen.getByText('Security Features Unavailable')).toBeInTheDocument()) + expect(await screen.findByText('Security Features Unavailable')).toBeInTheDocument() const crowdsecToggle = screen.getByTestId('toggle-crowdsec') expect(crowdsecToggle).toBeDisabled() }) @@ -243,6 +245,6 @@ describe('Security page', () => { renderWithProviders() // WAF now shows threat protection summary instead of mode text - await waitFor(() => expect(screen.getByText(/SQL injection, XSS, RCE/i)).toBeInTheDocument()) + expect(await screen.findByText(/SQL injection, XSS, RCE/i)).toBeInTheDocument() }) }) diff --git a/frontend/src/pages/__tests__/Security.test.tsx b/frontend/src/pages/__tests__/Security.test.tsx index f56a5ee0..9b39e60a 100644 --- a/frontend/src/pages/__tests__/Security.test.tsx +++ b/frontend/src/pages/__tests__/Security.test.tsx @@ -1,18 +1,21 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { act, render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { BrowserRouter } from 'react-router-dom' -import Security from '../Security' -import * as securityApi from '../../api/security' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as crowdsecApi from '../../api/crowdsec' +import * as securityApi from '../../api/security' import * as settingsApi from '../../api/settings' +import Security from '../Security' + +import type * as useSecurity from '../../hooks/useSecurity' vi.mock('../../api/security') vi.mock('../../api/crowdsec') vi.mock('../../api/settings') vi.mock('../../hooks/useSecurity', async (importOriginal) => { - const actual = await importOriginal() + const actual = await importOriginal() return { ...actual, useSecurityConfig: vi.fn(() => ({ data: { config: { admin_whitelist: '10.0.0.0/8' } } })), @@ -83,19 +86,19 @@ describe.skip('Security', () => { it('should show error if security status fails to load', async () => { vi.mocked(securityApi.getSecurityStatus).mockRejectedValue(new Error('Failed')) await renderSecurityPage() - await waitFor(() => expect(screen.getByText(/Failed to load security configuration/i)).toBeInTheDocument()) + expect(await screen.findByText(/Failed to load security configuration/i)).toBeInTheDocument() }) it('should render Cerberus Dashboard when status loads', async () => { vi.mocked(securityApi.getSecurityStatus).mockResolvedValue(mockSecurityStatus) await renderSecurityPage() - await waitFor(() => expect(screen.getByText(/Cerberus Dashboard/i)).toBeInTheDocument()) + expect(await screen.findByText(/Cerberus Dashboard/i)).toBeInTheDocument() }) it('should show banner when Cerberus is disabled', async () => { vi.mocked(securityApi.getSecurityStatus).mockResolvedValue({ ...mockSecurityStatus, cerberus: { enabled: false } }) await renderSecurityPage() - await waitFor(() => expect(screen.getByText(/Security Features Unavailable/i)).toBeInTheDocument()) + expect(await screen.findByText(/Security Features Unavailable/i)).toBeInTheDocument() }) }) @@ -293,7 +296,7 @@ describe.skip('Security', () => { const toggle = screen.getByTestId('toggle-waf') await user.click(toggle) - await waitFor(() => expect(screen.getByText(/Three heads turn/i)).toBeInTheDocument()) + expect(await screen.findByText(/Three heads turn/i)).toBeInTheDocument() }) it('should show overlay when starting CrowdSec', async () => { @@ -311,7 +314,7 @@ describe.skip('Security', () => { const toggle = screen.getByTestId('toggle-crowdsec') await user.click(toggle) - await waitFor(() => expect(screen.getByText(/Summoning the guardian/i)).toBeInTheDocument()) + expect(await screen.findByText(/Summoning the guardian/i)).toBeInTheDocument() }) it('should show overlay when stopping CrowdSec', async () => { @@ -326,7 +329,7 @@ describe.skip('Security', () => { const toggle = screen.getByTestId('toggle-crowdsec') await user.click(toggle) - await waitFor(() => expect(screen.getByText(/Guardian rests/i)).toBeInTheDocument()) + expect(await screen.findByText(/Guardian rests/i)).toBeInTheDocument() }) }) diff --git a/frontend/src/pages/__tests__/SecurityHeaders.test.tsx b/frontend/src/pages/__tests__/SecurityHeaders.test.tsx index 19184990..5a0ca82c 100644 --- a/frontend/src/pages/__tests__/SecurityHeaders.test.tsx +++ b/frontend/src/pages/__tests__/SecurityHeaders.test.tsx @@ -1,15 +1,16 @@ -import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { MemoryRouter } from 'react-router-dom'; import { describe, it, expect, vi, beforeEach } from 'vitest'; -import userEvent from '@testing-library/user-event'; -import SecurityHeaders from '../../pages/SecurityHeaders'; + +import { createBackup } from '../../api/backups'; import { securityHeadersApi, - SecurityHeaderProfile, + type SecurityHeaderProfile, type ScoreBreakdown, } from '../../api/securityHeaders'; -import { createBackup } from '../../api/backups'; +import SecurityHeaders from '../../pages/SecurityHeaders'; vi.mock('../../api/securityHeaders'); vi.mock('../../api/backups'); @@ -266,7 +267,7 @@ describe('SecurityHeaders', () => { vi.mocked(securityHeadersApi.listProfiles).mockResolvedValue(mockProfiles as SecurityHeaderProfile[]); vi.mocked(securityHeadersApi.getPresets).mockResolvedValue([]); vi.mocked(createBackup).mockResolvedValue({ filename: 'backup.tar.gz' }); - vi.mocked(securityHeadersApi.deleteProfile).mockResolvedValue(undefined); + vi.mocked(securityHeadersApi.deleteProfile).mockResolvedValue(); render(, { wrapper: createWrapper() }); diff --git a/frontend/src/pages/__tests__/Settings.test.tsx b/frontend/src/pages/__tests__/Settings.test.tsx index 86b6d176..766dfd0f 100644 --- a/frontend/src/pages/__tests__/Settings.test.tsx +++ b/frontend/src/pages/__tests__/Settings.test.tsx @@ -1,6 +1,7 @@ -import { describe, it, expect, vi } from 'vitest' import { render, screen } from '@testing-library/react' import { MemoryRouter, Routes, Route } from 'react-router-dom' +import { describe, it, expect, vi } from 'vitest' + import '@testing-library/jest-dom/vitest' import Settings from '../Settings' diff --git a/frontend/src/pages/__tests__/Setup.test.tsx b/frontend/src/pages/__tests__/Setup.test.tsx index 184700dc..68a6b925 100644 --- a/frontend/src/pages/__tests__/Setup.test.tsx +++ b/frontend/src/pages/__tests__/Setup.test.tsx @@ -1,10 +1,11 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { MemoryRouter } from 'react-router-dom'; import { vi, describe, it, expect, beforeEach } from 'vitest'; -import Setup from '../Setup'; + import * as setupApi from '../../api/setup'; +import Setup from '../Setup'; // Mock AuthContext so useAuth works in tests vi.mock('../../hooks/useAuth', () => ({ diff --git a/frontend/src/pages/__tests__/SystemSettings.test.tsx b/frontend/src/pages/__tests__/SystemSettings.test.tsx index d58eb3ec..99f6193f 100644 --- a/frontend/src/pages/__tests__/SystemSettings.test.tsx +++ b/frontend/src/pages/__tests__/SystemSettings.test.tsx @@ -1,13 +1,14 @@ +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { MemoryRouter } from 'react-router-dom' import { vi, describe, it, expect, beforeEach } from 'vitest' -import SystemSettings from '../SystemSettings' -import * as settingsApi from '../../api/settings' -import * as featureFlagsApi from '../../api/featureFlags' + import client from '../../api/client' +import * as featureFlagsApi from '../../api/featureFlags' +import * as settingsApi from '../../api/settings' import { LanguageProvider } from '../../context/LanguageContext' +import SystemSettings from '../SystemSettings' // Note: react-i18next mock is provided globally by src/test/setup.ts @@ -119,7 +120,7 @@ describe('SystemSettings', () => { }) it('saves SSL provider setting when save button is clicked', async () => { - vi.mocked(settingsApi.updateSetting).mockResolvedValue(undefined) + vi.mocked(settingsApi.updateSetting).mockResolvedValue() renderWithProviders() @@ -193,7 +194,7 @@ describe('SystemSettings', () => { }) it('saves all settings when save button is clicked', async () => { - vi.mocked(settingsApi.updateSetting).mockResolvedValue(undefined) + vi.mocked(settingsApi.updateSetting).mockResolvedValue() renderWithProviders() @@ -207,41 +208,41 @@ describe('SystemSettings', () => { await waitFor(() => { expect(settingsApi.updateSetting).toHaveBeenCalledTimes(6) - expect(settingsApi.updateSetting).toHaveBeenCalledWith( - 'caddy.admin_api', - expect.any(String), - 'caddy', - 'string' - ) - expect(settingsApi.updateSetting).toHaveBeenCalledWith( - 'caddy.ssl_provider', - expect.any(String), - 'caddy', - 'string' - ) - expect(settingsApi.updateSetting).toHaveBeenCalledWith( - 'caddy.keepalive_idle', - '', - 'caddy', - 'string' - ) - expect(settingsApi.updateSetting).toHaveBeenCalledWith( - 'caddy.keepalive_count', - '', - 'caddy', - 'string' - ) - expect(settingsApi.updateSetting).toHaveBeenCalledWith( + }) + expect(settingsApi.updateSetting).toHaveBeenCalledWith( 'ui.domain_link_behavior', expect.any(String), 'ui', 'string' ) - }) + expect(settingsApi.updateSetting).toHaveBeenCalledWith( + 'caddy.keepalive_count', + '', + 'caddy', + 'string' + ) + expect(settingsApi.updateSetting).toHaveBeenCalledWith( + 'caddy.keepalive_idle', + '', + 'caddy', + 'string' + ) + expect(settingsApi.updateSetting).toHaveBeenCalledWith( + 'caddy.ssl_provider', + expect.any(String), + 'caddy', + 'string' + ) + expect(settingsApi.updateSetting).toHaveBeenCalledWith( + 'caddy.admin_api', + expect.any(String), + 'caddy', + 'string' + ) }) it('saves keepalive settings when valid values are provided', async () => { - vi.mocked(settingsApi.updateSetting).mockResolvedValue(undefined) + vi.mocked(settingsApi.updateSetting).mockResolvedValue() renderWithProviders() @@ -267,13 +268,13 @@ describe('SystemSettings', () => { 'caddy', 'string' ) - expect(settingsApi.updateSetting).toHaveBeenCalledWith( + }) + expect(settingsApi.updateSetting).toHaveBeenCalledWith( 'caddy.keepalive_count', '3', 'caddy', 'string' ) - }) }) it('disables save when keepalive values are invalid', async () => { diff --git a/frontend/src/pages/__tests__/Uptime.spec.tsx b/frontend/src/pages/__tests__/Uptime.spec.tsx index 924fb785..0984a9b0 100644 --- a/frontend/src/pages/__tests__/Uptime.spec.tsx +++ b/frontend/src/pages/__tests__/Uptime.spec.tsx @@ -1,9 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor, within } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import Uptime from '../Uptime' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as uptimeApi from '../../api/uptime' +import Uptime from '../Uptime' vi.mock('react-hot-toast', () => ({ toast: { success: vi.fn(), error: vi.fn(), loading: vi.fn(), dismiss: vi.fn() } })) vi.mock('../../api/uptime') @@ -40,7 +41,7 @@ describe('Uptime page', () => { vi.mocked(uptimeApi.updateMonitor).mockResolvedValue({ ...monitor, enabled: false }) renderWithProviders() - await waitFor(() => expect(screen.getByText('Test Monitor')).toBeInTheDocument()) + expect(await screen.findByText('Test Monitor')).toBeInTheDocument() const card = screen.getByText('Test Monitor').closest('div') as HTMLElement const settingsBtn = within(card).getByTitle('Monitor settings') await userEvent.click(settingsBtn) @@ -58,7 +59,7 @@ describe('Uptime page', () => { vi.mocked(uptimeApi.getMonitorHistory).mockResolvedValue([]) renderWithProviders() - await waitFor(() => expect(screen.getByText('NoLastCheck')).toBeInTheDocument()) + expect(await screen.findByText('NoLastCheck')).toBeInTheDocument() const lastCheck = screen.getByText('Never') expect(lastCheck).toBeTruthy() }) @@ -72,7 +73,7 @@ describe('Uptime page', () => { vi.mocked(uptimeApi.getMonitorHistory).mockResolvedValue([]) renderWithProviders() - await waitFor(() => expect(screen.getByText('PausedMonitor')).toBeInTheDocument()) + expect(await screen.findByText('PausedMonitor')).toBeInTheDocument() expect(screen.getByText('PAUSED')).toBeTruthy() }) @@ -91,7 +92,7 @@ describe('Uptime page', () => { vi.mocked(uptimeApi.getMonitorHistory).mockResolvedValue(history) renderWithProviders() - await waitFor(() => expect(screen.getByText('WithHistory')).toBeInTheDocument()) + expect(await screen.findByText('WithHistory')).toBeInTheDocument() // Bar titles include 'Status:' and the status should be capitalized await waitFor(() => expect(document.querySelectorAll('[title*="Status:"]').length).toBeGreaterThanOrEqual(history.length)) @@ -109,7 +110,7 @@ describe('Uptime page', () => { vi.mocked(uptimeApi.getMonitorHistory).mockResolvedValue([]) renderWithProviders() - await waitFor(() => expect(screen.getByText('OrderTest')).toBeInTheDocument()) + expect(await screen.findByText('OrderTest')).toBeInTheDocument() const card = screen.getByText('OrderTest').closest('div') as HTMLElement await userEvent.click(within(card).getByTitle('Monitor settings')) @@ -138,11 +139,11 @@ describe('Uptime page', () => { } vi.mocked(uptimeApi.getMonitors).mockResolvedValue([monitor]) vi.mocked(uptimeApi.getMonitorHistory).mockResolvedValue([]) - vi.mocked(uptimeApi.deleteMonitor).mockResolvedValue(undefined) + vi.mocked(uptimeApi.deleteMonitor).mockResolvedValue() const confirmSpy = vi.spyOn(window, 'confirm').mockImplementation(() => true) renderWithProviders() - await waitFor(() => expect(screen.getByText('DeleteMe')).toBeInTheDocument()) + expect(await screen.findByText('DeleteMe')).toBeInTheDocument() const card = screen.getByText('DeleteMe').closest('div') as HTMLElement const settingsBtn = within(card).getByTitle('Monitor settings') await userEvent.click(settingsBtn) @@ -162,12 +163,12 @@ describe('Uptime page', () => { vi.mocked(uptimeApi.updateMonitor).mockResolvedValue({ ...monitor, max_retries: 6 }) renderWithProviders() - await waitFor(() => expect(screen.getByText('ConfigMe')).toBeInTheDocument()) + expect(await screen.findByText('ConfigMe')).toBeInTheDocument() const card = screen.getByText('ConfigMe').closest('div') as HTMLElement await userEvent.click(within(card).getByTitle('Monitor settings')) await userEvent.click(within(card).getByText('Configure')) // Modal should open - await waitFor(() => expect(screen.getByText('Configure Monitor')).toBeInTheDocument()) + expect(await screen.findByText('Configure Monitor')).toBeInTheDocument() const spinbuttons = screen.getAllByRole('spinbutton') const maxRetriesInput = spinbuttons.find(el => el.getAttribute('value') === '3') as HTMLInputElement await userEvent.clear(maxRetriesInput) @@ -185,11 +186,11 @@ describe('Uptime page', () => { } vi.mocked(uptimeApi.getMonitors).mockResolvedValue([monitor]) vi.mocked(uptimeApi.getMonitorHistory).mockResolvedValue([]) - vi.mocked(uptimeApi.deleteMonitor).mockResolvedValue(undefined) + vi.mocked(uptimeApi.deleteMonitor).mockResolvedValue() const confirmSpy = vi.spyOn(window, 'confirm').mockImplementation(() => false) renderWithProviders() - await waitFor(() => expect(screen.getByText('DoNotDelete')).toBeInTheDocument()) + expect(await screen.findByText('DoNotDelete')).toBeInTheDocument() const card = screen.getByText('DoNotDelete').closest('div') as HTMLElement await userEvent.click(within(card).getByTitle('Monitor settings')) await userEvent.click(within(card).getByText('Delete')) @@ -207,7 +208,7 @@ describe('Uptime page', () => { vi.mocked(uptimeApi.updateMonitor).mockRejectedValue(new Error('Update failed')) renderWithProviders() - await waitFor(() => expect(screen.getByText('ToggleFail')).toBeInTheDocument()) + expect(await screen.findByText('ToggleFail')).toBeInTheDocument() const card = screen.getByText('ToggleFail').closest('div') as HTMLElement await userEvent.click(within(card).getByTitle('Monitor settings')) await userEvent.click(within(card).getByText('Pause')) @@ -223,7 +224,7 @@ describe('Uptime page', () => { vi.mocked(uptimeApi.getMonitorHistory).mockResolvedValue([]) renderWithProviders() - await waitFor(() => expect(screen.getByText('Proxy Hosts')).toBeInTheDocument()) + expect(await screen.findByText('Proxy Hosts')).toBeInTheDocument() expect(screen.getByText('Remote Servers')).toBeInTheDocument() expect(screen.getByText('Other Monitors')).toBeInTheDocument() expect(screen.getByText('ProxyMon')).toBeInTheDocument() @@ -240,7 +241,7 @@ describe('Uptime page', () => { vi.mocked(uptimeApi.getMonitorHistory).mockResolvedValue([]) renderWithProviders() - await waitFor(() => expect(screen.getByText('PendingMonitor')).toBeInTheDocument()) + expect(await screen.findByText('PendingMonitor')).toBeInTheDocument() const badge = screen.getByTestId('status-badge') expect(badge).toHaveAttribute('data-status', 'pending') expect(badge).toHaveAttribute('role', 'status') @@ -262,7 +263,7 @@ describe('Uptime page', () => { vi.mocked(uptimeApi.getMonitorHistory).mockResolvedValue(history) renderWithProviders() - await waitFor(() => expect(screen.getByText('PendingWithHistory')).toBeInTheDocument()) + expect(await screen.findByText('PendingWithHistory')).toBeInTheDocument() await waitFor(() => { const badge = screen.getByTestId('status-badge') expect(badge.textContent).not.toContain('CHECKING...') @@ -279,7 +280,7 @@ describe('Uptime page', () => { vi.mocked(uptimeApi.getMonitorHistory).mockResolvedValue([]) renderWithProviders() - await waitFor(() => expect(screen.getByText('DownMonitor')).toBeInTheDocument()) + expect(await screen.findByText('DownMonitor')).toBeInTheDocument() const badge = screen.getByTestId('status-badge') expect(badge).toHaveAttribute('data-status', 'down') expect(badge.textContent).toContain('DOWN') diff --git a/frontend/src/pages/__tests__/Uptime.test.tsx b/frontend/src/pages/__tests__/Uptime.test.tsx index 96b0e93d..54f2ac7f 100644 --- a/frontend/src/pages/__tests__/Uptime.test.tsx +++ b/frontend/src/pages/__tests__/Uptime.test.tsx @@ -1,8 +1,10 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' import { screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import Uptime from '../Uptime' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' +import Uptime from '../Uptime' + import type { UptimeMonitor } from '../../api/uptime' // Mock react-i18next @@ -51,9 +53,9 @@ vi.mock('react-i18next', () => ({ } if (options && typeof options === 'object') { let result = translations[key] || key - Object.entries(options).forEach(([k, v]) => { + for (const [k, v] of Object.entries(options)) { result = result.replace(`{{${k}}}`, String(v)) - }) + } return result } return translations[key] || key @@ -149,7 +151,7 @@ describe('Uptime page', () => { vi.mocked(getMonitorHistory).mockResolvedValue([]) renderWithQueryClient() - await waitFor(() => expect(screen.getByText('UnknownStatusMonitor')).toBeInTheDocument()) + expect(await screen.findByText('UnknownStatusMonitor')).toBeInTheDocument() const badge = screen.getByTestId('status-badge') expect(badge).toHaveAttribute('data-status', 'down') diff --git a/frontend/src/pages/__tests__/UsersPage.test.tsx b/frontend/src/pages/__tests__/UsersPage.test.tsx index 045074ef..f6a32fb9 100644 --- a/frontend/src/pages/__tests__/UsersPage.test.tsx +++ b/frontend/src/pages/__tests__/UsersPage.test.tsx @@ -1,14 +1,15 @@ import { screen, waitFor, within, fireEvent } from '@testing-library/react' -import { act } from 'react' import userEvent from '@testing-library/user-event' +import { act } from 'react' import { vi, describe, it, expect, beforeEach } from 'vitest' -import UsersPage from '../UsersPage' -import * as usersApi from '../../api/users' -import * as proxyHostsApi from '../../api/proxyHosts' + import client from '../../api/client' +import * as proxyHostsApi from '../../api/proxyHosts' +import * as usersApi from '../../api/users' +import { useAuth } from '../../hooks/useAuth' import { renderWithQueryClient } from '../../test-utils/renderWithQueryClient' import { toast } from '../../utils/toast' -import { useAuth } from '../../hooks/useAuth' +import UsersPage from '../UsersPage' // Mock APIs vi.mock('../../api/users', () => ({ @@ -226,14 +227,13 @@ describe('UsersPage', () => { (sw) => !(sw as HTMLInputElement).disabled && (sw as HTMLInputElement).checked ) - if (userSwitch) { - const user = userEvent.setup() - await user.click(userSwitch) + expect(userSwitch).toBeDefined() + const user = userEvent.setup() + await user.click(userSwitch!) - await waitFor(() => { - expect(usersApi.updateUser).toHaveBeenCalledWith(2, { enabled: false }) - }) - } + await waitFor(() => { + expect(usersApi.updateUser).toHaveBeenCalledWith(2, { enabled: false }) + }) }) it('invites a new user', async () => { @@ -316,7 +316,7 @@ describe('UsersPage', () => { renderWithQueryClient() - await waitFor(() => expect(screen.getByText('Regular User')).toBeInTheDocument()) + expect(await screen.findByText('Regular User')).toBeInTheDocument() const editButtons = screen.getAllByTitle('Edit Permissions') const firstEditable = editButtons.find((btn) => !(btn as HTMLButtonElement).disabled) @@ -362,7 +362,7 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('Invite User')).toBeInTheDocument()) + expect(await screen.findByText('Invite User')).toBeInTheDocument() await user.click(screen.getByRole('button', { name: /Invite User/i })) await user.type(screen.getByPlaceholderText('user@example.com'), 'manual@example.com') await user.click(screen.getByRole('button', { name: /^Send Invite$/i })) @@ -399,7 +399,7 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('Invite User')).toBeTruthy()) + expect(await screen.findByText('Invite User')).toBeTruthy() await user.click(screen.getByRole('button', { name: /Invite User/i })) await waitFor(() => { @@ -414,7 +414,7 @@ describe('UsersPage', () => { renderWithQueryClient() - await waitFor(() => expect(screen.getByText('Regular User')).toBeTruthy()) + expect(await screen.findByText('Regular User')).toBeTruthy() const user = userEvent.setup() const editButtons = screen.getAllByTitle('Edit User') @@ -445,7 +445,7 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('Invite User')).toBeInTheDocument()) + expect(await screen.findByText('Invite User')).toBeInTheDocument() await user.click(screen.getByRole('button', { name: /Invite User/i })) const emailInput = screen.getByPlaceholderText('user@example.com') @@ -476,9 +476,9 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('Invite User')).toBeInTheDocument()) + expect(await screen.findByText('Invite User')).toBeInTheDocument() await user.click(screen.getByRole('button', { name: /Invite User/i })) - await waitFor(() => expect(screen.getByPlaceholderText('user@example.com')).toBeInTheDocument()) + expect(await screen.findByPlaceholderText('user@example.com')).toBeInTheDocument() vi.useFakeTimers() @@ -515,7 +515,7 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('Invite User')).toBeInTheDocument()) + expect(await screen.findByText('Invite User')).toBeInTheDocument() await user.click(screen.getByRole('button', { name: /Invite User/i })) const emailInput = screen.getByPlaceholderText('user@example.com') @@ -544,7 +544,7 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('Invite User')).toBeInTheDocument()) + expect(await screen.findByText('Invite User')).toBeInTheDocument() await user.click(screen.getByRole('button', { name: /Invite User/i })) const emailInput = screen.getByPlaceholderText('user@example.com') @@ -563,7 +563,7 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('Invite User')).toBeInTheDocument()) + expect(await screen.findByText('Invite User')).toBeInTheDocument() await user.click(screen.getByRole('button', { name: /Invite User/i })) const emailInput = screen.getByPlaceholderText('user@example.com') @@ -584,7 +584,7 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('Invite User')).toBeInTheDocument()) + expect(await screen.findByText('Invite User')).toBeInTheDocument() await user.click(screen.getByRole('button', { name: /Invite User/i })) const emailInput = screen.getByPlaceholderText('user@example.com') @@ -612,11 +612,11 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('Invite User')).toBeInTheDocument()) + expect(await screen.findByText('Invite User')).toBeInTheDocument() // Open invite modal await user.click(screen.getByRole('button', { name: /Invite User/i })) - await waitFor(() => expect(screen.getByLabelText(/Role/i)).toBeInTheDocument()) + expect(await screen.findByLabelText(/Role/i)).toBeInTheDocument() // Change role to passthrough await user.selectOptions(screen.getByLabelText(/Role/i), 'passthrough') @@ -627,7 +627,7 @@ describe('UsersPage', () => { // Reopen modal — role should be reset to 'user' await user.click(screen.getByRole('button', { name: /Invite User/i })) - await waitFor(() => expect(screen.getByLabelText(/Role/i)).toBeInTheDocument()) + expect(await screen.findByLabelText(/Role/i)).toBeInTheDocument() expect((screen.getByLabelText(/Role/i) as HTMLSelectElement).value).toBe('user') }) }) @@ -642,13 +642,13 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('Regular User')).toBeInTheDocument()) + expect(await screen.findByText('Regular User')).toBeInTheDocument() // Click Edit User for Regular User (second "Edit User" button in the table) const editButtons = screen.getAllByTitle('Edit User') await user.click(editButtons[1]) // index 1 = Regular User row - await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()) + expect(await screen.findByRole('dialog')).toBeInTheDocument() // Click Save await user.click(screen.getByRole('button', { name: /^Save$/i })) @@ -665,12 +665,12 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('My Profile')).toBeInTheDocument()) + expect(await screen.findByText('My Profile')).toBeInTheDocument() // Click Edit User in My Profile card (opens with isSelf=true) — card button is first await user.click(screen.getAllByRole('button', { name: /Edit User/i })[0]) - await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()) + expect(await screen.findByRole('dialog')).toBeInTheDocument() // Password fields should not be visible until toggled expect(screen.queryByLabelText(/Current Password/i)).toBeNull() @@ -691,14 +691,14 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('My Profile')).toBeInTheDocument()) + expect(await screen.findByText('My Profile')).toBeInTheDocument() await user.click(screen.getAllByRole('button', { name: /Edit User/i })[0]) - await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()) + expect(await screen.findByRole('dialog')).toBeInTheDocument() // Expand password section await user.click(screen.getAllByRole('button', { name: /Change Password/i })[0]) - await waitFor(() => expect(screen.getByLabelText(/Current Password/i)).toBeInTheDocument()) + expect(await screen.findByLabelText(/Current Password/i)).toBeInTheDocument() // Fill matching passwords await user.type(screen.getByLabelText(/Current Password/i), 'oldpass123') @@ -730,13 +730,13 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('My Profile')).toBeInTheDocument()) + expect(await screen.findByText('My Profile')).toBeInTheDocument() await user.click(screen.getAllByRole('button', { name: /Edit User/i })[0]) - await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()) + expect(await screen.findByRole('dialog')).toBeInTheDocument() await user.click(screen.getAllByRole('button', { name: /Change Password/i })[0]) - await waitFor(() => expect(screen.getByLabelText(/Current Password/i)).toBeInTheDocument()) + expect(await screen.findByLabelText(/Current Password/i)).toBeInTheDocument() await user.type(screen.getByLabelText(/Current Password/i), 'wrongpass') await user.type(screen.getByLabelText(/^New Password/i), 'newpass456') @@ -760,10 +760,10 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('My Profile')).toBeInTheDocument()) + expect(await screen.findByText('My Profile')).toBeInTheDocument() await user.click(screen.getAllByRole('button', { name: /Edit User/i })[0]) - await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()) + expect(await screen.findByRole('dialog')).toBeInTheDocument() await waitFor(() => { expect(screen.getByRole('button', { name: /Regenerate API Key/i })).toBeInTheDocument() @@ -784,10 +784,10 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('My Profile')).toBeInTheDocument()) + expect(await screen.findByText('My Profile')).toBeInTheDocument() await user.click(screen.getAllByRole('button', { name: /Edit User/i })[0]) - await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()) + expect(await screen.findByRole('dialog')).toBeInTheDocument() const dialog = screen.getByRole('dialog') await user.click(within(dialog).getByRole('button', { name: /^Save$/i })) @@ -805,11 +805,11 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('Regular User')).toBeInTheDocument()) + expect(await screen.findByText('Regular User')).toBeInTheDocument() const editButtons = screen.getAllByTitle('Edit User') await user.click(editButtons[1]) - await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()) + expect(await screen.findByRole('dialog')).toBeInTheDocument() const dialog = screen.getByRole('dialog') await user.click(within(dialog).getByRole('button', { name: /^Save$/i })) @@ -829,10 +829,10 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('My Profile')).toBeInTheDocument()) + expect(await screen.findByText('My Profile')).toBeInTheDocument() await user.click(screen.getAllByRole('button', { name: /Edit User/i })[0]) - await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()) + expect(await screen.findByRole('dialog')).toBeInTheDocument() await waitFor(() => { expect(screen.getByText('SK-****-masktest')).toBeInTheDocument() @@ -846,13 +846,13 @@ describe('UsersPage', () => { renderWithQueryClient() const user = userEvent.setup() - await waitFor(() => expect(screen.getByText('My Profile')).toBeInTheDocument()) + expect(await screen.findByText('My Profile')).toBeInTheDocument() await user.click(screen.getAllByRole('button', { name: /Edit User/i })[0]) - await waitFor(() => expect(screen.getByRole('dialog')).toBeInTheDocument()) + expect(await screen.findByRole('dialog')).toBeInTheDocument() await user.click(screen.getAllByRole('button', { name: /Change Password/i })[0]) - await waitFor(() => expect(screen.getByLabelText(/Current Password/i)).toBeInTheDocument()) + expect(await screen.findByLabelText(/Current Password/i)).toBeInTheDocument() await user.type(screen.getByLabelText(/Current Password/i), 'current123') await user.type(screen.getByLabelText(/^New Password/i), 'newpass1') diff --git a/frontend/src/pages/__tests__/WafConfig.spec.tsx b/frontend/src/pages/__tests__/WafConfig.spec.tsx index 6242952c..a56c2f99 100644 --- a/frontend/src/pages/__tests__/WafConfig.spec.tsx +++ b/frontend/src/pages/__tests__/WafConfig.spec.tsx @@ -1,10 +1,12 @@ -import { describe, it, expect, vi, beforeEach } from 'vitest' +import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { render, screen, waitFor } from '@testing-library/react' import userEvent from '@testing-library/user-event' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { BrowserRouter } from 'react-router-dom' -import WafConfig from '../WafConfig' +import { describe, it, expect, vi, beforeEach } from 'vitest' + import * as securityApi from '../../api/security' +import WafConfig from '../WafConfig' + import type { SecurityRuleSet, RuleSetsResponse } from '../../api/security' vi.mock('../../api/security') diff --git a/frontend/src/test-utils/renderWithQueryClient.tsx b/frontend/src/test-utils/renderWithQueryClient.tsx index 1394d402..6e598715 100644 --- a/frontend/src/test-utils/renderWithQueryClient.tsx +++ b/frontend/src/test-utils/renderWithQueryClient.tsx @@ -1,7 +1,7 @@ -import { QueryClient, QueryClientProvider, QueryClientConfig } from '@tanstack/react-query' -import { ReactNode } from 'react' -import { MemoryRouter, MemoryRouterProps } from 'react-router-dom' +import { QueryClient, QueryClientProvider, type QueryClientConfig } from '@tanstack/react-query' import { render } from '@testing-library/react' +import { type ReactNode } from 'react' +import { MemoryRouter, type MemoryRouterProps } from 'react-router-dom' const defaultConfig: QueryClientConfig = { defaultOptions: { diff --git a/frontend/src/test/createTestQueryClient.ts b/frontend/src/test/createTestQueryClient.ts index 5e19e1fd..e3cad59e 100644 --- a/frontend/src/test/createTestQueryClient.ts +++ b/frontend/src/test/createTestQueryClient.ts @@ -1,4 +1,4 @@ -import { QueryClient, QueryKey } from '@tanstack/react-query' +import { QueryClient, type QueryKey } from '@tanstack/react-query' interface InitialDataEntry { key: QueryKey @@ -13,6 +13,6 @@ export function createTestQueryClient(initialData: InitialDataEntry[] = []) { }, }) - initialData.forEach(({ key, data }) => client.setQueryData(key, data)) + for (const { key, data } of initialData) client.setQueryData(key, data) return client } diff --git a/frontend/src/test/mockData.ts b/frontend/src/test/mockData.ts index a31cb667..c12e4cb8 100644 --- a/frontend/src/test/mockData.ts +++ b/frontend/src/test/mockData.ts @@ -1,5 +1,5 @@ -import { ProxyHost } from '../hooks/useProxyHosts' -import { RemoteServer } from '../hooks/useRemoteServers' +import { type ProxyHost } from '../hooks/useProxyHosts' +import { type RemoteServer } from '../hooks/useRemoteServers' export const mockProxyHosts: ProxyHost[] = [ { diff --git a/frontend/src/test/setup.ts b/frontend/src/test/setup.ts index a126cb89..68eca292 100644 --- a/frontend/src/test/setup.ts +++ b/frontend/src/test/setup.ts @@ -41,9 +41,9 @@ vi.mock('react-i18next', async () => { let result = getTranslation(key) // Handle interpolation: replace {{variable}} with the value from options if (options && typeof result === 'string') { - Object.entries(options).forEach(([k, v]) => { + for (const [k, v] of Object.entries(options)) { result = result.replace(new RegExp(`\\{\\{${k}\\}\\}`, 'g'), String(v)) - }) + } } return result }, @@ -115,7 +115,7 @@ if (!anchorPrototype.__testNoNavClick) { HTMLAnchorElement.prototype.click = function() { const event = new MouseEvent('click', { bubbles: true, cancelable: true }) this.dispatchEvent(event) - return undefined + return } anchorPrototype.__originalClick = originalClick } @@ -125,16 +125,14 @@ const _origConsoleError = console.error console.error = (...args: unknown[]) => { try { const msg = args[0] - if (typeof msg === 'string') { - if ( + if (typeof msg === 'string' && ( msg.includes("The current testing environment is not configured to support act(") || msg.includes('not wrapped in act(') || msg.includes('Test connection failed') || msg.includes('Connection failed') - ) { + )) { return } - } } catch { // fallthrough to original } diff --git a/frontend/src/testUtils/createMockProxyHost.ts b/frontend/src/testUtils/createMockProxyHost.ts index 14df5ae9..0d803835 100644 --- a/frontend/src/testUtils/createMockProxyHost.ts +++ b/frontend/src/testUtils/createMockProxyHost.ts @@ -1,4 +1,4 @@ -import { ProxyHost } from '../api/proxyHosts' +import { type ProxyHost } from '../api/proxyHosts' export const createMockProxyHost = (overrides: Partial = {}): ProxyHost => ({ uuid: 'host-1', diff --git a/frontend/src/utils/__tests__/compareHosts.test.ts b/frontend/src/utils/__tests__/compareHosts.test.ts index 06f2cae7..c9d75ede 100644 --- a/frontend/src/utils/__tests__/compareHosts.test.ts +++ b/frontend/src/utils/__tests__/compareHosts.test.ts @@ -1,5 +1,7 @@ import { describe, it, expect } from 'vitest' + import compareHosts from '../compareHosts' + import type { ProxyHost } from '../../api/proxyHosts' const hostA: ProxyHost = { diff --git a/frontend/src/utils/__tests__/crowdsecExport.test.ts b/frontend/src/utils/__tests__/crowdsecExport.test.ts index 71831507..ddb4f70c 100644 --- a/frontend/src/utils/__tests__/crowdsecExport.test.ts +++ b/frontend/src/utils/__tests__/crowdsecExport.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest' + import { buildCrowdsecExportFilename, promptCrowdsecFilename, diff --git a/frontend/src/utils/__tests__/passwordStrength.test.ts b/frontend/src/utils/__tests__/passwordStrength.test.ts index 68c63f59..c6085a4a 100644 --- a/frontend/src/utils/__tests__/passwordStrength.test.ts +++ b/frontend/src/utils/__tests__/passwordStrength.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect } from 'vitest' + import { calculatePasswordStrength } from '../passwordStrength' describe('calculatePasswordStrength', () => { diff --git a/frontend/src/utils/__tests__/proxyHostsHelpers.test.ts b/frontend/src/utils/__tests__/proxyHostsHelpers.test.ts index 9f76b198..a36ca0d8 100644 --- a/frontend/src/utils/__tests__/proxyHostsHelpers.test.ts +++ b/frontend/src/utils/__tests__/proxyHostsHelpers.test.ts @@ -1,11 +1,13 @@ +import { vi } from 'vitest' + import { formatSettingLabel, settingHelpText, settingKeyToField, applyBulkSettingsToHosts, } from '../proxyHostsHelpers' + import type { ProxyHost } from '../../api/proxyHosts' -import { vi } from 'vitest' describe('proxyHostsHelpers', () => { describe('formatSettingLabel', () => { diff --git a/frontend/src/utils/__tests__/toast.test.ts b/frontend/src/utils/__tests__/toast.test.ts index 0dee4d67..c3513a4b 100644 --- a/frontend/src/utils/__tests__/toast.test.ts +++ b/frontend/src/utils/__tests__/toast.test.ts @@ -1,4 +1,5 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' + import { toast, toastCallbacks } from '../toast' describe('toast util', () => { diff --git a/frontend/src/utils/toast.ts b/frontend/src/utils/toast.ts index c8a1f53a..22526a1d 100644 --- a/frontend/src/utils/toast.ts +++ b/frontend/src/utils/toast.ts @@ -12,18 +12,18 @@ export const toastCallbacks = new Set<(toast: Toast) => void>() export const toast = { success: (message: string) => { const id = ++toastId - toastCallbacks.forEach(callback => callback({ id, message, type: 'success' })) + for (const callback of toastCallbacks) callback({ id, message, type: 'success' }) }, error: (message: string) => { const id = ++toastId - toastCallbacks.forEach(callback => callback({ id, message, type: 'error' })) + for (const callback of toastCallbacks) callback({ id, message, type: 'error' }) }, info: (message: string) => { const id = ++toastId - toastCallbacks.forEach(callback => callback({ id, message, type: 'info' })) + for (const callback of toastCallbacks) callback({ id, message, type: 'info' }) }, warning: (message: string) => { const id = ++toastId - toastCallbacks.forEach(callback => callback({ id, message, type: 'warning' })) + for (const callback of toastCallbacks) callback({ id, message, type: 'warning' }) }, } diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 497289d2..231f24e8 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' // https://vitejs.dev/config/ export default defineConfig({ diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index 5ac8abbd..831a8872 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' +import { defineConfig } from 'vitest/config' // Dynamic coverage threshold (align local and CI) const coverageThresholdValue =