Skip to content

Commit

Permalink
Grids a11y: FilterBuilder - not accessible at all (#28143)
Browse files Browse the repository at this point in the history
  • Loading branch information
tongsonbarbs authored Oct 16, 2024
1 parent febeb3d commit 1cd3f73
Show file tree
Hide file tree
Showing 31 changed files with 520 additions and 43 deletions.
104 changes: 104 additions & 0 deletions e2e/testcafe-devextreme/tests/filterBuilder/filterBuilderA11y.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/* eslint-disable @typescript-eslint/no-misused-promises */

import FilterBuilder from 'devextreme-testcafe-models/filterBuilder';
import { createWidget } from '../../helpers/createWidget';
import url from '../../helpers/getPageUrl';
import { fields, filter } from './data';
import { a11yCheck } from '../../helpers/accessibility/utils';

fixture`Filter Builder Accessibility Tests`.page(
url(__dirname, '../container.html'),
);

const elements = [
{
name: 'Root Element',
role: 'tree',
label: 'Filter builder',
selector: (filterBuilder) => filterBuilder.getRootElement(),
},
{
name: 'Group Item',
role: 'treeitem',
label: 'Group item',
selector: (filterBuilder) => filterBuilder.getGroupItem(),
},
{
name: 'Operation Button',
role: 'combobox',
label: 'Operation',
selector: (filterBuilder) => filterBuilder.getOperationButton(1),
},
{
name: 'Add Button',
role: 'combobox',
label: 'Add',
selector: (filterBuilder) => filterBuilder.getAddButton(1),
},
{
name: 'Remove Condition Button',
role: 'button',
label: 'Remove condition',
selector: (filterBuilder) => filterBuilder.getRemoveButton(0),
},
{
name: 'Remove Group Button',
role: 'button',
label: 'Remove group',
selector: (filterBuilder) => filterBuilder.getRemoveButton(1),
},
{
name: 'Item Field',
role: 'combobox',
label: 'Item field',
selector: (filterBuilder) => filterBuilder.getItem('field', 2),
},
{
name: 'Item Operation',
role: 'combobox',
label: 'Item operation',
selector: (filterBuilder) => filterBuilder.getItem('operation', 2),
},
{
name: 'Item Value',
role: 'button',
label: 'Item value',
selector: (filterBuilder) => filterBuilder.getItem('value', 2),
},
];

elements.forEach(({
name, role, label, selector,
}) => {
test(`Filter Builder - ${name} has correct ARIA attributes`, async (t) => {
const filterBuilder = new FilterBuilder('#parentContainer');
const elementSelector = selector(filterBuilder);
const labelValue = ['button', 'combobox'].includes(role) && await elementSelector.innerText ? 'title' : 'aria-label';

await t
.expect(elementSelector.getAttribute('role'))
.eql(role)
.expect(elementSelector.getAttribute(`${labelValue}`))
.eql(label);
}).before(async () => {
await createWidget('dxFilterBuilder', {
fields,
value: filter,
});
});
});

test('Filter Builder - ARIA Attributes axe test', async (t) => {
const filterBuilder = new FilterBuilder('#container');

await t
.expect(filterBuilder.isReady())
.ok();

await a11yCheck(t, {}, '#container');
}).before(async () => {
await createWidget('dxFilterBuilder', {
fields,
value: filter,
});
});
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable max-classes-per-file */
import registerComponent from '@js/core/component_registrator';
import domAdapter from '@js/core/dom_adapter';
import Guid from '@js/core/guid';
import $ from '@js/core/renderer';
import { when } from '@js/core/utils/deferred';
import { extend } from '@js/core/utils/extend';
Expand Down Expand Up @@ -256,14 +257,45 @@ class FilterBuilder extends Widget<any> {
this.$element().addClass(FILTER_BUILDER_CLASS);
// @ts-expect-error
super._initMarkup();

this._addAriaAttributes(this.$element(), messageLocalization.format('dxFilterBuilder-filterAriaRootElement'), 'tree');
this._createGroupElementByCriteria(this._model)
.appendTo(this.$element());
}

_createConditionElement(condition, parent) {
_addAriaAttributes($element, ariaLabel, role, hasPopup?, hasExpanded?, ariaLevel?) {
if (!$element || !$element.length) return;

const attributes = { role };

if (ariaLabel) {
if ($element.text().length > 0) {
// @ts-expect-error title attr
attributes.title = ariaLabel;
} else {
attributes['aria-label'] = ariaLabel;
}
}

if (isDefined(hasPopup)) {
attributes['aria-haspopup'] = `${hasPopup}`;
}

if (isDefined(hasExpanded)) {
attributes['aria-expanded'] = `${hasExpanded}`;
}

if (isDefined(ariaLevel)) {
attributes['aria-level'] = `${ariaLevel}`;
}

$element.attr(attributes);
}

_createConditionElement(condition, parent, groupLevel?) {
return $('<div>')
.addClass(FILTER_BUILDER_GROUP_CLASS)
.append(this._createConditionItem(condition, parent));
.append(this._createConditionItem(condition, parent, groupLevel));
}

_createGroupElementByCriteria(criteria, parent?, groupLevel = 0) {
Expand All @@ -277,26 +309,31 @@ class FilterBuilder extends Widget<any> {
this._createGroupElementByCriteria(innerCriteria, criteria, groupLevel + 1)
.appendTo($groupContent);
} else if (isCondition(innerCriteria)) {
this._createConditionElement(innerCriteria, criteria)
this._createConditionElement(innerCriteria, criteria, `${groupLevel + 1}`)
.appendTo($groupContent);
}
}
return $group;
}

_createGroupElement(criteria, parent, groupLevel) {
const $guid = new Guid();
const $groupItem = $('<div>').addClass(FILTER_BUILDER_GROUP_ITEM_CLASS);
const $groupContent = $('<div>').addClass(FILTER_BUILDER_GROUP_CONTENT_CLASS);
const $groupContent = $('<div>').addClass(FILTER_BUILDER_GROUP_CONTENT_CLASS).attr('id', `${$guid}`);
const $group = $('<div>').addClass(FILTER_BUILDER_GROUP_CLASS).append($groupItem).append($groupContent);

if (parent != null) {
this._createRemoveButton(() => {
removeItem(parent, criteria);
$group.remove();
this._updateFilter();
}).appendTo($groupItem);
}, 'group').appendTo($groupItem);
}

