mirror of
https://github.com/bulwarkmail/native.git
synced 2026-10-31 16:06:32 +00:00
fix: name the filter rule fields by their labels for screen readers
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
// A text field with no label of its own is named by its placeholder. When
|
||||
// the placeholder is an example, TalkBack read the filter rule's name field
|
||||
// as "e.g., Sort newsletters" (and the size condition as "e.g., 1000000").
|
||||
// Fields with an example placeholder get their label as the accessible name,
|
||||
// as in the webmail's filter-rule-modal.tsx.
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
const SRC = path.resolve(__dirname, '../..');
|
||||
|
||||
function sourceFiles(dir: string): string[] {
|
||||
return fs.readdirSync(dir, { withFileTypes: true }).flatMap((e) => {
|
||||
const p = path.join(dir, e.name);
|
||||
if (e.isDirectory()) return e.name === '__tests__' ? [] : sourceFiles(p);
|
||||
return e.name.endsWith('.tsx') ? [p] : [];
|
||||
});
|
||||
}
|
||||
|
||||
function openingTags(source: string, name: string): { tag: string; line: number }[] {
|
||||
const out: { tag: string; line: number }[] = [];
|
||||
const re = new RegExp(`<${name}\\b`, 'g');
|
||||
let m: RegExpExecArray | null;
|
||||
while ((m = re.exec(source))) {
|
||||
let depth = 0;
|
||||
let i = m.index;
|
||||
for (; i < source.length; i++) {
|
||||
const ch = source[i];
|
||||
if (ch === '{') depth++;
|
||||
else if (ch === '}') depth--;
|
||||
else if (ch === '>' && depth === 0 && source[i - 1] !== '=') break;
|
||||
}
|
||||
out.push({ tag: source.slice(m.index, i + 1), line: source.slice(0, m.index).split('\n').length });
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const EXAMPLE_PLACEHOLDER = /placeholder=\{[^}]*'e\.g\.?,?\s/;
|
||||
const NAMED = /accessibilityLabel=|\blabel=/;
|
||||
|
||||
describe('text fields with an example placeholder', () => {
|
||||
it('are named by a label', () => {
|
||||
const offenders = sourceFiles(SRC).flatMap((file) => {
|
||||
const source = fs.readFileSync(file, 'utf8');
|
||||
return ['Input', 'TextInput'].flatMap((name) => openingTags(source, name))
|
||||
.filter(({ tag }) => EXAMPLE_PLACEHOLDER.test(tag) && !NAMED.test(tag))
|
||||
.map(({ line }) => `${path.relative(SRC, file)}:${line}`);
|
||||
});
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
|
||||
it('spots an unnamed one', () => {
|
||||
const [tag] = openingTags("<Input value={name} placeholder={t('x', 'e.g., Sort newsletters')} />", 'Input');
|
||||
expect(EXAMPLE_PLACEHOLDER.test(tag.tag)).toBe(true);
|
||||
expect(NAMED.test(tag.tag)).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -329,6 +329,9 @@ export function FilterRuleModal({
|
||||
value={name}
|
||||
onChangeText={setName}
|
||||
placeholder={t('settings.filters.rule_name_placeholder', 'e.g., Sort newsletters')}
|
||||
// Named by its label, not the example in the placeholder
|
||||
// (webmail filter-rule-modal.tsx).
|
||||
accessibilityLabel={t('settings.filters.rule_name', 'Rule Name')}
|
||||
maxLength={200}
|
||||
/>
|
||||
</View>
|
||||
@@ -414,6 +417,7 @@ export function FilterRuleModal({
|
||||
}
|
||||
keyboardType={condition.field === 'size' ? 'numeric' : 'default'}
|
||||
autoCapitalize="none"
|
||||
accessibilityLabel={t('settings.filters.condition_value', 'Condition value')}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
|
||||
Reference in New Issue
Block a user