Skip to content
Open
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
10 changes: 8 additions & 2 deletions src/components/Chip/Chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,10 @@ const Chip = ({
children,
icon,
avatar,
selected = false,
selected,
disabled = false,
accessibilityState: _accessibilityState,
'aria-checked': _ariaChecked,
background,
'aria-label': ariaLabel,
role = 'button',
Expand Down Expand Up @@ -300,7 +302,11 @@ const Chip = ({
disabled={disabled}
aria-label={ariaLabel}
role={role}
aria-selected={selected}
{...(Platform.OS === 'web'
? selected !== undefined
? { 'aria-pressed': selected }
: undefined
: { 'aria-selected': selected ?? false })}
aria-disabled={disabled}
testID={testID}
theme={theme}
Expand Down
144 changes: 143 additions & 1 deletion src/components/__tests__/Chip.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { describe, expect, it } from '@jest/globals';
import { Platform } from 'react-native';

import { afterEach, beforeEach, describe, expect, it } from '@jest/globals';
import color from 'color';

import { render, screen } from '../../test-utils';
Expand Down Expand Up @@ -309,3 +311,143 @@ describe('getChipColor - border color', () => {
});
});
});

describe('accessibility', () => {
const originalPlatform = Platform.OS;

afterEach(() => {
Platform.OS = originalPlatform;
});

describe('web', () => {
beforeEach(() => {
Platform.OS = 'web';
});

it('does not expose aria-pressed for an action Chip when selected is omitted', async () => {
await render(<Chip onPress={() => {}}>Action Chip</Chip>);

const button = screen.getByRole('button');
expect(button).not.toHaveProp('aria-pressed');
});

it('exposes aria-pressed as true when selected is true', async () => {
await render(
<Chip onPress={() => {}} selected={true}>
Selected Chip
</Chip>
);

const button = screen.getByRole('button');
expect(button).toHaveProp('aria-pressed', true);
});

it('exposes aria-pressed as false when selected is explicitly false', async () => {
await render(
<Chip onPress={() => {}} selected={false}>
Unselected Chip
</Chip>
);

const button = screen.getByRole('button');
expect(button).toHaveProp('aria-pressed', false);
});

it('removes aria-selected from the interactive button', async () => {
await render(
<Chip onPress={() => {}} selected={true}>
Toggle Chip
</Chip>
);

const button = screen.getByRole('button');
expect(button).not.toHaveProp('aria-selected');
});

it('removes aria-selected when selected is omitted', async () => {
await render(<Chip onPress={() => {}}>Action Chip</Chip>);

const button = screen.getByRole('button');
expect(button).not.toHaveProp('aria-selected');
});
});

describe('native', () => {
it('preserves selected state when selected is true', async () => {
await render(
<Chip onPress={() => {}} selected={true}>
Selected Chip
</Chip>
);

const button = screen.getByRole('button');
expect(button).toHaveProp(
'accessibilityState',
expect.objectContaining({ selected: true })
);
});

it('produces native selected state false when selected is explicitly false', async () => {
await render(
<Chip onPress={() => {}} selected={false}>
Unselected Chip
</Chip>
);

const button = screen.getByRole('button');
expect(button).toHaveProp(
'accessibilityState',
expect.objectContaining({ selected: false })
);
});

it('produces native selected state false when selected is omitted', async () => {
await render(<Chip onPress={() => {}}>Default Chip</Chip>);

const button = screen.getByRole('button');
expect(button).toHaveProp(
'accessibilityState',
expect.objectContaining({ selected: false })
);
});
});

it('does not forward accessibilityState or aria-checked to the outer Surface or interactive TouchableRipple', async () => {
await render(
<Chip
onPress={() => {}}
testID="chip-touchable"
accessibilityState={{ checked: true, busy: true }}
aria-checked={true}
>
Chip
</Chip>
);

const touchable = screen.getByTestId('chip-touchable');
const surface = touchable.parent;

expect(surface).not.toHaveProp('accessibilityState');
expect(surface).not.toHaveProp('aria-checked');

expect(touchable).not.toHaveProp('aria-checked');
expect(touchable).toHaveProp(
'accessibilityState',
expect.not.objectContaining({
checked: expect.anything(),
busy: expect.anything(),
})
);
});

it('does not forward accessibilityState or aria-checked to the interactive button by default', async () => {
await render(<Chip onPress={() => {}}>Default Chip</Chip>);

const button = screen.getByRole('button');
expect(button).not.toHaveProp('aria-checked');
expect(button).toHaveProp(
'accessibilityState',
expect.not.objectContaining({ checked: expect.anything() })
);
});
});
Loading