test: add comprehensive frontend tests for Public URL and invite preview features

- Add API tests for validatePublicURL, testPublicURL, previewInviteURL
- Add UI tests for Public URL validation states and test button
- Add invite URL preview display and debouncing tests
- Increase frontend coverage from 34.85% to 87.7%

Addresses Codecov coverage gaps in PR #450
Closes coverage requirements for beta release

Coverage: 87.7% (1174 tests passing)
This commit is contained in:
GitHub Actions
2025-12-23 16:32:19 +00:00
parent 30f5033268
commit 74b7c1f299
8 changed files with 1821 additions and 0 deletions
@@ -15,6 +15,8 @@ import { LanguageProvider } from '../../context/LanguageContext'
vi.mock('../../api/settings', () => ({
getSettings: vi.fn(),
updateSetting: vi.fn(),
validatePublicURL: vi.fn(),
testPublicURL: vi.fn(),
}))
vi.mock('../../api/featureFlags', () => ({
@@ -25,6 +27,7 @@ vi.mock('../../api/featureFlags', () => ({
vi.mock('../../api/client', () => ({
default: {
get: vi.fn(),
post: vi.fn(),
},
}))
@@ -427,4 +430,215 @@ describe('SystemSettings', () => {
})
})
})
describe('Application URL Card', () => {
it('renders public URL input field', async () => {
renderWithProviders(<SystemSettings />)
await waitFor(() => {
expect(screen.getByPlaceholderText('https://charon.example.com')).toBeTruthy()
})
})
it('shows green border and checkmark when URL is valid', async () => {
vi.mocked(client.get).mockResolvedValue({
data: { status: 'healthy', service: 'charon', version: '1.0.0' },
})
renderWithProviders(<SystemSettings />)
await waitFor(() => {
expect(screen.getByPlaceholderText('https://charon.example.com')).toBeTruthy()
})
const user = userEvent.setup()
const input = screen.getByPlaceholderText('https://charon.example.com')
// Mock validation response for valid URL
vi.mocked(client.post).mockResolvedValue({
data: { valid: true, normalized: 'https://example.com' },
})
await user.clear(input)
await user.type(input, 'https://example.com')
// Wait for debounced validation
await waitFor(() => {
expect(client.post).toHaveBeenCalledWith('/settings/validate-url', {
url: 'https://example.com',
})
}, { timeout: 1000 })
await waitFor(() => {
const checkIcon = document.querySelector('.text-green-500')
expect(checkIcon).toBeTruthy()
})
await waitFor(() => {
expect(input.className).toContain('border-green-500')
})
})
it('shows red border and X icon when URL is invalid', async () => {
vi.mocked(client.get).mockResolvedValue({
data: { status: 'healthy', service: 'charon', version: '1.0.0' },
})
renderWithProviders(<SystemSettings />)
await waitFor(() => {
expect(screen.getByPlaceholderText('https://charon.example.com')).toBeTruthy()
})
const user = userEvent.setup()
const input = screen.getByPlaceholderText('https://charon.example.com')
// Mock validation response for invalid URL
vi.mocked(client.post).mockResolvedValue({
data: { valid: false, error: 'Invalid URL format' },
})
await user.clear(input)
await user.type(input, 'invalid-url')
await waitFor(() => {
expect(client.post).toHaveBeenCalledWith('/settings/validate-url', {
url: 'invalid-url',
})
}, { timeout: 1000 })
await waitFor(() => {
const xIcon = document.querySelector('.text-red-500')
expect(xIcon).toBeTruthy()
})
await waitFor(() => {
expect(input.className).toContain('border-red-500')
})
})
it('shows invalid URL error message when validation fails', async () => {
vi.mocked(client.get).mockResolvedValue({
data: { status: 'healthy', service: 'charon', version: '1.0.0' },
})
renderWithProviders(<SystemSettings />)
await waitFor(() => {
expect(screen.getByPlaceholderText('https://charon.example.com')).toBeTruthy()
})
const user = userEvent.setup()
const input = screen.getByPlaceholderText('https://charon.example.com')
vi.mocked(client.post).mockResolvedValue({
data: { valid: false, error: 'Invalid URL format' },
})
await user.clear(input)
await user.type(input, 'bad-url')
// Wait for debounce and validation
await new Promise(resolve => setTimeout(resolve, 400))
await waitFor(() => {
// Check for red border class indicating invalid state
const inputElement = screen.getByPlaceholderText('https://charon.example.com')
expect(inputElement.className).toContain('border-red')
}, { timeout: 1000 })
})
it('clears validation state when URL is cleared', async () => {
vi.mocked(settingsApi.getSettings).mockResolvedValue({
'app.public_url': 'https://example.com',
})
vi.mocked(client.get).mockResolvedValue({
data: { status: 'healthy', service: 'charon', version: '1.0.0' },
})
renderWithProviders(<SystemSettings />)
await waitFor(() => {
const input = screen.getByPlaceholderText('https://charon.example.com') as HTMLInputElement
expect(input.value).toBe('https://example.com')
})
const user = userEvent.setup()
const input = screen.getByPlaceholderText('https://charon.example.com')
await user.clear(input)
await waitFor(() => {
expect(input.className).not.toContain('border-green-500')
expect(input.className).not.toContain('border-red-500')
})
})
it('renders test button and verifies functionality', async () => {
vi.mocked(settingsApi.getSettings).mockResolvedValue({
'app.public_url': 'https://example.com',
})
vi.mocked(settingsApi.testPublicURL).mockResolvedValue({
reachable: true,
latency: 42,
})
renderWithProviders(<SystemSettings />)
await waitFor(() => {
expect(screen.getByPlaceholderText('https://charon.example.com')).toBeTruthy()
})
// Find test button by looking for buttons with External Link icon
const buttons = screen.getAllByRole('button')
const testButton = buttons.find((btn) => btn.querySelector('.lucide-external-link'))
expect(testButton).toBeTruthy()
expect(testButton).not.toBeDisabled()
const user = userEvent.setup()
await user.click(testButton!)
await waitFor(() => {
expect(settingsApi.testPublicURL).toHaveBeenCalledWith('https://example.com')
})
})
it('disables test button when URL is empty', async () => {
vi.mocked(settingsApi.getSettings).mockResolvedValue({
'app.public_url': '',
})
renderWithProviders(<SystemSettings />)
await waitFor(() => {
const input = screen.getByPlaceholderText('https://charon.example.com') as HTMLInputElement
expect(input.value).toBe('')
})
const buttons = screen.getAllByRole('button')
const testButton = buttons.find((btn) => btn.querySelector('.lucide-external-link'))
expect(testButton).toBeDisabled()
})
it('handles validation API error gracefully', async () => {
renderWithProviders(<SystemSettings />)
await waitFor(() => {
expect(screen.getByPlaceholderText('https://charon.example.com')).toBeTruthy()
})
const user = userEvent.setup()
const input = screen.getByPlaceholderText('https://charon.example.com')
vi.mocked(client.post).mockRejectedValue(new Error('Network error'))
await user.clear(input)
await user.type(input, 'https://example.com')
await waitFor(() => {
const xIcon = document.querySelector('.text-red-500')
expect(xIcon).toBeTruthy()
}, { timeout: 1000 })
})
})
})