Skip to content

Commit d443e90

Browse files
authored
fix(time-picker): announce invalid/warn state message (#22109)
* fix(time-picker): announce invalid state messages * test(time-picker): add tests * fix(time-picker): preserve aria-describedby and added tests
1 parent bf130cb commit d443e90

2 files changed

Lines changed: 75 additions & 0 deletions

File tree

packages/react/src/components/TimePicker/TimePicker-test.js

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,15 @@ describe('TimePicker', () => {
177177
expect(
178178
container.querySelector('.cds--form-requirement')
179179
).toHaveTextContent('Invalid time');
180+
const invalidText = screen.getByText('Invalid time');
181+
expect(screen.getByRole('textbox')).toHaveAttribute(
182+
'aria-describedby',
183+
invalidText.id
184+
);
185+
expect(screen.getByRole('textbox')).toHaveAttribute(
186+
'aria-invalid',
187+
'true'
188+
);
180189
});
181190

182191
it('should show warning state when warning is true', () => {
@@ -192,6 +201,12 @@ describe('TimePicker', () => {
192201
expect(
193202
container.querySelector('.cds--form-requirement')
194203
).toHaveTextContent('Warning message');
204+
const warningText = screen.getByText('Warning message');
205+
expect(screen.getByRole('textbox')).toHaveAttribute(
206+
'aria-describedby',
207+
warningText.id
208+
);
209+
expect(screen.getByRole('textbox')).not.toHaveAttribute('aria-invalid');
195210
});
196211

197212
it('should not show invalid state when disabled', () => {
@@ -289,5 +304,51 @@ describe('TimePicker', () => {
289304
container.querySelector('.cds--time-picker__input-field-error')
290305
).not.toBeInTheDocument();
291306
});
307+
308+
it('should preserve provided aria-describedby when not invalid or warning', () => {
309+
render(<TimePicker id="time-picker" aria-describedby="custom-hint" />);
310+
expect(screen.getByRole('textbox')).toHaveAttribute(
311+
'aria-describedby',
312+
'custom-hint'
313+
);
314+
});
315+
316+
it('should merge invalid aria attributes with consumer-provided aria-describedby', () => {
317+
render(
318+
<TimePicker
319+
id="time-picker"
320+
invalid
321+
invalidText="Invalid time"
322+
aria-describedby="custom-hint"
323+
aria-invalid={false}
324+
/>
325+
);
326+
const invalidText = screen.getByText('Invalid time');
327+
expect(screen.getByRole('textbox')).toHaveAttribute(
328+
'aria-describedby',
329+
`${invalidText.id} custom-hint`
330+
);
331+
expect(screen.getByRole('textbox')).toHaveAttribute(
332+
'aria-invalid',
333+
'true'
334+
);
335+
});
336+
337+
it('should merge warning aria attributes with consumer-provided aria-describedby', () => {
338+
render(
339+
<TimePicker
340+
id="time-picker"
341+
warning
342+
warningText="Warning message"
343+
aria-describedby="custom-hint"
344+
/>
345+
);
346+
const warningText = screen.getByText('Warning message');
347+
expect(screen.getByRole('textbox')).toHaveAttribute(
348+
'aria-describedby',
349+
`${warningText.id} custom-hint`
350+
);
351+
expect(screen.getByRole('textbox')).not.toHaveAttribute('aria-invalid');
352+
});
292353
});
293354
});

packages/react/src/components/TimePicker/TimePicker.tsx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ const frFn = forwardRef<HTMLInputElement, TimePickerProps>;
142142

143143
const TimePicker = frFn((props, ref) => {
144144
const {
145+
['aria-describedby']: ariaDescribedBy,
145146
children,
146147
className,
147148
inputClassName,
@@ -268,6 +269,17 @@ const TimePicker = frFn((props, ref) => {
268269
const readOnlyProps = {
269270
readOnly: readOnly,
270271
};
272+
const describedBy =
273+
[
274+
normalizedProps.invalid
275+
? normalizedProps.invalidId
276+
: normalizedProps.warn
277+
? normalizedProps.warnId
278+
: null,
279+
ariaDescribedBy,
280+
]
281+
.filter(Boolean)
282+
.join(' ') || undefined;
271283

272284
return (
273285
<div className={cx(`${prefix}--form-item`, className)}>
@@ -290,6 +302,8 @@ const TimePicker = frFn((props, ref) => {
290302
value={value}
291303
{...rest}
292304
{...readOnlyProps}
305+
aria-describedby={describedBy}
306+
aria-invalid={normalizedProps.invalid ? true : undefined}
293307
/>
294308
{(normalizedProps.invalid || normalizedProps.warn) &&
295309
normalizedProps.icon && (

0 commit comments

Comments
 (0)