Skip to content

Commit

Permalink
fix(ui-react): Add fieldset and legend to RadioGroupField (#3279)
Browse files Browse the repository at this point in the history
* add fieldset, visually hidden legend, and aria-hidden label

* update tests

* Create nine-ears-think.md

Co-authored-by: Joe Buono <joebuono@amazon.com>
  • Loading branch information
joebuono and Joe Buono authored Jan 10, 2023
1 parent b4f1f81 commit 4b051db
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 18 deletions.
5 changes: 5 additions & 0 deletions .changeset/nine-ears-think.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@aws-amplify/ui-react": patch
---

fix(ui-react): Add fieldset and legend to RadioGroupField for improved accessibility
14 changes: 10 additions & 4 deletions packages/react/src/primitives/RadioGroupField/RadioGroupField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import { ComponentClassNames } from '../shared/constants';
import { FieldErrorMessage, FieldDescription } from '../Field';
import { Flex } from '../Flex';
import { Label } from '../Label';
import { VisuallyHidden } from '../VisuallyHidden';
import { RadioGroupContext, RadioGroupContextType } from './context';
import { RadioGroupFieldProps, Primitive } from '../types';
import { getTestId } from '../utils/testUtils';
import { useStableId } from '../utils/useStableId';

// Note: RadioGroupField doesn't extend the JSX.IntrinsicElements<'input'> types (instead extending 'typeof Flex')
Expand All @@ -29,15 +31,16 @@ const RadioGroupFieldPrimitive: Primitive<RadioGroupFieldProps, typeof Flex> = (
onChange,
name,
size,
testId,
value,
...rest
},
ref
) => {
const fieldId = useStableId(id);
const labelId = useStableId();
const descriptionId = useStableId();
const ariaDescribedBy = descriptiveText ? descriptionId : undefined;
const radioGroupTestId = getTestId(testId, ComponentClassNames.RadioGroup);

const radioGroupContextValue: RadioGroupContextType = React.useMemo(
() => ({
Expand Down Expand Up @@ -68,16 +71,20 @@ const RadioGroupFieldPrimitive: Primitive<RadioGroupFieldProps, typeof Flex> = (

return (
<Flex
as="fieldset"
className={classNames(
ComponentClassNames.Field,
ComponentClassNames.RadioGroupField,
className
)}
data-size={size}
ref={ref}
role="radiogroup"
testId={testId}
{...rest}
>
<Label id={labelId} visuallyHidden={labelHidden}>
<VisuallyHidden as="legend">{label}</VisuallyHidden>
<Label aria-hidden={true} visuallyHidden={labelHidden}>
{label}
</Label>
<FieldDescription
Expand All @@ -87,10 +94,9 @@ const RadioGroupFieldPrimitive: Primitive<RadioGroupFieldProps, typeof Flex> = (
/>
<Flex
aria-describedby={ariaDescribedBy}
aria-labelledby={labelId}
className={ComponentClassNames.RadioGroup}
id={fieldId}
role="radiogroup"
testId={radioGroupTestId}
>
<RadioGroupContext.Provider value={radioGroupContextValue}>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,16 @@ import {
testFlexProps,
expectFlexContainerStyleProps,
} from '../../Flex/__tests__/Flex.test';
import { getTestId } from '../../utils/testUtils';

describe('RadioFieldGroup test suite', () => {
const basicProps = { label: 'testLabel', name: 'testName', testId: 'testId' };

const radioGroupTestId = getTestId(
basicProps.testId,
ComponentClassNames.RadioGroup
);

const getRadioFieldGroup = ({
label,
name,
Expand Down Expand Up @@ -66,7 +72,7 @@ describe('RadioFieldGroup test suite', () => {
render(<RadioGroupField {...basicProps} ref={ref}></RadioGroupField>);

await screen.findByTestId(basicProps.testId);
expect(ref.current?.nodeName).toBe('DIV');
expect(ref.current?.nodeName).toBe('FIELDSET');
});

it('should render all flex style props', async () => {
Expand All @@ -93,32 +99,36 @@ describe('RadioFieldGroup test suite', () => {
});

describe('Label', () => {
it('should render expected label classname', async () => {
it('should render visually-hidden legend element with label name', async () => {
render(getRadioFieldGroup({ ...basicProps }));

const labelElelment = (await screen.findByText(
const labelElement = (await screen.findAllByText(
basicProps.label
)) as HTMLLabelElement;
expect(labelElelment).toHaveClass(ComponentClassNames.Label);
)) as HTMLLabelElement[];
expect(labelElement[0].nodeName).toBe('LEGEND');
});

it('should map to label correctly', async () => {
it('should render expected label classname', async () => {
render(getRadioFieldGroup({ ...basicProps }));
const radioGroup = await screen.findByRole('radiogroup');
expect(radioGroup).toHaveAccessibleName(basicProps.label);

const labelElement = (await screen.findAllByText(
basicProps.label
)) as HTMLLabelElement[];

expect(labelElement[1]).toHaveClass(ComponentClassNames.Label);
});

it('should have `amplify-visually-hidden` class when labelHidden is true', async () => {
render(getRadioFieldGroup({ ...basicProps, labelHidden: true }));

const labelElelment = await screen.findByText(basicProps.label);
expect(labelElelment).toHaveClass('amplify-visually-hidden');
const labelElement = await screen.findAllByText(basicProps.label);
expect(labelElement[1]).toHaveClass('amplify-visually-hidden');
});
});

describe('RadioGroup', () => {
const expectFunctionality = async (componet) => {
render(componet);
const expectFunctionality = async (component) => {
render(component);
const radios = await screen.findAllByRole('radio');
const html = radios[0];
const css = radios[1];
Expand All @@ -140,7 +150,7 @@ describe('RadioFieldGroup test suite', () => {

it('should render default classname', async () => {
render(getRadioFieldGroup({ ...basicProps }));
const radioGroup = await screen.findByRole('radiogroup');
const radioGroup = await screen.findByTestId(radioGroupTestId);
expect(radioGroup).toHaveClass(ComponentClassNames.RadioGroup);
});

Expand Down Expand Up @@ -238,7 +248,7 @@ describe('RadioFieldGroup test suite', () => {
it('should map to descriptive text correctly', async () => {
const descriptiveText = 'Description';
render(getRadioFieldGroup({ ...basicProps, descriptiveText }));
const radioGroup = await screen.findByRole('radiogroup');
const radioGroup = await screen.findByTestId(radioGroupTestId);
expect(radioGroup).toHaveAccessibleDescription(descriptiveText);
});
});
Expand Down

0 comments on commit 4b051db

Please sign in to comment.