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%
158 lines
5.4 KiB
TypeScript
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')
|
|
})
|
|
})
|