diff --git a/components/email/__tests__/thread-list-item-account-dot.test.tsx b/components/email/__tests__/thread-list-item-account-dot.test.tsx new file mode 100644 index 000000000..482048d1f --- /dev/null +++ b/components/email/__tests__/thread-list-item-account-dot.test.tsx @@ -0,0 +1,110 @@ +import { render } from '@testing-library/react'; +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { ThreadListItem } from '../thread-list-item'; +import { useSettingsStore, DEFAULT_KEYWORDS } from '@/stores/settings-store'; +import { useEmailStore } from '@/stores/email-store'; +import { useUIStore } from '@/stores/ui-store'; +import { groupEmailsByThread } from '@/lib/thread-utils'; +import type { Email } from '@/lib/jmap/types'; + +vi.mock('@/hooks/use-email-drag', () => ({ + useEmailDrag: () => ({ dragHandlers: {}, isDragging: false }), +})); + +vi.mock('@/stores/auth-store', () => { + const state = { identities: [], username: null }; + return { + useAuthStore: (selector?: (s: typeof state) => unknown) => (selector ? selector(state) : state), + }; +}); + +const ACCOUNT_TEAL = { id: 'acct-teal', avatarColor: '#0d9488', label: 'Teal account' }; +const accountState = { + getAccountById: (id: string) => (id === ACCOUNT_TEAL.id ? ACCOUNT_TEAL : undefined), +}; +vi.mock('@/stores/account-store', () => ({ + useAccountStore: Object.assign( + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (selector: any) => (selector ? selector(accountState) : accountState), + { getState: () => accountState }, + ), +})); + +const makeEmail = (overrides: Partial = {}): Email => ({ + id: 'email-1', + threadId: 'thread-1', + mailboxIds: { inbox: true }, + keywords: { $seen: true }, + size: 1000, + receivedAt: '2024-01-15T10:00:00Z', + from: [{ name: 'Alice', email: 'alice@example.com' }], + subject: 'Test Subject', + hasAttachment: false, + accountId: ACCOUNT_TEAL.id, + accountLabel: ACCOUNT_TEAL.label, + ...overrides, +}); + +function renderEmails(emails: Email[]) { + const [thread] = groupEmailsByThread(emails); + return render( + {}} onEmailSelect={() => {}} />, + ); +} + +const single = () => [makeEmail()]; +const collapsedThread = () => [ + makeEmail({ id: 'e1', threadId: 't1' }), + makeEmail({ id: 'e2', threadId: 't1', receivedAt: '2024-01-16T10:00:00Z' }), +]; + +// The dot is the only element painted in the account colour. +const accountDot = (c: HTMLElement) => + Array.from(c.querySelectorAll('span.rounded-full')) + .find((el) => el.style.backgroundColor === 'rgb(13, 148, 136)') ?? null; + +describe('ThreadListItem account dot (#912)', () => { + beforeEach(() => { + useUIStore.setState({ isMobile: false, isTablet: false, isDesktop: true }); + useSettingsStore.setState({ + emailKeywords: [...DEFAULT_KEYWORDS], + showPreview: false, + interfaceLayout: 'default', + tintListRowsByAccount: false, + showAccountDot: true, + }); + useEmailStore.setState({ + selectedEmailKeys: new Set(), + selectedMailbox: 'inbox', + isUnifiedView: true, + }); + }); + + it('is on by default', () => { + expect(useSettingsStore.getInitialState().showAccountDot).toBe(true); + }); + + describe.each([ + ['single message', single], + ['collapsed thread', collapsedThread], + ])('%s', (_name, emails) => { + describe.each(['split', 'focus'] as const)('%s layout', (layout) => { + beforeEach(() => { + useSettingsStore.setState({ mailLayout: layout }); + }); + + it('shows the dot in the unified view', () => { + const { container } = renderEmails(emails()); + expect(accountDot(container)).not.toBeNull(); + }); + + it('hides the dot when the setting is off', () => { + useSettingsStore.setState({ showAccountDot: false }); + const { container } = renderEmails(emails()); + expect(accountDot(container)).toBeNull(); + // The rest of the row still renders. + expect(container.textContent).toContain('Test Subject'); + }); + }); + }); +}); diff --git a/components/email/thread-list-item.tsx b/components/email/thread-list-item.tsx index e48819e45..d35508403 100644 --- a/components/email/thread-list-item.tsx +++ b/components/email/thread-list-item.tsx @@ -360,6 +360,8 @@ const SingleEmailItem = React.forwardRef( const { variant: tagVariant, placement: tagPlacement } = useTagDisplay(); const tintListRowsByTag = useSettingsStore((state) => state.tintListRowsByTag); const tintListRowsByAccount = useSettingsStore((state) => state.tintListRowsByAccount); + // The tint replaces the dot, and the dot can be switched off on its own (#912). + const showAccountDot = useSettingsStore((state) => state.showAccountDot) && !tintListRowsByAccount; const density = useSettingsStore((state) => state.density); const mailLayout = useEffectiveMailLayout(); const timeFormat = useSettingsStore((state) => state.timeFormat); @@ -624,7 +626,7 @@ const SingleEmailItem = React.forwardRef( <>
- {isUnifiedView && email.accountId && accountColor && !tintListRowsByAccount && ( + {isUnifiedView && email.accountId && accountColor && showAccountDot && ( ( <>
- {isUnifiedView && email.accountId && accountColor && !tintListRowsByAccount && ( + {isUnifiedView && email.accountId && accountColor && showAccountDot && ( state.tintListRowsByTag); const tintListRowsByAccount = useSettingsStore((state) => state.tintListRowsByAccount); + // The tint replaces the dot, and the dot can be switched off on its own (#912). + const showAccountDot = useSettingsStore((state) => state.showAccountDot) && !tintListRowsByAccount; // A collapsed row speaks for every message under it, so it carries their tags too. const tagIds = sortTagIds(getThreadTagIds(thread.emails)); const threadTintSource = threadAccountColor @@ -1204,7 +1208,7 @@ export const ThreadListItem = React.forwardRef
- {isUnifiedView && latestEmail.accountId && threadAccountColor && !tintListRowsByAccount && ( + {isUnifiedView && latestEmail.accountId && threadAccountColor && showAccountDot && (
- {isUnifiedView && latestEmail.accountId && threadAccountColor && !tintListRowsByAccount && ( + {isUnifiedView && latestEmail.accountId && threadAccountColor && showAccountDot && ( s.accounts); const activeAccountId = useAccountStore(s => s.activeAccountId); @@ -263,6 +263,13 @@ export function LayoutSettings() { /> + + updateSetting('showAccountDot', checked)} + /> + + , @@ -1231,6 +1233,7 @@ export const useSettingsStore = create()( colorfulSidebarIcons: state.colorfulSidebarIcons, tintListRowsByTag: state.tintListRowsByTag, tintListRowsByAccount: state.tintListRowsByAccount, + showAccountDot: state.showAccountDot, showFolderTotalCount: state.showFolderTotalCount, showSharedMailAccounts: state.showSharedMailAccounts, hiddenSharedMailAccounts: state.hiddenSharedMailAccounts,