diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap index 21c4b9babb6a..84b2462251c6 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap +++ b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/options.integration.test.ts.snap @@ -123,6 +123,7 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource 1`] = `
@@ -490,6 +491,7 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource with exclude
@@ -857,6 +859,7 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource with exclude
@@ -1224,6 +1227,7 @@ exports[`Options Column.HeaderFilter dataSource: custom dataSource with filter v
@@ -1591,6 +1595,7 @@ exports[`Options Column.HeaderFilter filterType + values: exclude filter 1`] = `
@@ -2069,6 +2074,7 @@ exports[`Options Column.HeaderFilter filterType + values: exclude filter with va
@@ -2547,6 +2553,7 @@ exports[`Options Column.HeaderFilter filterType + values: filter values 1`] = `
@@ -3025,6 +3032,7 @@ exports[`Options HeaderFilter texts: custom translations 1`] = `
@@ -3355,6 +3363,7 @@ exports[`Options HeaderFilter texts: default translation 1`] = `
diff --git a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap index a6df92146a61..c57efecbe39b 100644 --- a/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap +++ b/packages/devextreme/js/__internal/grids/new/grid_core/filtering/header_filter/__snapshots__/view.integration.test.tsx.snap @@ -123,6 +123,7 @@ exports[`HeaderFilter View integration should render popup with list by default
@@ -601,6 +602,7 @@ exports[`HeaderFilter View integration should render popup with tree list if dat
diff --git a/packages/devextreme/js/__internal/ui/list/list.base.ts b/packages/devextreme/js/__internal/ui/list/list.base.ts index 572333a7db60..e5155a4407f6 100644 --- a/packages/devextreme/js/__internal/ui/list/list.base.ts +++ b/packages/devextreme/js/__internal/ui/list/list.base.ts @@ -1008,6 +1008,11 @@ export class ListBase extends CollectionWidget { this._setListAria(); } + _isMultiSelectMode(): boolean { + const { selectionMode } = this.option(); + return selectionMode === 'multiple' || selectionMode === 'all'; + } + _setListAria(): void { const { items, allowItemDeleting, collapsibleGroups } = this.option(); @@ -1020,6 +1025,8 @@ export class ListBase extends CollectionWidget { const listArea = { role: shouldSetAria ? 'listbox' : undefined, label: shouldSetAria ? label : undefined, + // eslint-disable-next-line spellcheck/spell-checker + multiselectable: shouldSetAria && this._isMultiSelectMode() ? 'true' : undefined, }; this.setAria(listArea, this._$listContainer); @@ -1145,6 +1152,8 @@ export class ListBase extends CollectionWidget { role: collapsibleGroups ? 'listbox' : undefined, // eslint-disable-next-line spellcheck/spell-checker labelledby: collapsibleGroups ? groupHeaderId : undefined, + // eslint-disable-next-line spellcheck/spell-checker + multiselectable: collapsibleGroups && this._isMultiSelectMode() ? 'true' : undefined, }; this.setAria(groupHeaderAria, $groupBody); diff --git a/packages/devextreme/testing/helpers/ariaAccessibilityTestHelper.js b/packages/devextreme/testing/helpers/ariaAccessibilityTestHelper.js index 4544a8a94ce9..ca263eb98a0e 100644 --- a/packages/devextreme/testing/helpers/ariaAccessibilityTestHelper.js +++ b/packages/devextreme/testing/helpers/ariaAccessibilityTestHelper.js @@ -39,6 +39,10 @@ class ariaAccessibilityTestHelper { return this.$itemContainer.find('.dx-list-items'); } + getGroupContainers() { + return this.$itemContainer.find('.dx-list-group'); + } + checkAttributes($target, expectedAttributes, prefix) { const element = $target.get(0); const skipAttributes = ['class', 'style', 'onclick']; diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/list.markup.tests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/list.markup.tests.js index 92e77f03bcc8..a103fae817fe 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/list.markup.tests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/list.markup.tests.js @@ -286,6 +286,10 @@ if(devices.real().deviceType === 'desktop') { role: 'listbox', 'aria-label': 'Items', }; + this.expectedItemsContainerMultipleModeAttrs = { + ...this.expectedItemsContainerAttrs, + 'aria-multiselectable': 'true', + }; this.expectedListAttrs = { role: 'group', 'aria-roledescription': localizedRoleDescription, @@ -344,7 +348,7 @@ if(devices.real().deviceType === 'desktop') { }); helper.checkAttributes(helper.$itemContainer, this.expectedContainerAttrs); - helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerAttrs); + helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerMultipleModeAttrs); helper.checkAttributes(helper.$widget, this.expectedListAttrs); helper.checkItemsAttributes([1, 2], { attributes: ['aria-selected'], role: 'option' }); }); @@ -411,6 +415,31 @@ if(devices.real().deviceType === 'desktop') { assert.strictEqual(helper.getListContainer().attr('aria-label'), undefined); }); + + [true, false].forEach(multiselectMode => { + QUnit.test(`list with collapsible groups should have correct aria-multiselectable attr in ${multiselectMode ? '' : 'non-'}multiselect mode`, function(assert) { + helper.createWidget({ + items: [ + { key: 'Group_1', items: ['Item_1', 'Item_2'] }, + { key: 'Group_2', items: ['Item_3'] }, + ], + grouped: true, + collapsibleGroups: true, + selectionMode: multiselectMode ? 'multiple' : 'single', + }); + + const $groupContainers = helper.getGroupContainers(); + $groupContainers.each((index, group) => { + const $groupBody = $(group).children(`.${LIST_GROUP_BODY_CLASS}`); + + if(multiselectMode) { + assert.strictEqual($groupBody.attr('aria-multiselectable'), 'true', `Group #${index} body has correct aria-multiselectable attr`); + } else { + assert.strictEqual($groupBody.attr('aria-multiselectable'), undefined, `Group #${index} body has no aria-multiselectable attr`); + } + }); + }); + }); }); } diff --git a/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js b/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js index 6372cb295413..28294779d0a6 100644 --- a/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js +++ b/packages/devextreme/testing/tests/DevExpress.ui.widgets/listParts/commonTests.js @@ -4814,6 +4814,10 @@ if(devices.real().deviceType === 'desktop') { role: 'listbox', 'aria-label': 'Items', }; + this.expectedItemsContainerMultipleModeAttrs = { + ...this.expectedItemsContainerAttrs, + 'aria-multiselectable': 'true', + }; }, afterEach: function() { this.clock.restore(); @@ -4858,7 +4862,7 @@ if(devices.real().deviceType === 'desktop') { helper.createWidget({ selectedItemKeys: ['Item_1', 'Item_3'], keyExpr: 'text', selectionMode: 'multiple' }); helper.checkAttributes(helper.$itemContainer, this.expectedContainerAttrs); - helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerAttrs); + helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerMultipleModeAttrs); helper.checkItemsAttributes([0, 2], { attributes: ['aria-selected'], role: 'option' }); const $item_1 = $(helper.getItems().eq(1)); @@ -4867,7 +4871,7 @@ if(devices.real().deviceType === 'desktop') { this.clock.tick(10); helper.checkAttributes(helper.$itemContainer, { ...this.expectedContainerAttrs, 'aria-activedescendant': helper.focusedItemId }); - helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerAttrs); + helper.checkAttributes(helper.getListContainer(), this.expectedItemsContainerMultipleModeAttrs); helper.checkItemsAttributes([0, 1, 2], { attributes: ['aria-selected'], focusedItemIndex: 1, role: 'option' }); }); });