From 55d370c097b8ff0b9dab012d364253fc44ab8a17 Mon Sep 17 00:00:00 2001 From: Jason Date: Fri, 20 Dec 2024 10:45:23 -0600 Subject: [PATCH] fix(react): fix listbox focus issue when listbox options have changed (#1777) --- .../react/src/components/Listbox/Listbox.tsx | 5 ++- .../src/components/Listbox/index.test.tsx | 40 ++++++++++++++++++- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/packages/react/src/components/Listbox/Listbox.tsx b/packages/react/src/components/Listbox/Listbox.tsx index 4aeabaa8c..2ee88e4d3 100644 --- a/packages/react/src/components/Listbox/Listbox.tsx +++ b/packages/react/src/components/Listbox/Listbox.tsx @@ -247,7 +247,10 @@ const Listbox = forwardRef< const handleFocus = useCallback( (event: React.FocusEvent) => { - if (!activeOption) { + if ( + !activeOption || + !options.some((option) => option.element === activeOption.element) + ) { const firstOption = options.find( (option) => !isDisabledOption(option) ); diff --git a/packages/react/src/components/Listbox/index.test.tsx b/packages/react/src/components/Listbox/index.test.tsx index 7f3a3e699..6ff5eda97 100644 --- a/packages/react/src/components/Listbox/index.test.tsx +++ b/packages/react/src/components/Listbox/index.test.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { render, screen, fireEvent } from '@testing-library/react'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import Listbox from './'; import { ListboxGroup, ListboxOption } from './'; import axe from '../../axe'; @@ -202,7 +202,7 @@ test('should set the first non-disabled option as active on focus', () => { ); - fireEvent.focus(screen.getByRole('option', { name: 'Banana' })); + fireEvent.focus(screen.getByRole('listbox')); expect(screen.getByRole('option', { name: 'Banana' })).toHaveClass( 'ListboxOption--active' ); @@ -212,6 +212,42 @@ test('should set the first non-disabled option as active on focus', () => { ); }); +test('should set the first non-disabled option as active on focus when the options have changed', () => { + const { rerender } = render( + + Apple + Banana + Cantaloupe + + ); + + waitFor(() => { + fireEvent.focus(screen.getByRole('listbox')); + expect(screen.getByRole('listbox')).toHaveFocus(); + }); + + rerender( + + Dragon Fruit + Elderberry + Fig + + ); + + waitFor(() => { + fireEvent.focus(screen.getByRole('listbox')); + expect(screen.getByRole('listbox')).toHaveFocus(); + }); + + expect(screen.getByRole('option', { name: 'Elderberry' })).toHaveClass( + 'ListboxOption--active' + ); + expect(screen.getByRole('listbox')).toHaveAttribute( + 'aria-activedescendant', + screen.getByRole('option', { name: 'Elderberry' }).getAttribute('id') + ); +}); + test('should set selected value with "value" prop when listbox option only has text label', () => { render(