feat(09-06): MergeTab multi-file UI + PFX convert option + render tests
- actions.ts: mergeCertsAction(files, outputFormat, password?) builds FormData with
multiple file fields; delegates to postForm('merge', ...) (T-09-02/T-09-04)
- MergeTab.tsx: multi-file state (local), file input (multiple), output selector
(pem|pfx), Zusammenfuehren button disabled when < 2 files (data-testid for tests),
onOutputFormatChange callback to page.tsx for shared PasswordField visibility
- ConvertTab.tsx: gains pfx option + onTargetFormatChange callback (same pattern)
- page.tsx: lifts mergeOutputFormat + convertOutputFormat state; showPassword now
also true when active tab's output format is 'pfx'; passes callbacks to tabs
- cert-manager.test.tsx: 5 new tests — MergeTab disabled/enabled by file count,
shared PasswordField appears on pfx output, downloadBase64 called on success;
ConvertTab pfx option present; all 19/19 web tests green
- All production cert-manager files type-clean (pre-existing test type issues unchanged)
This commit is contained in:
@@ -44,6 +44,7 @@ vi.mock('next-intl', () => ({
|
||||
import CertManagerPage from './page';
|
||||
import { InspectTab } from './components/InspectTab';
|
||||
import { SplitTab } from './components/SplitTab';
|
||||
import { MergeTab } from './components/MergeTab';
|
||||
import { ConvertTab } from './components/ConvertTab';
|
||||
import * as actions from './actions';
|
||||
|
||||
@@ -336,4 +337,94 @@ describe('ConvertTab', () => {
|
||||
);
|
||||
expect(errorEl.className).toContain('text-destructive');
|
||||
});
|
||||
|
||||
it('renders pem, der, p7b, and pfx options in the format selector', () => {
|
||||
render(<ConvertTab file={null} pemText="" password="" />);
|
||||
const select = screen.getByRole('combobox');
|
||||
const optionValues = Array.from(select.querySelectorAll('option')).map(
|
||||
(o) => (o as HTMLOptionElement).value,
|
||||
);
|
||||
expect(optionValues).toContain('pem');
|
||||
expect(optionValues).toContain('der');
|
||||
expect(optionValues).toContain('p7b');
|
||||
expect(optionValues).toContain('pfx');
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// MergeTab — unit tests (CERT-03, CERT-05)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
const MOCK_MERGE_PEM_RESPONSE: actions.FileResponse = {
|
||||
filename: 'chain.pem',
|
||||
content: btoa('-----BEGIN CERTIFICATE-----\nXXX\n-----END CERTIFICATE-----\n-----BEGIN CERTIFICATE-----\nYYY\n-----END CERTIFICATE-----'),
|
||||
mimeType: 'application/x-pem-file',
|
||||
};
|
||||
|
||||
describe('MergeTab', () => {
|
||||
it('disables Zusammenfuehren action button when fewer than 2 files are selected', () => {
|
||||
render(<MergeTab password="" />);
|
||||
|
||||
// The action button should be disabled (no files yet)
|
||||
const actionBtn = screen.getByTestId('merge-action-btn');
|
||||
expect(actionBtn).toBeDisabled();
|
||||
});
|
||||
|
||||
it('enables Zusammenfuehren button when 2 or more files are selected', async () => {
|
||||
render(<MergeTab password="" />);
|
||||
|
||||
const fileInput = screen.getByTestId('merge-file-input');
|
||||
const file1 = new File(['cert1'], 'cert1.pem', { type: 'application/x-pem-file' });
|
||||
const file2 = new File(['cert2'], 'cert2.pem', { type: 'application/x-pem-file' });
|
||||
|
||||
fireEvent.change(fileInput, { target: { files: [file1, file2] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('merge-action-btn')).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
it('shows password field in page.tsx when PFX output is selected in MergeTab', async () => {
|
||||
// Render the full page so the shared PasswordField behaviour can be tested
|
||||
render(<CertManagerPage />);
|
||||
|
||||
// Navigate to merge tab — only one "Zusammenfuehren" visible before tab switch
|
||||
fireEvent.click(screen.getByText('Zusammenfuehren'));
|
||||
|
||||
// Change format selector to pfx
|
||||
const formatSelect = screen.getByDisplayValue('PEM-Kette');
|
||||
fireEvent.change(formatSelect, { target: { value: 'pfx' } });
|
||||
|
||||
// The shared PasswordField should now be visible
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Passwort (PFX/P12)')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('calls downloadBase64 after successful merge when >= 2 files selected', async () => {
|
||||
vi.spyOn(actions, 'mergeCertsAction').mockResolvedValue(MOCK_MERGE_PEM_RESPONSE);
|
||||
const downloadSpy = vi.spyOn(actions, 'downloadBase64').mockImplementation(() => {});
|
||||
|
||||
render(<MergeTab password="" />);
|
||||
|
||||
const fileInput = screen.getByTestId('merge-file-input');
|
||||
const file1 = new File(['cert1'], 'cert1.pem', { type: 'application/x-pem-file' });
|
||||
const file2 = new File(['cert2'], 'cert2.pem', { type: 'application/x-pem-file' });
|
||||
|
||||
fireEvent.change(fileInput, { target: { files: [file1, file2] } });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('merge-action-btn')).not.toBeDisabled();
|
||||
});
|
||||
|
||||
fireEvent.click(screen.getByTestId('merge-action-btn'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(downloadSpy).toHaveBeenCalledWith(
|
||||
MOCK_MERGE_PEM_RESPONSE.filename,
|
||||
MOCK_MERGE_PEM_RESPONSE.content,
|
||||
MOCK_MERGE_PEM_RESPONSE.mimeType,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user