From 172d0f63e8c48839c30bfb516ac285a21715488c Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Sat, 31 Oct 2026 14:31:54 +0100 Subject: [PATCH] fix: let TalkBack read the unified inbox row as its name and unread count --- src/components/SidebarDrawer.tsx | 18 +++++++------ src/lib/__tests__/unread-row-label.test.ts | 30 ++++++++++++++++++++++ src/lib/unread-row-label.ts | 11 ++++++++ 3 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 src/lib/__tests__/unread-row-label.test.ts create mode 100644 src/lib/unread-row-label.ts diff --git a/src/components/SidebarDrawer.tsx b/src/components/SidebarDrawer.tsx index 1f8e5f1f..84db85dc 100644 --- a/src/components/SidebarDrawer.tsx +++ b/src/components/SidebarDrawer.tsx @@ -49,6 +49,7 @@ import { import { serverSupportsEmailPush } from '../lib/push-notifications'; import { mailboxIconKind, type MailboxIconKind } from '../lib/mailbox-icon'; import { useSheetBottomInset } from '../lib/sheet-bottom-inset'; +import { unreadRowLabel } from '../lib/unread-row-label'; const CHEVRON_SLOT = 20; const INDENT_STEP = 12; @@ -171,9 +172,7 @@ function SidebarRow({ const leftPad = ROW_PX_BASE + depth * INDENT_STEP; // Which folder is open, and the unread count with the folder's name // ("Inbox, 3 unread"), for screen readers (webmail 7c282f1f4). - const accessibleName = unread > 0 && !isAccountHeader - ? t('shell.name_unread', '{name}, {count} unread', { name: label, count: unread }) - : label; + const accessibleName = isAccountHeader ? label : unreadRowLabel(label, unread, t); return ( m.isShared && m.role === 'inbox'); + const unifiedInboxLabel = unifiedCrossAccount && accounts.length > 1 + ? t('sidebar.all_accounts', 'All accounts') + : t('sidebar.unified_inbox', 'All inboxes'); const showUnified = showUnifiedSection({ accountCount: accounts.length, unifiedCrossAccount, includeGroupInUnified, hasSharedInbox, }); @@ -1047,13 +1049,13 @@ export default function SidebarDrawer({ visible, onClose, docked = false }: Side [styles.quickRow, pressed && styles.rowPressed]} onPress={() => { onClose(); navigation.navigate('UnifiedInbox', { role: 'inbox' }); }} + // A name of its own, like a folder row: else TalkBack read its + // texts one by one ("All inboxes, /, 9"). + accessibilityRole="button" + accessibilityLabel={unreadRowLabel(unifiedInboxLabel, unifiedCounts.inbox.unread, t)} > - - {unifiedCrossAccount && accounts.length > 1 - ? t('sidebar.all_accounts', 'All accounts') - : t('sidebar.unified_inbox', 'All inboxes')} - + {unifiedInboxLabel} diff --git a/src/lib/__tests__/unread-row-label.test.ts b/src/lib/__tests__/unread-row-label.test.ts new file mode 100644 index 00000000..7d8ca7be --- /dev/null +++ b/src/lib/__tests__/unread-row-label.test.ts @@ -0,0 +1,30 @@ +import { describe, expect, it, vi } from 'vitest'; +import fs from 'node:fs'; +import path from 'node:path'; + +vi.mock('expo-localization', () => ({ getLocales: () => [] })); + +import { translate } from '../../i18n'; +import { unreadRowLabel } from '../unread-row-label'; + +const tEn = (key: string, fallback?: string, params?: Record) => translate('en', key, fallback, params); + +describe('unreadRowLabel', () => { + it('says the unread count after the name', () => { + expect(unreadRowLabel('All inboxes', 9, tEn)).toBe('All inboxes, 9 unread'); + expect(unreadRowLabel('All inboxes', 0, tEn)).toBe('All inboxes'); + }); +}); + +describe('the sidebar Unified Inbox row', () => { + // TalkBack read it as "All inboxes, /, 9": no label, so the row's texts + // (the name and RowCounts' unread / total) one by one. + it('is a button named by its label and unread count', () => { + const source = fs.readFileSync(path.resolve(__dirname, '../../components/SidebarDrawer.tsx'), 'utf8'); + const start = source.indexOf('{showUnified && ('); + const row = source.slice(start, source.indexOf('', start)); + expect(row).toContain("navigation.navigate('UnifiedInbox', { role: 'inbox' })"); + expect(row).toMatch(/accessibilityRole="button"/); + expect(row).toMatch(/accessibilityLabel=\{unreadRowLabel\(unifiedInboxLabel, unifiedCounts\.inbox\.unread, t\)\}/); + }); +}); diff --git a/src/lib/unread-row-label.ts b/src/lib/unread-row-label.ts new file mode 100644 index 00000000..11496e84 --- /dev/null +++ b/src/lib/unread-row-label.ts @@ -0,0 +1,11 @@ +type Translate = (key: string, fallback?: string, params?: Record) => string; + +/** + * What a screen reader calls a folder or view row: its name, with the unread + * count when there is unread mail ("Inbox, 3 unread"; webmail 7c282f1f4). + * Without a label of its own a row reads its texts one by one, so the + * Unified Inbox row came out as "All inboxes, /, 9". + */ +export function unreadRowLabel(name: string, unread: number, t: Translate): string { + return unread > 0 ? t('shell.name_unread', '{name}, {count} unread', { name, count: unread }) : name; +}