Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/hooks/useCombobox/__tests__/getInputProps.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,21 @@ describe('getInputProps', () => {
expect(getItems()).toHaveLength(items.length)
})

test('it skips disabled trailing options when opening the menu', async () => {
const {user} = renderCombobox({
isItemDisabled(_item, index) {
return index >= items.length - 2
},
})

await keyDownOnInput(user, '{ArrowUp}')

expect(getInput()).toHaveAttribute(
'aria-activedescendant',
defaultIds.getItemId(items.length - 3),
)
})

test('it opens the closed menu with selected option highlighted', async () => {
const selectedIndex = 4
const {user} = renderCombobox({
Expand Down Expand Up @@ -920,6 +935,21 @@ describe('getInputProps', () => {
expect(getItems()).toHaveLength(items.length)
})

test('it skips disabled leading options when opening the menu', async () => {
const {user} = renderCombobox({
isItemDisabled(_item, index) {
return index < 2
},
})

await keyDownOnInput(user, '{ArrowDown}')

expect(getInput()).toHaveAttribute(
'aria-activedescendant',
defaultIds.getItemId(2),
)
})

test('opens the closed menu with selected option highlighted', async () => {
const selectedIndex = 4
const {user} = renderCombobox({
Expand Down
30 changes: 30 additions & 0 deletions src/hooks/useSelect/__tests__/getToggleButtonProps.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -847,6 +847,21 @@ describe('getToggleButtonProps', () => {
expect(getItems()).toHaveLength(items.length)
})

test('it skips disabled trailing options when opening the menu', async () => {
const {user} = renderSelect({
isItemDisabled(_item, index) {
return index >= items.length - 2
},
})

await keyDownOnToggleButton(user, '{ArrowUp}')

expect(getToggleButton()).toHaveAttribute(
'aria-activedescendant',
defaultIds.getItemId(items.length - 3),
)
})

test('it opens the closed menu with selected option highlighted', async () => {
const selectedIndex = 4
const {user} = renderSelect({
Expand Down Expand Up @@ -1188,6 +1203,21 @@ describe('getToggleButtonProps', () => {
expect(getItems()).toHaveLength(items.length)
})

test('skips disabled leading options when opening the menu', async () => {
const {user} = renderSelect({
isItemDisabled(_item, index) {
return index < 2
},
})

await keyDownOnToggleButton(user, '{ArrowDown}')

expect(getToggleButton()).toHaveAttribute(
'aria-activedescendant',
defaultIds.getItemId(2),
)
})

test('opens the closed menu with selected option highlighted', async () => {
const selectedIndex = 4
const {user} = renderSelect({
Expand Down
67 changes: 59 additions & 8 deletions src/hooks/utils/__tests__/getHighlightedIndexOnOpen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ const isItemDisabled = () => false
const isFirstItemDisabled = (_item: string, index: number) => index === 0
const isLastItemDisabled = (_item: string, index: number) =>
index === items.length - 1
const areAllItemsDisabled = () => true
const areFirstTwoItemsDisabled = (_item: string, index: number) => index < 2
const areLastTwoItemsDisabled = (_item: string, index: number) => index > 0
const itemToKey = (item: string | null) => item

test('returns -1 when items is empty', () => {
Expand Down Expand Up @@ -70,7 +73,7 @@ test('skips initialHighlightedIndex when state highlightedIndex does not match',
).toBe(-1)
})

test('skips initialHighlightedIndex when that item is disabled', () => {
test('falls back to the next enabled item when initialHighlightedIndex is disabled', () => {
const initialHighlightedIndex = 0
const defaultHighlightedIndex = undefined
const selectedItem = null
Expand All @@ -88,7 +91,7 @@ test('skips initialHighlightedIndex when that item is disabled', () => {
highlightedIndex,
offset,
),
).toBe(-1)
).toBe(1)
})

test('returns defaultHighlightedIndex when it is not disabled', () => {
Expand All @@ -112,7 +115,7 @@ test('returns defaultHighlightedIndex when it is not disabled', () => {
).toBe(2)
})

test('skips defaultHighlightedIndex when that item is disabled', () => {
test('falls back to the next enabled item when defaultHighlightedIndex is disabled', () => {
const initialHighlightedIndex = undefined
const defaultHighlightedIndex = 0
const selectedItem = null
Expand All @@ -130,7 +133,7 @@ test('skips defaultHighlightedIndex when that item is disabled', () => {
highlightedIndex,
offset,
),
).toBe(-1)
).toBe(1)
})

test('returns index of selectedItem when selectedItem is set', () => {
Expand Down Expand Up @@ -175,7 +178,7 @@ test('returns last index when offset is negative and last item is not disabled',
).toBe(2)
})

test('skips last index when offset is negative and last item is disabled', () => {
test('returns previous enabled index when offset is negative and last item is disabled', () => {
const initialHighlightedIndex = undefined
const defaultHighlightedIndex = undefined
const selectedItem = null
Expand All @@ -193,7 +196,22 @@ test('skips last index when offset is negative and last item is disabled', () =>
highlightedIndex,
offset,
),
).toBe(-1)
).toBe(1)
})

test('skips consecutive disabled items when opening with a negative offset', () => {
expect(
getHighlightedIndexOnOpen(
items,
undefined,
undefined,
areLastTwoItemsDisabled,
itemToKey,
null,
-1,
-1,
),
).toBe(0)
})

test('returns 0 when offset is positive and first item is not disabled', () => {
Expand All @@ -217,7 +235,7 @@ test('returns 0 when offset is positive and first item is not disabled', () => {
).toBe(0)
})

test('skips 0 when offset is positive and first item is disabled', () => {
test('returns next enabled index when offset is positive and first item is disabled', () => {
const initialHighlightedIndex = undefined
const defaultHighlightedIndex = undefined
const selectedItem = null
Expand All @@ -235,9 +253,42 @@ test('skips 0 when offset is positive and first item is disabled', () => {
highlightedIndex,
offset,
),
).toBe(-1)
).toBe(1)
})

test('skips consecutive disabled items when opening with a positive offset', () => {
expect(
getHighlightedIndexOnOpen(
items,
undefined,
undefined,
areFirstTwoItemsDisabled,
itemToKey,
null,
-1,
1,
),
).toBe(2)
})

test.each([-1, 1])(
'returns -1 when all items are disabled and offset is %i',
offset => {
expect(
getHighlightedIndexOnOpen(
items,
undefined,
undefined,
areAllItemsDisabled,
itemToKey,
null,
-1,
offset,
),
).toBe(-1)
},
)

test('returns -1 when no conditions match', () => {
const initialHighlightedIndex = undefined
const defaultHighlightedIndex = undefined
Expand Down
15 changes: 6 additions & 9 deletions src/hooks/utils/getHighlightedIndexOnOpen.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {getNonDisabledIndex} from '../../utils/getNonDisabledIndex'

/* eslint-disable max-params */
/**
* Returns the highlighted index when the menu is opened.
Expand Down Expand Up @@ -58,16 +60,11 @@ export function getHighlightedIndexOnOpen<Item>(
return items.findIndex(item => itemToKey(selectedItem) === itemToKey(item))
}

if (
offset < 0 &&
items[items.length - 1] &&
!isItemDisabled(items[items.length - 1] as Item, items.length - 1)
) {
return items.length - 1
}
if (offset !== 0) {
const backwards = offset < 0
const startIndex = backwards ? items.length - 1 : 0

if (offset > 0 && items[0] && !isItemDisabled(items[0], 0)) {
return 0
return getNonDisabledIndex(startIndex, backwards, items, isItemDisabled)
}

return -1
Expand Down