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
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { render } from '@testing-library/react';

import Dropdown from '../../../../../lib/components/internal/components/dropdown';
import DropdownWrapper from '../../../../../lib/components/test-utils/dom/internal/dropdown';

function renderDropdown(jsx: React.ReactElement) {
const { container } = render(jsx);
const dropdownElement = container.querySelector<HTMLElement>(`.${DropdownWrapper.rootSelector}`)!;
return new DropdownWrapper(dropdownElement);
}

describe('Dropdown ARIA attributes', () => {
describe('ariaRole', () => {
test('applies role attribute when ariaRole is provided', () => {
const wrapper = renderDropdown(
<Dropdown trigger={<button />} open={true} ariaRole="dialog" content={<div>Content</div>} />
);
const dropdown = wrapper.findOpenDropdown()!.getElement();
expect(dropdown).toHaveAttribute('role', 'dialog');
});
});

describe('ariaLabel', () => {
test('applies aria-label attribute when ariaLabel is provided', () => {
const wrapper = renderDropdown(
<Dropdown trigger={<button />} open={true} ariaLabel="Select options" content={<div>Content</div>} />
);
const dropdown = wrapper.findOpenDropdown()!.getElement();
expect(dropdown).toHaveAttribute('aria-label', 'Select options');
});
});

describe('ariaLabelledby', () => {
test('applies aria-labelledby attribute when ariaLabelledby is provided', () => {
const wrapper = renderDropdown(
<Dropdown trigger={<button />} open={true} ariaLabelledby="label-id" content={<div>Content</div>} />
);
const dropdown = wrapper.findOpenDropdown()!.getElement();
expect(dropdown).toHaveAttribute('aria-labelledby', 'label-id');
});
});

describe('ariaDescribedby', () => {
test('applies aria-describedby attribute when ariaDescribedby is provided', () => {
const wrapper = renderDropdown(
<Dropdown trigger={<button />} open={true} ariaDescribedby="description-id" content={<div>Content</div>} />
);
const dropdown = wrapper.findOpenDropdown()!.getElement();
expect(dropdown).toHaveAttribute('aria-describedby', 'description-id');
});
});

describe('dropdownContentId', () => {
test('applies id attribute when dropdownContentId is provided', () => {
const wrapper = renderDropdown(
<Dropdown
trigger={<button />}
open={true}
dropdownContentId="custom-dropdown-id"
content={<div>Content</div>}
/>
);
const dropdown = wrapper.findOpenDropdown()!.getElement();
expect(dropdown).toHaveAttribute('id', 'custom-dropdown-id');
});
});

describe('aria-hidden', () => {
test('sets aria-hidden to true when dropdown is closed', () => {
const wrapper = renderDropdown(
<Dropdown trigger={<button />} open={false} ariaRole="dialog" content={<div>Content</div>} />
);
const dropdownContainer = wrapper.getElement().querySelector('[data-open]');
expect(dropdownContainer).toHaveAttribute('aria-hidden', 'true');
});

test('sets aria-hidden to false when dropdown is open', () => {
const wrapper = renderDropdown(
<Dropdown trigger={<button />} open={true} ariaRole="dialog" content={<div>Content</div>} />
);
const dropdown = wrapper.getElement().querySelector('[data-open]');
expect(dropdown).toHaveAttribute('aria-hidden', 'false');
});
});
});
15 changes: 10 additions & 5 deletions src/internal/components/dropdown/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ interface TransitionContentProps {
open?: boolean;
onMouseDown?: React.MouseEventHandler<Element>;
id?: string;
role?: string;
ariaRole?: string;
ariaLabel?: string;
ariaLabelledby?: string;
ariaDescribedby?: string;
}
Expand All @@ -105,7 +106,8 @@ const TransitionContent = ({
open,
onMouseDown,
id,
role,
ariaRole,
ariaLabel,
ariaLabelledby,
ariaDescribedby,
}: TransitionContentProps) => {
Expand All @@ -130,7 +132,8 @@ const TransitionContent = ({
})}
ref={contentRef}
id={id}
role={role}
role={ariaRole}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's on the div, so it stays role :)

