Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set the main ARIA 1.1 roles and properties for comboboxes #5582

Merged
merged 6 commits into from Jul 30, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/js/select2/dropdown/infiniteScroll.js
Expand Up @@ -78,7 +78,7 @@ define([
var $option = $(
'<li ' +
'class="select2-results__option select2-results__option--load-more"' +
'role="treeitem" aria-disabled="true"></li>'
'role="option" aria-disabled="true"></li>'
);

var message = this.options.get('translations').get('loadingMore');
Expand Down
15 changes: 14 additions & 1 deletion src/js/select2/dropdown/search.js
Expand Up @@ -11,7 +11,7 @@ define([
'<span class="select2-search select2-search--dropdown">' +
'<input class="select2-search__field" type="search" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
' spellcheck="false" role="textbox" />' +
' spellcheck="false" role="searchbox" aria-autocomplete="list" />' +
'</span>'
);

Expand All @@ -26,6 +26,8 @@ define([
Search.prototype.bind = function (decorated, container, $container) {
var self = this;

var resultsId = container.id + '-results';

decorated.call(this, container, $container);

this.$search.on('keydown', function (evt) {
Expand All @@ -48,6 +50,7 @@ define([

container.on('open', function () {
self.$search.attr('tabindex', 0);
self.$search.attr('aria-controls', resultsId);

self.$search.trigger('focus');

Expand All @@ -58,6 +61,8 @@ define([

container.on('close', function () {
self.$search.attr('tabindex', -1);
self.$search.removeAttr('aria-controls');
self.$search.removeAttr('aria-activedescendant');

self.$search.val('');
self.$search.trigger('blur');
Expand All @@ -80,6 +85,14 @@ define([
}
}
});

container.on('results:focus', function (params) {
if (params.data._resultId) {
self.$search.attr('aria-activedescendant', params.data._resultId);
} else {
self.$search.removeAttr('aria-activedescendant');
}
});
};

Search.prototype.handleSearch = function (evt) {
Expand Down
6 changes: 3 additions & 3 deletions src/js/select2/results.js
Expand Up @@ -14,7 +14,7 @@ define([

Results.prototype.render = function () {
var $results = $(
'<ul class="select2-results__options" role="tree"></ul>'
'<ul class="select2-results__options" role="listbox"></ul>'
);

if (this.options.get('multiple')) {
Expand All @@ -37,7 +37,7 @@ define([
this.hideLoading();

var $message = $(
'<li role="treeitem" aria-live="assertive"' +
'<li role="alert" aria-live="assertive"' +
' class="select2-results__option"></li>'
);

Expand Down Expand Up @@ -171,7 +171,7 @@ define([
option.className = 'select2-results__option';

var attrs = {
'role': 'treeitem',
'role': 'option',
'aria-selected': 'false'
};

Expand Down
12 changes: 10 additions & 2 deletions src/js/select2/selection/search.js
Expand Up @@ -12,7 +12,7 @@ define([
'<li class="select2-search select2-search--inline">' +
'<input class="select2-search__field" type="search" tabindex="-1"' +
' autocomplete="off" autocorrect="off" autocapitalize="none"' +
' spellcheck="false" role="textbox" aria-autocomplete="list" />' +
' spellcheck="false" role="searchbox" aria-autocomplete="list" />' +
'</li>'
);

Expand All @@ -29,14 +29,18 @@ define([
Search.prototype.bind = function (decorated, container, $container) {
var self = this;

var resultsId = container.id + '-results';

decorated.call(this, container, $container);

container.on('open', function () {
self.$search.attr('aria-controls', resultsId);
self.$search.trigger('focus');
});

container.on('close', function () {
self.$search.val('');
self.$search.removeAttr('aria-controls');
self.$search.removeAttr('aria-activedescendant');
self.$search.trigger('focus');
});
Expand All @@ -56,7 +60,11 @@ define([
});

container.on('results:focus', function (params) {
self.$search.attr('aria-activedescendant', params.id);
if (params.data._resultId) {
self.$search.attr('aria-activedescendant', params.data._resultId);
} else {
self.$search.removeAttr('aria-activedescendant');
}
});

this.$selection.on('focusin', '.select2-search--inline', function (evt) {
Expand Down
51 changes: 0 additions & 51 deletions tests/a11y/search-tests.js

This file was deleted.

185 changes: 185 additions & 0 deletions tests/dropdown/search-a11y-tests.js
@@ -0,0 +1,185 @@
module('Dropdown - Search - Accessibility');

var Utils = require('select2/utils');

var Dropdown = require('select2/dropdown');
var DropdownSearch = Utils.Decorate(
Dropdown,
require('select2/dropdown/search')
);

var $ = require('jquery');

var Options = require('select2/options');
var options = new Options({});

test('role attribute is set to searchbox', function (assert) {
var $select = $('#qunit-fixture .single');

var dropdown = new DropdownSearch($select, options);
var $dropdown = dropdown.render();

var container = new MockContainer();
dropdown.bind(container, $('<span></span>'));

assert.equal(
$dropdown.find('input').attr('role'),
'searchbox',
'The search box is marked as a search box'
);
});

test('aria-autocomplete attribute is present', function (assert) {
var $select = $('#qunit-fixture .single');

var dropdown = new DropdownSearch($select, options);
var $dropdown = dropdown.render();

var container = new MockContainer();
dropdown.bind(container, $('<span></span>'));

assert.equal(
$dropdown.find('input').attr('aria-autocomplete'),
'list',
'The search box is marked as autocomplete'
);
});

test('aria-activedescendant should not be set initiailly', function (assert) {
var $select = $('#qunit-fixture .single');

var dropdown = new DropdownSearch($select, options);
var $dropdown = dropdown.render();

var container = new MockContainer();
dropdown.bind(container, $('<span></span>'));

var $search = $dropdown.find('input');

assert.ok(
!$search.attr('aria-activedescendant'),
'The search box should not point to anything when it is first rendered'
);
});

test('aria-activedescendant should be set after highlight', function (assert) {
var $select = $('#qunit-fixture .single');

var dropdown = new DropdownSearch($select, options);
var $dropdown = dropdown.render();

var container = new MockContainer();
dropdown.bind(container, $('<span></span>'));

container.trigger('results:focus', {
data: {
_resultId: 'test'
}
});

var $search = $dropdown.find('input');

assert.equal(
$search.attr('aria-activedescendant'),
'test',
'The search is pointing to the focused result'
);
});

test('activedescendant should remove if there is no ID', function (assert) {
var $select = $('#qunit-fixture .single');

var dropdown = new DropdownSearch($select, options);
var $dropdown = dropdown.render();

var container = new MockContainer();
dropdown.bind(container, $('<span></span>'));

var $search = $dropdown.find('input');
$search.attr('aria-activedescendant', 'test');

container.trigger('results:focus', {
data: {}
});

assert.ok(
!$search.attr('aria-activedescendant'),
'There is no result for the search to be pointing to'
);
});

test('aria-activedescendant should be removed when closed', function (assert) {
var $select = $('#qunit-fixture .single');

var dropdown = new DropdownSearch($select, options);
var $dropdown = dropdown.render();

var container = new MockContainer();
dropdown.bind(container, $('<span></span>'));

var $search = $dropdown.find('input');
$search.attr('aria-activedescendant', 'something');

container.trigger('close');

assert.ok(
!$search.attr('aria-activedescendant'),
'There is no active descendant when the dropdown is closed'
);
});

test('aria-controls should not be set initiailly', function (assert) {
var $select = $('#qunit-fixture .single');

var dropdown = new DropdownSearch($select, options);
var $dropdown = dropdown.render();

var container = new MockContainer();
dropdown.bind(container, $('<span></span>'));

var $search = $dropdown.find('input');

assert.ok(
!$search.attr('aria-controls'),
'The search box should not point to the results when it is first rendered'
);
});

test('aria-controls should be set when opened', function (assert) {
var $select = $('#qunit-fixture .single');

var dropdown = new DropdownSearch($select, options);
var $dropdown = dropdown.render();

var container = new MockContainer();
dropdown.bind(container, $('<span></span>'));

var $search = $dropdown.find('input');

container.trigger('open');

assert.ok(
$search.attr('aria-controls'),
'The search should point to the results when it is opened'
);
});

test('aria-controls should be removed when closed', function (assert) {
var $select = $('#qunit-fixture .single');

var dropdown = new DropdownSearch($select, options);
var $dropdown = dropdown.render();

var container = new MockContainer();
dropdown.bind(container, $('<span></span>'));

var $search = $dropdown.find('input');
$search.attr('aria-controls', 'something');

container.trigger('close');

assert.ok(
!$search.attr('aria-controls'),
'There are no results for the search box to point to when it is closed'
);
});
25 changes: 25 additions & 0 deletions tests/results/a11y-tests.js
@@ -0,0 +1,25 @@
module('Results - Accessibility');

var $ = require('jquery');

var Options = require('select2/options');

var Results = require('select2/results');

test('role of results should be a listbox', function (assert) {
var results = new Results($('<select></select>'), new Options({}));

var $results = results.render();

assert.equal($results.attr('role'), 'listbox');
});

test('multiple select should have aria-multiselectable', function (assert) {
var results = new Results($('<select></select>'), new Options({
multiple: true
}));

var $results = results.render();

assert.equal($results.attr('aria-multiselectable'), 'true');
});