this._addAriaAttributes($groupItem, messageLocalization.format('dxFilterBuilder-filterAriaGroupItem'), 'treeitem', null, null, `${groupLevel + 1}`);
this._addAriaAttributes($groupContent, '', 'group');
$groupItem.attr('aria-owns', `${$guid}`);

this._createGroupOperationButton(criteria).appendTo($groupItem);

this._createAddButton(() => {
Expand All @@ -308,7 +345,7 @@ class FilterBuilder extends Widget<any> {
const field = this.option('fields')[0];
const newCondition = createCondition(field, this._customOperations);
addItem(newCondition, criteria);
this._createConditionElement(newCondition, criteria).appendTo($groupContent);
this._createConditionElement(newCondition, criteria, groupLevel + 1).appendTo($groupContent);
this._updateFilter();
}, groupLevel).appendTo($groupItem);

Expand Down Expand Up @@ -345,6 +382,9 @@ class FilterBuilder extends Widget<any> {
cssClass: FILTER_BUILDER_GROUP_OPERATIONS_CLASS,
},
});

this._addAriaAttributes($operationButton, messageLocalization.format('dxFilterBuilder-filterAriaOperationButton'), 'combobox', true, false);

return $operationButton.addClass(FILTER_BUILDER_ITEM_TEXT_CLASS)
.addClass(FILTER_BUILDER_GROUP_OPERATION_CLASS)
.attr('tabindex', 0);
Expand All @@ -354,7 +394,7 @@ class FilterBuilder extends Widget<any> {
const that = this;
const removeMenu = function () {
// @ts-expect-error
that.$element().find(`.${ACTIVE_CLASS}`).removeClass(ACTIVE_CLASS);
that.$element().find(`.${ACTIVE_CLASS}`).removeClass(ACTIVE_CLASS).attr('aria-expanded', 'false');
// @ts-expect-error
that.$element().find('.dx-overlay .dx-treeview').remove();
// @ts-expect-error
Expand All @@ -377,7 +417,7 @@ class FilterBuilder extends Widget<any> {
selectionMode: 'single',
onItemClick: menuOnItemClickWrapper(options.menu.onItemClick),
onHiding() {
$button.removeClass(ACTIVE_CLASS);
$button.removeClass(ACTIVE_CLASS).attr('aria-expanded', 'false');
},
position: {
my: `${position} top`, at: `${position} bottom`, offset: '0 1', of: $button, collision: 'flip',
Expand Down Expand Up @@ -414,7 +454,7 @@ class FilterBuilder extends Widget<any> {
this._subscribeOnClickAndEnterKey($button, () => {
removeMenu();
that._createPopupWithTreeView(options, that.$element());
$button.addClass(ACTIVE_CLASS);
$button.addClass(ACTIVE_CLASS).attr('aria-expanded', 'true');
});
return $button;
}
Expand Down Expand Up @@ -463,6 +503,7 @@ class FilterBuilder extends Widget<any> {
}).addClass(FILTER_BUILDER_ITEM_TEXT_CLASS)
.addClass(FILTER_BUILDER_ITEM_OPERATION_CLASS)
.attr('tabindex', 0);
this._addAriaAttributes($operationButton, messageLocalization.format('dxFilterBuilder-filterAriaItemOperation'), 'combobox', true, false);

return $operationButton;
}
Expand Down Expand Up @@ -517,14 +558,18 @@ class FilterBuilder extends Widget<any> {
.addClass(FILTER_BUILDER_ITEM_FIELD_CLASS)
.attr('tabindex', 0);

this._addAriaAttributes($fieldButton, messageLocalization.format('dxFilterBuilder-filterAriaItemField'), 'combobox', true, false);

return $fieldButton;
}

_createConditionItem(condition, parent) {
_createConditionItem(condition, parent, groupLevel?) {
const $item = $('<div>').addClass(FILTER_BUILDER_GROUP_ITEM_CLASS);
const fields = this._getNormalizedFields();
const field = getField(condition[0], fields);

this._addAriaAttributes($item, '', 'treeitem', null, null, groupLevel);

this._createRemoveButton(() => {
removeItem(parent, condition);
const isSingleChild = $item.parent().children().length === 1;
Expand All @@ -534,7 +579,7 @@ class FilterBuilder extends Widget<any> {
$item.remove();
}
this._updateFilter();
}).appendTo($item);
}, 'condition').appendTo($item);
this._createFieldButtonWithMenu(fields, condition, field).appendTo($item);
this._createOperationAndValueButtons(condition, field, $item);
return $item;
Expand All @@ -554,12 +599,16 @@ class FilterBuilder extends Widget<any> {
}));
}

