Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions src/components/uis/Button/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,80 @@ describe('[Button]', () => {
});
});

describe('[Button] accessibility', () => {
it('should have accessibilityRole="button"', () => {
testingLib = render(
Component({
props: {
testID: 'a11y-button',
text: 'Click me',
},
}),
);

const button = testingLib.getByTestId('a11y-button');
expect(button.props.accessibilityRole).toBe('button');
});

it('should use text as default accessibilityLabel', () => {
testingLib = render(
Component({
props: {
testID: 'a11y-button',
text: 'Submit',
},
}),
);

const button = testingLib.getByTestId('a11y-button');
expect(button.props.accessibilityLabel).toBe('Submit');
});

it('should use custom accessibilityLabel when provided', () => {
testingLib = render(
Component({
props: {
testID: 'a11y-button',
text: 'Submit',
accessibilityLabel: 'Submit form',
},
}),
);

const button = testingLib.getByTestId('a11y-button');
expect(button.props.accessibilityLabel).toBe('Submit form');
});

it('should have undefined accessibilityLabel when text is ReactElement', () => {
testingLib = render(
Component({
props: {
testID: 'a11y-button',
text: <Text>Custom</Text>,
},
}),
);

const button = testingLib.getByTestId('a11y-button');
expect(button.props.accessibilityLabel).toBeUndefined();
});

it('should allow custom accessibilityLabel with ReactElement text', () => {
testingLib = render(
Component({
props: {
testID: 'a11y-button',
text: <Text>Custom</Text>,
accessibilityLabel: 'Custom button',
},
}),
);

const button = testingLib.getByTestId('a11y-button');
expect(button.props.accessibilityLabel).toBe('Custom button');
});
});

