From 34e2105f4735da28ab6cc6754a697c97dd8b8c78 Mon Sep 17 00:00:00 2001 From: Minwook Shin <163576506+minwookshin@users.noreply.github.com> Date: Sat, 3 Oct 2026 08:01:10 -0400 Subject: [PATCH 1/2] fix(FormSelect): accept aria-labelledby as an accessible label Assisted-by: OpenAI Codex. Regression tests cover the accessible name and missing-label warning. --- .../src/components/FormSelect/FormSelect.tsx | 6 +-- .../FormSelect/__tests__/FormSelect.test.tsx | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/packages/react-core/src/components/FormSelect/FormSelect.tsx b/packages/react-core/src/components/FormSelect/FormSelect.tsx index 0e909bbeadc..f466f3efffe 100644 --- a/packages/react-core/src/components/FormSelect/FormSelect.tsx +++ b/packages/react-core/src/components/FormSelect/FormSelect.tsx @@ -31,7 +31,7 @@ export interface FormSelectProps onFocus?: (event: React.FormEvent) => void; /** Optional callback for updating when selection changes */ onChange?: (event: React.FormEvent, value: string) => void; - /** Custom flag to show that the FormSelect requires an associated id or aria-label. */ + /** Accessible label. Required unless an associated id or aria-labelledby is specified. */ 'aria-label'?: string; /** Value to overwrite the randomly generated data-ouia-component-id.*/ ouiaId?: number | string; @@ -43,9 +43,9 @@ class FormSelect extends Component { static displayName = 'FormSelect'; constructor(props: FormSelectProps) { super(props); - if (!props.id && !props['aria-label']) { + if (!props.id && !props['aria-label'] && !props['aria-labelledby']) { // eslint-disable-next-line no-console - console.error('FormSelect requires either an id or aria-label to be specified'); + console.error('FormSelect requires an id, aria-label, or aria-labelledby to be specified'); } } diff --git a/packages/react-core/src/components/FormSelect/__tests__/FormSelect.test.tsx b/packages/react-core/src/components/FormSelect/__tests__/FormSelect.test.tsx index 347a086492c..d2a453485af 100644 --- a/packages/react-core/src/components/FormSelect/__tests__/FormSelect.test.tsx +++ b/packages/react-core/src/components/FormSelect/__tests__/FormSelect.test.tsx @@ -186,4 +186,42 @@ describe('FormSelect', () => { expect(myMock).toHaveBeenCalled(); expect(myMock.mock.calls[0][1]).toEqual('mr'); }); + + test('uses aria-labelledby without generating a console error', () => { + const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); + + try { + render( + <> + Title + + + + + ); + + expect(screen.getByRole('combobox', { name: 'Title' })).toHaveAttribute('aria-labelledby', 'select-label'); + expect(consoleError).not.toHaveBeenCalled(); + } finally { + consoleError.mockRestore(); + } + }); + + test('warns when aria-labelledby is empty and no other label is provided', () => { + const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); + + try { + render( + + + + ); + + expect(consoleError).toHaveBeenCalledWith( + 'FormSelect requires an id, aria-label, or aria-labelledby to be specified' + ); + } finally { + consoleError.mockRestore(); + } + }); }); From e3e8cc8cdd33a38106e81f6b3e86d9428a573813 Mon Sep 17 00:00:00 2001 From: Minwook Shin <163576506+minwookshin@users.noreply.github.com> Date: Sat, 3 Oct 2026 23:33:35 -0400 Subject: [PATCH 2/2] fix(FormSelect): reject whitespace-only label references Assisted-by: OpenAI Codex --- .../src/components/FormSelect/FormSelect.tsx | 2 +- .../FormSelect/__tests__/FormSelect.test.tsx | 43 ++++++++++--------- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/packages/react-core/src/components/FormSelect/FormSelect.tsx b/packages/react-core/src/components/FormSelect/FormSelect.tsx index f466f3efffe..4703b82c523 100644 --- a/packages/react-core/src/components/FormSelect/FormSelect.tsx +++ b/packages/react-core/src/components/FormSelect/FormSelect.tsx @@ -43,7 +43,7 @@ class FormSelect extends Component { static displayName = 'FormSelect'; constructor(props: FormSelectProps) { super(props); - if (!props.id && !props['aria-label'] && !props['aria-labelledby']) { + if (!props.id && !props['aria-label'] && !props['aria-labelledby']?.trim()) { // eslint-disable-next-line no-console console.error('FormSelect requires an id, aria-label, or aria-labelledby to be specified'); } diff --git a/packages/react-core/src/components/FormSelect/__tests__/FormSelect.test.tsx b/packages/react-core/src/components/FormSelect/__tests__/FormSelect.test.tsx index d2a453485af..56c3012bfa1 100644 --- a/packages/react-core/src/components/FormSelect/__tests__/FormSelect.test.tsx +++ b/packages/react-core/src/components/FormSelect/__tests__/FormSelect.test.tsx @@ -187,32 +187,35 @@ describe('FormSelect', () => { expect(myMock.mock.calls[0][1]).toEqual('mr'); }); - test('uses aria-labelledby without generating a console error', () => { - const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); - - try { - render( - <> - Title - - - - - ); - - expect(screen.getByRole('combobox', { name: 'Title' })).toHaveAttribute('aria-labelledby', 'select-label'); - expect(consoleError).not.toHaveBeenCalled(); - } finally { - consoleError.mockRestore(); + test.each(['select-label', ' select-label \t'])( + 'uses aria-labelledby %p without generating a console error', + (labelledBy) => { + const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); + + try { + render( + <> + Title + + + + + ); + + expect(screen.getByRole('combobox', { name: 'Title' })).toHaveAttribute('aria-labelledby', labelledBy); + expect(consoleError).not.toHaveBeenCalled(); + } finally { + consoleError.mockRestore(); + } } - }); + ); - test('warns when aria-labelledby is empty and no other label is provided', () => { + test.each(['', ' ', '\t\n'])('warns when aria-labelledby is %p and no other label is provided', (labelledBy) => { const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {}); try { render( - + );