@arbor-education/design-system.components 0.5.5 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +39 -0
- package/dist/components/avatar/Avatar.d.ts +1 -1
- package/dist/components/avatar/Avatar.d.ts.map +1 -1
- package/dist/components/avatar/Avatar.js +1 -1
- package/dist/components/avatar/Avatar.js.map +1 -1
- package/dist/components/avatar/Avatar.stories.d.ts.map +1 -1
- package/dist/components/avatar/Avatar.stories.js +7 -0
- package/dist/components/avatar/Avatar.stories.js.map +1 -1
- package/dist/components/avatarGroup/AvatarGroup.d.ts +32 -0
- package/dist/components/avatarGroup/AvatarGroup.d.ts.map +1 -0
- package/dist/components/avatarGroup/AvatarGroup.js +37 -0
- package/dist/components/avatarGroup/AvatarGroup.js.map +1 -0
- package/dist/components/avatarGroup/AvatarGroup.stories.d.ts +14 -0
- package/dist/components/avatarGroup/AvatarGroup.stories.d.ts.map +1 -0
- package/dist/components/avatarGroup/AvatarGroup.stories.js +91 -0
- package/dist/components/avatarGroup/AvatarGroup.stories.js.map +1 -0
- package/dist/components/avatarGroup/AvatarGroup.test.d.ts +2 -0
- package/dist/components/avatarGroup/AvatarGroup.test.d.ts.map +1 -0
- package/dist/components/avatarGroup/AvatarGroup.test.js +155 -0
- package/dist/components/avatarGroup/AvatarGroup.test.js.map +1 -0
- package/dist/components/avatarGroup/avatarGroupOverflow.d.ts +8 -0
- package/dist/components/avatarGroup/avatarGroupOverflow.d.ts.map +1 -0
- package/dist/components/avatarGroup/avatarGroupOverflow.js +14 -0
- package/dist/components/avatarGroup/avatarGroupOverflow.js.map +1 -0
- package/dist/components/avatarGroup/collectAvatarGroupChildren.d.ts +4 -0
- package/dist/components/avatarGroup/collectAvatarGroupChildren.d.ts.map +1 -0
- package/dist/components/avatarGroup/collectAvatarGroupChildren.js +26 -0
- package/dist/components/avatarGroup/collectAvatarGroupChildren.js.map +1 -0
- package/dist/components/avatarGroup/useAvatarGroupItems.d.ts +9 -0
- package/dist/components/avatarGroup/useAvatarGroupItems.d.ts.map +1 -0
- package/dist/components/avatarGroup/useAvatarGroupItems.js +24 -0
- package/dist/components/avatarGroup/useAvatarGroupItems.js.map +1 -0
- package/dist/components/badge/Badge.d.ts +12 -0
- package/dist/components/badge/Badge.d.ts.map +1 -0
- package/dist/components/badge/Badge.js +6 -0
- package/dist/components/badge/Badge.js.map +1 -0
- package/dist/components/badge/Badge.stories.d.ts +10 -0
- package/dist/components/badge/Badge.stories.d.ts.map +1 -0
- package/dist/components/badge/Badge.stories.js +51 -0
- package/dist/components/badge/Badge.stories.js.map +1 -0
- package/dist/components/badge/Badge.test.d.ts +2 -0
- package/dist/components/badge/Badge.test.d.ts.map +1 -0
- package/dist/components/badge/Badge.test.js +23 -0
- package/dist/components/badge/Badge.test.js.map +1 -0
- package/dist/components/card/Card.js +1 -1
- package/dist/components/card/Card.js.map +1 -1
- package/dist/components/combobox/Combobox.d.ts +16 -0
- package/dist/components/combobox/Combobox.d.ts.map +1 -0
- package/dist/components/combobox/Combobox.js +195 -0
- package/dist/components/combobox/Combobox.js.map +1 -0
- package/dist/components/combobox/Combobox.stories.d.ts +24 -0
- package/dist/components/combobox/Combobox.stories.d.ts.map +1 -0
- package/dist/components/combobox/Combobox.stories.js +246 -0
- package/dist/components/combobox/Combobox.stories.js.map +1 -0
- package/dist/components/combobox/Combobox.test.d.ts +2 -0
- package/dist/components/combobox/Combobox.test.d.ts.map +1 -0
- package/dist/components/combobox/Combobox.test.js +798 -0
- package/dist/components/combobox/Combobox.test.js.map +1 -0
- package/dist/components/combobox/ComboboxButtonTrigger.d.ts +28 -0
- package/dist/components/combobox/ComboboxButtonTrigger.d.ts.map +1 -0
- package/dist/components/combobox/ComboboxButtonTrigger.js +64 -0
- package/dist/components/combobox/ComboboxButtonTrigger.js.map +1 -0
- package/dist/components/combobox/ComboboxListbox.d.ts +44 -0
- package/dist/components/combobox/ComboboxListbox.d.ts.map +1 -0
- package/dist/components/combobox/ComboboxListbox.js +37 -0
- package/dist/components/combobox/ComboboxListbox.js.map +1 -0
- package/dist/components/combobox/ComboboxOptionRow.d.ts +23 -0
- package/dist/components/combobox/ComboboxOptionRow.d.ts.map +1 -0
- package/dist/components/combobox/ComboboxOptionRow.js +27 -0
- package/dist/components/combobox/ComboboxOptionRow.js.map +1 -0
- package/dist/components/combobox/ComboboxTrigger.d.ts +35 -0
- package/dist/components/combobox/ComboboxTrigger.d.ts.map +1 -0
- package/dist/components/combobox/ComboboxTrigger.js +15 -0
- package/dist/components/combobox/ComboboxTrigger.js.map +1 -0
- package/dist/components/combobox/buildListboxDisplayOptions.d.ts +3 -0
- package/dist/components/combobox/buildListboxDisplayOptions.d.ts.map +1 -0
- package/dist/components/combobox/buildListboxDisplayOptions.js +13 -0
- package/dist/components/combobox/buildListboxDisplayOptions.js.map +1 -0
- package/dist/components/combobox/buildListboxDisplayOptions.test.d.ts +2 -0
- package/dist/components/combobox/buildListboxDisplayOptions.test.d.ts.map +1 -0
- package/dist/components/combobox/buildListboxDisplayOptions.test.js +22 -0
- package/dist/components/combobox/buildListboxDisplayOptions.test.js.map +1 -0
- package/dist/components/combobox/comboboxKeyboardTypes.d.ts +41 -0
- package/dist/components/combobox/comboboxKeyboardTypes.d.ts.map +1 -0
- package/dist/components/combobox/comboboxKeyboardTypes.js +2 -0
- package/dist/components/combobox/comboboxKeyboardTypes.js.map +1 -0
- package/dist/components/combobox/highlightLabel.d.ts +10 -0
- package/dist/components/combobox/highlightLabel.d.ts.map +1 -0
- package/dist/components/combobox/highlightLabel.js +18 -0
- package/dist/components/combobox/highlightLabel.js.map +1 -0
- package/dist/components/combobox/normaliseComboboxQuery.d.ts +2 -0
- package/dist/components/combobox/normaliseComboboxQuery.d.ts.map +1 -0
- package/dist/components/combobox/normaliseComboboxQuery.js +2 -0
- package/dist/components/combobox/normaliseComboboxQuery.js.map +1 -0
- package/dist/components/combobox/types.d.ts +46 -0
- package/dist/components/combobox/types.d.ts.map +1 -0
- package/dist/components/combobox/types.js +2 -0
- package/dist/components/combobox/types.js.map +1 -0
- package/dist/components/combobox/useChipSelection.d.ts +11 -0
- package/dist/components/combobox/useChipSelection.d.ts.map +1 -0
- package/dist/components/combobox/useChipSelection.js +35 -0
- package/dist/components/combobox/useChipSelection.js.map +1 -0
- package/dist/components/combobox/useComboboxChipKeyboard.d.ts +3 -0
- package/dist/components/combobox/useComboboxChipKeyboard.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxChipKeyboard.js +103 -0
- package/dist/components/combobox/useComboboxChipKeyboard.js.map +1 -0
- package/dist/components/combobox/useComboboxChipKeyboard.test.d.ts +2 -0
- package/dist/components/combobox/useComboboxChipKeyboard.test.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxChipKeyboard.test.js +116 -0
- package/dist/components/combobox/useComboboxChipKeyboard.test.js.map +1 -0
- package/dist/components/combobox/useComboboxKeyboard.d.ts +4 -0
- package/dist/components/combobox/useComboboxKeyboard.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxKeyboard.js +68 -0
- package/dist/components/combobox/useComboboxKeyboard.js.map +1 -0
- package/dist/components/combobox/useComboboxListboxDom.d.ts +11 -0
- package/dist/components/combobox/useComboboxListboxDom.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxListboxDom.js +15 -0
- package/dist/components/combobox/useComboboxListboxDom.js.map +1 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.d.ts +3 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.js +143 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.js.map +1 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.test.d.ts +2 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.test.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.test.js +152 -0
- package/dist/components/combobox/useComboboxListboxKeyboard.test.js.map +1 -0
- package/dist/components/combobox/useComboboxPopoverBehavior.d.ts +38 -0
- package/dist/components/combobox/useComboboxPopoverBehavior.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxPopoverBehavior.js +104 -0
- package/dist/components/combobox/useComboboxPopoverBehavior.js.map +1 -0
- package/dist/components/combobox/useComboboxState.d.ts +27 -0
- package/dist/components/combobox/useComboboxState.d.ts.map +1 -0
- package/dist/components/combobox/useComboboxState.js +122 -0
- package/dist/components/combobox/useComboboxState.js.map +1 -0
- package/dist/components/combobox/useElementWidth.d.ts +2 -0
- package/dist/components/combobox/useElementWidth.d.ts.map +1 -0
- package/dist/components/combobox/useElementWidth.js +31 -0
- package/dist/components/combobox/useElementWidth.js.map +1 -0
- package/dist/components/combobox/useVisibleChips.d.ts +21 -0
- package/dist/components/combobox/useVisibleChips.d.ts.map +1 -0
- package/dist/components/combobox/useVisibleChips.js +59 -0
- package/dist/components/combobox/useVisibleChips.js.map +1 -0
- package/dist/components/combobox/useVisibleChips.test.d.ts +2 -0
- package/dist/components/combobox/useVisibleChips.test.d.ts.map +1 -0
- package/dist/components/combobox/useVisibleChips.test.js +81 -0
- package/dist/components/combobox/useVisibleChips.test.js.map +1 -0
- package/dist/components/dot/Dot.d.ts +8 -0
- package/dist/components/dot/Dot.d.ts.map +1 -0
- package/dist/components/dot/Dot.js +6 -0
- package/dist/components/dot/Dot.js.map +1 -0
- package/dist/components/dot/Dot.stories.d.ts +15 -0
- package/dist/components/dot/Dot.stories.d.ts.map +1 -0
- package/dist/components/dot/Dot.stories.js +25 -0
- package/dist/components/dot/Dot.stories.js.map +1 -0
- package/dist/components/dot/Dot.test.d.ts +2 -0
- package/dist/components/dot/Dot.test.d.ts.map +1 -0
- package/dist/components/dot/Dot.test.js +19 -0
- package/dist/components/dot/Dot.test.js.map +1 -0
- package/dist/components/formField/FormField.d.ts +8 -4
- package/dist/components/formField/FormField.d.ts.map +1 -1
- package/dist/components/formField/FormField.js +7 -6
- package/dist/components/formField/FormField.js.map +1 -1
- package/dist/components/formField/FormField.stories.d.ts +1 -0
- package/dist/components/formField/FormField.stories.d.ts.map +1 -1
- package/dist/components/formField/FormField.stories.js +13 -1
- package/dist/components/formField/FormField.stories.js.map +1 -1
- package/dist/components/formField/FormField.test.js +10 -0
- package/dist/components/formField/FormField.test.js.map +1 -1
- package/dist/components/icon/allowedIcons.d.ts +1 -0
- package/dist/components/icon/allowedIcons.d.ts.map +1 -1
- package/dist/components/icon/allowedIcons.js +2 -1
- package/dist/components/icon/allowedIcons.js.map +1 -1
- package/dist/components/progress/Progress.stories.d.ts +49 -49
- package/dist/components/singleUser/SingleUser.d.ts +15 -0
- package/dist/components/singleUser/SingleUser.d.ts.map +1 -0
- package/dist/components/singleUser/SingleUser.js +9 -0
- package/dist/components/singleUser/SingleUser.js.map +1 -0
- package/dist/components/singleUser/SingleUser.stories.d.ts +11 -0
- package/dist/components/singleUser/SingleUser.stories.d.ts.map +1 -0
- package/dist/components/singleUser/SingleUser.stories.js +52 -0
- package/dist/components/singleUser/SingleUser.stories.js.map +1 -0
- package/dist/components/singleUser/SingleUser.test.d.ts +2 -0
- package/dist/components/singleUser/SingleUser.test.d.ts.map +1 -0
- package/dist/components/singleUser/SingleUser.test.js +30 -0
- package/dist/components/singleUser/SingleUser.test.js.map +1 -0
- package/dist/components/table/Table.d.ts +1 -0
- package/dist/components/table/Table.d.ts.map +1 -1
- package/dist/components/table/Table.js +2 -0
- package/dist/components/table/Table.js.map +1 -1
- package/dist/components/table/Table.stories.d.ts.map +1 -1
- package/dist/components/table/Table.stories.js +447 -77
- package/dist/components/table/Table.stories.js.map +1 -1
- package/dist/components/table/Table.test.js +1 -1
- package/dist/components/table/Table.test.js.map +1 -1
- package/dist/components/table/TableSettingsDropdown.d.ts.map +1 -0
- package/dist/components/table/{pagination/TableSettingsDropdown.js → TableSettingsDropdown.js} +6 -6
- package/dist/components/table/TableSettingsDropdown.js.map +1 -0
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.d.ts +2 -5
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.d.ts.map +1 -1
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.js +7 -5
- package/dist/components/table/cellRenderers/SelectDropdownCellRenderer.js.map +1 -1
- package/dist/components/tabs/TabsItem.stories.d.ts +2 -2
- package/dist/components/tag/Tag.d.ts +9 -6
- package/dist/components/tag/Tag.d.ts.map +1 -1
- package/dist/components/tag/Tag.js +8 -2
- package/dist/components/tag/Tag.js.map +1 -1
- package/dist/components/tag/Tag.stories.d.ts +11 -6
- package/dist/components/tag/Tag.stories.d.ts.map +1 -1
- package/dist/components/tag/Tag.stories.js +68 -4
- package/dist/components/tag/Tag.stories.js.map +1 -1
- package/dist/components/tag/Tag.test.js +86 -50
- package/dist/components/tag/Tag.test.js.map +1 -1
- package/dist/components/toggle/Toggle.d.ts +3 -0
- package/dist/components/toggle/Toggle.d.ts.map +1 -0
- package/dist/components/toggle/Toggle.js +8 -0
- package/dist/components/toggle/Toggle.js.map +1 -0
- package/dist/components/toggle/Toggle.stories.d.ts +97 -0
- package/dist/components/toggle/Toggle.stories.d.ts.map +1 -0
- package/dist/components/toggle/Toggle.stories.js +186 -0
- package/dist/components/toggle/Toggle.stories.js.map +1 -0
- package/dist/components/toggle/Toggle.test.d.ts +2 -0
- package/dist/components/toggle/Toggle.test.d.ts.map +1 -0
- package/dist/components/toggle/Toggle.test.js +58 -0
- package/dist/components/toggle/Toggle.test.js.map +1 -0
- package/dist/index.css +689 -25
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +34 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -24
- package/dist/index.js.map +1 -1
- package/dist/mocks/comboboxStoryOptions.d.ts +5 -0
- package/dist/mocks/comboboxStoryOptions.d.ts.map +1 -0
- package/dist/mocks/comboboxStoryOptions.js +22 -0
- package/dist/mocks/comboboxStoryOptions.js.map +1 -0
- package/dist/utils/isSelectAllChord.d.ts +5 -0
- package/dist/utils/isSelectAllChord.d.ts.map +1 -0
- package/dist/utils/isSelectAllChord.js +7 -0
- package/dist/utils/isSelectAllChord.js.map +1 -0
- package/dist/utils/isSelectAllChord.test.d.ts +2 -0
- package/dist/utils/isSelectAllChord.test.d.ts.map +1 -0
- package/dist/utils/isSelectAllChord.test.js +19 -0
- package/dist/utils/isSelectAllChord.test.js.map +1 -0
- package/dist/utils/nextCircularIndex.d.ts +3 -0
- package/dist/utils/nextCircularIndex.d.ts.map +1 -0
- package/dist/utils/nextCircularIndex.js +10 -0
- package/dist/utils/nextCircularIndex.js.map +1 -0
- package/dist/utils/nextCircularIndex.test.d.ts +2 -0
- package/dist/utils/nextCircularIndex.test.d.ts.map +1 -0
- package/dist/utils/nextCircularIndex.test.js +23 -0
- package/dist/utils/nextCircularIndex.test.js.map +1 -0
- package/dist/utils/scrollElementIntoViewById.d.ts +2 -0
- package/dist/utils/scrollElementIntoViewById.d.ts.map +1 -0
- package/dist/utils/scrollElementIntoViewById.js +16 -0
- package/dist/utils/scrollElementIntoViewById.js.map +1 -0
- package/dist/utils/scrollElementIntoViewById.test.d.ts +2 -0
- package/dist/utils/scrollElementIntoViewById.test.d.ts.map +1 -0
- package/dist/utils/scrollElementIntoViewById.test.js +31 -0
- package/dist/utils/scrollElementIntoViewById.test.js.map +1 -0
- package/package.json +1 -1
- package/src/components/avatar/Avatar.stories.tsx +8 -0
- package/src/components/avatar/Avatar.tsx +3 -3
- package/src/components/avatarGroup/AvatarGroup.stories.tsx +218 -0
- package/src/components/avatarGroup/AvatarGroup.test.tsx +298 -0
- package/src/components/avatarGroup/AvatarGroup.tsx +127 -0
- package/src/components/avatarGroup/avatarGroup.scss +31 -0
- package/src/components/avatarGroup/avatarGroupOverflow.ts +29 -0
- package/src/components/avatarGroup/collectAvatarGroupChildren.ts +30 -0
- package/src/components/avatarGroup/useAvatarGroupItems.ts +39 -0
- package/src/components/badge/Badge.stories.tsx +74 -0
- package/src/components/badge/Badge.test.tsx +28 -0
- package/src/components/badge/Badge.tsx +35 -0
- package/src/components/badge/badge.scss +86 -0
- package/src/components/card/Card.tsx +1 -1
- package/src/components/combobox/Combobox.stories.tsx +340 -0
- package/src/components/combobox/Combobox.test.tsx +1160 -0
- package/src/components/combobox/Combobox.tsx +434 -0
- package/src/components/combobox/ComboboxButtonTrigger.tsx +195 -0
- package/src/components/combobox/ComboboxListbox.tsx +224 -0
- package/src/components/combobox/ComboboxOptionRow.tsx +128 -0
- package/src/components/combobox/ComboboxTrigger.tsx +134 -0
- package/src/components/combobox/buildListboxDisplayOptions.test.ts +24 -0
- package/src/components/combobox/buildListboxDisplayOptions.ts +12 -0
- package/src/components/combobox/combobox.scss +390 -0
- package/src/components/combobox/comboboxKeyboardTypes.ts +45 -0
- package/src/components/combobox/highlightLabel.tsx +42 -0
- package/src/components/combobox/normaliseComboboxQuery.ts +1 -0
- package/src/components/combobox/types.ts +53 -0
- package/src/components/combobox/useChipSelection.ts +53 -0
- package/src/components/combobox/useComboboxChipKeyboard.test.tsx +141 -0
- package/src/components/combobox/useComboboxChipKeyboard.ts +121 -0
- package/src/components/combobox/useComboboxKeyboard.ts +108 -0
- package/src/components/combobox/useComboboxListboxDom.ts +36 -0
- package/src/components/combobox/useComboboxListboxKeyboard.test.tsx +186 -0
- package/src/components/combobox/useComboboxListboxKeyboard.ts +172 -0
- package/src/components/combobox/useComboboxPopoverBehavior.ts +179 -0
- package/src/components/combobox/useComboboxState.ts +232 -0
- package/src/components/combobox/useElementWidth.ts +40 -0
- package/src/components/combobox/useVisibleChips.test.tsx +91 -0
- package/src/components/combobox/useVisibleChips.ts +100 -0
- package/src/components/dot/Dot.stories.tsx +41 -0
- package/src/components/dot/Dot.test.tsx +21 -0
- package/src/components/dot/Dot.tsx +18 -0
- package/src/components/dot/dot.scss +35 -0
- package/src/components/formField/FormField.stories.tsx +30 -1
- package/src/components/formField/FormField.test.tsx +20 -0
- package/src/components/formField/FormField.tsx +11 -5
- package/src/components/formField/inputs/number/numberInput.scss +12 -4
- package/src/components/icon/allowedIcons.tsx +2 -0
- package/src/components/pill/pill.scss +4 -6
- package/src/components/singleUser/SingleUser.stories.tsx +63 -0
- package/src/components/singleUser/SingleUser.test.tsx +61 -0
- package/src/components/singleUser/SingleUser.tsx +45 -0
- package/src/components/singleUser/singleUser.scss +14 -0
- package/src/components/table/Table.stories.tsx +678 -265
- package/src/components/table/Table.test.tsx +1 -1
- package/src/components/table/Table.tsx +2 -0
- package/src/components/table/{pagination/TableSettingsDropdown.tsx → TableSettingsDropdown.tsx} +1 -1
- package/src/components/table/cellRenderers/SelectDropdownCellRenderer.tsx +42 -12
- package/src/components/tag/Tag.stories.tsx +88 -6
- package/src/components/tag/Tag.test.tsx +110 -44
- package/src/components/tag/Tag.tsx +38 -14
- package/src/components/tag/tag.scss +45 -30
- package/src/components/toggle/Toggle.stories.tsx +239 -0
- package/src/components/toggle/Toggle.test.tsx +66 -0
- package/src/components/toggle/Toggle.tsx +12 -0
- package/src/components/toggle/toggle.scss +126 -0
- package/src/global.scss +1 -1
- package/src/index.scss +6 -0
- package/src/index.ts +47 -24
- package/src/mocks/comboboxStoryOptions.ts +25 -0
- package/src/tokens.scss +35 -4
- package/src/utils/isSelectAllChord.test.ts +24 -0
- package/src/utils/isSelectAllChord.ts +8 -0
- package/src/utils/nextCircularIndex.test.ts +26 -0
- package/src/utils/nextCircularIndex.ts +15 -0
- package/src/utils/scrollElementIntoViewById.test.ts +38 -0
- package/src/utils/scrollElementIntoViewById.ts +20 -0
- package/tokens/json/Arbor.json +3816 -3680
- package/dist/components/table/pagination/TableSettingsDropdown.d.ts.map +0 -1
- package/dist/components/table/pagination/TableSettingsDropdown.js.map +0 -1
- /package/dist/components/table/{pagination/TableSettingsDropdown.d.ts → TableSettingsDropdown.d.ts} +0 -0
|
@@ -0,0 +1,798 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import '@testing-library/jest-dom/vitest';
|
|
3
|
+
import { fireEvent, render, screen, within } from '@testing-library/react';
|
|
4
|
+
import userEvent from '@testing-library/user-event';
|
|
5
|
+
import { describe, expect, test, vi } from 'vitest';
|
|
6
|
+
import { Combobox } from './Combobox';
|
|
7
|
+
const people = [
|
|
8
|
+
{ value: 'alice', label: 'Alice Johnson', iconName: 'user' },
|
|
9
|
+
{ value: 'bob', label: 'Bob Smith', iconName: 'user' },
|
|
10
|
+
{ value: 'charlie', label: 'Charlie Brown', iconName: 'user' },
|
|
11
|
+
];
|
|
12
|
+
describe('Combobox', () => {
|
|
13
|
+
const openWithChevron = async () => {
|
|
14
|
+
if (screen.queryByRole('listbox'))
|
|
15
|
+
return;
|
|
16
|
+
await userEvent.click(screen.getByRole('button', { name: 'Open suggestions' }));
|
|
17
|
+
};
|
|
18
|
+
test('renders with placeholder when nothing is selected', () => {
|
|
19
|
+
render(_jsx(Combobox, { options: people, placeholder: "Search people..." }));
|
|
20
|
+
expect(screen.getByPlaceholderText('Search people...')).toBeInTheDocument();
|
|
21
|
+
});
|
|
22
|
+
test('has correct ARIA attributes on the input', () => {
|
|
23
|
+
render(_jsx(Combobox, { options: people, "aria-label": "People" }));
|
|
24
|
+
const input = screen.getByRole('combobox');
|
|
25
|
+
expect(input).toHaveAttribute('aria-autocomplete', 'list');
|
|
26
|
+
expect(input).toHaveAttribute('aria-expanded', 'false');
|
|
27
|
+
expect(input).toHaveAttribute('aria-label', 'People');
|
|
28
|
+
});
|
|
29
|
+
test('aria-controls always references the listbox id', () => {
|
|
30
|
+
render(_jsx(Combobox, { options: people, id: "test-cb" }));
|
|
31
|
+
const input = screen.getByRole('combobox');
|
|
32
|
+
expect(input).toHaveAttribute('aria-controls', 'test-cb-listbox');
|
|
33
|
+
});
|
|
34
|
+
test('opens listbox on focus by default when options are available', async () => {
|
|
35
|
+
render(_jsx(Combobox, { options: people }));
|
|
36
|
+
const input = screen.getByRole('combobox');
|
|
37
|
+
fireEvent.focus(input);
|
|
38
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
39
|
+
});
|
|
40
|
+
test('does not open listbox on focus when dropdownOnFocus is false', () => {
|
|
41
|
+
render(_jsx(Combobox, { options: people, dropdownOnFocus: false }));
|
|
42
|
+
const input = screen.getByRole('combobox');
|
|
43
|
+
fireEvent.focus(input);
|
|
44
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
45
|
+
});
|
|
46
|
+
test('does not force an empty panel on focus when dropdownOnFocus is true but no items are available', () => {
|
|
47
|
+
render(_jsx(Combobox, { options: [], dropdownOnFocus: true }));
|
|
48
|
+
const input = screen.getByRole('combobox');
|
|
49
|
+
fireEvent.focus(input);
|
|
50
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
51
|
+
});
|
|
52
|
+
test('showDropdownTrigger=false hides the chevron button', () => {
|
|
53
|
+
render(_jsx(Combobox, { options: people, showDropdownTrigger: false }));
|
|
54
|
+
expect(screen.queryByRole('button', { name: /suggestions/i })).not.toBeInTheDocument();
|
|
55
|
+
});
|
|
56
|
+
test('showDropdownTrigger=false still allows focus opening', () => {
|
|
57
|
+
render(_jsx(Combobox, { options: people, showDropdownTrigger: false }));
|
|
58
|
+
const input = screen.getByRole('combobox');
|
|
59
|
+
fireEvent.focus(input);
|
|
60
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
61
|
+
});
|
|
62
|
+
test('clicking the input opens reliably when dropdownOnFocus is enabled', async () => {
|
|
63
|
+
render(_jsx(Combobox, { options: people }));
|
|
64
|
+
const input = screen.getByRole('combobox');
|
|
65
|
+
await userEvent.click(input);
|
|
66
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
67
|
+
});
|
|
68
|
+
test('clicking the trigger reopens even when the input is already focused', async () => {
|
|
69
|
+
render(_jsx(Combobox, { options: people }));
|
|
70
|
+
const input = screen.getByRole('combobox');
|
|
71
|
+
const trigger = input.closest('.ds-combobox__trigger');
|
|
72
|
+
fireEvent.focus(input);
|
|
73
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
74
|
+
fireEvent.keyDown(input, { key: 'Escape' });
|
|
75
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
76
|
+
expect(trigger).toBeTruthy();
|
|
77
|
+
await userEvent.click(trigger);
|
|
78
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
79
|
+
});
|
|
80
|
+
test('clicking the input while open does not collapse the dropdown', async () => {
|
|
81
|
+
render(_jsx(Combobox, { options: people }));
|
|
82
|
+
const input = screen.getByRole('combobox');
|
|
83
|
+
fireEvent.focus(input);
|
|
84
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
85
|
+
await userEvent.click(input);
|
|
86
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
87
|
+
});
|
|
88
|
+
test('opens listbox on chevron click and shows options', async () => {
|
|
89
|
+
render(_jsx(Combobox, { options: people }));
|
|
90
|
+
await openWithChevron();
|
|
91
|
+
const listbox = screen.getByRole('listbox');
|
|
92
|
+
expect(listbox).toBeInTheDocument();
|
|
93
|
+
const options = within(listbox).getAllByRole('option');
|
|
94
|
+
expect(options).toHaveLength(3);
|
|
95
|
+
expect(screen.getByRole('combobox')).toHaveAttribute('aria-expanded', 'true');
|
|
96
|
+
});
|
|
97
|
+
test('button trigger variant has no inline input before open', () => {
|
|
98
|
+
render(_jsx(Combobox, { options: people, triggerVariant: "button", placeholder: "Students" }));
|
|
99
|
+
expect(screen.queryByRole('combobox')).not.toBeInTheDocument();
|
|
100
|
+
expect(screen.getByRole('button', { name: 'Open suggestions' })).toBeInTheDocument();
|
|
101
|
+
});
|
|
102
|
+
test('button trigger variant opens on Enter and renders search input in dropdown', async () => {
|
|
103
|
+
render(_jsx(Combobox, { options: people, triggerVariant: "button", placeholder: "Students" }));
|
|
104
|
+
const trigger = screen.getByRole('button', { name: /students/i });
|
|
105
|
+
fireEvent.keyDown(trigger, { key: 'Enter' });
|
|
106
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
107
|
+
expect(screen.getByRole('combobox')).toBeInTheDocument();
|
|
108
|
+
});
|
|
109
|
+
test('button trigger variant dropdown search input receives focus when clicked', async () => {
|
|
110
|
+
render(_jsx(Combobox, { options: people, triggerVariant: "button", placeholder: "Students" }));
|
|
111
|
+
await userEvent.click(screen.getByRole('button', { name: 'Open suggestions' }));
|
|
112
|
+
const search = screen.getByRole('combobox');
|
|
113
|
+
await userEvent.click(search);
|
|
114
|
+
expect(search).toHaveFocus();
|
|
115
|
+
});
|
|
116
|
+
test('button trigger single-select does not show selection count badge', () => {
|
|
117
|
+
render(_jsx(Combobox, { options: people, triggerVariant: "button", defaultValue: ['alice'] }));
|
|
118
|
+
expect(screen.queryByLabelText(/selected item/i)).not.toBeInTheDocument();
|
|
119
|
+
});
|
|
120
|
+
test('button trigger multi-select shows salmon count badge when multiple chips', () => {
|
|
121
|
+
render(_jsx(Combobox, { options: people, triggerVariant: "button", multiple: true, defaultValue: ['alice', 'bob'] }));
|
|
122
|
+
const badge = screen.getByLabelText(/2 selected items/i);
|
|
123
|
+
expect(badge).toHaveClass('ds-badge--salmon');
|
|
124
|
+
expect(badge).toHaveTextContent('2');
|
|
125
|
+
});
|
|
126
|
+
test('button trigger does not show ellipsis when all selected chips fit', () => {
|
|
127
|
+
const rectSpy = vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(function mockRect() {
|
|
128
|
+
let width = 0;
|
|
129
|
+
if (this.classList.contains('ds-combobox__button-content'))
|
|
130
|
+
width = 420;
|
|
131
|
+
else if (this.classList.contains('ds-combobox__measure-chip'))
|
|
132
|
+
width = 80;
|
|
133
|
+
else if (this.classList.contains('ds-combobox__button-ellipsis'))
|
|
134
|
+
width = 11.6;
|
|
135
|
+
else if (this.classList.contains('ds-badge'))
|
|
136
|
+
width = 24.2;
|
|
137
|
+
return {
|
|
138
|
+
width,
|
|
139
|
+
height: 0,
|
|
140
|
+
top: 0,
|
|
141
|
+
left: 0,
|
|
142
|
+
right: width,
|
|
143
|
+
bottom: 0,
|
|
144
|
+
x: 0,
|
|
145
|
+
y: 0,
|
|
146
|
+
toJSON() {
|
|
147
|
+
return {};
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
});
|
|
151
|
+
try {
|
|
152
|
+
render(_jsx(Combobox, { options: people, triggerVariant: "button", multiple: true, defaultValue: ['alice', 'bob', 'charlie'] }));
|
|
153
|
+
expect(document.querySelector('.ds-combobox__button-tags-viewport .ds-combobox__button-ellipsis')).not.toBeInTheDocument();
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
rectSpy.mockRestore();
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
test('button trigger truncates tags and shows ellipsis when space is constrained', () => {
|
|
160
|
+
const rectSpy = vi.spyOn(HTMLElement.prototype, 'getBoundingClientRect').mockImplementation(function mockRect() {
|
|
161
|
+
let width = 0;
|
|
162
|
+
if (this.classList.contains('ds-combobox__button-content'))
|
|
163
|
+
width = 220;
|
|
164
|
+
else if (this.classList.contains('ds-combobox__measure-chip'))
|
|
165
|
+
width = 120.4;
|
|
166
|
+
else if (this.classList.contains('ds-combobox__button-ellipsis'))
|
|
167
|
+
width = 11.6;
|
|
168
|
+
else if (this.classList.contains('ds-badge'))
|
|
169
|
+
width = 24.2;
|
|
170
|
+
return {
|
|
171
|
+
width,
|
|
172
|
+
height: 0,
|
|
173
|
+
top: 0,
|
|
174
|
+
left: 0,
|
|
175
|
+
right: width,
|
|
176
|
+
bottom: 0,
|
|
177
|
+
x: 0,
|
|
178
|
+
y: 0,
|
|
179
|
+
toJSON() {
|
|
180
|
+
return {};
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
const longOptions = [
|
|
185
|
+
{ value: 'a', label: 'Alexandria Montgomery' },
|
|
186
|
+
{ value: 'b', label: 'Benjamin Rutherford' },
|
|
187
|
+
{ value: 'c', label: 'Charlotte Kensington' },
|
|
188
|
+
];
|
|
189
|
+
const { container } = render(_jsx("div", { style: { width: 280 }, children: _jsx(Combobox, { options: longOptions, triggerVariant: "button", multiple: true, defaultValue: ['a', 'b', 'c'] }) }));
|
|
190
|
+
try {
|
|
191
|
+
expect(container.querySelector('.ds-combobox__button-tags-viewport .ds-combobox__button-ellipsis')).toBeInTheDocument();
|
|
192
|
+
expect(screen.getByLabelText(/3 selected items/i)).toBeInTheDocument();
|
|
193
|
+
const visibleTags = container.querySelectorAll('.ds-combobox__button-tags-viewport .ds-tag');
|
|
194
|
+
expect(visibleTags.length).toBeLessThan(3);
|
|
195
|
+
}
|
|
196
|
+
finally {
|
|
197
|
+
rectSpy.mockRestore();
|
|
198
|
+
}
|
|
199
|
+
});
|
|
200
|
+
test('selected options remain in the listbox without duplication', async () => {
|
|
201
|
+
render(_jsx(Combobox, { options: people, triggerVariant: "button", multiple: true, defaultValue: ['alice', 'bob'] }));
|
|
202
|
+
await userEvent.click(screen.getByRole('button', { name: 'Open suggestions' }));
|
|
203
|
+
const listbox = screen.getByRole('listbox');
|
|
204
|
+
expect(within(listbox).getAllByRole('option')).toHaveLength(people.length);
|
|
205
|
+
});
|
|
206
|
+
test('when dropdownOnFocus is false, clearing the search hides the listbox again', async () => {
|
|
207
|
+
render(_jsx(Combobox, { options: people, dropdownOnFocus: false }));
|
|
208
|
+
const input = screen.getByRole('combobox');
|
|
209
|
+
await userEvent.type(input, 'ali');
|
|
210
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
211
|
+
await userEvent.clear(input);
|
|
212
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
213
|
+
});
|
|
214
|
+
test('selecting an option in single-select mode adds a chip', async () => {
|
|
215
|
+
const onValueChange = vi.fn();
|
|
216
|
+
render(_jsx(Combobox, { options: people, onValueChange: onValueChange }));
|
|
217
|
+
await openWithChevron();
|
|
218
|
+
await userEvent.click(screen.getByText('Alice Johnson'));
|
|
219
|
+
expect(onValueChange).toHaveBeenCalledWith(['alice']);
|
|
220
|
+
});
|
|
221
|
+
test('single-select replaces the current chip on new selection', async () => {
|
|
222
|
+
const onValueChange = vi.fn();
|
|
223
|
+
render(_jsx(Combobox, { options: people, defaultValue: ['alice'], onValueChange: onValueChange }));
|
|
224
|
+
await openWithChevron();
|
|
225
|
+
await userEvent.click(screen.getByText('Bob Smith'));
|
|
226
|
+
expect(onValueChange).toHaveBeenCalledWith(['bob']);
|
|
227
|
+
});
|
|
228
|
+
test('multi-select allows multiple chips', async () => {
|
|
229
|
+
const onValueChange = vi.fn();
|
|
230
|
+
render(_jsx(Combobox, { options: people, multiple: true, onValueChange: onValueChange }));
|
|
231
|
+
await openWithChevron();
|
|
232
|
+
await userEvent.click(screen.getByText('Alice Johnson'));
|
|
233
|
+
await openWithChevron();
|
|
234
|
+
await userEvent.click(screen.getByText('Bob Smith'));
|
|
235
|
+
expect(onValueChange).toHaveBeenLastCalledWith(['alice', 'bob']);
|
|
236
|
+
});
|
|
237
|
+
test('Ctrl+A selects all chips when input is focused', async () => {
|
|
238
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'] }));
|
|
239
|
+
const input = screen.getByRole('combobox');
|
|
240
|
+
input.focus();
|
|
241
|
+
await userEvent.keyboard('{Control>}a{/Control}');
|
|
242
|
+
const selectedTags = document.querySelectorAll('.ds-tag--selected');
|
|
243
|
+
expect(selectedTags).toHaveLength(2);
|
|
244
|
+
});
|
|
245
|
+
test('Cmd+A selects all chips in single-select mode', async () => {
|
|
246
|
+
render(_jsx(Combobox, { options: people, defaultValue: ['alice'] }));
|
|
247
|
+
const input = screen.getByRole('combobox');
|
|
248
|
+
input.focus();
|
|
249
|
+
fireEvent.keyDown(input, { key: 'a', metaKey: true });
|
|
250
|
+
const selectedTags = document.querySelectorAll('.ds-tag--selected');
|
|
251
|
+
expect(selectedTags).toHaveLength(1);
|
|
252
|
+
});
|
|
253
|
+
test('Backspace clears all selected chips after select-all', async () => {
|
|
254
|
+
const onValueChange = vi.fn();
|
|
255
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'], onValueChange: onValueChange }));
|
|
256
|
+
const input = screen.getByRole('combobox');
|
|
257
|
+
input.focus();
|
|
258
|
+
await userEvent.keyboard('{Control>}a{/Control}');
|
|
259
|
+
await userEvent.keyboard('{Backspace}');
|
|
260
|
+
expect(onValueChange).toHaveBeenLastCalledWith([]);
|
|
261
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(0);
|
|
262
|
+
});
|
|
263
|
+
test('Delete clears all selected chips after select-all', async () => {
|
|
264
|
+
const onValueChange = vi.fn();
|
|
265
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'], onValueChange: onValueChange }));
|
|
266
|
+
const input = screen.getByRole('combobox');
|
|
267
|
+
input.focus();
|
|
268
|
+
await userEvent.keyboard('{Control>}a{/Control}');
|
|
269
|
+
await userEvent.keyboard('{Delete}');
|
|
270
|
+
expect(onValueChange).toHaveBeenLastCalledWith([]);
|
|
271
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(0);
|
|
272
|
+
});
|
|
273
|
+
test('typing after chip select-all clears selected chip styling', async () => {
|
|
274
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'] }));
|
|
275
|
+
const input = screen.getByRole('combobox');
|
|
276
|
+
input.focus();
|
|
277
|
+
await userEvent.keyboard('{Control>}a{/Control}');
|
|
278
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(2);
|
|
279
|
+
await userEvent.type(input, 'c');
|
|
280
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(0);
|
|
281
|
+
});
|
|
282
|
+
test('Ctrl+A with no chips keeps native input behavior and does not crash', async () => {
|
|
283
|
+
render(_jsx(Combobox, { options: people }));
|
|
284
|
+
const input = screen.getByRole('combobox');
|
|
285
|
+
input.focus();
|
|
286
|
+
await userEvent.keyboard('{Control>}a{/Control}');
|
|
287
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(0);
|
|
288
|
+
});
|
|
289
|
+
test('removing a chip calls onValueChange without that value', async () => {
|
|
290
|
+
const onValueChange = vi.fn();
|
|
291
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'], onValueChange: onValueChange }));
|
|
292
|
+
const aliceTag = screen.getByText('Alice Johnson').closest('.ds-tag');
|
|
293
|
+
const removeBtn = within(aliceTag).getByRole('button', { name: 'Remove Alice Johnson' });
|
|
294
|
+
await userEvent.click(removeBtn);
|
|
295
|
+
expect(onValueChange).toHaveBeenCalledWith(['bob']);
|
|
296
|
+
});
|
|
297
|
+
test('uses resolved tag labels in button trigger remove button labels', () => {
|
|
298
|
+
render(_jsx(Combobox, { options: [
|
|
299
|
+
{ value: 'alice', label: 'Alice Johnson', tagLabel: 'Alice J.', iconName: 'user' },
|
|
300
|
+
], triggerVariant: "button", multiple: true, defaultValue: ['alice'] }));
|
|
301
|
+
expect(screen.getByRole('button', { name: 'Remove Alice J.' })).toBeInTheDocument();
|
|
302
|
+
});
|
|
303
|
+
test('typing filters options client-side', async () => {
|
|
304
|
+
render(_jsx(Combobox, { options: people }));
|
|
305
|
+
const input = screen.getByRole('combobox');
|
|
306
|
+
await userEvent.type(input, 'Ali');
|
|
307
|
+
const options = screen.getAllByRole('option');
|
|
308
|
+
expect(options).toHaveLength(1);
|
|
309
|
+
expect(options[0]).toHaveTextContent('Alice Johnson');
|
|
310
|
+
});
|
|
311
|
+
test('default searchType uses prefix matching rather than substring matching', async () => {
|
|
312
|
+
render(_jsx(Combobox, { options: people }));
|
|
313
|
+
const input = screen.getByRole('combobox');
|
|
314
|
+
await userEvent.type(input, 'son');
|
|
315
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
316
|
+
});
|
|
317
|
+
test('substring searchType matches text anywhere in the label', async () => {
|
|
318
|
+
render(_jsx(Combobox, { options: people, searchType: "substring" }));
|
|
319
|
+
const input = screen.getByRole('combobox');
|
|
320
|
+
await userEvent.type(input, 'son');
|
|
321
|
+
const options = screen.getAllByRole('option');
|
|
322
|
+
expect(options).toHaveLength(1);
|
|
323
|
+
expect(options[0]).toHaveTextContent('Alice Johnson');
|
|
324
|
+
});
|
|
325
|
+
test('function-valued searchType can provide custom matching', async () => {
|
|
326
|
+
const initialsSearch = (option, query) => option.label
|
|
327
|
+
.toLowerCase()
|
|
328
|
+
.split(' ')
|
|
329
|
+
.map(part => part[0])
|
|
330
|
+
.join('')
|
|
331
|
+
.includes(query.toLowerCase());
|
|
332
|
+
render(_jsx(Combobox, { options: people, searchType: initialsSearch }));
|
|
333
|
+
const input = screen.getByRole('combobox');
|
|
334
|
+
await userEvent.type(input, 'aj');
|
|
335
|
+
const options = screen.getAllByRole('option');
|
|
336
|
+
expect(options).toHaveLength(1);
|
|
337
|
+
expect(options[0]).toHaveTextContent('Alice Johnson');
|
|
338
|
+
});
|
|
339
|
+
test('renderOption allows custom option row content', async () => {
|
|
340
|
+
render(_jsx(Combobox, { options: people, renderOption: (option, selected) => (_jsx("span", { children: selected ? `Selected: ${option.label}` : `Row: ${option.label}` })) }));
|
|
341
|
+
const input = screen.getByRole('combobox');
|
|
342
|
+
await userEvent.click(input);
|
|
343
|
+
expect(screen.getByText('Row: Alice Johnson')).toBeInTheDocument();
|
|
344
|
+
});
|
|
345
|
+
test('highlightStringMatches marks prefix matches when enabled', async () => {
|
|
346
|
+
render(_jsx(Combobox, { options: people, highlightStringMatches: true }));
|
|
347
|
+
const input = screen.getByRole('combobox');
|
|
348
|
+
await userEvent.type(input, 'Ali');
|
|
349
|
+
const option = screen.getByRole('option');
|
|
350
|
+
expect(within(option).getByText('Ali', { selector: 'mark' })).toBeInTheDocument();
|
|
351
|
+
});
|
|
352
|
+
test('highlightStringMatches marks substring matches when enabled', async () => {
|
|
353
|
+
render(_jsx(Combobox, { options: people, searchType: "substring", highlightStringMatches: true }));
|
|
354
|
+
const input = screen.getByRole('combobox');
|
|
355
|
+
await userEvent.type(input, 'son');
|
|
356
|
+
const option = screen.getByRole('option');
|
|
357
|
+
expect(within(option).getByText('son', { selector: 'mark' })).toBeInTheDocument();
|
|
358
|
+
});
|
|
359
|
+
test('function-valued searchType does not apply built-in mark highlighting', async () => {
|
|
360
|
+
const initialsSearch = (option, query) => option.label
|
|
361
|
+
.toLowerCase()
|
|
362
|
+
.split(' ')
|
|
363
|
+
.map(part => part[0])
|
|
364
|
+
.join('')
|
|
365
|
+
.includes(query.toLowerCase());
|
|
366
|
+
render(_jsx(Combobox, { options: people, searchType: initialsSearch, highlightStringMatches: true }));
|
|
367
|
+
const input = screen.getByRole('combobox');
|
|
368
|
+
await userEvent.type(input, 'aj');
|
|
369
|
+
const option = screen.getByRole('option');
|
|
370
|
+
expect(option).toHaveTextContent('Alice Johnson');
|
|
371
|
+
expect(within(option).queryByText(/aj/i, { selector: 'mark' })).not.toBeInTheDocument();
|
|
372
|
+
});
|
|
373
|
+
test('clears filter text and hides popup when no matches and allowCreate is false', async () => {
|
|
374
|
+
render(_jsx(Combobox, { options: people }));
|
|
375
|
+
const input = screen.getByRole('combobox');
|
|
376
|
+
await userEvent.type(input, 'zzzzz');
|
|
377
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
378
|
+
});
|
|
379
|
+
test('keyboard: ArrowDown opens the listbox', async () => {
|
|
380
|
+
render(_jsx(Combobox, { options: people }));
|
|
381
|
+
const input = screen.getByRole('combobox');
|
|
382
|
+
input.focus();
|
|
383
|
+
await userEvent.keyboard('{ArrowDown}');
|
|
384
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
385
|
+
});
|
|
386
|
+
test('keyboard: Alt+ArrowDown opens the listbox', async () => {
|
|
387
|
+
render(_jsx(Combobox, { options: people }));
|
|
388
|
+
const input = screen.getByRole('combobox');
|
|
389
|
+
input.focus();
|
|
390
|
+
await userEvent.keyboard('{Alt>}{ArrowDown}{/Alt}');
|
|
391
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
392
|
+
});
|
|
393
|
+
test('keyboard: ArrowDown/ArrowUp moves highlight', async () => {
|
|
394
|
+
render(_jsx(Combobox, { options: people }));
|
|
395
|
+
const input = screen.getByRole('combobox');
|
|
396
|
+
await openWithChevron();
|
|
397
|
+
await userEvent.keyboard('{ArrowDown}');
|
|
398
|
+
const firstOption = screen.getAllByRole('option')[0];
|
|
399
|
+
expect(firstOption).toHaveClass('ds-combobox__option-row--highlighted');
|
|
400
|
+
expect(input).toHaveAttribute('aria-activedescendant', firstOption.id);
|
|
401
|
+
await userEvent.keyboard('{ArrowDown}');
|
|
402
|
+
const secondOption = screen.getAllByRole('option')[1];
|
|
403
|
+
expect(secondOption).toHaveClass('ds-combobox__option-row--highlighted');
|
|
404
|
+
});
|
|
405
|
+
test('keyboard: Enter selects the highlighted option', async () => {
|
|
406
|
+
const onValueChange = vi.fn();
|
|
407
|
+
render(_jsx(Combobox, { options: people, onValueChange: onValueChange }));
|
|
408
|
+
await openWithChevron();
|
|
409
|
+
await userEvent.keyboard('{ArrowDown}');
|
|
410
|
+
await userEvent.keyboard('{Enter}');
|
|
411
|
+
expect(onValueChange).toHaveBeenCalledWith(['alice']);
|
|
412
|
+
});
|
|
413
|
+
test('keyboard: Escape closes the listbox', async () => {
|
|
414
|
+
render(_jsx(Combobox, { options: people }));
|
|
415
|
+
await openWithChevron();
|
|
416
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
417
|
+
await userEvent.keyboard('{Escape}');
|
|
418
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
419
|
+
});
|
|
420
|
+
test('keyboard: Backspace on empty input removes last chip', async () => {
|
|
421
|
+
const onValueChange = vi.fn();
|
|
422
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'], onValueChange: onValueChange }));
|
|
423
|
+
const input = screen.getByRole('combobox');
|
|
424
|
+
input.focus();
|
|
425
|
+
await userEvent.keyboard('{Backspace}');
|
|
426
|
+
expect(onValueChange).toHaveBeenCalledWith(['alice']);
|
|
427
|
+
});
|
|
428
|
+
test('allowCreate shows a create row when no exact match', async () => {
|
|
429
|
+
const onCreateNew = vi.fn();
|
|
430
|
+
render(_jsx(Combobox, { options: people, allowCreate: true, onCreateNew: onCreateNew }));
|
|
431
|
+
const input = screen.getByRole('combobox');
|
|
432
|
+
await userEvent.type(input, 'Zara');
|
|
433
|
+
const createRow = screen.getByText(/Create/);
|
|
434
|
+
expect(createRow).toBeInTheDocument();
|
|
435
|
+
await userEvent.click(createRow.closest('[role="option"]'));
|
|
436
|
+
expect(onCreateNew).toHaveBeenCalledWith('Zara');
|
|
437
|
+
});
|
|
438
|
+
test('create row has descriptive aria-label', async () => {
|
|
439
|
+
render(_jsx(Combobox, { options: people, allowCreate: true }));
|
|
440
|
+
const input = screen.getByRole('combobox');
|
|
441
|
+
await userEvent.type(input, 'Zara');
|
|
442
|
+
const createOption = screen.getByRole('option', { name: /Create new option: Zara/i });
|
|
443
|
+
expect(createOption).toBeInTheDocument();
|
|
444
|
+
});
|
|
445
|
+
test('allowCreate: Enter creates and selects in multi mode', async () => {
|
|
446
|
+
const onCreateNew = vi.fn((name) => ({
|
|
447
|
+
value: name.toLowerCase(),
|
|
448
|
+
label: name,
|
|
449
|
+
}));
|
|
450
|
+
const onValueChange = vi.fn();
|
|
451
|
+
render(_jsx(Combobox, { options: people, allowCreate: true, multiple: true, onCreateNew: onCreateNew, onValueChange: onValueChange }));
|
|
452
|
+
const input = screen.getByRole('combobox');
|
|
453
|
+
await userEvent.type(input, 'NewPerson');
|
|
454
|
+
await userEvent.keyboard('{Enter}');
|
|
455
|
+
expect(onCreateNew).toHaveBeenCalledWith('NewPerson');
|
|
456
|
+
expect(onValueChange).toHaveBeenCalledWith(['newperson']);
|
|
457
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
458
|
+
});
|
|
459
|
+
test('allowCreate creates and replaces current selection in single mode', async () => {
|
|
460
|
+
const onCreateNew = vi.fn((name) => ({
|
|
461
|
+
value: `new-${name.toLowerCase()}`,
|
|
462
|
+
label: name,
|
|
463
|
+
}));
|
|
464
|
+
const onValueChange = vi.fn();
|
|
465
|
+
render(_jsx(Combobox, { options: people, allowCreate: true, defaultValue: ['alice'], onCreateNew: onCreateNew, onValueChange: onValueChange }));
|
|
466
|
+
const input = screen.getByRole('combobox');
|
|
467
|
+
await userEvent.type(input, 'Taylor');
|
|
468
|
+
await userEvent.keyboard('{Enter}');
|
|
469
|
+
expect(onCreateNew).toHaveBeenCalledWith('Taylor');
|
|
470
|
+
expect(onValueChange).toHaveBeenCalledWith(['new-taylor']);
|
|
471
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
472
|
+
});
|
|
473
|
+
test('allowCreate duplicate prevention selects existing option', async () => {
|
|
474
|
+
const onCreateNew = vi.fn();
|
|
475
|
+
const onValueChange = vi.fn();
|
|
476
|
+
render(_jsx(Combobox, { options: people, allowCreate: true, multiple: true, onCreateNew: onCreateNew, onValueChange: onValueChange }));
|
|
477
|
+
const input = screen.getByRole('combobox');
|
|
478
|
+
await userEvent.type(input, 'alice johnson');
|
|
479
|
+
await userEvent.keyboard('{Enter}');
|
|
480
|
+
expect(onCreateNew).not.toHaveBeenCalled();
|
|
481
|
+
expect(onValueChange).toHaveBeenCalledWith(['alice']);
|
|
482
|
+
});
|
|
483
|
+
test('allowCreate does not show a create row when the query only differs by surrounding spaces', async () => {
|
|
484
|
+
const onCreateNew = vi.fn();
|
|
485
|
+
render(_jsx(Combobox, { options: people, allowCreate: true, onCreateNew: onCreateNew }));
|
|
486
|
+
const input = screen.getByRole('combobox');
|
|
487
|
+
await userEvent.type(input, ' Alice Johnson ');
|
|
488
|
+
expect(screen.queryByText(/Create/)).not.toBeInTheDocument();
|
|
489
|
+
await userEvent.keyboard('{Enter}');
|
|
490
|
+
expect(onCreateNew).not.toHaveBeenCalled();
|
|
491
|
+
});
|
|
492
|
+
test('allowCreate auto-highlights create row so Enter creates immediately', async () => {
|
|
493
|
+
const onCreateNew = vi.fn((name) => ({
|
|
494
|
+
value: name.toLowerCase(),
|
|
495
|
+
label: name,
|
|
496
|
+
}));
|
|
497
|
+
const onValueChange = vi.fn();
|
|
498
|
+
render(_jsx(Combobox, { options: people, allowCreate: true, onCreateNew: onCreateNew, onValueChange: onValueChange }));
|
|
499
|
+
const input = screen.getByRole('combobox');
|
|
500
|
+
await userEvent.type(input, 'Zed');
|
|
501
|
+
const createOption = screen.getByRole('option', { name: /Create/i });
|
|
502
|
+
expect(createOption).toHaveClass('ds-combobox__option--highlighted');
|
|
503
|
+
await userEvent.keyboard('{Enter}');
|
|
504
|
+
expect(onCreateNew).toHaveBeenCalledWith('Zed');
|
|
505
|
+
expect(onValueChange).toHaveBeenCalledWith(['zed']);
|
|
506
|
+
});
|
|
507
|
+
test('clear-all button removes all selections', async () => {
|
|
508
|
+
const onValueChange = vi.fn();
|
|
509
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'], showClearAll: true, onValueChange: onValueChange }));
|
|
510
|
+
const clearAll = screen.getByText('Clear all');
|
|
511
|
+
await userEvent.click(clearAll);
|
|
512
|
+
expect(onValueChange).toHaveBeenCalledWith([]);
|
|
513
|
+
});
|
|
514
|
+
test('clear-all is hidden when nothing is selected', () => {
|
|
515
|
+
render(_jsx(Combobox, { options: people, multiple: true, showClearAll: true }));
|
|
516
|
+
expect(screen.queryByText('Clear all')).not.toBeInTheDocument();
|
|
517
|
+
});
|
|
518
|
+
test('disabled combobox cannot be interacted with', async () => {
|
|
519
|
+
render(_jsx(Combobox, { options: people, disabled: true, placeholder: "Disabled" }));
|
|
520
|
+
const input = screen.getByRole('combobox');
|
|
521
|
+
expect(input).toBeDisabled();
|
|
522
|
+
await userEvent.click(input);
|
|
523
|
+
expect(screen.queryByRole('listbox')).not.toBeInTheDocument();
|
|
524
|
+
});
|
|
525
|
+
test('disabled options cannot be selected via click', async () => {
|
|
526
|
+
const onValueChange = vi.fn();
|
|
527
|
+
const opts = [
|
|
528
|
+
{ value: 'a', label: 'Enabled' },
|
|
529
|
+
{ value: 'b', label: 'Disabled', disabled: true },
|
|
530
|
+
];
|
|
531
|
+
render(_jsx(Combobox, { options: opts, onValueChange: onValueChange }));
|
|
532
|
+
await openWithChevron();
|
|
533
|
+
await userEvent.click(screen.getByText('Disabled'));
|
|
534
|
+
expect(onValueChange).not.toHaveBeenCalled();
|
|
535
|
+
});
|
|
536
|
+
test('renders option groups with headers', async () => {
|
|
537
|
+
const grouped = [
|
|
538
|
+
{ value: 'a', label: 'Alice', group: 'Team A' },
|
|
539
|
+
{ value: 'b', label: 'Bob', group: 'Team B' },
|
|
540
|
+
];
|
|
541
|
+
render(_jsx(Combobox, { options: grouped }));
|
|
542
|
+
await openWithChevron();
|
|
543
|
+
expect(screen.getByText('Team A')).toBeInTheDocument();
|
|
544
|
+
expect(screen.getByText('Team B')).toBeInTheDocument();
|
|
545
|
+
});
|
|
546
|
+
test('multi-select shows aria-selected on selected options', async () => {
|
|
547
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice'] }));
|
|
548
|
+
await openWithChevron();
|
|
549
|
+
const options = screen.getAllByRole('option');
|
|
550
|
+
const aliceOption = options.find(o => o.textContent?.includes('Alice'));
|
|
551
|
+
expect(aliceOption).toHaveAttribute('aria-selected', 'true');
|
|
552
|
+
});
|
|
553
|
+
test('listbox has aria-multiselectable in multi mode', async () => {
|
|
554
|
+
render(_jsx(Combobox, { options: people, multiple: true }));
|
|
555
|
+
await openWithChevron();
|
|
556
|
+
const listbox = screen.getByRole('listbox');
|
|
557
|
+
expect(listbox).toHaveAttribute('aria-multiselectable', 'true');
|
|
558
|
+
});
|
|
559
|
+
test('loading state sets aria-busy, role=status (spinner only, accessible name), and hides options', async () => {
|
|
560
|
+
render(_jsx(Combobox, { options: people, loading: true, onSearch: () => { }, "aria-label": "People" }));
|
|
561
|
+
await openWithChevron();
|
|
562
|
+
const input = screen.getByRole('combobox');
|
|
563
|
+
const listbox = screen.getByRole('listbox');
|
|
564
|
+
expect(listbox).toHaveAttribute('aria-busy', 'true');
|
|
565
|
+
expect(input).toHaveAttribute('aria-busy', 'true');
|
|
566
|
+
const status = screen.getByRole('status', { name: 'Loading suggestions' });
|
|
567
|
+
expect(status.querySelector('.ds-combobox__loading-spinner')).toBeInTheDocument();
|
|
568
|
+
expect(within(listbox).queryAllByRole('option')).toHaveLength(0);
|
|
569
|
+
});
|
|
570
|
+
test('loading is false does not set aria-busy on listbox', async () => {
|
|
571
|
+
render(_jsx(Combobox, { options: people, onSearch: () => { } }));
|
|
572
|
+
await openWithChevron();
|
|
573
|
+
expect(screen.getByRole('listbox')).not.toHaveAttribute('aria-busy');
|
|
574
|
+
expect(screen.getByRole('combobox')).not.toHaveAttribute('aria-busy');
|
|
575
|
+
});
|
|
576
|
+
describe('delete created items', () => {
|
|
577
|
+
const setupCreate = () => {
|
|
578
|
+
const onCreateNew = vi.fn((name) => ({
|
|
579
|
+
value: name.toLowerCase().replace(/\s/g, '-'),
|
|
580
|
+
label: name,
|
|
581
|
+
}));
|
|
582
|
+
const onDeleteCreated = vi.fn();
|
|
583
|
+
const onValueChange = vi.fn();
|
|
584
|
+
render(_jsx(Combobox, { options: people, multiple: true, allowCreate: true, onCreateNew: onCreateNew, onDeleteCreated: onDeleteCreated, onValueChange: onValueChange }));
|
|
585
|
+
return { onCreateNew, onDeleteCreated, onValueChange };
|
|
586
|
+
};
|
|
587
|
+
const highlightCreatedOption = async () => {
|
|
588
|
+
const input = screen.getByRole('combobox');
|
|
589
|
+
const listbox = screen.getByRole('listbox');
|
|
590
|
+
const createdRow = within(listbox).getByText('NewPerson').closest('.ds-combobox__option-row');
|
|
591
|
+
expect(createdRow).toBeInTheDocument();
|
|
592
|
+
fireEvent.mouseEnter(createdRow);
|
|
593
|
+
input.focus();
|
|
594
|
+
return input;
|
|
595
|
+
};
|
|
596
|
+
test('created items show a trash button with title `Delete ${option.label}`', async () => {
|
|
597
|
+
setupCreate();
|
|
598
|
+
const input = screen.getByRole('combobox');
|
|
599
|
+
await userEvent.type(input, 'NewPerson');
|
|
600
|
+
await userEvent.keyboard('{Enter}');
|
|
601
|
+
await openWithChevron();
|
|
602
|
+
const deleteBtn = screen.getByRole('button', { name: 'Delete NewPerson' });
|
|
603
|
+
expect(deleteBtn).toBeInTheDocument();
|
|
604
|
+
expect(deleteBtn).toHaveAttribute('title', 'Delete NewPerson');
|
|
605
|
+
expect(deleteBtn).toHaveAttribute('tabindex', '-1');
|
|
606
|
+
});
|
|
607
|
+
test('original options do not show trash button', async () => {
|
|
608
|
+
setupCreate();
|
|
609
|
+
await openWithChevron();
|
|
610
|
+
expect(screen.queryByTitle('Delete NewPerson')).not.toBeInTheDocument();
|
|
611
|
+
});
|
|
612
|
+
test('ArrowRight from a highlighted created item focuses its delete button', async () => {
|
|
613
|
+
setupCreate();
|
|
614
|
+
const input = screen.getByRole('combobox');
|
|
615
|
+
await userEvent.type(input, 'NewPerson');
|
|
616
|
+
await userEvent.keyboard('{Enter}');
|
|
617
|
+
await openWithChevron();
|
|
618
|
+
await highlightCreatedOption();
|
|
619
|
+
const deleteBtn = screen.getByRole('button', { name: 'Delete NewPerson' });
|
|
620
|
+
expect(deleteBtn).toHaveAttribute('tabindex', '0');
|
|
621
|
+
await userEvent.keyboard('{ArrowRight}');
|
|
622
|
+
expect(deleteBtn).toHaveFocus();
|
|
623
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
624
|
+
});
|
|
625
|
+
test('ArrowLeft from a created delete button returns focus to the input', async () => {
|
|
626
|
+
setupCreate();
|
|
627
|
+
const input = screen.getByRole('combobox');
|
|
628
|
+
await userEvent.type(input, 'NewPerson');
|
|
629
|
+
await userEvent.keyboard('{Enter}');
|
|
630
|
+
await openWithChevron();
|
|
631
|
+
await highlightCreatedOption();
|
|
632
|
+
await userEvent.keyboard('{ArrowRight}');
|
|
633
|
+
const deleteBtn = screen.getByRole('button', { name: 'Delete NewPerson' });
|
|
634
|
+
expect(deleteBtn).toHaveFocus();
|
|
635
|
+
await userEvent.keyboard('{ArrowLeft}');
|
|
636
|
+
expect(input).toHaveFocus();
|
|
637
|
+
expect(deleteBtn).toHaveAttribute('tabindex', '0');
|
|
638
|
+
});
|
|
639
|
+
test('Tab and Shift+Tab move between the input and the highlighted created delete button', async () => {
|
|
640
|
+
setupCreate();
|
|
641
|
+
const input = screen.getByRole('combobox');
|
|
642
|
+
await userEvent.type(input, 'NewPerson');
|
|
643
|
+
await userEvent.keyboard('{Enter}');
|
|
644
|
+
await openWithChevron();
|
|
645
|
+
await highlightCreatedOption();
|
|
646
|
+
const deleteBtn = screen.getByRole('button', { name: 'Delete NewPerson' });
|
|
647
|
+
await userEvent.tab();
|
|
648
|
+
expect(deleteBtn).toHaveFocus();
|
|
649
|
+
expect(screen.getByRole('listbox')).toBeInTheDocument();
|
|
650
|
+
await userEvent.tab({ shift: true });
|
|
651
|
+
expect(input).toHaveFocus();
|
|
652
|
+
});
|
|
653
|
+
test('clicking trash removes the option from the list and fires onDeleteCreated', async () => {
|
|
654
|
+
const { onDeleteCreated } = setupCreate();
|
|
655
|
+
const input = screen.getByRole('combobox');
|
|
656
|
+
await userEvent.type(input, 'NewPerson');
|
|
657
|
+
await userEvent.keyboard('{Enter}');
|
|
658
|
+
await openWithChevron();
|
|
659
|
+
const deleteBtn = screen.getByRole('button', { name: 'Delete NewPerson' });
|
|
660
|
+
await userEvent.click(deleteBtn);
|
|
661
|
+
expect(onDeleteCreated).toHaveBeenCalledWith('newperson');
|
|
662
|
+
const listbox = screen.queryByRole('listbox');
|
|
663
|
+
if (listbox) {
|
|
664
|
+
const remainingOptions = within(listbox).getAllByRole('option');
|
|
665
|
+
const labels = remainingOptions.map(o => o.textContent);
|
|
666
|
+
expect(labels.some(l => l?.includes('NewPerson'))).toBe(false);
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
test('deleting a selected item also removes the chip', async () => {
|
|
670
|
+
const { onValueChange } = setupCreate();
|
|
671
|
+
const input = screen.getByRole('combobox');
|
|
672
|
+
await userEvent.type(input, 'NewPerson');
|
|
673
|
+
await userEvent.keyboard('{Enter}');
|
|
674
|
+
await openWithChevron();
|
|
675
|
+
const deleteBtn = screen.getByRole('button', { name: 'Delete NewPerson' });
|
|
676
|
+
await userEvent.click(deleteBtn);
|
|
677
|
+
expect(onValueChange).toHaveBeenLastCalledWith([]);
|
|
678
|
+
});
|
|
679
|
+
test('trash click does not select/deselect the option (stopPropagation)', async () => {
|
|
680
|
+
const { onValueChange } = setupCreate();
|
|
681
|
+
const input = screen.getByRole('combobox');
|
|
682
|
+
await userEvent.type(input, 'NewPerson');
|
|
683
|
+
await userEvent.keyboard('{Enter}');
|
|
684
|
+
onValueChange.mockClear();
|
|
685
|
+
await openWithChevron();
|
|
686
|
+
const deleteBtn = screen.getByRole('button', { name: 'Delete NewPerson' });
|
|
687
|
+
await userEvent.click(deleteBtn);
|
|
688
|
+
const calls = onValueChange.mock.calls;
|
|
689
|
+
expect(calls.length).toBe(1);
|
|
690
|
+
expect(calls[0][0]).toEqual([]);
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
describe('chip keyboard navigation', () => {
|
|
694
|
+
test('ArrowLeft at caret 0 focuses the last chip', () => {
|
|
695
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'] }));
|
|
696
|
+
const input = screen.getByRole('combobox');
|
|
697
|
+
input.focus();
|
|
698
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
699
|
+
const tags = document.querySelectorAll('.ds-tag');
|
|
700
|
+
expect(tags[1]).toHaveClass('ds-tag--selected');
|
|
701
|
+
expect(tags[0]).not.toHaveClass('ds-tag--selected');
|
|
702
|
+
});
|
|
703
|
+
test('ArrowLeft navigates through chips from right to left', () => {
|
|
704
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob', 'charlie'] }));
|
|
705
|
+
const input = screen.getByRole('combobox');
|
|
706
|
+
input.focus();
|
|
707
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
708
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
709
|
+
const tags = document.querySelectorAll('.ds-tag');
|
|
710
|
+
expect(tags[1]).toHaveClass('ds-tag--selected');
|
|
711
|
+
expect(tags[2]).not.toHaveClass('ds-tag--selected');
|
|
712
|
+
});
|
|
713
|
+
test('ArrowLeft does not go past the first chip', () => {
|
|
714
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'] }));
|
|
715
|
+
const input = screen.getByRole('combobox');
|
|
716
|
+
input.focus();
|
|
717
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
718
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
719
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
720
|
+
const tags = document.querySelectorAll('.ds-tag');
|
|
721
|
+
expect(tags[0]).toHaveClass('ds-tag--selected');
|
|
722
|
+
});
|
|
723
|
+
test('ArrowRight past the last chip returns focus to input (exits chip nav)', () => {
|
|
724
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'] }));
|
|
725
|
+
const input = screen.getByRole('combobox');
|
|
726
|
+
input.focus();
|
|
727
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
728
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(1);
|
|
729
|
+
fireEvent.keyDown(input, { key: 'ArrowRight' });
|
|
730
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(0);
|
|
731
|
+
});
|
|
732
|
+
test('Backspace on focused chip removes it', () => {
|
|
733
|
+
const onValueChange = vi.fn();
|
|
734
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob', 'charlie'], onValueChange: onValueChange }));
|
|
735
|
+
const input = screen.getByRole('combobox');
|
|
736
|
+
input.focus();
|
|
737
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
738
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
739
|
+
fireEvent.keyDown(input, { key: 'Backspace' });
|
|
740
|
+
expect(onValueChange).toHaveBeenCalledWith(['alice', 'charlie']);
|
|
741
|
+
});
|
|
742
|
+
test('Delete on focused chip removes it', () => {
|
|
743
|
+
const onValueChange = vi.fn();
|
|
744
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'], onValueChange: onValueChange }));
|
|
745
|
+
const input = screen.getByRole('combobox');
|
|
746
|
+
input.focus();
|
|
747
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
748
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
749
|
+
fireEvent.keyDown(input, { key: 'Delete' });
|
|
750
|
+
expect(onValueChange).toHaveBeenCalledWith(['bob']);
|
|
751
|
+
});
|
|
752
|
+
test('Backspace on first chip focuses next or exits', () => {
|
|
753
|
+
const onValueChange = vi.fn();
|
|
754
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice'], onValueChange: onValueChange }));
|
|
755
|
+
const input = screen.getByRole('combobox');
|
|
756
|
+
input.focus();
|
|
757
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
758
|
+
fireEvent.keyDown(input, { key: 'Backspace' });
|
|
759
|
+
expect(onValueChange).toHaveBeenCalledWith([]);
|
|
760
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(0);
|
|
761
|
+
});
|
|
762
|
+
test('typing exits chip nav mode', async () => {
|
|
763
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'] }));
|
|
764
|
+
const input = screen.getByRole('combobox');
|
|
765
|
+
input.focus();
|
|
766
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
767
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(1);
|
|
768
|
+
await userEvent.type(input, 'c');
|
|
769
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(0);
|
|
770
|
+
});
|
|
771
|
+
test('Escape exits chip nav mode', () => {
|
|
772
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'] }));
|
|
773
|
+
const input = screen.getByRole('combobox');
|
|
774
|
+
input.focus();
|
|
775
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
776
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(1);
|
|
777
|
+
fireEvent.keyDown(input, { key: 'Escape' });
|
|
778
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(0);
|
|
779
|
+
});
|
|
780
|
+
test('Cmd+A during chip nav enters bulk-select-all mode', () => {
|
|
781
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice', 'bob'] }));
|
|
782
|
+
const input = screen.getByRole('combobox');
|
|
783
|
+
input.focus();
|
|
784
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
785
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(1);
|
|
786
|
+
fireEvent.keyDown(input, { key: 'a', metaKey: true });
|
|
787
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(2);
|
|
788
|
+
});
|
|
789
|
+
test('ArrowLeft does nothing when input has text and caret is not at 0', async () => {
|
|
790
|
+
render(_jsx(Combobox, { options: people, multiple: true, defaultValue: ['alice'] }));
|
|
791
|
+
const input = screen.getByRole('combobox');
|
|
792
|
+
await userEvent.type(input, 'test');
|
|
793
|
+
fireEvent.keyDown(input, { key: 'ArrowLeft' });
|
|
794
|
+
expect(document.querySelectorAll('.ds-tag--selected')).toHaveLength(0);
|
|
795
|
+
});
|
|
796
|
+
});
|
|
797
|
+
});
|
|
798
|
+
//# sourceMappingURL=Combobox.test.js.map
|