describe('[Button] custom theme', () => {
it('should render with custom primary color from theme', () => {
const customTheme = {
Expand Down
4 changes: 4 additions & 0 deletions src/components/uis/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export type Props = {
>;
hitSlop?: null | Insets | number | undefined;
hapticFeedback?: Haptics.ImpactFeedbackStyle;
accessibilityLabel?: string;
};

type Styles = {
Expand Down Expand Up @@ -241,6 +242,7 @@ export function Button({
loadingColor,
hitSlop = {top: 8, bottom: 8, left: 8, right: 8},
hapticFeedback,
accessibilityLabel,
}: Props): ReactElement {
const ref = useRef<React.ElementRef<typeof TouchableHighlight>>(null);
const hovered = useHover(ref);
Expand Down Expand Up @@ -410,6 +412,8 @@ export function Button({

return (
<TouchableHighlight
accessibilityLabel={accessibilityLabel ?? (typeof text === 'string' ? text : undefined)}
accessibilityRole="button"
activeOpacity={activeOpacity}
delayPressIn={30}
disabled={innerDisabled || loading || !onPress}
Expand Down
96 changes: 95 additions & 1 deletion src/components/uis/Checkbox/Checkbox.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {type ReactElement} from 'react';
import {View} from 'react-native';
import {Text, View} from 'react-native';
import type {RenderAPI} from '@testing-library/react-native';
import {render} from '@testing-library/react-native';

Expand Down Expand Up @@ -170,4 +170,98 @@ describe('[Checkbox]', () => {
expect(checkbox).toBeTruthy();
});
});

describe('accessibility', () => {
it('should have accessibilityRole="checkbox"', () => {
testingLib = render(
Component({
text: 'Accept terms',
}),
);

const json = testingLib.toJSON();
expect(json).toBeTruthy();
// The root element should have accessibilityRole
expect((json as any).props.accessibilityRole).toBe('checkbox');
});

it('should use text as default accessibilityLabel', () => {
testingLib = render(
Component({
text: 'Accept terms',
}),
);

const json = testingLib.toJSON();
expect((json as any).props.accessibilityLabel).toBe('Accept terms');
});

it('should use custom accessibilityLabel when provided', () => {
testingLib = render(
Component({
text: 'Accept',
accessibilityLabel: 'Accept terms and conditions',
}),
);

const json = testingLib.toJSON();
expect((json as any).props.accessibilityLabel).toBe('Accept terms and conditions');
});

it('should have accessibilityState with checked=true when checked', () => {
testingLib = render(
Component({
text: 'Accept terms',
checked: true,
}),
);

const json = testingLib.toJSON();
expect((json as any).props.accessibilityState).toEqual({
checked: true,
disabled: false,
});
});

it('should have accessibilityState with checked=false when unchecked', () => {
testingLib = render(
Component({
text: 'Accept terms',
checked: false,
}),
);

const json = testingLib.toJSON();
expect((json as any).props.accessibilityState).toEqual({
checked: false,
disabled: false,
});
});

it('should have accessibilityState with disabled=true when disabled', () => {
testingLib = render(
Component({
text: 'Accept terms',
disabled: true,
}),
);

const json = testingLib.toJSON();
expect((json as any).props.accessibilityState).toEqual({
checked: false,
disabled: true,
});
});

it('should have undefined accessibilityLabel when text is ReactElement', () => {
testingLib = render(
Component({
text: <Text>Custom text</Text>,
}),
);

const json = testingLib.toJSON();
expect((json as any).props.accessibilityLabel).toBeUndefined();
});
});
});
5 changes: 5 additions & 0 deletions src/components/uis/Checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface CheckboxProps {
checked?: boolean;
text?: string | ReactElement;
direction?: 'left' | 'right';
accessibilityLabel?: string;
}

const Container = styled(TouchableOpacity)`
Expand Down Expand Up @@ -77,6 +78,7 @@ export function Checkbox({
disabled = false,
checked = false,
onPress,
accessibilityLabel,
}: CheckboxProps): ReactElement {
const checkboxSize = typeof size === 'number'
? size
Expand Down Expand Up @@ -188,6 +190,9 @@ export function Checkbox({

return (
<Container
accessibilityLabel={accessibilityLabel ?? (typeof text === 'string' ? text : undefined)}
accessibilityRole="checkbox"
accessibilityState={{checked, disabled}}
activeOpacity={0.9}
disabled={disabled}
onPress={onPress}
Expand Down
3 changes: 3 additions & 0 deletions src/components/uis/EditText/EditText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export type EditTextProps = {
numberOfLines?: TextInputProps['numberOfLines'];
maxLength?: TextInputProps['maxLength'];
hideCounter?: boolean;
accessibilityLabel?: string;

textInputProps?: Omit<
TextInputProps,
Expand Down Expand Up @@ -149,6 +150,7 @@ export const EditText = forwardRef<TextInput, EditTextProps>(
decoration = 'underline',
colors = {},
required = false,
accessibilityLabel,
}: EditTextProps,
ref,
): ReactElement => {
Expand Down Expand Up @@ -410,6 +412,7 @@ export const EditText = forwardRef<TextInput, EditTextProps>(
})
: startElement}
<TextInput
accessibilityLabel={accessibilityLabel ?? (typeof label === 'string' ? label : placeholder)}
autoCapitalize={autoCapitalize}
autoComplete={autoComplete}
editable={editable}
Expand Down
39 changes: 39 additions & 0 deletions src/components/uis/IconButton/IconButton.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -162,4 +162,43 @@ describe('[IconButton]', () => {

expect(json).toBeTruthy();
});

describe('accessibility', () => {
it('should have accessibilityRole="button"', () => {
testingLib = render(
Component({
testID: 'a11y-icon-button',
icon: 'Plus',
}),
);

const button = testingLib.getByTestId('a11y-icon-button');
expect(button.props.accessibilityRole).toBe('button');
});

it('should use icon name as default accessibilityLabel', () => {
testingLib = render(
Component({
testID: 'a11y-icon-button',
icon: 'Plus',
}),
);

const button = testingLib.getByTestId('a11y-icon-button');
expect(button.props.accessibilityLabel).toBe('Plus');
});

it('should use custom accessibilityLabel when provided', () => {
testingLib = render(
Component({
testID: 'a11y-icon-button',
icon: 'Plus',
accessibilityLabel: 'Add new item',
}),
);

const button = testingLib.getByTestId('a11y-icon-button');
expect(button.props.accessibilityLabel).toBe('Add new item');
});
});
});
4 changes: 4 additions & 0 deletions src/components/uis/IconButton/IconButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export type IconButtonProps = {
activeOpacity?: number;
touchableHighlightProps?: Omit<TouchableHighlightProps, 'onPress' | 'style'>;
hapticFeedback?: Haptics.ImpactFeedbackStyle;
accessibilityLabel?: string;
};

export function IconButton({
Expand All @@ -117,6 +118,7 @@ export function IconButton({
activeOpacity = 0.95,
touchableHighlightProps,
hapticFeedback,
accessibilityLabel,
}: IconButtonProps): ReactElement {
const ref = useRef<React.ElementRef<typeof TouchableHighlight>>(null);
const hovered = useHover(ref);
Expand Down Expand Up @@ -245,6 +247,8 @@ export function IconButton({
return (
<View style={containerStyles}>
<TouchableHighlight
accessibilityLabel={accessibilityLabel ?? icon}
accessibilityRole="button"
activeOpacity={activeOpacity}
delayPressIn={50}
disabled={disabled || loading}
Expand Down
5 changes: 5 additions & 0 deletions src/components/uis/RadioGroup/RadioButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export type RadioButtonProps = {
selected?: boolean;
endElement?: ReactElement;
startElement?: ReactElement;
accessibilityLabel?: string;
};

const Container = styled.TouchableOpacity`
Expand All @@ -60,6 +61,7 @@ export default function RadioButton({
onPress,
label,
labelPosition = 'right',
accessibilityLabel,
}: RadioButtonProps): ReactElement {
const [innerLayout, setInnerLayout] = useState<LayoutRectangle>();
const fadeAnim = useRef(new Animated.Value(selected ? 1 : 0)).current;
Expand Down Expand Up @@ -131,6 +133,9 @@ export default function RadioButton({

return (
<Container
accessibilityLabel={accessibilityLabel ?? label}
accessibilityRole="radio"
accessibilityState={{selected, disabled}}
activeOpacity={0.9}
disabled={disabled}
onPress={onPress}
Expand Down
Loading