Skip to content

Commit 106c92e

Browse files
authored
List: screen reader should read selected state of items in multiselect mode (T1320659) (#32360)
1 parent f4ef763 commit 106c92e

File tree

6 files changed

+60
-3
lines changed

6 files changed

+60
-3
lines changed

packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource 1`] = `
123123
</div>
124124
<div
125125
aria-label="Items"
126+
aria-multiselectable="true"
126127
class="dx-list-items"
127128
role="listbox"
128129
>
@@ -490,6 +491,7 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource with exclude
490491
</div>
491492
<div
492493
aria-label="Items"
494+
aria-multiselectable="true"
493495
class="dx-list-items"
494496
role="listbox"
495497
>
@@ -857,6 +859,7 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource with exclude
857859
</div>
858860
<div
859861
aria-label="Items"
862+
aria-multiselectable="true"
860863
class="dx-list-items"
861864
role="listbox"
862865
>
@@ -1224,6 +1227,7 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource with filter v
12241227
</div>
12251228
<div
12261229
aria-label="Items"
1230+
aria-multiselectable="true"
12271231
class="dx-list-items"
12281232
role="listbox"
12291233
>
@@ -1591,6 +1595,7 @@ exports[`Options Column.HeaderFilter filterType + values: exclude filter 1`] = `
15911595
</div>
15921596
<div
15931597
aria-label="Items"
1598+
aria-multiselectable="true"
15941599
class="dx-list-items"
15951600
role="listbox"
15961601
>
@@ -2069,6 +2074,7 @@ exports[`Options Column.HeaderFilter filterType + values: exclude filter with va
20692074
</div>
20702075
<div
20712076
aria-label="Items"
2077+
aria-multiselectable="true"
20722078
class="dx-list-items"
20732079
role="listbox"
20742080
>
@@ -2547,6 +2553,7 @@ exports[`Options Column.HeaderFilter filterType + values: filter values 1`] = `
25472553
</div>
25482554
<div
25492555
aria-label="Items"
2556+
aria-multiselectable="true"
25502557
class="dx-list-items"
25512558
role="listbox"
25522559
>
@@ -3025,6 +3032,7 @@ exports[`Options HeaderFilter texts: custom translations 1`] = `
30253032
</div>
30263033
<div
30273034
aria-label="Items"
3035+
aria-multiselectable="true"
30283036
class="dx-list-items"
30293037
role="listbox"
30303038
>
@@ -3355,6 +3363,7 @@ exports[`Options HeaderFilter texts: default translation 1`] = `
33553363
</div>
33563364
<div
33573365
aria-label="Items"
3366+
aria-multiselectable="true"
33583367
class="dx-list-items"
33593368
role="listbox"
33603369
>

packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ exports[`HeaderFilter View integration should render popup with list by default
123123
</div>
124124
<div
125125
aria-label="Items"
126+
aria-multiselectable="true"
126127
class="dx-list-items"
127128
role="listbox"
128129
>
@@ -601,6 +602,7 @@ exports[`HeaderFilter View integration should render popup with tree list if dat
601602
</div>
602603
<div
603604
aria-label="Items"
605+
aria-multiselectable="true"
604606
class="dx-list-items"
605607
role="listbox"
606608
>

packages/devextreme/js/__internal/ui/list/list.base.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,11 @@ export class ListBase extends CollectionWidget<ListBaseProperties, Item> {
10081008
this._setListAria();
10091009
}
10101010

1011+
_isMultiSelectMode(): boolean {
1012+
const { selectionMode } = this.option();
1013+
return selectionMode === 'multiple' || selectionMode === 'all';
1014+
}
1015+
10111016
_setListAria(): void {
10121017
const { items, allowItemDeleting, collapsibleGroups } = this.option();
10131018

@@ -1020,6 +1025,8 @@ export class ListBase extends CollectionWidget<ListBaseProperties, Item> {
10201025
const listArea = {
10211026
role: shouldSetAria ? 'listbox' : undefined,
10221027
label: shouldSetAria ? label : undefined,
1028+
// eslint-disable-next-line spellcheck/spell-checker
1029+
multiselectable: shouldSetAria && this._isMultiSelectMode() ? 'true' : undefined,
10231030
};
10241031

10251032
this.setAria(listArea, this._$listContainer);
@@ -1145,6 +1152,8 @@ export class ListBase extends CollectionWidget<ListBaseProperties, Item> {
11451152
role: collapsibleGroups ? 'listbox' : undefined,
11461153
// eslint-disable-next-line spellcheck/spell-checker
11471154
labelledby: collapsibleGroups ? groupHeaderId : undefined,
1155+
// eslint-disable-next-line spellcheck/spell-checker
1156+
multiselectable: collapsibleGroups && this._isMultiSelectMode() ? 'true' : undefined,
11481157
};
11491158

11501159
this.setAria(groupHeaderAria, $groupBody);

packages/devextreme/testing/helpers/ariaAccessibilityTestHelper.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ class ariaAccessibilityTestHelper {
3939
return this.$itemContainer.find('.dx-list-items');
4040
}
4141

42+
getGroupContainers() {
43+
return this.$itemContainer.find('.dx-list-group');
44+
}
45+
4246
checkAttributes($target, expectedAttributes, prefix) {
4347
const element = $target.get(0);
4448
const skipAttributes = ['class', 'style', 'onclick'];

packages/devextreme/testing/tests/DevExpress.ui.widgets/list.markup.tests.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,10 @@ if(devices.real().deviceType === 'desktop') {
286286
role: 'listbox',
287287
'aria-label': 'Items',
288288
};
289+
this.expectedItemsContainerMultipleModeAttrs = {
290+
...this.expectedItemsContainerAttrs,
291+
'aria-multiselectable': 'true',
292+
};
289293
this.expectedListAttrs = {
290294
role: 'group',
291295
'aria-roledescription': localizedRoleDescription,
@@ -344,7 +348,7 @@ if(devices.real().deviceType === 'desktop') {
344348
});
345349

346350
helper.checkAttributes(helper.$itemContainer, this.expectedContainerAttrs);
347-
helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerAttrs);
351+
helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerMultipleModeAttrs);
348352
helper.checkAttributes(helper.$widget, this.expectedListAttrs);
349353
helper.checkItemsAttributes([1, 2], { attributes: ['aria-selected'], role: 'option' });
350354
});
@@ -411,6 +415,31 @@ if(devices.real().deviceType === 'desktop') {
411415

412416
assert.strictEqual(helper.getListContainer().attr('aria-label'), undefined);
413417
});
418+
419+
[true, false].forEach(multiselectMode => {
420+
QUnit.test(`list with collapsible groups should have correct aria-multiselectable attr in ${multiselectMode ? '' : 'non-'}multiselect mode`, function(assert) {
421+
helper.createWidget({
422+
items: [
423+
{ key: 'Group_1', items: ['Item_1', 'Item_2'] },
424+
{ key: 'Group_2', items: ['Item_3'] },
425+
],
426+
grouped: true,
427+
collapsibleGroups: true,
428+
selectionMode: multiselectMode ? 'multiple' : 'single',
429+
});
430+
431+
const $groupContainers = helper.getGroupContainers();
432+
$groupContainers.each((index, group) => {
433+
const $groupBody = $(group).children(`.${LIST_GROUP_BODY_CLASS}`);
434+
435+
if(multiselectMode) {
436+
assert.strictEqual($groupBody.attr('aria-multiselectable'), 'true', `Group #${index} body has correct aria-multiselectable attr`);
437+
} else {
438+
assert.strictEqual($groupBody.attr('aria-multiselectable'), undefined, `Group #${index} body has no aria-multiselectable attr`);
439+
}
440+
});
441+
});
442+
});
414443
});
415444
}
416445

packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4814,6 +4814,10 @@ if(devices.real().deviceType === 'desktop') {
48144814
role: 'listbox',
48154815
'aria-label': 'Items',
48164816
};
4817+
this.expectedItemsContainerMultipleModeAttrs = {
4818+
...this.expectedItemsContainerAttrs,
4819+
'aria-multiselectable': 'true',
4820+
};
48174821
},
48184822
afterEach: function() {
48194823
this.clock.restore();
@@ -4858,7 +4862,7 @@ if(devices.real().deviceType === 'desktop') {
48584862
helper.createWidget({ selectedItemKeys: ['Item_1', 'Item_3'], keyExpr: 'text', selectionMode: 'multiple' });
48594863

48604864
helper.checkAttributes(helper.$itemContainer, this.expectedContainerAttrs);
4861-
helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerAttrs);
4865+
helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerMultipleModeAttrs);
48624866
helper.checkItemsAttributes([0, 2], { attributes: ['aria-selected'], role: 'option' });
48634867

48644868
const $item_1 = $(helper.getItems().eq(1));
@@ -4867,7 +4871,7 @@ if(devices.real().deviceType === 'desktop') {
48674871
this.clock.tick(10);
48684872

48694873
helper.checkAttributes(helper.$itemContainer, { ...this.expectedContainerAttrs, 'aria-activedescendant': helper.focusedItemId });
4870-
helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerAttrs);
4874+
helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerMultipleModeAttrs);
48714875
helper.checkItemsAttributes([0, 1, 2], { attributes: ['aria-selected'], focusedItemIndex: 1, role: 'option' });
48724876
});
48734877
});

0 commit comments

Comments
 (0)