chore: remove cached
This commit is contained in:
@@ -1,118 +0,0 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
||||
import ImportReviewTable from '../ImportReviewTable'
|
||||
import { mockImportPreview } from '../../test/mockData'
|
||||
|
||||
describe('ImportReviewTable', () => {
|
||||
const mockOnCommit = vi.fn(() => Promise.resolve())
|
||||
const mockOnCancel = vi.fn()
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('displays hosts to import', () => {
|
||||
render(
|
||||
<ImportReviewTable
|
||||
hosts={mockImportPreview.hosts}
|
||||
conflicts={[]}
|
||||
errors={[]}
|
||||
onCommit={mockOnCommit}
|
||||
onCancel={mockOnCancel}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Review Imported Hosts')).toBeInTheDocument()
|
||||
expect(screen.getByText('test.example.com')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('displays conflicts with resolution dropdowns', () => {
|
||||
const conflicts = ['test.example.com']
|
||||
render(
|
||||
<ImportReviewTable
|
||||
hosts={mockImportPreview.hosts}
|
||||
conflicts={conflicts}
|
||||
errors={[]}
|
||||
onCommit={mockOnCommit}
|
||||
onCancel={mockOnCancel}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('test.example.com')).toBeInTheDocument()
|
||||
expect(screen.getByRole('combobox')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('displays errors', () => {
|
||||
const errors = ['Invalid Caddyfile syntax', 'Missing required field']
|
||||
|
||||
render(
|
||||
<ImportReviewTable
|
||||
hosts={mockImportPreview.hosts}
|
||||
conflicts={[]}
|
||||
errors={errors}
|
||||
onCommit={mockOnCommit}
|
||||
onCancel={mockOnCancel}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Issues found during parsing')).toBeInTheDocument()
|
||||
expect(screen.getByText('Invalid Caddyfile syntax')).toBeInTheDocument()
|
||||
expect(screen.getByText('Missing required field')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('calls onCommit with resolutions', async () => {
|
||||
const conflicts = ['test.example.com']
|
||||
render(
|
||||
<ImportReviewTable
|
||||
hosts={mockImportPreview.hosts}
|
||||
conflicts={conflicts}
|
||||
errors={[]}
|
||||
onCommit={mockOnCommit}
|
||||
onCancel={mockOnCancel}
|
||||
/>
|
||||
)
|
||||
|
||||
const dropdown = screen.getByRole('combobox')
|
||||
fireEvent.change(dropdown, { target: { value: 'overwrite' } })
|
||||
|
||||
const commitButton = screen.getByText('Commit Import')
|
||||
fireEvent.click(commitButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnCommit).toHaveBeenCalledWith({
|
||||
'test.example.com': 'overwrite',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('calls onCancel when cancel button is clicked', () => {
|
||||
render(
|
||||
<ImportReviewTable
|
||||
hosts={mockImportPreview.hosts}
|
||||
conflicts={[]}
|
||||
errors={[]}
|
||||
onCommit={mockOnCommit}
|
||||
onCancel={mockOnCancel}
|
||||
/>
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('Back'))
|
||||
expect(mockOnCancel).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('shows conflict indicator on conflicting hosts', () => {
|
||||
const conflicts = ['test.example.com']
|
||||
render(
|
||||
<ImportReviewTable
|
||||
hosts={mockImportPreview.hosts}
|
||||
conflicts={conflicts}
|
||||
errors={[]}
|
||||
onCommit={mockOnCommit}
|
||||
onCancel={mockOnCancel}
|
||||
/>
|
||||
)
|
||||
|
||||
expect(screen.getByRole('combobox')).toBeInTheDocument()
|
||||
expect(screen.queryByText('No conflict')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1,129 +0,0 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { describe, it, expect, vi } from 'vitest'
|
||||
import { render, screen, fireEvent } from '@testing-library/react'
|
||||
import { BrowserRouter } from 'react-router-dom'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import Layout from '../Layout'
|
||||
import { ThemeProvider } from '../../context/ThemeContext'
|
||||
|
||||
const mockLogout = vi.fn()
|
||||
|
||||
// Mock AuthContext
|
||||
vi.mock('../../hooks/useAuth', () => ({
|
||||
useAuth: () => ({
|
||||
logout: mockLogout,
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock API
|
||||
vi.mock('../../api/health', () => ({
|
||||
checkHealth: vi.fn().mockResolvedValue({
|
||||
version: '0.1.0',
|
||||
git_commit: 'abcdef1',
|
||||
}),
|
||||
}))
|
||||
|
||||
const renderWithProviders = (children: ReactNode) => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<BrowserRouter>
|
||||
<ThemeProvider>
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
</BrowserRouter>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
describe('Layout', () => {
|
||||
it('renders the application title', () => {
|
||||
renderWithProviders(
|
||||
<Layout>
|
||||
<div>Test Content</div>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
expect(screen.getAllByText('CPM+')[0]).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders all navigation items', () => {
|
||||
renderWithProviders(
|
||||
<Layout>
|
||||
<div>Test Content</div>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
expect(screen.getByText('Dashboard')).toBeInTheDocument()
|
||||
expect(screen.getByText('Proxy Hosts')).toBeInTheDocument()
|
||||
expect(screen.getByText('Remote Servers')).toBeInTheDocument()
|
||||
expect(screen.getByText('Certificates')).toBeInTheDocument()
|
||||
expect(screen.getByText('Import Caddyfile')).toBeInTheDocument()
|
||||
expect(screen.getByText('Settings')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders children content', () => {
|
||||
renderWithProviders(
|
||||
<Layout>
|
||||
<div data-testid="test-content">Test Content</div>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('test-content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('displays version information', async () => {
|
||||
renderWithProviders(
|
||||
<Layout>
|
||||
<div>Test Content</div>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
expect(await screen.findByText('Version 0.1.0')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('calls logout when logout button is clicked', () => {
|
||||
renderWithProviders(
|
||||
<Layout>
|
||||
<div>Test Content</div>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
const logoutButton = screen.getByText('Logout')
|
||||
fireEvent.click(logoutButton)
|
||||
|
||||
expect(mockLogout).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('toggles sidebar on mobile', () => {
|
||||
renderWithProviders(
|
||||
<Layout>
|
||||
<div>Test Content</div>
|
||||
</Layout>
|
||||
)
|
||||
|
||||
// Initially sidebar is hidden on mobile (by CSS class, but we can check if the toggle button exists)
|
||||
// The toggle button has text '☰' when closed
|
||||
const toggleButton = screen.getByText('☰')
|
||||
fireEvent.click(toggleButton)
|
||||
|
||||
// Now it should show '✕'
|
||||
expect(screen.getByText('✕')).toBeInTheDocument()
|
||||
|
||||
// And the overlay should be present
|
||||
// The overlay has class 'fixed inset-0 bg-black/50 z-20 lg:hidden'
|
||||
// We can find it by class or just assume if we click it it closes
|
||||
// Let's try to click the overlay. It doesn't have text.
|
||||
// We can query by selector if we add a test id or just rely on structure.
|
||||
// But let's just click the toggle button again to close.
|
||||
fireEvent.click(screen.getByText('✕'))
|
||||
expect(screen.getByText('☰')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
@@ -1,171 +0,0 @@
|
||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import NotificationCenter from '../NotificationCenter'
|
||||
import * as api from '../../api/system'
|
||||
|
||||
// Mock the API
|
||||
vi.mock('../../api/system', () => ({
|
||||
getNotifications: vi.fn(),
|
||||
markNotificationRead: vi.fn(),
|
||||
markAllNotificationsRead: vi.fn(),
|
||||
checkUpdates: vi.fn(),
|
||||
}))
|
||||
|
||||
const createWrapper = () => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
return ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const mockNotifications: api.Notification[] = [
|
||||
{
|
||||
id: '1',
|
||||
type: 'info',
|
||||
title: 'Info Notification',
|
||||
message: 'This is an info message',
|
||||
read: false,
|
||||
created_at: '2025-01-01T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
type: 'success',
|
||||
title: 'Success Notification',
|
||||
message: 'This is a success message',
|
||||
read: false,
|
||||
created_at: '2025-01-01T11:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
type: 'warning',
|
||||
title: 'Warning Notification',
|
||||
message: 'This is a warning message',
|
||||
read: false,
|
||||
created_at: '2025-01-01T12:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
type: 'error',
|
||||
title: 'Error Notification',
|
||||
message: 'This is an error message',
|
||||
read: false,
|
||||
created_at: '2025-01-01T13:00:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
describe('NotificationCenter', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(api.checkUpdates).mockResolvedValue({
|
||||
available: false,
|
||||
latest_version: '0.0.0',
|
||||
changelog_url: '',
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('renders bell icon and unread count', async () => {
|
||||
vi.mocked(api.getNotifications).mockResolvedValue(mockNotifications)
|
||||
render(<NotificationCenter />, { wrapper: createWrapper() })
|
||||
|
||||
expect(screen.getByRole('button', { name: /notifications/i })).toBeInTheDocument()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('4')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('opens notification panel on click', async () => {
|
||||
vi.mocked(api.getNotifications).mockResolvedValue(mockNotifications)
|
||||
render(<NotificationCenter />, { wrapper: createWrapper() })
|
||||
|
||||
const bellButton = screen.getByRole('button', { name: /notifications/i })
|
||||
fireEvent.click(bellButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Notifications')).toBeInTheDocument()
|
||||
expect(screen.getByText('Info Notification')).toBeInTheDocument()
|
||||
expect(screen.getByText('Success Notification')).toBeInTheDocument()
|
||||
expect(screen.getByText('Warning Notification')).toBeInTheDocument()
|
||||
expect(screen.getByText('Error Notification')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('displays empty state when no notifications', async () => {
|
||||
vi.mocked(api.getNotifications).mockResolvedValue([])
|
||||
render(<NotificationCenter />, { wrapper: createWrapper() })
|
||||
|
||||
const bellButton = screen.getByRole('button', { name: /notifications/i })
|
||||
fireEvent.click(bellButton)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('No new notifications')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('marks single notification as read', async () => {
|
||||
vi.mocked(api.getNotifications).mockResolvedValue(mockNotifications)
|
||||
vi.mocked(api.markNotificationRead).mockResolvedValue()
|
||||
|
||||
render(<NotificationCenter />, { wrapper: createWrapper() })
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /notifications/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Info Notification')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
const closeButtons = screen.getAllByRole('button', { name: /close/i })
|
||||
fireEvent.click(closeButtons[0])
|
||||
|
||||
await waitFor(() => {
|
||||
expect(api.markNotificationRead).toHaveBeenCalledWith('1', expect.anything())
|
||||
})
|
||||
})
|
||||
|
||||
it('marks all notifications as read', async () => {
|
||||
vi.mocked(api.getNotifications).mockResolvedValue(mockNotifications)
|
||||
vi.mocked(api.markAllNotificationsRead).mockResolvedValue()
|
||||
|
||||
render(<NotificationCenter />, { wrapper: createWrapper() })
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /notifications/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Mark all read')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByText('Mark all read'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(api.markAllNotificationsRead).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
it('closes panel when clicking outside', async () => {
|
||||
vi.mocked(api.getNotifications).mockResolvedValue(mockNotifications)
|
||||
render(<NotificationCenter />, { wrapper: createWrapper() })
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /notifications/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Notifications')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.click(screen.getByTestId('notification-backdrop'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('Notifications')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,227 +0,0 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
||||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
||||
import ProxyHostForm from '../ProxyHostForm'
|
||||
import { mockRemoteServers } from '../../test/mockData'
|
||||
|
||||
// Mock the hooks
|
||||
vi.mock('../../hooks/useRemoteServers', () => ({
|
||||
useRemoteServers: vi.fn(() => ({
|
||||
servers: mockRemoteServers,
|
||||
isLoading: false,
|
||||
error: null,
|
||||
createRemoteServer: vi.fn(),
|
||||
updateRemoteServer: vi.fn(),
|
||||
deleteRemoteServer: vi.fn(),
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('../../hooks/useDocker', () => ({
|
||||
useDocker: vi.fn(() => ({
|
||||
containers: [
|
||||
{
|
||||
id: 'container-123',
|
||||
names: ['my-app'],
|
||||
image: 'nginx:latest',
|
||||
state: 'running',
|
||||
status: 'Up 2 hours',
|
||||
network: 'bridge',
|
||||
ip: '172.17.0.2',
|
||||
ports: [{ private_port: 80, public_port: 8080, type: 'tcp' }]
|
||||
}
|
||||
],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
refetch: vi.fn(),
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('../../hooks/useDomains', () => ({
|
||||
useDomains: vi.fn(() => ({
|
||||
domains: [
|
||||
{ uuid: 'domain-1', name: 'existing.com' }
|
||||
],
|
||||
createDomain: vi.fn().mockResolvedValue({}),
|
||||
isLoading: false,
|
||||
error: null,
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('../../hooks/useCertificates', () => ({
|
||||
useCertificates: vi.fn(() => ({
|
||||
certificates: [
|
||||
{ id: 1, name: 'Cert 1', domain: 'example.com', provider: 'custom' }
|
||||
],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
})),
|
||||
}))
|
||||
|
||||
vi.mock('../../api/proxyHosts', () => ({
|
||||
testProxyHostConnection: vi.fn(),
|
||||
}))
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
const renderWithClient = (ui: React.ReactElement) => {
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{ui}
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
import { testProxyHostConnection } from '../../api/proxyHosts'
|
||||
|
||||
describe('ProxyHostForm', () => {
|
||||
const mockOnSubmit = vi.fn((_data: any) => Promise.resolve())
|
||||
const mockOnCancel = vi.fn()
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('handles scheme selection', async () => {
|
||||
renderWithClient(
|
||||
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Add Proxy Host')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Find scheme select - it defaults to HTTP
|
||||
// We can find it by label "Scheme"
|
||||
const schemeSelect = screen.getByLabelText('Scheme')
|
||||
fireEvent.change(schemeSelect, { target: { value: 'https' } })
|
||||
|
||||
expect(schemeSelect).toHaveValue('https')
|
||||
})
|
||||
|
||||
it('prompts to save new base domain', async () => {
|
||||
renderWithClient(
|
||||
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
const domainInput = screen.getByPlaceholderText('example.com, www.example.com')
|
||||
|
||||
// Enter a subdomain of a new base domain
|
||||
fireEvent.change(domainInput, { target: { value: 'sub.newdomain.com' } })
|
||||
fireEvent.blur(domainInput)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('New Base Domain Detected')).toBeInTheDocument()
|
||||
expect(screen.getByText('newdomain.com')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Click "Yes, save it"
|
||||
fireEvent.click(screen.getByText('Yes, save it'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('New Base Domain Detected')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('respects "Dont ask me again" for new domains', async () => {
|
||||
renderWithClient(
|
||||
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
const domainInput = screen.getByPlaceholderText('example.com, www.example.com')
|
||||
|
||||
// Trigger prompt
|
||||
fireEvent.change(domainInput, { target: { value: 'sub.another.com' } })
|
||||
fireEvent.blur(domainInput)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('New Base Domain Detected')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Check "Don't ask me again"
|
||||
fireEvent.click(screen.getByLabelText("Don't ask me again"))
|
||||
|
||||
// Click "No, thanks"
|
||||
fireEvent.click(screen.getByText('No, thanks'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByText('New Base Domain Detected')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
// Try another new domain - should not prompt
|
||||
fireEvent.change(domainInput, { target: { value: 'sub.yetanother.com' } })
|
||||
fireEvent.blur(domainInput)
|
||||
|
||||
// Should not see prompt
|
||||
expect(screen.queryByText('New Base Domain Detected')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('tests connection successfully', async () => {
|
||||
(testProxyHostConnection as any).mockResolvedValue({})
|
||||
|
||||
renderWithClient(
|
||||
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
// Fill required fields for test connection
|
||||
fireEvent.change(screen.getByLabelText(/^Host$/), { target: { value: '10.0.0.5' } })
|
||||
fireEvent.change(screen.getByLabelText(/^Port$/), { target: { value: '80' } })
|
||||
|
||||
const testBtn = screen.getByTitle('Test connection to the forward host')
|
||||
fireEvent.click(testBtn)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(testProxyHostConnection).toHaveBeenCalledWith('10.0.0.5', 80)
|
||||
})
|
||||
})
|
||||
|
||||
it('handles connection test failure', async () => {
|
||||
(testProxyHostConnection as any).mockRejectedValue(new Error('Connection failed'))
|
||||
|
||||
renderWithClient(
|
||||
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/^Host$/), { target: { value: '10.0.0.5' } })
|
||||
fireEvent.change(screen.getByLabelText(/^Port$/), { target: { value: '80' } })
|
||||
|
||||
const testBtn = screen.getByTitle('Test connection to the forward host')
|
||||
fireEvent.click(testBtn)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(testProxyHostConnection).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// Should show error state (red button) - we can check class or icon
|
||||
// The button changes class to bg-red-600
|
||||
await waitFor(() => {
|
||||
expect(testBtn).toHaveClass('bg-red-600')
|
||||
})
|
||||
})
|
||||
|
||||
it('handles base domain selection', async () => {
|
||||
renderWithClient(
|
||||
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByLabelText('Base Domain (Auto-fill)')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
fireEvent.change(screen.getByLabelText('Base Domain (Auto-fill)'), { target: { value: 'existing.com' } })
|
||||
|
||||
// Should not update domain names yet as no container selected
|
||||
expect(screen.getByLabelText(/Domain Names/i)).toHaveValue('')
|
||||
|
||||
// Select container then base domain
|
||||
fireEvent.change(screen.getByLabelText('Containers'), { target: { value: 'container-123' } })
|
||||
fireEvent.change(screen.getByLabelText('Base Domain (Auto-fill)'), { target: { value: 'existing.com' } })
|
||||
|
||||
expect(screen.getByLabelText(/Domain Names/i)).toHaveValue('my-app.existing.com')
|
||||
})
|
||||
})
|
||||
@@ -1,194 +0,0 @@
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest'
|
||||
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
|
||||
import RemoteServerForm from '../RemoteServerForm'
|
||||
import * as remoteServersApi from '../../api/remoteServers'
|
||||
|
||||
// Mock the API
|
||||
vi.mock('../../api/remoteServers', () => ({
|
||||
testRemoteServerConnection: vi.fn(() => Promise.resolve({ address: 'localhost:8080' })),
|
||||
testCustomRemoteServerConnection: vi.fn(() => Promise.resolve({ address: 'localhost:8080', reachable: true })),
|
||||
}))
|
||||
|
||||
describe('RemoteServerForm', () => {
|
||||
const mockOnSubmit = vi.fn(() => Promise.resolve())
|
||||
const mockOnCancel = vi.fn()
|
||||
|
||||
afterEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
it('renders create form', () => {
|
||||
render(
|
||||
<RemoteServerForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
expect(screen.getByText('Add Remote Server')).toBeInTheDocument()
|
||||
expect(screen.getByPlaceholderText('My Production Server')).toHaveValue('')
|
||||
})
|
||||
|
||||
it('renders edit form with pre-filled data', () => {
|
||||
const mockServer = {
|
||||
uuid: '123',
|
||||
name: 'Test Server',
|
||||
provider: 'docker',
|
||||
host: 'localhost',
|
||||
port: 5000,
|
||||
username: 'admin',
|
||||
enabled: true,
|
||||
reachable: true,
|
||||
created_at: '2025-11-18T10:00:00Z',
|
||||
updated_at: '2025-11-18T10:00:00Z',
|
||||
}
|
||||
|
||||
render(
|
||||
<RemoteServerForm server={mockServer} onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
expect(screen.getByText('Edit Remote Server')).toBeInTheDocument()
|
||||
expect(screen.getByDisplayValue('Test Server')).toBeInTheDocument()
|
||||
expect(screen.getByDisplayValue('localhost')).toBeInTheDocument()
|
||||
expect(screen.getByDisplayValue('5000')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('shows test connection button in create and edit mode', () => {
|
||||
const { rerender } = render(
|
||||
<RemoteServerForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
expect(screen.getByText('Test Connection')).toBeInTheDocument()
|
||||
|
||||
const mockServer = {
|
||||
uuid: '123',
|
||||
name: 'Test Server',
|
||||
provider: 'docker',
|
||||
host: 'localhost',
|
||||
port: 5000,
|
||||
enabled: true,
|
||||
reachable: false,
|
||||
created_at: '2025-11-18T10:00:00Z',
|
||||
updated_at: '2025-11-18T10:00:00Z',
|
||||
}
|
||||
|
||||
rerender(
|
||||
<RemoteServerForm server={mockServer} onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
expect(screen.getByText('Test Connection')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('calls onCancel when cancel button is clicked', () => {
|
||||
render(
|
||||
<RemoteServerForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('Cancel'))
|
||||
expect(mockOnCancel).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('submits form with correct data', async () => {
|
||||
render(
|
||||
<RemoteServerForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
const nameInput = screen.getByPlaceholderText('My Production Server')
|
||||
const hostInput = screen.getByPlaceholderText('192.168.1.100')
|
||||
const portInput = screen.getByDisplayValue('22')
|
||||
|
||||
fireEvent.change(nameInput, { target: { value: 'New Server' } })
|
||||
fireEvent.change(hostInput, { target: { value: '10.0.0.5' } })
|
||||
fireEvent.change(portInput, { target: { value: '9090' } })
|
||||
|
||||
fireEvent.click(screen.getByText('Create'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockOnSubmit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
name: 'New Server',
|
||||
host: '10.0.0.5',
|
||||
port: 9090,
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('handles provider selection', () => {
|
||||
render(
|
||||
<RemoteServerForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
const providerSelect = screen.getByDisplayValue('Generic')
|
||||
fireEvent.change(providerSelect, { target: { value: 'docker' } })
|
||||
|
||||
expect(providerSelect).toHaveValue('docker')
|
||||
})
|
||||
|
||||
it('handles submission error', async () => {
|
||||
const mockErrorSubmit = vi.fn(() => Promise.reject(new Error('Submission failed')))
|
||||
render(
|
||||
<RemoteServerForm onSubmit={mockErrorSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
// Fill required fields
|
||||
fireEvent.change(screen.getByPlaceholderText('My Production Server'), { target: { value: 'Test Server' } })
|
||||
fireEvent.change(screen.getByPlaceholderText('192.168.1.100'), { target: { value: '10.0.0.1' } })
|
||||
|
||||
fireEvent.click(screen.getByText('Create'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Submission failed')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('handles test connection success', async () => {
|
||||
const mockServer = {
|
||||
uuid: '123',
|
||||
name: 'Test Server',
|
||||
provider: 'docker',
|
||||
host: 'localhost',
|
||||
port: 5000,
|
||||
enabled: true,
|
||||
reachable: true,
|
||||
created_at: '2025-11-18T10:00:00Z',
|
||||
updated_at: '2025-11-18T10:00:00Z',
|
||||
}
|
||||
|
||||
render(
|
||||
<RemoteServerForm server={mockServer} onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
const testButton = screen.getByText('Test Connection')
|
||||
fireEvent.click(testButton)
|
||||
|
||||
await waitFor(() => {
|
||||
// Check for success state (green background)
|
||||
expect(testButton).toHaveClass('bg-green-600')
|
||||
})
|
||||
})
|
||||
|
||||
it('handles test connection failure', async () => {
|
||||
// Override mock for this test
|
||||
vi.mocked(remoteServersApi.testCustomRemoteServerConnection).mockRejectedValueOnce(new Error('Connection failed'))
|
||||
|
||||
const mockServer = {
|
||||
uuid: '123',
|
||||
name: 'Test Server',
|
||||
provider: 'docker',
|
||||
host: 'localhost',
|
||||
port: 5000,
|
||||
enabled: true,
|
||||
reachable: true,
|
||||
created_at: '2025-11-18T10:00:00Z',
|
||||
updated_at: '2025-11-18T10:00:00Z',
|
||||
}
|
||||
|
||||
render(
|
||||
<RemoteServerForm server={mockServer} onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByText('Test Connection'))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Connection failed')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
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 * as systemApi from '../../api/system'
|
||||
|
||||
// Mock the API module
|
||||
vi.mock('../../api/system', () => ({
|
||||
checkUpdates: vi.fn(),
|
||||
}))
|
||||
|
||||
const renderWithClient = (ui: React.ReactElement) => {
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
return render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{ui}
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
||||
|
||||
describe('SystemStatus', () => {
|
||||
it('calls checkUpdates on mount', async () => {
|
||||
vi.mocked(systemApi.checkUpdates).mockResolvedValue({
|
||||
available: false,
|
||||
latest_version: '1.0.0',
|
||||
changelog_url: '',
|
||||
})
|
||||
|
||||
renderWithClient(<SystemStatus />)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(systemApi.checkUpdates).toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user