fix(frontend): E2E-Test-Suite vollständig grün machen

- Robuster gegen undefined API-Daten in Mail, ContactDetail, ContactsList, Settings, Sidebar
- E2E-Mocks korrigiert für Kontakt-Detail, Mail-Liste/Folders und Plugin-Toggle
- Auth-Store mit persist-Middleware für E2E-Login
- test-results/ in .gitignore aufgenommen

Playwright E2E: 34/34 passed
This commit is contained in:
Agent Zero
2026-08-07 22:03:11 +02:00
parent 8d2aa58665
commit fdabd2e74c
18 changed files with 368 additions and 401 deletions
+8 -6
View File
@@ -70,6 +70,7 @@ export function ContactsListPage() {
const [saveViewDialogOpen, setSaveViewDialogOpen] = useState(false);
const [selectedContactIds, setSelectedContactIds] = useState<Set<string>>(new Set());
const openWindow = useWindowStore((s) => s.openWindow);
const closeWindow = useWindowStore((s) => s.closeWindow);
// Bulk action mutations
const deleteContactMut = useDeleteUnifiedContact();
@@ -223,32 +224,32 @@ export function ContactsListPage() {
// Handle create
const handleCreate = useCallback(() => {
openWindow({
const windowId = openWindow({
title: t('contacts.create'),
type: 'contact-create',
component: ContactEditForm,
componentProps: {
onClose: () => {},
onClose: () => closeWindow(windowId),
onSaved: handleSaved,
},
});
}, [openWindow, t, handleSaved]);
}, [openWindow, closeWindow, t, handleSaved]);
// Handle edit
const handleEdit = useCallback(() => {
if (selectedContact) {
openWindow({
const windowId = openWindow({
title: t('contacts.edit'),
type: 'contact-edit',
component: ContactEditForm,
componentProps: {
contact: selectedContact,
onClose: () => {},
onClose: () => closeWindow(windowId),
onSaved: handleSaved,
},
});
}
}, [selectedContact, openWindow, t, handleSaved]);
}, [selectedContact, openWindow, closeWindow, t, handleSaved]);
// Handle delete (from detail)
const handleDeleted = useCallback(() => {
@@ -747,6 +748,7 @@ export function ContactsListPage() {
loading={loadingDetail}
onEdit={handleEdit}
onDeleted={handleDeleted}
dataTestId="contact-detail-mobile"
/>
</div>
)}
+19 -15
View File
@@ -99,10 +99,10 @@ export function MailPage() {
// Apply filter + sort to mails before rendering
const processedMails = useMemo(() => {
let result = mails;
if (mailFilterState.conditions.length > 0) {
if ((mailFilterState.conditions || []).length > 0) {
result = applyMailFilters(result, mailFilterState);
}
if (mailSortState.conditions.length > 0) {
if ((mailSortState.conditions || []).length > 0) {
result = applyMailSorting(result, mailSortState);
}
return result;
@@ -110,7 +110,7 @@ export function MailPage() {
// Apply grouping after filter+sort
const groupedMails = useMemo(() => {
if (mailGroupState.conditions.length === 0) return null;
if ((mailGroupState.conditions || []).length === 0) return null;
return applyMailGrouping(processedMails, mailGroupState);
}, [processedMails, mailGroupState]);
@@ -153,7 +153,7 @@ export function MailPage() {
// Load folders for ALL accounts
const loadAllFolders = useCallback(async () => {
if (accounts.length === 0) return;
if ((accounts?.length ?? 0) === 0) return;
setLoadingFolders(true);
try {
const allFolders: MailFolder[] = [];
@@ -175,7 +175,7 @@ export function MailPage() {
// Auto-select first folder when folders are loaded
useEffect(() => {
if (folders.length > 0 && !selectedFolderId) {
if ((folders?.length ?? 0) > 0 && !selectedFolderId) {
setSelectedFolderId(folders[0].id);
setSelectedAccountId(folders[0].account_id);
}
@@ -189,21 +189,25 @@ export function MailPage() {
try {
if (searchQuery.trim()) {
const result = await searchMails(searchQuery);
const resultMails = result.mails ?? [];
const resultTotal = result.total ?? 0;
// Only update if still on the same folder
if (selectedFolderIdRef.current === currentFolderId) {
setMails(result.mails);
setMailsTotal(result.total);
setMails(resultMails);
setMailsTotal(resultTotal);
}
} else {
// When grouping is active, load all mails at once (no pagination)
const isGrouping = mailGroupState.conditions.length > 0;
const isGrouping = (mailGroupState.conditions || []).length > 0;
const pageToLoad = isGrouping ? 1 : mailsPage;
const result = await fetchMails(currentFolderId, pageToLoad, sortBy, sortOrder, isGrouping ? 10000 : undefined);
const resultMails = result.mails ?? [];
const resultTotal = result.total ?? 0;
// Only update if still on the same folder
if (selectedFolderIdRef.current === currentFolderId) {
// Append for infinite scroll (page > 1), replace on page 1 or folder change or grouping
setMails(!isGrouping && mailsPage > 1 ? (prev) => [...prev, ...result.mails] : result.mails);
setMailsTotal(result.total);
setMails(!isGrouping && mailsPage > 1 ? (prev) => [...(prev ?? []), ...resultMails] : resultMails);
setMailsTotal(resultTotal);
}
}
} catch (err) {
@@ -851,7 +855,7 @@ export function MailPage() {
);
}
if (accounts.length === 0) {
if ((accounts?.length ?? 0) === 0) {
return (
<div className="p-6 max-w-7xl mx-auto" data-testid="mail-page">
<EmptyState
@@ -911,11 +915,11 @@ export function MailPage() {
onToggleSelect={handleToggleSelect}
onSelectAll={handleSelectAll}
onLoadMore={() => {
if (mails.length < mailsTotal) {
if ((mails?.length ?? 0) < mailsTotal) {
setMailsPage((p) => p + 1);
}
}}
hasMore={mailGroupState.conditions.length === 0 && mails.length < mailsTotal}
hasMore={(mailGroupState.conditions || []).length === 0 && (mails?.length ?? 0) < mailsTotal}
/>
</ResizablePanel>
@@ -982,11 +986,11 @@ export function MailPage() {
onToggleSelect={handleToggleSelect}
onSelectAll={handleSelectAll}
onLoadMore={() => {
if (mails.length < mailsTotal) {
if ((mails?.length ?? 0) < mailsTotal) {
setMailsPage((p) => p + 1);
}
}}
hasMore={mailGroupState.conditions.length === 0 && mails.length < mailsTotal}
hasMore={(mailGroupState.conditions || []).length === 0 && (mails?.length ?? 0) < mailsTotal}
/>
</div>
)}
+3 -2
View File
@@ -8,8 +8,9 @@ export function SettingsPage() {
const { t } = useTranslation();
const manifests = usePluginStore(s => s.manifests);
const pluginSettingsPages = useMemo(
() => manifests
.flatMap((m) => m.settings_pages)
() => (manifests || [])
.flatMap((m) => (Array.isArray(m.settings_pages) ? m.settings_pages : []))
.filter((p): p is NonNullable<typeof p> => !!p && !!p.path)
.sort((a, b) => a.order - b.order),
[manifests]
);
+3 -1
View File
@@ -180,7 +180,9 @@ export function SettingsPluginsPage() {
const [confirmUninstall, setConfirmUninstall] = useState<Plugin | null>(null);
const [confirmRemoveData, setConfirmRemoveData] = useState(false);
const plugins: Plugin[] = data ?? [];
const plugins: Plugin[] = Array.isArray(data)
? data
: (data as any)?.plugins ?? [];
const handleInstall = async (plugin: Plugin) => {
try {