Skip to content

Commit 3d35405

Browse files
committed
fix(ui-date-time-input,ui-date-input,ui-calendar): set missing aria-selected on calendar days, BREAKING CHANGE: add mandatory prop selectedLabel, change datePickerDialog prop to mandatory
1 parent 8083f00 commit 3d35405

28 files changed

Lines changed: 569 additions & 135 deletions

File tree

packages/ui-calendar/src/Calendar/__tests__/Calendar.test.tsx

Lines changed: 165 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const generateDays = (count = Calendar.DAY_COUNT) => {
4040
key={date.toISOString()}
4141
date={date.toISOString()}
4242
label={date.toISOString()}
43+
selectedLabel="Selected"
4344
isOutsideMonth={date.getMonth() !== 7}
4445
>
4546
{date.getDate()}
@@ -71,14 +72,14 @@ describe('<Calendar />', () => {
7172

7273
describe('with minimal config', () => {
7374
it('should render 44 buttons without config', async () => {
74-
render(<Calendar />)
75+
render(<Calendar selectedLabel="Selected" />)
7576
const buttons = document.getElementsByTagName('button')
7677

7778
expect(buttons.length).toEqual(44)
7879
})
7980

8081
it('should render proper week names by default', async () => {
81-
const { container } = render(<Calendar />)
82+
const { container } = render(<Calendar selectedLabel="Selected" />)
8283
const thead = container.querySelector('thead')
8384

8485
expect(thead).toHaveTextContent(
@@ -87,7 +88,9 @@ describe('<Calendar />', () => {
8788
})
8889

8990
it('should render proper week names if locale is set', async () => {
90-
const { container } = render(<Calendar locale="hu" />)
91+
const { container } = render(
92+
<Calendar locale="hu" selectedLabel="Selected" />
93+
)
9194
const thead = container.querySelector('thead')
9295

9396
expect(thead).toHaveTextContent(
@@ -100,6 +103,7 @@ describe('<Calendar />', () => {
100103
<Calendar
101104
currentDate="2023-12-15"
102105
disabledDates={['2023-12-22', '2023-12-12', '2023-12-11']}
106+
selectedLabel="Selected"
103107
/>
104108
)
105109
const buttons = container.querySelectorAll('button[disabled]')
@@ -115,23 +119,29 @@ describe('<Calendar />', () => {
115119
})
116120

117121
it('should indicate selected day', async () => {
118-
render(<Calendar currentDate="2023-12-15" selectedDate="2023-12-22" />)
122+
render(
123+
<Calendar
124+
currentDate="2023-12-15"
125+
selectedDate="2023-12-22"
126+
selectedLabel="Selected"
127+
/>
128+
)
119129

120-
const selectedDay =
121-
screen.queryByText('22 December 2023')?.parentElement?.parentElement
130+
const selectedDay = screen.queryByText('22 December 2023, Selected')
131+
?.parentElement?.parentElement
122132

123133
expect(selectedDay).toBeDefined()
124-
if (selectedDay) {
125-
expect(window.getComputedStyle(selectedDay)?.background).toBe(
126-
'rgb(3, 137, 61)'
127-
)
128-
}
134+
expect(window.getComputedStyle(selectedDay!).background).toBe(
135+
'rgb(3, 137, 61)'
136+
)
129137
})
130138
})
131139

132140
it('should render children', async () => {
133141
const { container } = render(
134-
<Calendar renderWeekdayLabels={weekdayLabels}>{generateDays()}</Calendar>
142+
<Calendar renderWeekdayLabels={weekdayLabels} selectedLabel="Selected">
143+
{generateDays()}
144+
</Calendar>
135145
)
136146

137147
const calendarDays = container.querySelectorAll(
@@ -145,7 +155,7 @@ describe('<Calendar />', () => {
145155
const count = Calendar.DAY_COUNT - 1
146156

147157
render(
148-
<Calendar renderWeekdayLabels={weekdayLabels}>
158+
<Calendar renderWeekdayLabels={weekdayLabels} selectedLabel="Selected">
149159
{generateDays(count)}
150160
</Calendar>
151161
)
@@ -160,7 +170,9 @@ describe('<Calendar />', () => {
160170

161171
it('should render weekday labels', async () => {
162172
const { container, rerender } = render(
163-
<Calendar renderWeekdayLabels={weekdayLabels}>{generateDays()}</Calendar>
173+
<Calendar renderWeekdayLabels={weekdayLabels} selectedLabel="Selected">
174+
{generateDays()}
175+
</Calendar>
164176
)
165177

166178
const originalHeaders = container.querySelectorAll('th')
@@ -182,7 +194,10 @@ describe('<Calendar />', () => {
182194

183195
// Set prop: renderWeekdayLabels
184196
rerender(
185-
<Calendar renderWeekdayLabels={functionalWeekdayLabels}>
197+
<Calendar
198+
renderWeekdayLabels={functionalWeekdayLabels}
199+
selectedLabel="Selected"
200+
>
186201
{generateDays()}
187202
</Calendar>
188203
)
@@ -196,7 +211,11 @@ describe('<Calendar />', () => {
196211
})
197212

198213
it('should warn if 7 weekday labels are not provided', async () => {
199-
render(<Calendar renderWeekdayLabels={[]}>{generateDays()}</Calendar>)
214+
render(
215+
<Calendar renderWeekdayLabels={[]} selectedLabel="Selected">
216+
{generateDays()}
217+
</Calendar>
218+
)
200219

201220
const expectedErrorMessage =
202221
'`renderWeekdayLabels` should be an array with 7 labels (one for each weekday). 0 provided.'
@@ -209,7 +228,9 @@ describe('<Calendar />', () => {
209228

210229
it('should format the weekday labels and days correctly', async () => {
211230
const { container } = render(
212-
<Calendar renderWeekdayLabels={weekdayLabels}>{generateDays()}</Calendar>
231+
<Calendar renderWeekdayLabels={weekdayLabels} selectedLabel="Selected">
232+
{generateDays()}
233+
</Calendar>
213234
)
214235

215236
const headerRow = container.querySelectorAll('thead > tr')
@@ -239,6 +260,7 @@ describe('<Calendar />', () => {
239260
<Calendar
240261
renderWeekdayLabels={weekdayLabels}
241262
renderNavigationLabel={navLabel}
263+
selectedLabel="Selected"
242264
>
243265
{generateDays()}
244266
</Calendar>
@@ -254,6 +276,7 @@ describe('<Calendar />', () => {
254276
<Calendar
255277
renderWeekdayLabels={weekdayLabels}
256278
renderNavigationLabel={() => navLabel}
279+
selectedLabel="Selected"
257280
>
258281
{generateDays()}
259282
</Calendar>
@@ -267,7 +290,9 @@ describe('<Calendar />', () => {
267290

268291
it('should render next and prev buttons', async () => {
269292
const { rerender } = render(
270-
<Calendar renderWeekdayLabels={weekdayLabels}>{generateDays()}</Calendar>
293+
<Calendar renderWeekdayLabels={weekdayLabels} selectedLabel="Selected">
294+
{generateDays()}
295+
</Calendar>
271296
)
272297
const defaultPrevButton = screen.getByText('Previous month')
273298
const defaultNextButton = screen.getByText('Next month')
@@ -280,6 +305,7 @@ describe('<Calendar />', () => {
280305
renderWeekdayLabels={weekdayLabels}
281306
renderPrevMonthButton={<button>test-prev</button>}
282307
renderNextMonthButton={<button>test-next</button>}
308+
selectedLabel="Selected"
283309
>
284310
{generateDays()}
285311
</Calendar>
@@ -295,6 +321,7 @@ describe('<Calendar />', () => {
295321
renderWeekdayLabels={weekdayLabels}
296322
renderPrevMonthButton={() => <button>func-test-prev</button>}
297323
renderNextMonthButton={() => <button>func-test-next</button>}
324+
selectedLabel="Selected"
298325
>
299326
{generateDays()}
300327
</Calendar>
@@ -317,6 +344,7 @@ describe('<Calendar />', () => {
317344
renderNextMonthButton={<button>next month</button>}
318345
onRequestRenderPrevMonth={onRequestRenderPrevMonth}
319346
onRequestRenderNextMonth={onRequestRenderNextMonth}
347+
selectedLabel="Selected"
320348
>
321349
{generateDays()}
322350
</Calendar>
@@ -340,7 +368,11 @@ describe('<Calendar />', () => {
340368
describe('when role="listbox"', () => {
341369
it('should set role="listbox" on table root and role="presentation" on the correct elements', async () => {
342370
const { container } = render(
343-
<Calendar renderWeekdayLabels={weekdayLabels} role="listbox">
371+
<Calendar
372+
renderWeekdayLabels={weekdayLabels}
373+
role="listbox"
374+
selectedLabel="Selected"
375+
>
344376
{generateDays()}
345377
</Calendar>
346378
)
@@ -361,7 +393,11 @@ describe('<Calendar />', () => {
361393

362394
it("should link each day with it's weekday header via `aria-describedby`", async () => {
363395
const { container } = render(
364-
<Calendar renderWeekdayLabels={weekdayLabels} role="listbox">
396+
<Calendar
397+
renderWeekdayLabels={weekdayLabels}
398+
role="listbox"
399+
selectedLabel="Selected"
400+
>
365401
{generateDays()}
366402
</Calendar>
367403
)
@@ -379,11 +415,119 @@ describe('<Calendar />', () => {
379415
)
380416
})
381417
})
418+
419+
it('should set role="option" and aria-selected on each day', async () => {
420+
const { container } = render(
421+
<Calendar
422+
renderWeekdayLabels={weekdayLabels}
423+
role="listbox"
424+
currentDate="2019-08-01"
425+
selectedDate="2019-08-02"
426+
selectedLabel="Selected"
427+
>
428+
{generateDays()}
429+
</Calendar>
430+
)
431+
432+
const days = container.querySelectorAll('tbody [data-cid="Calendar.Day"]')
433+
expect(days.length).toBe(Calendar.DAY_COUNT)
434+
days.forEach((day) => {
435+
expect(day).toHaveAttribute('role', 'option')
436+
expect(day).toHaveAttribute('aria-selected')
437+
})
438+
})
439+
440+
it('should announce selected state via accessible label when selectedLabel is provided', async () => {
441+
const { container } = render(
442+
<Calendar
443+
renderWeekdayLabels={weekdayLabels}
444+
role="listbox"
445+
currentDate="2019-08-01"
446+
selectedDate="2019-08-02"
447+
selectedLabel="selected"
448+
/>
449+
)
450+
451+
const days = container.querySelectorAll('tbody [data-cid="Calendar.Day"]')
452+
453+
// Check that the selected day's accessible label includes "selected"
454+
const selectedDay = Array.from(days).find((day) => {
455+
const screenReaderContent = day.querySelector(
456+
'[class*="-screenReaderContent"]'
457+
)
458+
return screenReaderContent?.textContent?.includes('selected')
459+
})
460+
expect(selectedDay).toBeDefined()
461+
462+
// Unselected days should not have "selected" in their label
463+
const unselectedDays = Array.from(days).filter((day) => {
464+
const screenReaderContent = day.querySelector(
465+
'[class*="-screenReaderContent"]'
466+
)
467+
return !screenReaderContent?.textContent?.includes('selected')
468+
})
469+
expect(unselectedDays.length).toBe(Calendar.DAY_COUNT - 1)
470+
})
471+
})
472+
473+
describe('when role="table" (default)', () => {
474+
it('should set role="button" on each day and not set aria-selected', async () => {
475+
const { container } = render(
476+
<Calendar
477+
renderWeekdayLabels={weekdayLabels}
478+
currentDate="2019-08-01"
479+
selectedDate="2019-08-02"
480+
selectedLabel="Selected"
481+
/>
482+
)
483+
484+
const days = container.querySelectorAll('tbody [data-cid="Calendar.Day"]')
485+
expect(days.length).toBe(Calendar.DAY_COUNT)
486+
days.forEach((day) => {
487+
expect(day).toHaveAttribute('role', 'button')
488+
expect(day).not.toHaveAttribute('aria-selected')
489+
})
490+
})
491+
492+
it('should announce selected state via accessible label when selectedLabel is provided', async () => {
493+
const { container } = render(
494+
<Calendar
495+
renderWeekdayLabels={weekdayLabels}
496+
currentDate="2019-08-01"
497+
selectedDate="2019-08-02"
498+
selectedLabel="selected"
499+
/>
500+
)
501+
502+
const days = container.querySelectorAll('tbody [data-cid="Calendar.Day"]')
503+
504+
// Check that the selected day's accessible label includes "selected"
505+
const selectedDay = Array.from(days).find((day) => {
506+
const screenReaderContent = day.querySelector(
507+
'[class*="-screenReaderContent"]'
508+
)
509+
return screenReaderContent?.textContent?.includes('selected')
510+
})
511+
expect(selectedDay).toBeDefined()
512+
513+
// Unselected days should not have "selected" in their label
514+
const unselectedDays = Array.from(days).filter((day) => {
515+
const screenReaderContent = day.querySelector(
516+
'[class*="-screenReaderContent"]'
517+
)
518+
return !screenReaderContent?.textContent?.includes('selected')
519+
})
520+
expect(unselectedDays.length).toBe(Calendar.DAY_COUNT - 1)
521+
})
382522
})
383523

384524
it('should render root as designated by the `as` prop', async () => {
385525
render(
386-
<Calendar renderWeekdayLabels={weekdayLabels} as="ul">
526+
<Calendar
527+
renderWeekdayLabels={weekdayLabels}
528+
as="ul"
529+
selectedLabel="Selected"
530+
>
387531
{generateDays()}
388532
</Calendar>
389533
)

0 commit comments

Comments
 (0)