feat: add BulkDeleteCertificateDialog component for bulk certificate deletion

- Implemented BulkDeleteCertificateDialog with confirmation and listing of certificates to be deleted.
- Added translations for bulk delete functionality in English, German, Spanish, French, and Chinese.
- Created unit tests for BulkDeleteCertificateDialog to ensure proper rendering and functionality.
- Developed end-to-end tests for bulk certificate deletion, covering selection, confirmation, and cancellation scenarios.
This commit is contained in:
GitHub Actions
2026-03-23 00:04:30 +00:00
parent 5b8941554b
commit 69736503ac
11 changed files with 1532 additions and 408 deletions

View File

@@ -0,0 +1,129 @@
import { render, screen, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi } from 'vitest'
import BulkDeleteCertificateDialog from '../../dialogs/BulkDeleteCertificateDialog'
import type { Certificate } from '../../../api/certificates'
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string, opts?: Record<string, unknown>) => (opts ? JSON.stringify(opts) : key),
i18n: { language: 'en', changeLanguage: vi.fn() },
}),
}))
const makeCert = (overrides: Partial<Certificate>): Certificate => ({
id: 1,
name: 'Test Cert',
domain: 'test.example.com',
issuer: 'Custom CA',
expires_at: '2026-01-01T00:00:00Z',
status: 'valid',
provider: 'custom',
...overrides,
})
const certs: Certificate[] = [
makeCert({ id: 1, name: 'Cert One', domain: 'one.example.com' }),
makeCert({ id: 2, name: 'Cert Two', domain: 'two.example.com', provider: 'letsencrypt-staging', status: 'untrusted' }),
makeCert({ id: 3, name: 'Cert Three', domain: 'three.example.com', provider: 'letsencrypt', status: 'expired' }),
]
describe('BulkDeleteCertificateDialog', () => {
it('renders dialog with count in title when 3 certs supplied', () => {
render(
<BulkDeleteCertificateDialog
certificates={certs}
open={true}
onConfirm={vi.fn()}
onCancel={vi.fn()}
isDeleting={false}
/>
)
const dialog = screen.getByRole('dialog')
expect(within(dialog).getByRole('heading', { name: '{"count":3}' })).toBeInTheDocument()
})
it('lists each certificate name in the scrollable list', () => {
render(
<BulkDeleteCertificateDialog
certificates={certs}
open={true}
onConfirm={vi.fn()}
onCancel={vi.fn()}
isDeleting={false}
/>
)
expect(screen.getByText('Cert One')).toBeInTheDocument()
expect(screen.getByText('Cert Two')).toBeInTheDocument()
expect(screen.getByText('Cert Three')).toBeInTheDocument()
expect(screen.getByText('Custom')).toBeInTheDocument()
expect(screen.getByText('Staging')).toBeInTheDocument()
expect(screen.getByText('Expired LE')).toBeInTheDocument()
})
it('calls onConfirm when the Delete button is clicked', async () => {
const onConfirm = vi.fn()
const user = userEvent.setup()
render(
<BulkDeleteCertificateDialog
certificates={certs}
open={true}
onConfirm={onConfirm}
onCancel={vi.fn()}
isDeleting={false}
/>
)
const dialog = screen.getByRole('dialog')
await user.click(within(dialog).getByRole('button', { name: '{"count":3}' }))
expect(onConfirm).toHaveBeenCalled()
})
it('calls onCancel when the Cancel button is clicked', async () => {
const onCancel = vi.fn()
const user = userEvent.setup()
render(
<BulkDeleteCertificateDialog
certificates={certs}
open={true}
onConfirm={vi.fn()}
onCancel={onCancel}
isDeleting={false}
/>
)
const dialog = screen.getByRole('dialog')
await user.click(within(dialog).getByRole('button', { name: 'common.cancel' }))
expect(onCancel).toHaveBeenCalled()
})
it('Delete button is loading/disabled when isDeleting is true', () => {
render(
<BulkDeleteCertificateDialog
certificates={certs}
open={true}
onConfirm={vi.fn()}
onCancel={vi.fn()}
isDeleting={true}
/>
)
const dialog = screen.getByRole('dialog')
const deleteBtn = within(dialog).getByRole('button', { name: '{"count":3}' })
expect(deleteBtn).toBeDisabled()
const cancelBtn = within(dialog).getByRole('button', { name: 'common.cancel' })
expect(cancelBtn).toBeDisabled()
})
it('returns null when certificates array is empty', () => {
const { container } = render(
<BulkDeleteCertificateDialog
certificates={[]}
open={true}
onConfirm={vi.fn()}
onCancel={vi.fn()}
isDeleting={false}
/>
)
expect(container.innerHTML).toBe('')
})
})