feat(#359): W4a Phase 2 — zentraler Import/Export-Dialog (Frontend)

- ImportExportDialog.tsx (neu): Modal lg/xl nach bestehendem ui/Modal-Muster
- Export-Tab: Formatauswahl (csv/xlsx/json), Download, Fehler-Handling
- Import-Tab: 4 Schritte (Datei -> Mapping -> Dry-Run -> Ausführung+Report),
  Mapping-Vorschau mit Modul-Heuristik, Background-Job-Polling ab 1000 Zeilen
- i18n: 24 importexport.*-Keys in de.json + en.json (keine hardcoded Strings)
- Integration: ContactsList Toolbar-Button (contacts:read-Gate, Upload-Icon,
  entityType=contacts vorgewählt) über bestehendes pluginToolbarStore-Muster

Gates: Vitest 12/12 (routePermissions + importExportDialog), tsc exit 0,
Production-Build exit 0 (vor Commit). 6 Failures in contacts/shell Suiten
als Vorbestand bewiesen (Stash-Test: identisch auf clean HEAD f27f047).

fixes #359 (Phase 2)
This commit is contained in:
Agent Zero
2026-08-27 21:34:13 +02:00
parent cd8ef7500c
commit 38df597f11
5 changed files with 558 additions and 1 deletions
@@ -0,0 +1,70 @@
import { describe, expect, it } from 'vitest';
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
/**
* W4a Phase 2 (Spec #359): Central Import/Export dialog.
*
* Source-inspection based regression test — proves the dialog exists,
* is wired to the contacts toolbar, and covers the Spec flows
* (export 1 step, import 4 steps, background job polling).
* (Render-level tests of the AppShell have a known worker-hang issue,
* see PROGRESS.md — source inspection is the stable proof here.)
*/
const dialogSource = readFileSync(
join(__dirname, '..', '..', 'components', 'importexport', 'ImportExportDialog.tsx'),
'utf-8',
);
const contactsSource = readFileSync(
join(__dirname, '..', '..', 'pages', 'ContactsList.tsx'),
'utf-8',
);
describe('ImportExportDialog (W4a Phase 2)', () => {
it('dialog exists with Export and Import tabs', () => {
expect(dialogSource).toContain("'export'");
expect(dialogSource).toContain("'import'");
expect(dialogSource).toContain("data-testid=\"ie-tab-export\"");
expect(dialogSource).toContain("data-testid=\"ie-tab-import\"");
});
it('export tab offers csv, xlsx and json formats', () => {
expect(dialogSource).toContain("id: 'csv'");
expect(dialogSource).toContain("id: 'xlsx'");
expect(dialogSource).toContain("id: 'json'");
expect(dialogSource).toContain("data-testid=\"ie-export-download\"");
});
it('import flow covers all 4 spec steps', () => {
// Step 1: file
expect(dialogSource).toContain("data-testid=\"ie-file-input\"");
// Step 2: mapping
expect(dialogSource).toContain("data-testid=\"ie-import-validate\"");
expect(dialogSource).toContain('mapping_suggestion');
// Step 3: dry-run
expect(dialogSource).toContain("data-testid=\"ie-dry-valid\"");
expect(dialogSource).toContain("data-testid=\"ie-dry-invalid\"");
// Step 4: execution + report
expect(dialogSource).toContain("data-testid=\"ie-import-execute\"");
expect(dialogSource).toContain("data-testid=\"ie-import-result\"");
});
it('background job polling is implemented', () => {
expect(dialogSource).toContain('getImportJobStatus');
expect(dialogSource).toContain('setInterval');
});
it('contacts page opens the dialog via toolbar button', () => {
expect(contactsSource).toContain('ImportExportDialog');
expect(contactsSource).toContain("id: 'import-export'");
expect(contactsSource).toContain('setImportExportOpen(true)');
// Entity is pre-selected
expect(contactsSource).toContain('entityType="contacts"');
});
it('dialog uses the shared ui/Modal component', () => {
expect(dialogSource).toContain("from '@/components/ui/Modal'");
});
});