Skip to content

Commit 67ccf27

Browse files
authored
Scheduler - A11y - Add Home and End hotkeys, change scheduler's role, extend appointment's aria-describedby (#32575)
1 parent 41fced4 commit 67ccf27

File tree

42 files changed

+441
-66
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+441
-66
lines changed

e2e/testcafe-devextreme/tests/accessibility/scheduler/appointment.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,15 @@ const currentDate = Date.UTC(2021, 1, 1);
1616
const appointmentTemplate = ({ appointmentData }) => `<div>${appointmentData.text}</div>`;
1717

1818
['month', 'week', 'day', 'agenda'].forEach((currentView) => {
19-
test(`appointment should have correct aria-label without description (${currentView})`, async (t) => {
19+
test(`appointment should have correct aria-label and have description (${currentView})`, async (t) => {
2020
const scheduler = new Scheduler('#container');
2121
const appointment = scheduler.getAppointment('App 1');
2222

2323
await t
2424
.expect(appointment.getAriaLabel())
2525
.eql('App 1: February 1, 2021, 12:00 PM - 1:00 PM')
2626
.expect(await appointment.hasAriaDescription())
27-
.notOk();
27+
.ok();
2828

2929
await a11yCheck(t, a11yCheckConfig, '#container');
3030
}).before(async () => {
@@ -36,15 +36,15 @@ const appointmentTemplate = ({ appointmentData }) => `<div>${appointmentData.tex
3636
});
3737
});
3838

39-
test(`appointment with template should have correct aria-label without description (${currentView})`, async (t) => {
39+
test(`appointment with template should have correct aria-label and have description (${currentView})`, async (t) => {
4040
const scheduler = new Scheduler('#container');
4141
const appointment = scheduler.getAppointment('App 1');
4242

4343
await t
4444
.expect(appointment.getAriaLabel())
4545
.eql('App 1: February 1, 2021, 12:00 PM - 1:00 PM')
4646
.expect(await appointment.hasAriaDescription())
47-
.notOk();
47+
.ok();
4848

4949
await a11yCheck(t, a11yCheckConfig, '#container');
5050
}).before(async () => {
@@ -65,7 +65,7 @@ const appointmentTemplate = ({ appointmentData }) => `<div>${appointmentData.tex
6565
.expect(appointment.getAriaLabel())
6666
.eql('App 1: February 1, 2021, 12:00 PM - 1:00 PM')
6767
.expect(await appointment.getAriaDescription())
68-
.eql('Group: resource1; Group 1: resource1');
68+
.contains('Group: resource1; Group 1: resource1');
6969

7070
await a11yCheck(t, a11yCheckConfig, '#container');
7171
}).before(async () => {
@@ -99,7 +99,7 @@ const appointmentTemplate = ({ appointmentData }) => `<div>${appointmentData.tex
9999
.expect(appointment.getAriaLabel())
100100
.eql('App 1: February 1, 2021, 12:00 PM - 1:00 PM')
101101
.expect(await appointment.getAriaDescription())
102-
.eql('Group: resource1; Group 1: resource1');
102+
.contains('Group: resource1; Group 1: resource1');
103103

104104
await a11yCheck(t, a11yCheckConfig, '#container');
105105
}).before(async () => {
@@ -134,7 +134,7 @@ const appointmentTemplate = ({ appointmentData }) => `<div>${appointmentData.tex
134134
.expect(appointment.getAriaLabel())
135135
.eql('App 1: February 1, 2021, 12:00 PM - 1:00 PM')
136136
.expect(await appointment.getAriaDescription())
137-
.eql('Group: resource11, resource21; Group 1: resource11; Group 2: resource21, resource22');
137+
.contains('Group: resource11, resource21; Group 1: resource11; Group 2: resource21, resource22');
138138

139139
await a11yCheck(t, a11yCheckConfig, '#container');
140140
}).before(async () => {

e2e/testcafe-devextreme/tests/accessibility/scheduler/scheduler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ test('Scheduler should have right aria attributes after view changed', async (t)
1313
await t.expect(scheduler.element.getAttribute('aria-label')).contains('Scheduler. Month view');
1414
await t.expect(scheduler.getGeneralStatusContainer().textContent).contains('Scheduler. Month view');
1515

16-
await t.expect(scheduler.element.getAttribute('role')).eql('group');
16+
await t.expect(scheduler.element.getAttribute('role')).eql('application');
1717

1818
await scheduler.option('currentView', 'week');
1919

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import Scheduler from 'devextreme-testcafe-models/scheduler';
2+
import { ClientFunction } from 'testcafe';
3+
import url from '../../../../helpers/getPageUrl';
4+
import { createWidget } from '../../../../helpers/createWidget';
5+
import { getDocumentScrollTop } from '../../../../helpers/domUtils';
6+
7+
fixture.disablePageReloads`KeyboardNavigation.Appointments`
8+
.page(url(__dirname, '../../../container.html'));
9+
10+
const SCHEDULER_SELECTOR = '#container';
11+
12+
test('Document should not scroll on \'End\' press when appointment is focused', async (t) => {
13+
const scheduler = new Scheduler(SCHEDULER_SELECTOR);
14+
15+
await t.click(scheduler.getAppointment('Appointment 1').element);
16+
17+
const expectedScrollTop = await getDocumentScrollTop();
18+
19+
await t
20+
.pressKey('End')
21+
.expect(getDocumentScrollTop()).eql(expectedScrollTop);
22+
}).before(async () => {
23+
await ClientFunction(() => {
24+
document.body.style.height = '2000px';
25+
})();
26+
27+
await createWidget('dxScheduler', {
28+
dataSource: [
29+
{
30+
text: 'Appointment 1',
31+
startDate: new Date(2015, 1, 9, 8),
32+
endDate: new Date(2015, 1, 9, 9),
33+
},
34+
{
35+
text: 'Appointment 2',
36+
startDate: new Date(2015, 1, 9, 10),
37+
endDate: new Date(2015, 1, 9, 11),
38+
},
39+
{
40+
text: 'Appointment 3',
41+
startDate: new Date(2015, 1, 9, 12),
42+
endDate: new Date(2015, 1, 9, 13),
43+
},
44+
],
45+
height: 300,
46+
currentView: 'day',
47+
currentDate: new Date(2015, 1, 9),
48+
});
49+
}).after(async () => {
50+
await ClientFunction(() => {
51+
document.body.style.height = '';
52+
})();
53+
});
54+
55+
test('Document should not scroll on \'Home\' press when appointment is focused', async (t) => {
56+
const scheduler = new Scheduler(SCHEDULER_SELECTOR);
57+
58+
await t
59+
.scroll(0, 100)
60+
.click(scheduler.getAppointment('Appointment 1').element);
61+
62+
const expectedScrollTop = await getDocumentScrollTop();
63+
64+
await t
65+
.pressKey('Home')
66+
.expect(getDocumentScrollTop()).eql(expectedScrollTop);
67+
}).before(async () => {
68+
await ClientFunction(() => {
69+
document.body.style.height = '2000px';
70+
})();
71+
72+
await createWidget('dxScheduler', {
73+
dataSource: [
74+
{
75+
text: 'Appointment 1',
76+
startDate: new Date(2015, 1, 9, 8),
77+
endDate: new Date(2015, 1, 9, 9),
78+
},
79+
{
80+
text: 'Appointment 2',
81+
startDate: new Date(2015, 1, 9, 10),
82+
endDate: new Date(2015, 1, 9, 11),
83+
},
84+
{
85+
text: 'Appointment 3',
86+
startDate: new Date(2015, 1, 9, 12),
87+
endDate: new Date(2015, 1, 9, 13),
88+
},
89+
],
90+
height: 300,
91+
currentView: 'day',
92+
currentDate: new Date(2015, 1, 9),
93+
});
94+
}).after(async () => {
95+
await ClientFunction(() => {
96+
document.body.style.height = '';
97+
})();
98+
});

packages/devextreme/js/__internal/scheduler/__tests__/__mock__/model/appointment.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,11 @@ export interface AppointmentModel<T = HTMLDivElement> {
88
getText: () => string;
99
getDisplayDate: () => string;
1010
getAriaLabel: () => string;
11+
getAriaDescription: () => string;
1112
getGeometry: () => Position;
1213
getColor: (view: string) => string | undefined;
1314
getSnapshot: () => object;
15+
isFocused: () => boolean;
1416
}
1517

1618
const getColor = (appointment: HTMLDivElement): string => appointment.style.backgroundColor;
@@ -45,6 +47,11 @@ export const createAppointmentModel = <T extends HTMLDivElement | null>(
4547
getText: () => getText(element),
4648
getDisplayDate: () => getDisplayDate(element),
4749
getAriaLabel: () => element?.getAttribute('aria-label') ?? '',
50+
getAriaDescription: (): string => {
51+
const id = element?.getAttribute('aria-describedby') ?? '';
52+
const descriptionElement = id ? document.getElementById(id) : null;
53+
return descriptionElement?.textContent ?? '';
54+
},
4855
getGeometry: () => getGeometry(element),
4956
getColor(view: string): string | undefined {
5057
if (!element) {
@@ -60,4 +67,5 @@ export const createAppointmentModel = <T extends HTMLDivElement | null>(
6067
date: getDisplayDate(element),
6168
...getGeometry(element),
6269
}),
70+
isFocused: () => element?.classList.contains('dx-state-focused') ?? false,
6371
});

0 commit comments

Comments
 (0)