mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-03 16:07:32 +00:00
fix: stop announcing the select all checkbox as mixed with nothing selected
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { selectAllA11y } from '../select-all-a11y';
|
||||
|
||||
// Trash's Select all checkbox was announced "mixed" with nothing selected:
|
||||
// Android kept the "mixed" a string state had written into the description
|
||||
// after the state went back to false. The checkbox now never uses 'mixed'.
|
||||
|
||||
const t = (_key: string, fallback: string, values?: Record<string, string | number>) =>
|
||||
fallback.replace('{count, plural, one {1 email} other {# emails}}', `${values?.count} emails`);
|
||||
const read = (rel: string) => fs.readFileSync(path.resolve(__dirname, '..', '..', rel), 'utf8');
|
||||
|
||||
describe('Select all checkbox for screen readers', () => {
|
||||
it('is plain unchecked with nothing selected', () => {
|
||||
expect(selectAllA11y(false, 0, t)).toEqual({ label: 'Select all', checked: false });
|
||||
});
|
||||
|
||||
it('names the count in the label while some rows are selected', () => {
|
||||
expect(selectAllA11y(false, 3, t)).toEqual({ label: 'Select all, 3 emails selected', checked: false });
|
||||
});
|
||||
|
||||
it('is checked with every row selected', () => {
|
||||
expect(selectAllA11y(true, 5, t)).toEqual({ label: 'Select all', checked: true });
|
||||
});
|
||||
|
||||
it('never hands the list checkbox a mixed state', () => {
|
||||
const list = read('screens/EmailListScreen.tsx');
|
||||
expect(list).not.toMatch(/'mixed'/);
|
||||
expect(list).toMatch(/accessibilityLabel=\{selectAll\.label\}\s+accessibilityState=\{\{ checked: selectAll\.checked \}\}/);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
// What a screen reader hears for the list's Select all checkbox.
|
||||
//
|
||||
// It used to say checked: 'mixed' while some rows were selected. Android
|
||||
// writes "mixed" into the view's content description and, going back to
|
||||
// checked: false, rebuilds that description only for a string state, so it
|
||||
// kept saying "mixed" with nothing selected (seen in Trash). The count goes
|
||||
// into the label instead: a changed label always rebuilds the description.
|
||||
|
||||
type Translate = (key: string, fallback: string, values?: Record<string, string | number>) => string;
|
||||
|
||||
export function selectAllA11y(
|
||||
allSelected: boolean,
|
||||
selectedCount: number,
|
||||
t: Translate,
|
||||
): { label: string; checked: boolean } {
|
||||
const label = t('email_list.batch_actions.select_all', 'Select all');
|
||||
if (allSelected || selectedCount === 0) return { label, checked: allSelected };
|
||||
const selected = t(
|
||||
'email_list.batch_actions.selected_messages',
|
||||
'{count, plural, one {1 email} other {# emails}} selected',
|
||||
{ count: selectedCount },
|
||||
);
|
||||
return { label: `${label}, ${selected}`, checked: false };
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import { SwipeableRow } from '../components/SwipeableRow';
|
||||
import { MoveSheet } from '../components/MoveSheet';
|
||||
import { TagSheet } from '../components/TagSheet';
|
||||
import { useTagUndoGroup } from '../lib/tag-undo-group';
|
||||
import { selectAllA11y } from '../lib/select-all-a11y';
|
||||
import { OfflineBanner } from '../components/OfflineBanner';
|
||||
import { TrashRecoveryLink } from '../components/email/TrashRecoveryLink';
|
||||
import {
|
||||
@@ -721,6 +722,7 @@ export default function EmailListScreen({
|
||||
const selectedLabel = t('email_list.batch_actions.selected_messages', '{count, plural, one {1 email} other {# emails}} selected', { count: selectedIds.size });
|
||||
const allSelected =
|
||||
visibleEmails.length > 0 && visibleEmails.every((e) => selectedIds.has(rowKeyOf(e)));
|
||||
const selectAll = selectAllA11y(allSelected, selectedIds.size, t);
|
||||
|
||||
// Refs so row press handlers stay referentially stable across renders.
|
||||
// FlatList rows then skip re-render when the parent re-renders for unrelated
|
||||
@@ -1274,8 +1276,8 @@ export default function EmailListScreen({
|
||||
onPress={toggleSelectAllVisible}
|
||||
hitSlop={6}
|
||||
accessibilityRole="checkbox"
|
||||
accessibilityLabel={t('email_list.batch_actions.select_all', 'Select all')}
|
||||
accessibilityState={{ checked: allSelected ? true : selectionMode ? 'mixed' : false }}
|
||||
accessibilityLabel={selectAll.label}
|
||||
accessibilityState={{ checked: selectAll.checked }}
|
||||
>
|
||||
{allSelected ? (
|
||||
<SquareCheck size={18} color={c.primary} />
|
||||
|
||||
Reference in New Issue
Block a user