feat(tests): add comprehensive tests for ProxyHosts and Uptime components

- Introduced isolated coverage tests for ProxyHosts with various scenarios including rendering, bulk apply, and link behavior.
- Enhanced existing ProxyHosts coverage tests to include additional assertions and error handling.
- Added tests for Uptime component to verify rendering and monitoring toggling functionality.
- Created utility functions for setting labels and help texts related to proxy host settings.
- Implemented bulk settings application logic with progress tracking and error handling.
- Added toast utility tests to ensure callback functionality and ID incrementing.
- Improved type safety in test files by using appropriate TypeScript types.
This commit is contained in:
GitHub Actions
2025-11-30 15:17:38 +00:00
parent d80f545a6e
commit 224a53975d
38 changed files with 1821 additions and 233 deletions

View File

@@ -0,0 +1,91 @@
import { render, screen, waitFor, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import ProxyHostForm from '../ProxyHostForm'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
vi.mock('../../api/uptime', () => ({
syncMonitors: vi.fn(() => Promise.resolve({})),
}))
// Minimal hook mocks used by the component
vi.mock('../../hooks/useRemoteServers', () => ({
useRemoteServers: vi.fn(() => ({
servers: [],
isLoading: false,
error: null,
createRemoteServer: vi.fn(),
updateRemoteServer: vi.fn(),
deleteRemoteServer: vi.fn(),
})),
}))
vi.mock('../../hooks/useDocker', () => ({
useDocker: vi.fn(() => ({ containers: [], isLoading: false, error: null, refetch: vi.fn() })),
}))
vi.mock('../../hooks/useDomains', () => ({
useDomains: vi.fn(() => ({ domains: [], createDomain: vi.fn().mockResolvedValue({}), isLoading: false, error: null })),
}))
vi.mock('../../hooks/useCertificates', () => ({
useCertificates: vi.fn(() => ({ certificates: [], isLoading: false, error: null })),
}))
// stub global fetch for health endpoint
vi.stubGlobal('fetch', vi.fn(() => Promise.resolve({ json: () => Promise.resolve({ internal_ip: '127.0.0.1' }) })))
describe('ProxyHostForm Add Uptime flow', () => {
it('submits host and requests uptime sync when Add Uptime is checked', async () => {
const onSubmit = vi.fn(() => Promise.resolve())
const onCancel = vi.fn()
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
render(
<QueryClientProvider client={queryClient}>
<ProxyHostForm onSubmit={onSubmit} onCancel={onCancel} />
</QueryClientProvider>
)
// Fill required fields
await userEvent.type(screen.getByPlaceholderText('My Service'), 'My Service')
await userEvent.type(screen.getByPlaceholderText('example.com, www.example.com'), 'example.com')
await userEvent.type(screen.getByLabelText(/^Host$/), '127.0.0.1')
await userEvent.clear(screen.getByLabelText(/^Port$/))
await userEvent.type(screen.getByLabelText(/^Port$/), '8080')
// Check Add Uptime
const addUptimeCheckbox = screen.getByLabelText(/Add Uptime monitoring for this host/i)
await userEvent.click(addUptimeCheckbox)
// Adjust uptime options — locate the container for the uptime inputs
const uptimeCheckbox = screen.getByLabelText(/Add Uptime monitoring for this host/i)
const uptimeContainer = uptimeCheckbox.closest('label')?.parentElement
if (!uptimeContainer) throw new Error('Uptime container not found')
const { within } = await import('@testing-library/react')
const spinbuttons = within(uptimeContainer).getAllByRole('spinbutton')
// first spinbutton is interval, second is max retries
fireEvent.change(spinbuttons[0], { target: { value: '30' } })
fireEvent.change(spinbuttons[1], { target: { value: '2' } })
// Submit
const submitBtn = document.querySelector('button[type="submit"]') as HTMLButtonElement
if (!submitBtn) throw new Error('Submit button not found')
await userEvent.click(submitBtn)
// wait for onSubmit to have been called
await waitFor(() => expect(onSubmit).toHaveBeenCalled())
// Ensure uptime API was called with provided options
const uptime = await import('../../api/uptime')
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 submittedPayload = onSubmitMock.mock.calls[0][0]
expect(submittedPayload).not.toHaveProperty('addUptime')
expect(submittedPayload).not.toHaveProperty('uptimeInterval')
expect(submittedPayload).not.toHaveProperty('uptimeMaxRetries')
})
})

View File

@@ -4,6 +4,7 @@ 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 { mockRemoteServers } from '../../test/mockData'
// Mock the hooks
@@ -445,7 +446,7 @@ describe('ProxyHostForm', () => {
})
it('auto-populates advanced_config when selecting plex preset and field empty', async () => {
renderWithClient(
await renderWithClientAct(
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
)
@@ -488,7 +489,7 @@ describe('ProxyHostForm', () => {
}
renderWithClient(
<ProxyHostForm host={existingHost as any} onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
<ProxyHostForm host={existingHost as ProxyHost} onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
)
// Select Plex preset (should prompt since advanced_config is non-empty)
@@ -531,7 +532,7 @@ describe('ProxyHostForm', () => {
}
renderWithClient(
<ProxyHostForm host={existingHost as any} onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
<ProxyHostForm host={existingHost as ProxyHost} onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
)
// The restore button should be visible
@@ -609,7 +610,7 @@ describe('ProxyHostForm', () => {
})
it('does not show config helper when preset is none', async () => {
renderWithClient(
await renderWithClientAct(
<ProxyHostForm onSubmit={mockOnSubmit} onCancel={mockOnCancel} />
)