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
+3 -3
View File
@@ -110,12 +110,12 @@ export function MailDetail({
</div>
<div className="flex flex-col sm:flex-row sm:gap-2">
<span className="font-medium text-secondary-500 flex-shrink-0">{t('mail.to')}:</span>
<span className="break-words">{mail.to_addresses.join(', ')}</span>
<span className="break-words">{(mail.to_addresses ?? []).join(', ')}</span>
</div>
{mail.cc_addresses.length > 0 && (
{mail.cc_addresses && mail.cc_addresses.length > 0 && (
<div className="flex flex-col sm:flex-row sm:gap-2">
<span className="font-medium text-secondary-500 flex-shrink-0">{t('mail.cc')}:</span>
<span className="break-words">{mail.cc_addresses.join(', ')}</span>
<span className="break-words">{(mail.cc_addresses ?? []).join(', ')}</span>
</div>
)}
<div className="flex flex-col sm:flex-row sm:gap-2">
@@ -151,7 +151,7 @@ function matchesCondition(mail: any, cond: FilterCondition): boolean {
}
export function applyFilters(mails: any[], filters: FilterState): any[] {
if (!filters.conditions.length) return mails;
if (!(filters.conditions?.length ?? 0)) return mails;
if (filters.logic === 'AND') {
return mails.filter((m) => filters.conditions.every((cond) => matchesCondition(m, cond)));
} else {
@@ -208,7 +208,7 @@ export function MailFilterPanel({ filters, onFiltersChange, savedFilters = [], o
setOpen(!open);
};
const activeCount = filters.conditions.length;
const activeCount = filters.conditions?.length ?? 0;
const addCondition = () => {
onFiltersChange({
@@ -248,7 +248,7 @@ export function MailFilterPanel({ filters, onFiltersChange, savedFilters = [], o
const handleSaveFilter = () => {
if (!onSaveFilter) return;
if (filters.conditions.length === 0) return;
if ((filters.conditions?.length ?? 0) === 0) return;
const name = window.prompt('Name für diesen Filter:', 'Mein Filter');
if (!name) return;
onSaveFilter(name, { ...filters, conditions: filters.conditions.map((c) => ({ ...c })) });
@@ -345,7 +345,7 @@ export function MailFilterPanel({ filters, onFiltersChange, savedFilters = [], o
)}
{/* Saved filters */}
{savedFilters.length > 0 && (
{(savedFilters?.length ?? 0) > 0 && (
<div className="px-4 py-2 border-b border-secondary-100">
<div className="text-[10px] font-semibold uppercase tracking-wide text-secondary-400 mb-1.5">Gespeicherte Filter</div>
<div className="space-y-0.5">
@@ -366,7 +366,7 @@ export function MailFilterPanel({ filters, onFiltersChange, savedFilters = [], o
{/* Filter rows */}
<div className="px-4 py-3 space-y-2">
{filters.conditions.length === 0 && (
{(filters.conditions?.length ?? 0) === 0 && (
<div className="text-center py-6 text-xs text-secondary-400">
Keine Filter aktiv. Klicke unten um eine Bedingung hinzuzufügen.
</div>
@@ -301,7 +301,7 @@ export function MailFolderTree({ accounts, folders, selectedFolderId, onSelect,
);
}
if (accounts.length === 0) {
if ((accounts?.length ?? 0) === 0) {
return (
<EmptyState
title={t('mail.noFolders')}
@@ -73,7 +73,7 @@ export interface GroupedMails {
}
export function applyGrouping(mails: any[], groupState: GroupState): GroupedMails[] {
if (!groupState.conditions.length) return [{ key: 'all', label: 'Alle', mails }];
if (!(groupState.conditions?.length ?? 0)) return [{ key: 'all', label: 'Alle', mails }];
const defs = GROUP_FIELDS;
function groupRecursive(items: any[], conditions: GroupCondition[], depth: number): GroupedMails[] {
@@ -139,7 +139,7 @@ export function MailGroupPanel({ groupState, onGroupChange }: MailGroupPanelProp
setOpen(!open);
};
const activeCount = groupState.conditions.length;
const activeCount = groupState.conditions?.length ?? 0;
const addCondition = () => {
onGroupChange({ conditions: [...groupState.conditions, { id: newGroupId(), field: 'from' }] });
+7 -7
View File
@@ -52,8 +52,8 @@ function GroupSection({
const hasSubGroups = group.subGroups && group.subGroups.length > 0;
// Count total mails including subGroups
const totalCount = hasSubGroups
? group.subGroups!.reduce((sum, sub) => sum + sub.mails.length, 0)
: group.mails.length;
? group.subGroups!.reduce((sum, sub) => sum + (sub.mails?.length ?? 0), 0)
: (group.mails?.length ?? 0);
return (
<li className="bg-secondary-50/30">
@@ -110,7 +110,7 @@ export function MailList({
hasMore = false,
}: MailListProps) {
const { t } = useTranslation();
const allSelected = mails.length > 0 && mails.every((m) => selectedMailIds.has(m.id));
const allSelected = (mails?.length ?? 0) > 0 && mails.every((m) => selectedMailIds.has(m.id));
const scrollRef = useRef<HTMLUListElement>(null);
// Infinite scroll handler
@@ -121,7 +121,7 @@ export function MailList({
}
};
if (loading && mails.length === 0) {
if (loading && (mails?.length ?? 0) === 0) {
return (
<div className="flex items-center justify-center py-12" data-testid="mail-list-loading">
<Loader2 className="animate-spin h-5 w-5 text-secondary-400" aria-hidden="true" />
@@ -130,7 +130,7 @@ export function MailList({
);
}
if (mails.length === 0) {
if ((mails?.length ?? 0) === 0) {
return (
<EmptyState
title={t('mail.noMails')}
@@ -228,7 +228,7 @@ export function MailList({
</span>
)}
</div>
{loading && mails.length > 0 && <Loader2 className="w-4 h-4 animate-spin text-secondary-400" />}
{loading && (mails?.length ?? 0) > 0 && <Loader2 className="w-4 h-4 animate-spin text-secondary-400" />}
</div>
{/* Mail list — infinite scroll with optional grouping */}
@@ -254,7 +254,7 @@ export function MailList({
) : (
mails.map((mail) => renderMailItem(mail))
)}
{loading && mails.length > 0 && (
{loading && (mails?.length ?? 0) > 0 && (
<li className="flex items-center justify-center py-4">
<Loader2 className="w-5 h-5 animate-spin text-secondary-400" />
</li>
@@ -60,7 +60,7 @@ function compareValues(a: any, b: any, fieldType: FieldType): number {
}
export function applySorting(mails: any[], sortState: SortState): any[] {
if (!sortState.conditions.length) return mails;
if (!(sortState.conditions?.length ?? 0)) return mails;
const sorted = [...mails];
sorted.sort((a, b) => {
for (const cond of sortState.conditions) {
@@ -111,7 +111,7 @@ export function MailSortPanel({ sortState, onSortChange }: MailSortPanelProps) {
setOpen(!open);
};
const activeCount = sortState.conditions.length;
const activeCount = sortState.conditions?.length ?? 0;
const addCondition = () => {
onSortChange({