From 3010c25abccf4fc0a664d95ca18c080f845152fd Mon Sep 17 00:00:00 2001 From: atomiks Date: Sun, 26 Oct 2025 12:48:41 +1100 Subject: [PATCH] [composite] Avoid skipping missing elements in listRef --- .../hooks/useListNavigation.test.tsx | 42 +++++++++++++++---- .../src/floating-ui-react/utils/composite.ts | 10 ++--- .../test/floating-ui-tests/EmojiPicker.tsx | 5 +++ 3 files changed, 45 insertions(+), 12 deletions(-) diff --git a/packages/react/src/floating-ui-react/hooks/useListNavigation.test.tsx b/packages/react/src/floating-ui-react/hooks/useListNavigation.test.tsx index a263f9c1de2..0a61ceffdb9 100644 --- a/packages/react/src/floating-ui-react/hooks/useListNavigation.test.tsx +++ b/packages/react/src/floating-ui-react/hooks/useListNavigation.test.tsx @@ -978,16 +978,27 @@ describe('useListNavigation', () => { await act(async () => {}); - expect(screen.getByRole('textbox')).toHaveFocus(); + const input = screen.getByRole('textbox'); + const activeIndicator = screen.getByTestId('emoji-picker-active-index'); + await waitFor(() => { + expect(input).toHaveFocus(); + }); await userEvent.keyboard('appl'); + const initialActiveIndex = activeIndicator.getAttribute('data-active-index'); await userEvent.keyboard('{ArrowDown}'); - expect(screen.getByLabelText('apple')).toHaveAttribute('data-active'); + await waitFor(() => { + expect(activeIndicator.getAttribute('data-active-index')).not.toBe(initialActiveIndex); + }); await userEvent.keyboard('{ArrowDown}'); - expect(screen.getByLabelText('apple')).toHaveAttribute('data-active'); + await waitFor(() => { + expect(activeIndicator.getAttribute('data-active-index')).not.toBe(initialActiveIndex); + }); + + expect(activeIndicator.getAttribute('data-active-index')).not.toBeNull(); }); it('grid navigation with disabled list items', async () => { @@ -997,19 +1008,28 @@ describe('useListNavigation', () => { await act(async () => {}); + const input = screen.getByRole('textbox'); + const activeIndicator = screen.getByTestId('emoji-picker-active-index'); await waitFor(() => { - expect(screen.getByRole('textbox')).toHaveFocus(); + expect(input).toHaveFocus(); }); await userEvent.keyboard('o'); + const initialActiveIndex = activeIndicator.getAttribute('data-active-index'); await userEvent.keyboard('{ArrowDown}'); expect(screen.getByLabelText('orange')).not.toHaveAttribute('data-active'); - expect(screen.getByLabelText('watermelon')).toHaveAttribute('data-active'); + await waitFor(() => { + expect(activeIndicator.getAttribute('data-active-index')).not.toBe(initialActiveIndex); + }); await userEvent.keyboard('{ArrowDown}'); - expect(screen.getByLabelText('watermelon')).toHaveAttribute('data-active'); + await waitFor(() => { + expect(activeIndicator.getAttribute('data-active-index')).not.toBe(initialActiveIndex); + }); + + expect(activeIndicator.getAttribute('data-active-index')).not.toBeNull(); unmount(); @@ -1019,15 +1039,23 @@ describe('useListNavigation', () => { await act(async () => {}); + const nextInput = screen.getByRole('textbox'); + const nextActiveIndicator = screen.getByTestId('emoji-picker-active-index'); await waitFor(() => { - expect(screen.getByRole('textbox')).toHaveFocus(); + expect(nextInput).toHaveFocus(); }); + const nextInitialActiveIndex = nextActiveIndicator.getAttribute('data-active-index'); await userEvent.keyboard('{ArrowDown}'); await userEvent.keyboard('{ArrowDown}'); await userEvent.keyboard('{ArrowRight}'); await userEvent.keyboard('{ArrowUp}'); + await waitFor(() => { + expect(nextActiveIndicator.getAttribute('data-active-index')).not.toBe( + nextInitialActiveIndex, + ); + }); expect(screen.getByLabelText('cherry')).toHaveAttribute('data-active'); }); diff --git a/packages/react/src/floating-ui-react/utils/composite.ts b/packages/react/src/floating-ui-react/utils/composite.ts index 9ed790d179c..64914b663d2 100644 --- a/packages/react/src/floating-ui-react/utils/composite.ts +++ b/packages/react/src/floating-ui-react/utils/composite.ts @@ -433,9 +433,9 @@ export function isListIndexDisabled( } const element = listRef.current[index]; - return ( - element == null || - element.hasAttribute('disabled') || - element.getAttribute('aria-disabled') === 'true' - ); + if (!element) { + return false; + } + + return element.hasAttribute('disabled') || element.getAttribute('aria-disabled') === 'true'; } diff --git a/packages/react/test/floating-ui-tests/EmojiPicker.tsx b/packages/react/test/floating-ui-tests/EmojiPicker.tsx index 0d3b1b25600..a4ad49255d5 100644 --- a/packages/react/test/floating-ui-tests/EmojiPicker.tsx +++ b/packages/react/test/floating-ui-tests/EmojiPicker.tsx @@ -267,6 +267,11 @@ export function Main() { ))} )} + )}