-
Notifications
You must be signed in to change notification settings - Fork 155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: Support for toggle buttons in button group #2909
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3790,11 +3790,21 @@ exports[`Documenter definition for button-group matches the snapshot: button-gro | |
"detailInlineType": { | ||
"name": "InternalButtonGroupProps.ItemClickDetails", | ||
"properties": [ | ||
{ | ||
"name": "checked", | ||
"optional": true, | ||
"type": "false | true", | ||
}, | ||
{ | ||
"name": "id", | ||
"optional": false, | ||
"type": "string", | ||
}, | ||
{ | ||
"name": "pressed", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This property is present when the new toggle button is clicked. |
||
"optional": true, | ||
"type": "false | true", | ||
}, | ||
], | ||
"type": "object", | ||
}, | ||
|
@@ -3861,15 +3871,33 @@ use the \`id\` attribute, consider setting it on a parent element instead.", | |
### icon-button | ||
|
||
* \`id\` (string) - The unique identifier of the button, used as detail in \`onItemClick\` handler and to focus the button using \`ref.focus(id)\`. | ||
* \`text\` (string) - The name shown as a tooltip or menu text for this button. | ||
* \`disabled\` (optional, boolean) - The disabled state indication for the button. | ||
* \`loading\` (optional, boolean) - The loading state indication for the button. | ||
* \`text\` (string) - The name shown as a tooltip for this button. | ||
* \`disabled\` (optional, boolean) - The disabled state indication for this button. | ||
* \`loading\` (optional, boolean) - The loading state indication for this button. | ||
* \`loadingText\` (optional, string) - The loading text announced to screen readers. | ||
* \`iconName\` (optional, string) - Specifies the name of the icon, used with the [icon component](/components/icon/). | ||
* \`iconAlt\` (optional, string) - Specifies alternate text for the icon when using \`iconUrl\`. | ||
* \`iconUrl\` (optional, string) - Specifies the URL of a custom icon. | ||
* \`iconSvg\` (optional, ReactNode) - Custom SVG icon. Equivalent to the \`svg\` slot of the [icon component](/components/icon/). | ||
* \`popoverFeedback\` (optional, string) - Text that appears when the user clicks the button. Use to provide feedback to the user. | ||
* \`popoverFeedback\` (optional, ReactNode) - Text that appears when the user clicks the button. Use to provide feedback to the user. | ||
|
||
### icon-toggle-button | ||
|
||
* \`id\` (string) - The unique identifier of the button, used as detail in \`onItemClick\` handler and to focus the button using \`ref.focus(id)\`. | ||
* \`pressed\` (boolean) - The toggle button pressed state. | ||
* \`text\` (string) - The name shown as a tooltip for this button. | ||
* \`pressedText\` (string) - The name shown as a tooltip for this button in pressed state. | ||
* \`disabled\` (optional, boolean) - The disabled state indication for this button. | ||
* \`loading\` (optional, boolean) - The loading state indication for this button. | ||
* \`loadingText\` (optional, string) - The loading text announced to screen readers. | ||
* \`iconName\` (optional, string) - Specifies the name of the icon, used with the [icon component](/components/icon/). | ||
* \`iconUrl\` (optional, string) - Specifies the URL of a custom icon. | ||
* \`iconSvg\` (optional, ReactNode) - Custom SVG icon. Equivalent to the \`svg\` slot of the [icon component](/components/icon/). | ||
* \`pressedIconName\` (optional, string) - Specifies the name of the icon in pressed state, used with the [icon component](/components/icon/). | ||
* \`pressedIconUrl\` (optional, string) - Specifies the URL of a custom icon in pressed state. | ||
* \`pressedIconSvg\` (optional, ReactNode) - Custom SVG icon in pressed state. Equivalent to the \`svg\` slot of the [icon component](/components/icon/). | ||
* \`popoverFeedback\` (optional, ReactNode) - Text that appears when the user clicks the button. Use to provide feedback to the user. | ||
* \`pressedPopoverFeedback\` (optional, ReactNode) - Text that appears when the user clicks the button in pressed state. Defaults to \`popoverFeedback\`. | ||
|
||
### menu-dropdown | ||
|
||
|
@@ -3880,7 +3908,7 @@ use the \`id\` attribute, consider setting it on a parent element instead.", | |
* \`loadingText\` (optional, string) - The loading text announced to screen readers. | ||
* \`items\` (ButtonDropdownProps.ItemOrGroup[]) - The array of dropdown items that belong to this menu. | ||
|
||
group | ||
### group | ||
|
||
* \`text\` (string) - The name of the group rendered as ARIA label for this group. | ||
* \`items\` ((ButtonGroupProps.IconButton | ButtonGroupProps.MenuDropdown)[]) - The array of items that belong to this group. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I reworked button group unit tests. Now all tests are split across multiple files focusing on specific categories of tests. The tests ensure full coverage for all button group item types. In the dev tests we validate dev warnings, fallbacks, and callbacks. |
||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
import { warnOnce } from '@cloudscape-design/component-toolkit/internal'; | ||
|
||
import { ButtonGroupProps } from '../../../lib/components/button-group'; | ||
import { renderButtonGroup } from './common'; | ||
|
||
import buttonStyles from '../../../lib/components/button/styles.css.js'; | ||
|
||
jest.mock('@cloudscape-design/component-toolkit/internal', () => ({ | ||
...jest.requireActual('@cloudscape-design/component-toolkit/internal'), | ||
warnOnce: jest.fn(), | ||
})); | ||
|
||
afterEach(() => { | ||
(warnOnce as jest.Mock).mockReset(); | ||
}); | ||
|
||
const emptyGroup: ButtonGroupProps.ItemOrGroup[] = [ | ||
{ | ||
type: 'group', | ||
text: 'Feedback', | ||
items: [], | ||
}, | ||
]; | ||
|
||
test('warns and renders some icon when no icon specified for icon button', () => { | ||
const { wrapper } = renderButtonGroup({ items: [{ type: 'icon-button', id: 'search', text: 'Search' }] }); | ||
|
||
expect(warnOnce).toHaveBeenCalledTimes(1); | ||
expect(warnOnce).toHaveBeenCalledWith('ButtonGroup', 'Missing icon for item with id: search'); | ||
expect(wrapper.findMenuById('search')!.findAll(`.${buttonStyles.icon}`)).toHaveLength(1); | ||
}); | ||
|
||
test.each([{ pressed: false }, { pressed: true }])( | ||
'warns and renders some icon when no icon specified for icon toggle button, pressed=$pressed', | ||
({ pressed }) => { | ||
const { wrapper } = renderButtonGroup({ | ||
items: [{ type: 'icon-toggle-button', id: 'like', pressed, text: 'Like', pressedText: 'Liked' }], | ||
}); | ||
|
||
expect(warnOnce).toHaveBeenCalledTimes(2); | ||
expect(warnOnce).toHaveBeenCalledWith('ButtonGroup', 'Missing icon for item with id: like'); | ||
expect(warnOnce).toHaveBeenCalledWith('ButtonGroup', 'Missing pressed icon for item with id: like'); | ||
expect(wrapper.findMenuById('like')!.findAll(`.${buttonStyles.icon}`)).toHaveLength(1); | ||
} | ||
); | ||
|
||
test('warns if empty group is provided', () => { | ||
renderButtonGroup({ items: emptyGroup }); | ||
|
||
expect(warnOnce).toHaveBeenCalledTimes(1); | ||
expect(warnOnce).toHaveBeenCalledWith('ButtonGroup', 'Empty group detected. Empty groups are not allowed.'); | ||
}); | ||
|
||
test('uses non-pressed popover feedback if pressed is not provided', () => { | ||
const { wrapper } = renderButtonGroup({ | ||
items: [ | ||
{ | ||
type: 'icon-toggle-button', | ||
id: 'like', | ||
pressed: true, | ||
text: 'Like', | ||
pressedText: 'Liked', | ||
popoverFeedback: 'You like it!', | ||
}, | ||
], | ||
}); | ||
|
||
wrapper.findToggleButtonById('like')!.click(); | ||
expect(wrapper.findTooltip()!.getElement()).toHaveTextContent('You like it!'); | ||
}); | ||
|
||
test('handles item click', () => { | ||
const onItemClick = jest.fn(); | ||
const { wrapper } = renderButtonGroup({ | ||
items: [{ type: 'icon-button', id: 'search', text: 'Search' }], | ||
onItemClick, | ||
}); | ||
|
||
wrapper.findButtonById('search')!.click(); | ||
|
||
expect(onItemClick).toHaveBeenCalledWith(expect.objectContaining({ detail: { id: 'search' } })); | ||
}); | ||
|
||
test('handles toggle item click', () => { | ||
const onItemClick = jest.fn(); | ||
const { wrapper } = renderButtonGroup({ | ||
items: [ | ||
{ | ||
type: 'icon-toggle-button', | ||
id: 'like', | ||
pressed: false, | ||
text: 'Like', | ||
pressedText: 'Liked', | ||
}, | ||
{ | ||
type: 'icon-toggle-button', | ||
id: 'dislike', | ||
pressed: true, | ||
text: 'Dislike', | ||
pressedText: 'Disliked', | ||
}, | ||
], | ||
onItemClick, | ||
}); | ||
|
||
wrapper.findButtonById('like')!.click(); | ||
wrapper.findButtonById('dislike')!.click(); | ||
|
||
expect(onItemClick).toHaveBeenCalledWith(expect.objectContaining({ detail: { id: 'like', pressed: true } })); | ||
expect(onItemClick).toHaveBeenCalledWith(expect.objectContaining({ detail: { id: 'dislike', pressed: false } })); | ||
}); | ||
|
||
test('handles menu click', () => { | ||
const onItemClick = jest.fn(); | ||
const { wrapper } = renderButtonGroup({ | ||
items: [ | ||
{ | ||
type: 'menu-dropdown', | ||
id: 'misc', | ||
text: 'Misc', | ||
items: [ | ||
{ id: 'dark-mode', itemType: 'checkbox', text: 'Dark mode', checked: false }, | ||
{ id: 'compact-mode', itemType: 'checkbox', text: 'Compact mode', checked: true }, | ||
], | ||
}, | ||
], | ||
onItemClick, | ||
}); | ||
|
||
wrapper.findMenuById('misc')!.openDropdown(); | ||
wrapper.findMenuById('misc')!.findItemById('dark-mode')!.click(); | ||
expect(onItemClick).toHaveBeenCalledWith(expect.objectContaining({ detail: { id: 'dark-mode', checked: true } })); | ||
|
||
wrapper.findMenuById('misc')!.openDropdown(); | ||
wrapper.findMenuById('misc')!.findItemById('compact-mode')!.click(); | ||
expect(onItemClick).toHaveBeenCalledWith(expect.objectContaining({ detail: { id: 'compact-mode', checked: false } })); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This property is present when a menu item of type "checkbox" is clicked.