diff --git a/src/hooks/useCombobox/__tests__/getInputProps.test.js b/src/hooks/useCombobox/__tests__/getInputProps.test.js index 3ed86f83..bbf09f5a 100644 --- a/src/hooks/useCombobox/__tests__/getInputProps.test.js +++ b/src/hooks/useCombobox/__tests__/getInputProps.test.js @@ -577,6 +577,81 @@ describe('getInputProps', () => { ) }) + test('initialHighlightedIndex is ignored if item is disabled', async () => { + const initialHighlightedIndex = 2 + renderCombobox({ + initialHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === initialHighlightedIndex + }, + }) + + await keyDownOnInput('{ArrowUp}') + + expect(getInput()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(items.length - 1), + ) + }) + + test('initialHighlightedIndex is ignored and defaultHighlightedIndex is chosen if enabled', async () => { + const initialHighlightedIndex = 0 + const defaultHighlightedIndex = 2 + renderCombobox({ + initialHighlightedIndex, + defaultHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === initialHighlightedIndex + }, + }) + + await keyDownOnInput('{ArrowUp}') + + expect(getInput()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(defaultHighlightedIndex), + ) + }) + + test('defaultHighlightedIndex is ignored if item is disabled', async () => { + const defaultHighlightedIndex = 2 + renderCombobox({ + defaultHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === defaultHighlightedIndex + }, + }) + + await keyDownOnInput('{ArrowUp}') + + expect(getInput()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(items.length - 1), + ) + }) + + test('both defaultHighlightedIndex and initialHighlightedIndex are ignored if items are disabled', async () => { + const initialHighlightedIndex = 1 + const defaultHighlightedIndex = 2 + renderCombobox({ + initialHighlightedIndex, + defaultHighlightedIndex, + isItemDisabled(item) { + return [ + initialHighlightedIndex, + defaultHighlightedIndex, + ].includes(items.indexOf(item)) + }, + }) + + await keyDownOnInput('{ArrowUp}') + + expect(getInput()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(items.length - 1), + ) + }) + test('it opens the closed menu and keeps focus on the combobox', async () => { renderCombobox() @@ -824,6 +899,81 @@ describe('getInputProps', () => { expect(getItems()).toHaveLength(items.length) }) + test('initialHighlightedIndex is ignored if item is disabled', async () => { + const initialHighlightedIndex = 2 + renderCombobox({ + initialHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === initialHighlightedIndex + }, + }) + + await keyDownOnInput('{ArrowDown}') + + expect(getInput()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(0), + ) + }) + + test('initialHighlightedIndex is ignored and defaultHighlightedIndex is chosen if enabled', async () => { + const initialHighlightedIndex = 0 + const defaultHighlightedIndex = 2 + renderCombobox({ + initialHighlightedIndex, + defaultHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === initialHighlightedIndex + }, + }) + + await keyDownOnInput('{ArrowDown}') + + expect(getInput()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(defaultHighlightedIndex), + ) + }) + + test('defaultHighlightedIndex is ignored if item is disabled', async () => { + const defaultHighlightedIndex = 2 + renderCombobox({ + defaultHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === defaultHighlightedIndex + }, + }) + + await keyDownOnInput('{ArrowDown}') + + expect(getInput()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(0), + ) + }) + + test('both defaultHighlightedIndex and initialHighlightedIndex are ignored if items are disabled', async () => { + const initialHighlightedIndex = 1 + const defaultHighlightedIndex = 2 + renderCombobox({ + initialHighlightedIndex, + defaultHighlightedIndex, + isItemDisabled(item) { + return [ + initialHighlightedIndex, + defaultHighlightedIndex, + ].includes(items.indexOf(item)) + }, + }) + + await keyDownOnInput('{ArrowDown}') + + expect(getInput()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(0), + ) + }) + test('opens the closed menu and keeps focus on the button', async () => { renderCombobox() @@ -1816,6 +1966,73 @@ describe('getInputProps', () => { defaultIds.getItemId(defaultHighlightedIndex), ) }) + + + test('initialHighlightedIndex is ignored if item is disabled', async () => { + const initialHighlightedIndex = 2 + renderCombobox({ + initialHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === initialHighlightedIndex + }, + }) + + await clickOnInput() + + expect(getInput()).toHaveAttribute('aria-activedescendant', '') + }) + + test('initialHighlightedIndex is ignored and defaultHighlightedIndex is chosen if enabled', async () => { + const initialHighlightedIndex = 0 + const defaultHighlightedIndex = 2 + renderCombobox({ + initialHighlightedIndex, + defaultHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === initialHighlightedIndex + }, + }) + + await clickOnInput() + + expect(getInput()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(defaultHighlightedIndex), + ) + }) + + test('defaultHighlightedIndex is ignored if item is disabled', async () => { + const defaultHighlightedIndex = 2 + renderCombobox({ + defaultHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === defaultHighlightedIndex + }, + }) + + await clickOnInput() + + expect(getInput()).toHaveAttribute('aria-activedescendant', '') + }) + + test('both defaultHighlightedIndex and initialHighlightedIndex are ignored if items are disabled', async () => { + const initialHighlightedIndex = 0 + const defaultHighlightedIndex = 2 + renderCombobox({ + initialHighlightedIndex, + defaultHighlightedIndex, + isItemDisabled(item) { + return [initialHighlightedIndex, defaultHighlightedIndex].includes( + items.indexOf(item), + ) + }, + }) + + await clickOnInput() + + expect(getInput()).toHaveAttribute('aria-activedescendant', '') + }) + }) }) diff --git a/src/hooks/useCombobox/__tests__/props.test.js b/src/hooks/useCombobox/__tests__/props.test.js index 8d4c671c..a93a95ae 100644 --- a/src/hooks/useCombobox/__tests__/props.test.js +++ b/src/hooks/useCombobox/__tests__/props.test.js @@ -334,71 +334,6 @@ describe('props', () => { expect(input).toHaveAttribute('aria-activedescendant', expectedItemId) }) - test('initialHighlightedIndex is ignored if item is disabled', async () => { - const initialHighlightedIndex = 2 - renderCombobox({ - initialHighlightedIndex, - isItemDisabled(item) { - return items.indexOf(item) === initialHighlightedIndex - }, - }) - - await clickOnInput() - - expect(getInput()).toHaveAttribute('aria-activedescendant', '') - }) - - test('initialHighlightedIndex is ignored and defaultHighlightedIndex is chosen if enabled', async () => { - const initialHighlightedIndex = 0 - const defaultHighlightedIndex = 2 - renderCombobox({ - initialHighlightedIndex, - defaultHighlightedIndex, - isItemDisabled(item) { - return items.indexOf(item) === initialHighlightedIndex - }, - }) - - await clickOnInput() - - expect(getInput()).toHaveAttribute( - 'aria-activedescendant', - defaultIds.getItemId(defaultHighlightedIndex), - ) - }) - - test('defaultHighlightedIndex is ignored if item is disabled', async () => { - const defaultHighlightedIndex = 2 - renderCombobox({ - defaultHighlightedIndex, - isItemDisabled(item) { - return items.indexOf(item) === defaultHighlightedIndex - }, - }) - - await clickOnInput() - - expect(getInput()).toHaveAttribute('aria-activedescendant', '') - }) - - test('both defaultHighlightedIndex and initialHighlightedIndex are ignored if items are disabled', async () => { - const initialHighlightedIndex = 0 - const defaultHighlightedIndex = 2 - renderCombobox({ - initialHighlightedIndex, - defaultHighlightedIndex, - isItemDisabled(item) { - return [initialHighlightedIndex, defaultHighlightedIndex].includes( - items.indexOf(item), - ) - }, - }) - - await clickOnInput() - - expect(getInput()).toHaveAttribute('aria-activedescendant', '') - }) - describe('inputValue', () => { test('controls the state property if passed', async () => { renderCombobox({isOpen: true, inputValue: 'Dohn Joe'}) diff --git a/src/hooks/useSelect/__tests__/getToggleButtonProps.test.js b/src/hooks/useSelect/__tests__/getToggleButtonProps.test.js index aa44f73d..32ff4bff 100644 --- a/src/hooks/useSelect/__tests__/getToggleButtonProps.test.js +++ b/src/hooks/useSelect/__tests__/getToggleButtonProps.test.js @@ -386,6 +386,71 @@ describe('getToggleButtonProps', () => { ) }) + test('initialHighlightedIndex is ignored if item is disabled', async () => { + const initialHighlightedIndex = 2 + renderSelect({ + initialHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === initialHighlightedIndex + }, + }) + + await clickOnToggleButton() + + expect(getToggleButton()).toHaveAttribute('aria-activedescendant', '') + }) + + test('initialHighlightedIndex is ignored and defaultHighlightedIndex is chosen if enabled', async () => { + const initialHighlightedIndex = 0 + const defaultHighlightedIndex = 2 + renderSelect({ + initialHighlightedIndex, + defaultHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === initialHighlightedIndex + }, + }) + + await clickOnToggleButton() + + expect(getToggleButton()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(defaultHighlightedIndex), + ) + }) + + test('defaultHighlightedIndex is ignored if item is disabled', async () => { + const defaultHighlightedIndex = 2 + renderSelect({ + defaultHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === defaultHighlightedIndex + }, + }) + + await clickOnToggleButton() + + expect(getToggleButton()).toHaveAttribute('aria-activedescendant', '') + }) + + test('both defaultHighlightedIndex and initialHighlightedIndex are ignored if items are disabled', async () => { + const initialHighlightedIndex = 0 + const defaultHighlightedIndex = 2 + renderSelect({ + initialHighlightedIndex, + defaultHighlightedIndex, + isItemDisabled(item) { + return [initialHighlightedIndex, defaultHighlightedIndex].includes( + items.indexOf(item), + ) + }, + }) + + await clickOnToggleButton() + + expect(getToggleButton()).toHaveAttribute('aria-activedescendant', '') + }) + test('opens the closed menu at highlightedIndex from props, on every click', async () => { const highlightedIndex = 3 renderSelect({ @@ -781,6 +846,81 @@ describe('getToggleButtonProps', () => { ) }) + test('initialHighlightedIndex is ignored if item is disabled', async () => { + const initialHighlightedIndex = 2 + renderSelect({ + initialHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === initialHighlightedIndex + }, + }) + + await keyDownOnToggleButton('{ArrowUp}') + + expect(getToggleButton()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(items.length - 1), + ) + }) + + test('initialHighlightedIndex is ignored and defaultHighlightedIndex is chosen if enabled', async () => { + const initialHighlightedIndex = 0 + const defaultHighlightedIndex = 2 + renderSelect({ + initialHighlightedIndex, + defaultHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === initialHighlightedIndex + }, + }) + + await keyDownOnToggleButton('{ArrowUp}') + + expect(getToggleButton()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(defaultHighlightedIndex), + ) + }) + + test('defaultHighlightedIndex is ignored if item is disabled', async () => { + const defaultHighlightedIndex = 2 + renderSelect({ + defaultHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === defaultHighlightedIndex + }, + }) + + await keyDownOnToggleButton('{ArrowUp}') + + expect(getToggleButton()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(items.length - 1), + ) + }) + + test('both defaultHighlightedIndex and initialHighlightedIndex are ignored if items are disabled', async () => { + const initialHighlightedIndex = 0 + const defaultHighlightedIndex = 2 + renderSelect({ + initialHighlightedIndex, + defaultHighlightedIndex, + isItemDisabled(item) { + return [ + initialHighlightedIndex, + defaultHighlightedIndex, + ].includes(items.indexOf(item)) + }, + }) + + await keyDownOnToggleButton('{ArrowUp}') + + expect(getToggleButton()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(items.length - 1), + ) + }) + test('it opens the closed menu and keeps focus on the combobox', async () => { renderSelect() @@ -1038,6 +1178,81 @@ describe('getToggleButtonProps', () => { ) }) + test('initialHighlightedIndex is ignored if item is disabled', async () => { + const initialHighlightedIndex = 2 + renderSelect({ + initialHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === initialHighlightedIndex + }, + }) + + await keyDownOnToggleButton('{ArrowDown}') + + expect(getToggleButton()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(0), + ) + }) + + test('initialHighlightedIndex is ignored and defaultHighlightedIndex is chosen if enabled', async () => { + const initialHighlightedIndex = 0 + const defaultHighlightedIndex = 2 + renderSelect({ + initialHighlightedIndex, + defaultHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === initialHighlightedIndex + }, + }) + + await keyDownOnToggleButton('{ArrowDown}') + + expect(getToggleButton()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(defaultHighlightedIndex), + ) + }) + + test('defaultHighlightedIndex is ignored if item is disabled', async () => { + const defaultHighlightedIndex = 2 + renderSelect({ + defaultHighlightedIndex, + isItemDisabled(item) { + return items.indexOf(item) === defaultHighlightedIndex + }, + }) + + await keyDownOnToggleButton('{ArrowDown}') + + expect(getToggleButton()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(0), + ) + }) + + test('both defaultHighlightedIndex and initialHighlightedIndex are ignored if items are disabled', async () => { + const initialHighlightedIndex = 1 + const defaultHighlightedIndex = 2 + renderSelect({ + initialHighlightedIndex, + defaultHighlightedIndex, + isItemDisabled(item) { + return [ + initialHighlightedIndex, + defaultHighlightedIndex, + ].includes(items.indexOf(item)) + }, + }) + + await keyDownOnToggleButton('{ArrowDown}') + + expect(getToggleButton()).toHaveAttribute( + 'aria-activedescendant', + defaultIds.getItemId(0), + ) + }) + test('opens the closed menu and keeps focus on the button', async () => { renderSelect() diff --git a/src/hooks/useSelect/__tests__/props.test.js b/src/hooks/useSelect/__tests__/props.test.js index f83a8982..a6626a5d 100644 --- a/src/hooks/useSelect/__tests__/props.test.js +++ b/src/hooks/useSelect/__tests__/props.test.js @@ -234,71 +234,6 @@ describe('props', () => { } }) - test('initialHighlightedIndex is ignored if item is disabled', async () => { - const initialHighlightedIndex = 2 - renderSelect({ - initialHighlightedIndex, - isItemDisabled(item) { - return items.indexOf(item) === initialHighlightedIndex - }, - }) - - await clickOnToggleButton() - - expect(getToggleButton()).toHaveAttribute('aria-activedescendant', '') - }) - - test('initialHighlightedIndex is ignored and defaultHighlightedIndex is chosen if enabled', async () => { - const initialHighlightedIndex = 0 - const defaultHighlightedIndex = 2 - renderSelect({ - initialHighlightedIndex, - defaultHighlightedIndex, - isItemDisabled(item) { - return items.indexOf(item) === initialHighlightedIndex - }, - }) - - await clickOnToggleButton() - - expect(getToggleButton()).toHaveAttribute( - 'aria-activedescendant', - defaultIds.getItemId(defaultHighlightedIndex), - ) - }) - - test('defaultHighlightedIndex is ignored if item is disabled', async () => { - const defaultHighlightedIndex = 2 - renderSelect({ - defaultHighlightedIndex, - isItemDisabled(item) { - return items.indexOf(item) === defaultHighlightedIndex - }, - }) - - await clickOnToggleButton() - - expect(getToggleButton()).toHaveAttribute('aria-activedescendant', '') - }) - - test('both defaultHighlightedIndex and initialHighlightedIndex are ignored if items are disabled', async () => { - const initialHighlightedIndex = 0 - const defaultHighlightedIndex = 2 - renderSelect({ - initialHighlightedIndex, - defaultHighlightedIndex, - isItemDisabled(item) { - return [initialHighlightedIndex, defaultHighlightedIndex].includes( - items.indexOf(item), - ) - }, - }) - - await clickOnToggleButton() - - expect(getToggleButton()).toHaveAttribute('aria-activedescendant', '') - }) - test('isOpen controls the state property if passed', async () => { renderSelect({isOpen: true}) expect(getItems()).toHaveLength(items.length) diff --git a/src/hooks/utils.js b/src/hooks/utils.js index 9e0c4c7f..08c9e50e 100644 --- a/src/hooks/utils.js +++ b/src/hooks/utils.js @@ -331,19 +331,27 @@ function getHighlightedIndexOnOpen(props, state, offset) { ) { return initialHighlightedIndex } + if ( defaultHighlightedIndex !== undefined && !isItemDisabled(items[defaultHighlightedIndex]) ) { return defaultHighlightedIndex } + if (selectedItem) { return items.findIndex(item => itemToKey(selectedItem) === itemToKey(item)) } - if (offset === 0) { - return -1 + + if (offset < 0 && !isItemDisabled(items[items.length - 1])) { + return items.length - 1 + } + + if (offset > 0 && !isItemDisabled(items[0])) { + return 0 } - return offset < 0 ? items.length - 1 : 0 + + return -1 } /** * Tracks mouse and touch events, such as mouseDown, touchMove and touchEnd.