mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-02 00:06:23 +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}
|
value={name}
|
||||||
onChangeText={setName}
|
onChangeText={setName}
|
||||||
placeholder={t('settings.filters.rule_name_placeholder', 'e.g., Sort newsletters')}
|
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}
|
maxLength={200}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
@@ -414,6 +417,7 @@ export function FilterRuleModal({
|
|||||||
}
|
}
|
||||||
keyboardType={condition.field === 'size' ? 'numeric' : 'default'}
|
keyboardType={condition.field === 'size' ? 'numeric' : 'default'}
|
||||||
autoCapitalize="none"
|
autoCapitalize="none"
|
||||||
|
accessibilityLabel={t('settings.filters.condition_value', 'Condition value')}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
Reference in New Issue
Block a user