Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 2 additions & 3 deletions packages/react-core/src/components/Spinner/Spinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export const Spinner: React.FunctionComponent<SpinnerProps> = ({
'aria-valuetext': ariaValueText = 'Loading...',
diameter,
isInline = false,
'aria-label': ariaLabel,
'aria-label': ariaLabel = 'Contents',
'aria-labelledBy': ariaLabelledBy,
...props
}: SpinnerProps) => (
Expand All @@ -42,9 +42,8 @@ export const Spinner: React.FunctionComponent<SpinnerProps> = ({
aria-valuetext={ariaValueText}
viewBox="0 0 100 100"
{...(diameter && { style: { [cssDiameter.name]: diameter } as React.CSSProperties })}
{...(ariaLabel && { 'aria-label': ariaLabel })}
aria-label={ariaLabel}
{...(ariaLabelledBy && { 'aria-labelledBy': ariaLabelledBy })}
{...(!ariaLabel && !ariaLabelledBy && { 'aria-label': 'Contents' })}
{...props}
>
<circle className={styles.spinnerPath} cx="50" cy="50" r="45" fill="none" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import { render } from '@testing-library/react';
import { render, screen } from '@testing-library/react';
import { Spinner } from '../Spinner';

test('simple spinner', () => {
const { asFragment } = render(<Spinner />);
expect(asFragment()).toMatchSnapshot();
});

test('uses default aria-label of "Contents" when none is provided', () => {
render(<Spinner />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Contents');
});

test('uses a custom aria-label when one is provided', () => {
render(<Spinner aria-label="Loading users" />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Loading users');
});
Comment on lines +9 to +17
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add regression coverage for aria-labelledby + default aria-label coexistence.

These tests cover default and custom label values, but they don’t lock the third behavior this PR depends on: when only aria-labelledby is provided, aria-label="Contents" should still be present.

Suggested test addition
 test('uses a custom aria-label when one is provided', () => {
   render(<Spinner aria-label="Loading users" />);
   expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Loading users');
 });
+
+test('keeps default aria-label when aria-labelledby is provided', () => {
+  render(<Spinner aria-labelledby="external-label" />);
+  const spinner = screen.getByRole('progressbar');
+  expect(spinner).toHaveAttribute('aria-labelledby', 'external-label');
+  expect(spinner).toHaveAttribute('aria-label', 'Contents');
+});
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
test('uses default aria-label of "Contents" when none is provided', () => {
render(<Spinner />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Contents');
});
test('uses a custom aria-label when one is provided', () => {
render(<Spinner aria-label="Loading users" />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Loading users');
});
test('uses default aria-label of "Contents" when none is provided', () => {
render(<Spinner />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Contents');
});
test('uses a custom aria-label when one is provided', () => {
render(<Spinner aria-label="Loading users" />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-label', 'Loading users');
});
test('keeps default aria-label when aria-labelledby is provided', () => {
render(<Spinner aria-labelledby="external-label" />);
const spinner = screen.getByRole('progressbar');
expect(spinner).toHaveAttribute('aria-labelledby', 'external-label');
expect(spinner).toHaveAttribute('aria-label', 'Contents');
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-core/src/components/Spinner/__tests__/Spinner.test.tsx` around
lines 9 - 17, Add a regression test in Spinner.test.tsx that renders <Spinner
aria-labelledby="some-id" /> and asserts that screen.getByRole('progressbar')
has both aria-labelledby="some-id" and the default aria-label="Contents"; update
the test suite by adding a new test case (similar to the existing ones) to
exercise the coexistence of aria-labelledby and the default aria-label so the
Spinner component preserves aria-label when only aria-labelledby is provided.


test('renders aria-labelledBy when provided', () => {
render(<Spinner aria-labelledBy="external-label" />);
expect(screen.getByRole('progressbar')).toHaveAttribute('aria-labelledBy', 'external-label');
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

New test codifies the pre-existing aria-labelledBy (wrong casing) behavior.

The prop and assertion both use aria-labelledBy (capital B). Since React camelCases all attributes except data-* and aria-*, it passes aria-* names as-is to the DOM. On the SVG element (XML/case-sensitive), this produces a non-standard aria-labelledBy attribute that screen readers won't find — they look for aria-labelledby (all lowercase, per WAI-ARIA spec).

The test will pass today (JSDOM stores aria-labelledBy exactly as written and toHaveAttribute finds it), but it is asserting broken accessibility behavior. The correct attribute name throughout — interface, component, and test — should be aria-labelledby.

✅ Corrected test (aligned with WAI-ARIA spec)
 test('renders aria-labelledBy when provided', () => {
-  render(<Spinner aria-labelledBy="external-label" />);
-  expect(screen.getByRole('progressbar')).toHaveAttribute('aria-labelledBy', 'external-label');
+  render(<Spinner aria-labelledby="external-label" />);
+  expect(screen.getByRole('progressbar')).toHaveAttribute('aria-labelledby', 'external-label');
 });

The full fix also requires renaming the prop in SpinnerProps from 'aria-labelledBy'?: string to 'aria-labelledby'?: string and updating the corresponding destructuring and spread in Spinner.tsx. Because that is a public API rename, it may warrant its own PR and a deprecation notice.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-core/src/components/Spinner/__tests__/Spinner.test.tsx` around
lines 19 - 22, Change the misspelled ARIA prop from "aria-labelledBy" to the
correct "aria-labelledby" across the API and tests: update the test case (the
'renders aria-labelledBy when provided' test) to render <Spinner
aria-labelledby="external-label" /> and assert for 'aria-labelledby'; rename the
prop in SpinnerProps from 'aria-labelledBy'?: string to 'aria-labelledby'?:
string and update Spinner component's prop destructuring/spread where it uses
that key so the SVG receives aria-labelledby; if this is a breaking public API
change, mark for separate PR/deprecation notice as appropriate.


test('small spinner', () => {
const { asFragment } = render(<Spinner size="sm" />);
expect(asFragment()).toMatchSnapshot();
Expand Down
Loading