mirror of
https://github.com/bulwarkmail/native.git
synced 2026-11-01 00:07:01 +00:00
fix: toggle a settings switch by tapping anywhere on its row
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
|
||||
vi.mock('../../ToggleSwitch', () => ({ default: () => null }));
|
||||
vi.mock('../../icons', () => ({}));
|
||||
vi.mock('../../../stores/locale-store', () => ({ useLocaleStore: () => undefined }));
|
||||
vi.mock('../../../theme/colors', () => ({ useColors: () => ({}) }));
|
||||
vi.mock('../search-highlight', () => ({ useSearchHighlight: () => ({}) }));
|
||||
vi.mock('../../../lib/modal-orientations', () => ({ MODAL_ORIENTATIONS: [] }));
|
||||
vi.mock('react-native', () => ({ StyleSheet: { create: (s: unknown) => s } }));
|
||||
|
||||
import { switchRowPress } from '../settings-section';
|
||||
|
||||
// Tapping the "Colour-blind-safe colours" label did nothing: only the switch
|
||||
// itself flipped the setting.
|
||||
|
||||
describe('a settings row with a switch', () => {
|
||||
it('flips the switch when the row is tapped', () => {
|
||||
const onChange = vi.fn();
|
||||
switchRowPress(false, onChange)!();
|
||||
expect(onChange).toHaveBeenLastCalledWith(true);
|
||||
switchRowPress(true, onChange)!();
|
||||
expect(onChange).toHaveBeenLastCalledWith(false);
|
||||
});
|
||||
|
||||
it('leaves a disabled switch alone', () => {
|
||||
expect(switchRowPress(false, vi.fn(), true)).toBeNull();
|
||||
});
|
||||
|
||||
it('hands the switch to its row, which presses it unless the setting is locked', () => {
|
||||
const src = fs.readFileSync(path.resolve(__dirname, '..', 'settings-section.tsx'), 'utf8');
|
||||
expect(src).toMatch(/rowToggle\.current = switchRowPress\(checked, onChange, disabled\);/);
|
||||
expect(src).toMatch(/onPress=\{\(\) => \{ if \(!locked\) rowToggle\.current\?\.\(\); \}\}/);
|
||||
expect(src).toMatch(/<SettingToggleContext\.Provider value=\{rowToggle\}>/);
|
||||
// The colour-blind row is such a row.
|
||||
const a11y = fs.readFileSync(path.resolve(__dirname, '..', 'AccessibilitySettings.tsx'), 'utf8');
|
||||
expect(a11y).toMatch(/label=\{t\('settings\.accessibility\.cvd_palette\.label'[\s\S]{0,300}<ToggleSwitch\s+checked=\{colorBlindPalette\}/);
|
||||
});
|
||||
});
|
||||
@@ -76,6 +76,16 @@ const STACK_BELOW = 140;
|
||||
// its own, so without it a screen reader only announces "switch, off".
|
||||
const SettingLabelContext = React.createContext<string | undefined>(undefined);
|
||||
|
||||
// What a tap on the row does when its control is a switch: flip it, so the
|
||||
// label and the description are as good a target as the 44 x 24 switch
|
||||
// (tapping "Colour-blind-safe colours" did nothing).
|
||||
const SettingToggleContext = React.createContext<React.MutableRefObject<(() => void) | null> | null>(null);
|
||||
|
||||
/** The row tap for a switch: flips it; null while it is disabled. */
|
||||
export function switchRowPress(checked: boolean, onChange: (v: boolean) => void, disabled?: boolean): (() => void) | null {
|
||||
return disabled ? null : () => onChange(!checked);
|
||||
}
|
||||
|
||||
interface SettingItemProps {
|
||||
label: string;
|
||||
description?: string;
|
||||
@@ -100,6 +110,8 @@ export function SettingItem({
|
||||
const styles = React.useMemo(() => makeStyles(c), [c]);
|
||||
const t = useLocaleStore((s) => s.t);
|
||||
const highlight = useSearchHighlight(label);
|
||||
// Set by a ToggleSwitch in the row (switchRowPress).
|
||||
const rowToggle = React.useRef<(() => void) | null>(null);
|
||||
// The control sits on the right. When that leaves the label column narrower
|
||||
// than STACK_BELOW (a long translation, two buttons), the control moves
|
||||
// under the label, as the webmail stacks it on a phone. A new row width
|
||||
@@ -130,8 +142,10 @@ export function SettingItem({
|
||||
</View>
|
||||
{children && (
|
||||
<SettingLabelContext.Provider value={label}>
|
||||
{/* A locked setting shows its value but can't be changed. */}
|
||||
<View style={styles.settingRight} pointerEvents={locked ? 'none' : 'auto'}>{children}</View>
|
||||
<SettingToggleContext.Provider value={rowToggle}>
|
||||
{/* A locked setting shows its value but can't be changed. */}
|
||||
<View style={styles.settingRight} pointerEvents={locked ? 'none' : 'auto'}>{children}</View>
|
||||
</SettingToggleContext.Provider>
|
||||
</SettingLabelContext.Provider>
|
||||
)}
|
||||
{onPress && (link === 'external'
|
||||
@@ -152,10 +166,18 @@ export function SettingItem({
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
// A tap anywhere on the row flips its switch, if it has one. Not a
|
||||
// control of its own for a screen reader: the switch carries the label.
|
||||
return (
|
||||
<View ref={highlight.ref} onLayout={onRowLayout} style={rowStyle}>
|
||||
<Pressable
|
||||
ref={highlight.ref}
|
||||
onLayout={onRowLayout}
|
||||
onPress={() => { if (!locked) rowToggle.current?.(); }}
|
||||
accessible={false}
|
||||
style={rowStyle}
|
||||
>
|
||||
{body}
|
||||
</View>
|
||||
</Pressable>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -219,6 +241,12 @@ interface ToggleSwitchProps {
|
||||
|
||||
export function ToggleSwitch({ checked, onChange, disabled, accessibilityLabel }: ToggleSwitchProps) {
|
||||
const settingLabel = useContext(SettingLabelContext);
|
||||
const rowToggle = useContext(SettingToggleContext);
|
||||
React.useEffect(() => {
|
||||
if (!rowToggle) return undefined;
|
||||
rowToggle.current = switchRowPress(checked, onChange, disabled);
|
||||
return () => { rowToggle.current = null; };
|
||||
}, [rowToggle, checked, onChange, disabled]);
|
||||
return (
|
||||
<ToggleSwitchComponent
|
||||
value={checked}
|
||||
|
||||
Reference in New Issue
Block a user