fix: hide the account dot in the unified view with only one account

This commit is contained in:
Linus Rath
2026-10-31 15:57:34 +01:00
parent 3bdbdb1364
commit 8a4d9e36c9
12 changed files with 32 additions and 11 deletions
@@ -19,7 +19,7 @@ vi.mock('@/stores/auth-store', async () => {
};
});
vi.mock('@/stores/account-store', () => {
const state = { getAccountById: () => undefined };
const state = { accounts: [], getAccountById: () => undefined };
return {
useAccountStore: Object.assign(
(selector?: (s: typeof state) => unknown) => (selector ? selector(state) : state),
@@ -35,7 +35,7 @@ vi.mock('@/stores/auth-store', async () => {
};
});
vi.mock('@/stores/account-store', () => {
const state = { getAccountById: () => undefined };
const state = { accounts: [], getAccountById: () => undefined };
return {
useAccountStore: Object.assign(
(selector?: (s: typeof state) => unknown) => (selector ? selector(state) : state),
@@ -21,7 +21,7 @@ vi.mock('@/stores/auth-store', async () => {
};
});
vi.mock('@/stores/account-store', () => {
const state = { getAccountById: () => undefined };
const state = { accounts: [], getAccountById: () => undefined };
return {
useAccountStore: Object.assign(
(selector?: (s: typeof state) => unknown) => (selector ? selector(state) : state),
@@ -19,7 +19,7 @@ vi.mock('@/stores/auth-store', async () => {
};
});
vi.mock('@/stores/account-store', () => {
const state = { getAccountById: () => undefined };
const state = { accounts: [], getAccountById: () => undefined };
return {
useAccountStore: Object.assign(
(selector?: (s: typeof state) => unknown) => (selector ? selector(state) : state),
@@ -15,7 +15,7 @@ vi.mock('@/stores/auth-store', async () => {
};
});
vi.mock('@/stores/account-store', () => {
const state = { getAccountById: () => undefined };
const state = { accounts: [], getAccountById: () => undefined };
return {
useAccountStore: Object.assign(
(selector?: (s: typeof state) => unknown) => (selector ? selector(state) : state),
@@ -47,7 +47,7 @@ vi.mock('@/stores/auth-store', async () => {
};
});
vi.mock('@/stores/account-store', () => {
const state = { getAccountById: () => undefined };
const state = { accounts: [], getAccountById: () => undefined };
return {
useAccountStore: Object.assign(
(selector?: (s: typeof state) => unknown) => (selector ? selector(state) : state),
@@ -21,7 +21,7 @@ vi.mock('@/stores/auth-store', async () => {
};
});
vi.mock('@/stores/account-store', () => {
const state = { getAccountById: () => undefined };
const state = { accounts: [], getAccountById: () => undefined };
return {
useAccountStore: Object.assign(
(selector?: (s: typeof state) => unknown) => (selector ? selector(state) : state),
@@ -24,7 +24,7 @@ vi.mock('@/stores/auth-store', () => {
});
vi.mock('@/stores/account-store', () => {
const state = { getAccountById: () => undefined };
const state = { accounts: [], getAccountById: () => undefined };
return {
useAccountStore: Object.assign(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -19,7 +19,9 @@ vi.mock('@/stores/auth-store', () => {
});
const ACCOUNT_TEAL = { id: 'acct-teal', avatarColor: '#0d9488', label: 'Teal account' };
const ACCOUNT_OTHER = { id: 'acct-other', avatarColor: '#dc2626', label: 'Other account' };
const accountState = {
accounts: [ACCOUNT_TEAL, ACCOUNT_OTHER] as { id: string }[],
getAccountById: (id: string) => (id === ACCOUNT_TEAL.id ? ACCOUNT_TEAL : undefined),
};
vi.mock('@/stores/account-store', () => ({
@@ -65,6 +67,7 @@ const accountDot = (c: HTMLElement) =>
describe('ThreadListItem account dot (#912)', () => {
beforeEach(() => {
accountState.accounts = [ACCOUNT_TEAL, ACCOUNT_OTHER];
useUIStore.setState({ isMobile: false, isTablet: false, isDesktop: true });
useSettingsStore.setState({
emailKeywords: [...DEFAULT_KEYWORDS],
@@ -105,6 +108,13 @@ describe('ThreadListItem account dot (#912)', () => {
// The rest of the row still renders.
expect(container.textContent).toContain('Test Subject');
});
it('hides the dot with only one account signed in', () => {
accountState.accounts = [ACCOUNT_TEAL];
const { container } = renderEmails(emails());
expect(accountDot(container)).toBeNull();
expect(container.textContent).toContain('Test Subject');
});
});
});
});
@@ -21,7 +21,7 @@ vi.mock('@/stores/auth-store', () => {
};
});
const accountState = { getAccountById: () => undefined };
const accountState = { accounts: [], getAccountById: () => undefined };
vi.mock('@/stores/account-store', () => ({
useAccountStore: Object.assign(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -23,6 +23,7 @@ vi.mock('@/stores/auth-store', () => {
const ACCOUNT_TEAL = { id: 'acct-teal', avatarColor: '#0d9488', label: 'Teal account' };
const accountState = {
accounts: [ACCOUNT_TEAL],
getAccountById: (id: string) => (id === ACCOUNT_TEAL.id ? ACCOUNT_TEAL : undefined),
};
vi.mock('@/stores/account-store', () => ({
+12 -2
View File
@@ -84,6 +84,16 @@ export function listMailboxRole(state: {
?? (state.isUnifiedView ? (state.unifiedRole ?? undefined) : undefined);
}
/**
* The account dot tells signed-in accounts apart. With a single account every
* row would carry the same dot, so it only shows once there are two or more.
*/
function useShowAccountDot(tintListRowsByAccount: boolean): boolean {
const enabled = useSettingsStore((state) => state.showAccountDot);
const severalAccounts = useAccountStore((state) => state.accounts.length > 1);
return enabled && severalAccounts && !tintListRowsByAccount;
}
/** Hover actions mount on the first pointerover or keyboard focus in the row. */
function useArmOnPointerOver(): [boolean, () => void] {
const [armed, setArmed] = React.useState(false);
@@ -445,7 +455,7 @@ const SingleEmailItem = React.forwardRef<HTMLDivElement, SingleEmailItemProps>(
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 showAccountDot = useShowAccountDot(tintListRowsByAccount);
const density = useSettingsStore((state) => state.density);
const timeFormat = useSettingsStore((state) => state.timeFormat);
const showAvatarsInJunk = useSettingsStore((state) => state.showAvatarsInJunk);
@@ -1175,7 +1185,7 @@ export const ThreadListItem = React.forwardRef<HTMLDivElement, ThreadListItemPro
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 showAccountDot = useShowAccountDot(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