_createRemoveButton(handler) {
_createRemoveButton(handler, type?) {
const $removeButton = $('<div>')
.addClass(FILTER_BUILDER_IMAGE_CLASS)
.addClass(FILTER_BUILDER_IMAGE_REMOVE_CLASS)
.addClass(FILTER_BUILDER_ACTION_CLASS)
.attr('tabindex', 0);
if (type) {
const removeMessage = (messageLocalization.format as any)('dxFilterBuilder-filterAriaRemoveButton', type);
this._addAriaAttributes($removeButton, removeMessage, 'button');
}
this._subscribeOnClickAndEnterKey($removeButton, handler);
return $removeButton;
}
Expand Down Expand Up @@ -588,6 +637,9 @@ class FilterBuilder extends Widget<any> {
},
});
}

this._addAriaAttributes($button, messageLocalization.format('dxFilterBuilder-filterAriaAddButton'), 'combobox', true, false);

return $button.addClass(FILTER_BUILDER_IMAGE_CLASS)
.addClass(FILTER_BUILDER_IMAGE_ADD_CLASS)
.addClass(FILTER_BUILDER_ACTION_CLASS)
Expand All @@ -601,6 +653,7 @@ class FilterBuilder extends Widget<any> {
.addClass(FILTER_BUILDER_ITEM_VALUE_TEXT_CLASS)
.attr('tabindex', 0)
.appendTo($container);
this._addAriaAttributes($text, messageLocalization.format('dxFilterBuilder-filterAriaItemValue'), 'button', true);
const value = item[2];

const customOperation = getCustomOperation(that._customOperations, item[1]);
Expand Down Expand Up @@ -768,7 +821,6 @@ class FilterBuilder extends Widget<any> {
const $valueButton = $('<div>')
.addClass(FILTER_BUILDER_ITEM_TEXT_CLASS)
.addClass(FILTER_BUILDER_ITEM_VALUE_CLASS);

this._createValueText(item, field, $valueButton);
return $valueButton;
}
Expand Down
9 changes: 9 additions & 0 deletions packages/devextreme/js/localization/messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,15 @@
"dxFilterBuilder-filterOperationBetween": "بين",
"dxFilterBuilder-filterOperationAnyOf": "أي من",
"dxFilterBuilder-filterOperationNoneOf": "ليس من",
"dxFilterBuilder-filterAriaRootElement": "Filter builder",
"dxFilterBuilder-filterAriaGroupLevel": "Level {0}",
"dxFilterBuilder-filterAriaGroupItem": "Group item",
"dxFilterBuilder-filterAriaOperationButton": "Operation",
"dxFilterBuilder-filterAriaAddButton": "Add",
"dxFilterBuilder-filterAriaRemoveButton": "Remove {0}",
"dxFilterBuilder-filterAriaItemField": "Item field",
"dxFilterBuilder-filterAriaItemOperation": "Item operation",
"dxFilterBuilder-filterAriaItemValue": "Item value",

"dxHtmlEditor-dialogColorCaption": "تغيير لون الخط",
"dxHtmlEditor-dialogBackgroundCaption": "تغيير لون الخلفية",
Expand Down
9 changes: 9 additions & 0 deletions packages/devextreme/js/localization/messages/ca.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,15 @@
"dxFilterBuilder-filterOperationBetween": "És entre",
"dxFilterBuilder-filterOperationAnyOf": "És alguna de",
"dxFilterBuilder-filterOperationNoneOf": "No és cap",
"dxFilterBuilder-filterAriaRootElement": "Filter builder",
"dxFilterBuilder-filterAriaGroupLevel": "Level {0}",
"dxFilterBuilder-filterAriaGroupItem": "Group item",
"dxFilterBuilder-filterAriaOperationButton": "Operation",
"dxFilterBuilder-filterAriaAddButton": "Add",
"dxFilterBuilder-filterAriaRemoveButton": "Remove {0}",
"dxFilterBuilder-filterAriaItemField": "Item field",
"dxFilterBuilder-filterAriaItemOperation": "Item operation",
"dxFilterBuilder-filterAriaItemValue": "Item value",

"dxHtmlEditor-dialogColorCaption": "Canvia el color de la lletra",
"dxHtmlEditor-dialogBackgroundCaption": "Canvieu el color de fons",
Expand Down
9 changes: 9 additions & 0 deletions packages/devextreme/js/localization/messages/cs.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,15 @@
"dxFilterBuilder-filterOperationBetween": "Mezi",
"dxFilterBuilder-filterOperationAnyOf": "Libovolný z",
"dxFilterBuilder-filterOperationNoneOf": "Žádný z",
"dxFilterBuilder-filterAriaRootElement": "Filter builder",
"dxFilterBuilder-filterAriaGroupLevel": "Level {0}",
"dxFilterBuilder-filterAriaGroupItem": "Group item",
"dxFilterBuilder-filterAriaOperationButton": "Operation",
"dxFilterBuilder-filterAriaAddButton": "Add",
"dxFilterBuilder-filterAriaRemoveButton": "Remove {0}",
"dxFilterBuilder-filterAriaItemField": "Item field",
"dxFilterBuilder-filterAriaItemOperation": "Item operation",
"dxFilterBuilder-filterAriaItemValue": "Item value",

"dxHtmlEditor-dialogColorCaption": "Změna barvy písma",
"dxHtmlEditor-dialogBackgroundCaption": "Změna barvy pozadí",
Expand Down
9 changes: 9 additions & 0 deletions packages/devextreme/js/localization/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,15 @@
"dxFilterBuilder-filterOperationBetween": "Zwischen",
"dxFilterBuilder-filterOperationAnyOf": "Ist enthalten in",
"dxFilterBuilder-filterOperationNoneOf": "Ist nicht enthalten in",
"dxFilterBuilder-filterAriaRootElement": "Filter builder",
"dxFilterBuilder-filterAriaGroupLevel": "Level {0}",
"dxFilterBuilder-filterAriaGroupItem": "Group item",
"dxFilterBuilder-filterAriaOperationButton": "Operation",
"dxFilterBuilder-filterAriaAddButton": "Add",
"dxFilterBuilder-filterAriaRemoveButton": "Remove {0}",
"dxFilterBuilder-filterAriaItemField": "Item field",
"dxFilterBuilder-filterAriaItemOperation": "Item operation",
"dxFilterBuilder-filterAriaItemValue": "Item value",

"dxHtmlEditor-dialogColorCaption": "Schriftfarbe ändern",
"dxHtmlEditor-dialogBackgroundCaption": "Hintergrundfarbe ändern",
Expand Down
Loading

0 comments on commit 1cd3f73

Please sign in to comment.