diff --git a/components/layout/__tests__/sidebar-unified-section.test.tsx b/components/layout/__tests__/sidebar-unified-section.test.tsx new file mode 100644 index 000000000..1501f126b --- /dev/null +++ b/components/layout/__tests__/sidebar-unified-section.test.tsx @@ -0,0 +1,72 @@ +import { render, screen } from '@testing-library/react'; +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import type { Mailbox } from '@/lib/jmap/types'; + +// #843: the "Unified Mailbox" header used to appear as soon as two accounts +// were connected, but mail-app only populates the section when the admin gate +// and the user's cross-account toggle are both on (`crossAccountActive`), or a +// merged group inbox / cross view exists. An empty header is not shown. + +vi.mock('@/components/plugins/plugin-slot', () => ({ PluginSlot: () => null })); +vi.mock('@/components/tour/tour-provider', () => ({ + useTour: () => ({ startTour: vi.fn(), resetTourCompletion: vi.fn(), activeTour: null, endTour: vi.fn(), registerStep: vi.fn() }), +})); +vi.mock('@/contexts/drag-drop-context', () => ({ + useDragDropContext: () => ({ isDragging: false }), +})); +vi.mock('@/hooks/use-mailbox-drop', () => ({ + useMailboxDrop: () => ({ dropHandlers: {}, isValidDropTarget: false, isInvalidDropTarget: false }), +})); +vi.mock('@/hooks/use-tag-drop', () => ({ + useTagDrop: () => ({ dropHandlers: {}, isValidDropTarget: false }), +})); +vi.mock('../account-switcher', () => ({ AccountSwitcher: () => null })); + +const { Sidebar } = await import('../sidebar'); +const { useAccountStore } = await import('@/stores/account-store'); +const { useSettingsStore } = await import('@/stores/settings-store'); + +const mailboxes: Mailbox[] = [ + { id: 'inbox', name: 'Inbox', role: 'inbox', parentId: null, totalEmails: 0, unreadEmails: 0, myRights: { mayAddItems: true } }, +] as unknown as Mailbox[]; + +function account(id: string) { + return { + id, + label: `${id}@example.com`, + email: `${id}@example.com`, + username: `${id}@example.com`, + cookieSlot: id === 'a' ? 0 : 1, + isConnected: true, + }; +} + +describe('Sidebar unified mailbox section (#843)', () => { + beforeEach(() => { + useAccountStore.setState({ accounts: [account('a'), account('b')] } as never); + useSettingsStore.setState({ + enableUnifiedMailbox: true, + includeGroupInUnified: false, + hideAccountSwitcher: true, + } as never); + }); + + it('hides the header with two accounts when cross-account is not active', () => { + render(); + + expect(screen.queryByText('unified_mailbox')).toBeNull(); + expect(screen.queryByText('all_accounts')).toBeNull(); + }); + + it('shows the header once cross-account is active', () => { + render(); + + expect(screen.getByText('all_accounts')).toBeInTheDocument(); + }); + + it('shows the header for a cross view even without cross-account', () => { + render(); + + expect(screen.getByText('unified_mailbox')).toBeInTheDocument(); + }); +}); diff --git a/components/layout/sidebar.tsx b/components/layout/sidebar.tsx index 92668a88f..297442b60 100644 --- a/components/layout/sidebar.tsx +++ b/components/layout/sidebar.tsx @@ -904,9 +904,14 @@ export function Sidebar({ // single account we still surface unified when the user has opted into // merging group/shared inboxes — otherwise the counts would just duplicate // the one inbox. + // Only show the section when mail-app will actually populate it: cross-account + // needs the admin gate + user toggle (`crossAccountActive`, which already + // implies 2+ connected accounts), otherwise a merged group inbox or one of + // the cross views must exist. A bare "2+ accounts" left an empty header. (#843) + const anyCrossViewEnabled = showCrossUnread || showCrossStarred || showCrossAll; const showUnified = (multiAccountMode || enableUnifiedMailbox) && - (connectedAccounts.length > 1 || (includeGroupInUnified && hasGroupInboxes)); + (crossAccountActive || (includeGroupInUnified && hasGroupInboxes) || anyCrossViewEnabled); const { unifiedCounts } = useEmailStore(); const t = useTranslations('sidebar');