aria-label={ariaLabel}
aria-labelledby={ariaLabelledby}
aria-describedby={ariaDescribedby}
data-open={open}
Expand Down Expand Up @@ -181,7 +184,8 @@ const Dropdown = ({
onBlur,
contentKey,
dropdownContentId,
dropdownContentRole,
ariaRole,
ariaLabel,
ariaLabelledby,
ariaDescribedby,
}: DropdownProps) => {
Expand Down Expand Up @@ -489,7 +493,8 @@ const Dropdown = ({
verticalContainerRef={verticalContainerRef}
position={position}
id={dropdownContentId}
role={dropdownContentRole}
ariaRole={ariaRole}
ariaLabel={ariaLabel}
ariaLabelledby={ariaLabelledby}
ariaDescribedby={ariaDescribedby}
/>
Expand Down
7 changes: 6 additions & 1 deletion src/internal/components/dropdown/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,12 @@ export interface DropdownProps extends ExpandToViewport {
/**
* HTML role for the dropdown content wrapper
*/
dropdownContentRole?: string;
ariaRole?: string;

/**
* Aria label for the dropdown content wrapper
*/
ariaLabel?: string;

/**
* Labelledby for the dropdown (required when role="dialog")
Expand Down
6 changes: 2 additions & 4 deletions src/multiselect/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,8 @@ const InternalMultiselect = React.forwardRef(
>
<Dropdown
{...dropdownProps}
ariaLabelledby={dropdownProps.dropdownContentRole ? joinStrings(ariaLabelId, controlId) : undefined}
ariaDescribedby={
dropdownProps.dropdownContentRole ? (dropdownStatus.content ? footerId : undefined) : undefined
}
ariaLabelledby={dropdownProps.ariaRole ? joinStrings(ariaLabelId, controlId) : undefined}
ariaDescribedby={dropdownProps.ariaRole ? (dropdownStatus.content ? footerId : undefined) : undefined}
open={multiselectProps.isOpen}
minWidth={expandToViewport ? undefined : 'trigger'}
maxWidth={getBreakpointValue('xxs')} // AWSUI-19898
Expand Down
6 changes: 2 additions & 4 deletions src/select/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,8 @@ const InternalSelect = React.forwardRef(
>
<Dropdown
{...dropdownProps}
ariaLabelledby={dropdownProps.dropdownContentRole ? joinStrings(selectAriaLabelId, controlId) : undefined}
ariaDescribedby={
dropdownProps.dropdownContentRole ? (dropdownStatus.content ? footerId : undefined) : undefined
}
ariaLabelledby={dropdownProps.ariaRole ? joinStrings(selectAriaLabelId, controlId) : undefined}
ariaDescribedby={dropdownProps.ariaRole ? (dropdownStatus.content ? footerId : undefined) : undefined}
open={isOpen}
stretchTriggerHeight={!!__inFilteringToken}
minWidth={expandToViewport ? undefined : 'trigger'}
Expand Down
7 changes: 2 additions & 5 deletions src/select/utils/use-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,11 @@ export function useSelect({
goHome: goHomeWithKeyboard,
});

const getDropdownProps: () => Pick<
DropdownProps,
'onFocus' | 'onBlur' | 'dropdownContentId' | 'dropdownContentRole'
> = () => ({
const getDropdownProps: () => Pick<DropdownProps, 'onFocus' | 'onBlur' | 'dropdownContentId' | 'ariaRole'> = () => ({
onFocus: handleFocus,
onBlur: handleBlur,
dropdownContentId: dialogId,
dropdownContentRole: hasFilter ? 'dialog' : undefined,
ariaRole: hasFilter ? 'dialog' : undefined,
});

const getTriggerProps = (disabled = false, autoFocus = false) => {
Expand Down
Loading