Files
Charon/frontend/src/components/ui/__tests__/FileDropZone.test.tsx
GitHub Actions e1bc648dfc test: add certificate feature unit tests and null-safety fix
Add comprehensive unit tests for the certificate upload, export,
and detail management feature:

- CertificateExportDialog: 21 tests covering format selection,
  blob download, error handling, and password-protected exports
- CertificateUploadDialog: 23 tests covering file validation,
  format detection, drag-and-drop, and upload flow
- CertificateDetailDialog: 19 tests covering detail display,
  loading state, missing fields, and branch coverage
- CertificateChainViewer: 8 tests covering chain visualization
- CertificateValidationPreview: 16 tests covering validation display
- FileDropZone: 18 tests covering drag-and-drop interactions
- useCertificates hooks: 10 tests covering all React Query hooks
- certificates API: 7 new tests for previously uncovered endpoints

Fix null-safety issue in ProxyHosts where cert.domains could be
undefined, causing a runtime error on split().

Frontend patch coverage: 90.6%, overall lines: 89.09%
2026-04-13 04:02:31 +00:00

158 lines
5.4 KiB
TypeScript

import { render, screen, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { FileDropZone } from '../FileDropZone'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
i18n: { language: 'en', changeLanguage: vi.fn() },
}),
}))
const defaultProps = {
id: 'cert-file',
label: 'Certificate File',
file: null as File | null,
onFileChange: vi.fn(),
}
function createFile(name = 'test.pem', type = 'application/x-pem-file'): File {
return new File(['cert-content'], name, { type })
}
describe('FileDropZone', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('renders label and empty drop zone', () => {
render(<FileDropZone {...defaultProps} />)
expect(screen.getByText('Certificate File')).toBeTruthy()
expect(screen.getByText('certificates.dropFileHere')).toBeTruthy()
})
it('shows required asterisk when required', () => {
render(<FileDropZone {...defaultProps} required />)
expect(screen.getByText('*')).toBeTruthy()
})
it('displays file name when a file is provided', () => {
const file = createFile('my-cert.pem')
render(<FileDropZone {...defaultProps} file={file} />)
expect(screen.getByText('my-cert.pem')).toBeTruthy()
})
it('displays format badge when file is provided', () => {
const file = createFile('my-cert.pem')
render(<FileDropZone {...defaultProps} file={file} formatBadge="PEM" />)
expect(screen.getByText('PEM')).toBeTruthy()
})
it('triggers file input on click', async () => {
render(<FileDropZone {...defaultProps} />)
const dropZone = screen.getByRole('button')
await userEvent.click(dropZone)
// The hidden file input should exist
const input = document.getElementById('cert-file') as HTMLInputElement
expect(input).toBeTruthy()
expect(input.type).toBe('file')
})
it('calls onFileChange when a file is selected via input', () => {
render(<FileDropZone {...defaultProps} />)
const input = document.getElementById('cert-file') as HTMLInputElement
const file = createFile()
fireEvent.change(input, { target: { files: [file] } })
expect(defaultProps.onFileChange).toHaveBeenCalledWith(file)
})
it('calls onFileChange on drop', () => {
render(<FileDropZone {...defaultProps} />)
const dropZone = screen.getByRole('button')
const file = createFile()
fireEvent.dragOver(dropZone, { dataTransfer: { files: [file] } })
fireEvent.drop(dropZone, { dataTransfer: { files: [file] } })
expect(defaultProps.onFileChange).toHaveBeenCalledWith(file)
})
it('does not call onFileChange on drop when disabled', () => {
render(<FileDropZone {...defaultProps} disabled />)
const dropZone = screen.getByRole('button')
const file = createFile()
fireEvent.drop(dropZone, { dataTransfer: { files: [file] } })
expect(defaultProps.onFileChange).not.toHaveBeenCalled()
})
it('activates via keyboard Enter', async () => {
render(<FileDropZone {...defaultProps} />)
const dropZone = screen.getByRole('button')
fireEvent.keyDown(dropZone, { key: 'Enter' })
// Should not throw; input ref click would be called
})
it('activates via keyboard Space', async () => {
render(<FileDropZone {...defaultProps} />)
const dropZone = screen.getByRole('button')
fireEvent.keyDown(dropZone, { key: ' ' })
})
it('does not activate via keyboard when disabled', () => {
render(<FileDropZone {...defaultProps} disabled />)
const dropZone = screen.getByRole('button')
fireEvent.keyDown(dropZone, { key: 'Enter' })
// No crash, no file change
expect(defaultProps.onFileChange).not.toHaveBeenCalled()
})
it('sets aria-disabled when disabled', () => {
render(<FileDropZone {...defaultProps} disabled />)
const dropZone = screen.getByRole('button')
expect(dropZone.getAttribute('aria-disabled')).toBe('true')
})
it('has tabIndex=-1 when disabled', () => {
render(<FileDropZone {...defaultProps} disabled />)
const dropZone = screen.getByRole('button')
expect(dropZone.tabIndex).toBe(-1)
})
it('has tabIndex=0 when not disabled', () => {
render(<FileDropZone {...defaultProps} />)
const dropZone = screen.getByRole('button')
expect(dropZone.tabIndex).toBe(0)
})
it('has appropriate aria-label when file is selected', () => {
const file = createFile('cert.pem')
render(<FileDropZone {...defaultProps} file={file} />)
const dropZone = screen.getByRole('button')
expect(dropZone.getAttribute('aria-label')).toBe('Certificate File: cert.pem')
})
it('handles dragLeave event', () => {
render(<FileDropZone {...defaultProps} />)
const dropZone = screen.getByRole('button')
fireEvent.dragOver(dropZone, { dataTransfer: { files: [] } })
fireEvent.dragLeave(dropZone)
// No crash; drag state should reset
})
it('sets accept attribute on input', () => {
render(<FileDropZone {...defaultProps} accept=".pem,.crt" />)
const input = document.getElementById('cert-file') as HTMLInputElement
expect(input.getAttribute('accept')).toBe('.pem,.crt')
})
it('sets aria-required on input when required', () => {
render(<FileDropZone {...defaultProps} required />)
const input = document.getElementById('cert-file') as HTMLInputElement
expect(input.getAttribute('aria-required')).toBe('true')
})
})