chore: Refactor tests to use findBy queries for better async handling, update mock implementations, and clean up imports across various test files. Adjust toast utility to use for-of loops for callback execution. Update Vite and Vitest configuration files for consistency.

This commit is contained in:
GitHub Actions
2026-03-11 02:24:28 +00:00
parent c977c6f9a4
commit 3e32610ea1
286 changed files with 1632 additions and 1315 deletions

View File

@@ -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

View File

@@ -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', () => {

View File

@@ -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', () => ({

View File

@@ -1,4 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import client from '../../api/client'
import { getBackups, createBackup, restoreBackup, deleteBackup } from '../backups'

View File

@@ -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: {

View File

@@ -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<never>
@@ -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)

View File

@@ -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 () => {

View File

@@ -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')

View File

@@ -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')

View File

@@ -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')

View File

@@ -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')

View File

@@ -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: {

View File

@@ -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: {

View File

@@ -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')

View File

@@ -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: {

View File

@@ -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: {

View File

@@ -1,4 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { connectLiveLogs } from '../logs';
// Mock WebSocket

View File

@@ -1,4 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import client from '../client'
import { downloadLog, getLogContent, getLogs } from '../logs'

View File

@@ -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: {

View File

@@ -1,4 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import client from '../client'
import {
getProviders,

View File

@@ -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: {

View File

@@ -1,4 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import client from '../client';
import {
getPlugins,

View File

@@ -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')

View File

@@ -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();

View File

@@ -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', () => ({

View File

@@ -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: {

View File

@@ -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')

View File

@@ -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: {

View File

@@ -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')

View File

@@ -1,4 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import client from '../../api/client'
import { getSetupStatus, performSetup } from '../setup'

View File

@@ -1,4 +1,5 @@
import { describe, it, expect, vi, afterEach } from 'vitest'
import client from '../client'
import { checkUpdates, getNotifications, markNotificationRead, markAllNotificationsRead } from '../system'

View File

@@ -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')

View File

@@ -1,4 +1,5 @@
import { beforeEach, describe, expect, it, vi } from 'vitest'
import client from '../client'
import { getProfile, regenerateApiKey, updateProfile } from '../users'

View File

@@ -1,4 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import client from '../client'
import {
listUsers,

View File

@@ -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');

View File

@@ -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: {

View File

@@ -1,4 +1,5 @@
import client from './client'
import type { DNSProvider } from './dnsProviders'
/** DNS provider detection result */

View File

@@ -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 () => {

View File

@@ -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: {

View File

@@ -1,4 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import client from './client'
import {
getProviders,

View File

@@ -1,4 +1,5 @@
import { describe, it, expect, vi, beforeEach } from 'vitest'
import client from './client'
import {
listUsers,

View File

@@ -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;

View File

@@ -1,5 +1,6 @@
import { useAccessLists } from '../hooks/useAccessLists';
import { ExternalLink } from 'lucide-react';
import { useAccessLists } from '../hooks/useAccessLists';
import {
Select,
SelectContent,

View File

@@ -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 {

View File

@@ -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'

View File

@@ -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<string>()
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,

View File

@@ -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

View File

@@ -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'

View File

@@ -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'

View File

@@ -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 {

View File

@@ -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()

View File

@@ -1,5 +1,6 @@
import { useTranslation } from 'react-i18next'
import { Star } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import {
Select,
SelectContent,

View File

@@ -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<Record<string, string>>(() => {
const init: Record<string, string> = {}
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)

View File

@@ -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()
})
})

View File

@@ -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

View File

@@ -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' },

View File

@@ -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 ? (
<img src="/logo.png" alt="Charon" className="h-12 w-auto" />
) : (
<img src="/banner.png" alt="Charon" className="h-14 w-auto max-max-w-50 object-contain" />
<img src="/banner.png" alt="Charon" className="h-14 w-auto max-w-[200px] object-contain" />
)}
</div>

View File

@@ -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

View File

@@ -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 {

View File

@@ -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[];

View File

@@ -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 = () => {

View File

@@ -1,4 +1,5 @@
import React from 'react';
import { calculatePasswordStrength } from '../utils/passwordStrength';
interface Props {

View File

@@ -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);
};

View File

@@ -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' },

View File

@@ -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 {

View File

@@ -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();

View File

@@ -1,5 +1,6 @@
import React from 'react'
import { Navigate } from 'react-router-dom'
import { useAuth } from '../hooks/useAuth'
interface RequireRoleProps {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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

View File

@@ -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<Toast[]>([])
@@ -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'

View File

@@ -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({

View File

@@ -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;

View File

@@ -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 () => {

View File

@@ -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');

View File

@@ -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

View File

@@ -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', () => {

View File

@@ -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', () => ({

View File

@@ -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'

View File

@@ -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', () => ({

View File

@@ -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',

View File

@@ -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', () => ({

View File

@@ -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'

View File

@@ -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 = {

View File

@@ -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')

View File

@@ -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', () => {

View File

@@ -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())

View File

@@ -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', () => ({

View File

@@ -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()

View File

@@ -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(<LiveLogViewer />);
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(<LiveLogViewer mode="security" />);
// 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(<LiveLogViewer mode="security" />);
// 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(<LiveLogViewer mode="security" />);
// 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(<LiveLogViewer mode="security" />);
// 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(<LiveLogViewer mode="security" />);
// Wait for connection to establish
await waitFor(() => expect(screen.getByText('Connected')).toBeTruthy());
expect(await screen.findByText('Connected')).toBeTruthy();
if (mockOnSecurityMessage) {
mockOnSecurityMessage({

View File

@@ -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', () => {

View File

@@ -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,

View File

@@ -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')

View File

@@ -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', () => ({

View File

@@ -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', () => {

View File

@@ -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 = {

View File

@@ -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', () => ({

View File

@@ -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', () => ({

View File

@@ -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', () => ({

Some files were not shown because too many files have changed in this diff Show More