Skip to content

Commit

Permalink
Accessibility improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
cconard96 authored Nov 14, 2024
1 parent d50e348 commit 484a265
Show file tree
Hide file tree
Showing 16 changed files with 255 additions and 15 deletions.
4 changes: 2 additions & 2 deletions js/glpi_dialog.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@ var glpi_html_dialog = function({

const data_bs_focus = !bs_focus ? 'data-bs-focus="false"' : '';

var modal = `<div class="modal fade ${modalclass}" id="${id}" role="dialog" ${data_bs_focus}>
var modal = `<div class="modal fade ${modalclass}" id="${id}" role="dialog" ${data_bs_focus} aria-labelledby="${id}_title">
<div class="modal-dialog ${dialogclass}">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">${title}</h4>
<h2 id="${id}_title" class="fs-4 modal-title" tabindex="-1">${title}</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal"
aria-label="${__("Close")}"></button>
</div>
Expand Down
3 changes: 2 additions & 1 deletion src/Glpi/Search/Input/QueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,8 @@ public static function displaySearchoptionValue($request = [])
$pattern = $fieldpattern['pattern'];
$message = $fieldpattern['validation_message'];

echo "<input type='text' class='form-control' size='13' name='$inputname' value=\"" .
$field_title = __s('Criteria value');
echo "<input type='text' class='form-control' size='13' aria-label='{$field_title}' name='{$inputname}' value=\"" .
htmlescape($request['value']) . "\" pattern=\"" . htmlescape($pattern) . "\">" .
"<span class='invalid-tooltip'>" . htmlescape($message) . "</span>";
}
Expand Down
8 changes: 6 additions & 2 deletions templates/components/dropdown/limit.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,12 @@
{% if user_pref('list_limit') not in options %}
{% set options = options|merge([user_pref('list_limit')]) %}
{% endif %}
<select class="form-select form-select-sm mx-1 d-inline-block w-auto {{ select_class|default('') }}"
{% if not no_onchange %}onChange="{{ href }}"{% endif %}>
{% set select_name = select_name|default('list_limit') %}
<select id="{{ select_name|slug }}{{ rand|default(random()) }}" class="form-select form-select-sm mx-1 d-inline-block w-auto {{ select_class|default('') }}"
{% if not no_onchange %}onChange="{{ href }}"{% endif %} name="{{ select_name }}"
{% for attr, value in additional_attributes %}
{{ attr }}="{{ value }}"
{% endfor %}>
{% for value in options|sort %}
<option value="{{ value }}" {{ value == user_pref('list_limit') ? 'selected' : '' }}>
{{ value }}
Expand Down
2 changes: 1 addition & 1 deletion templates/components/form/basic_inputs_macros.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@

{% if not options.readonly %}
{% set calendar_icon = options.enableTime ? 'ti ti-calendar-time' : 'ti ti-calendar' %}
<button type="button" class="btn btn-outline-secondary btn-sm" data-toggle>
<button type="button" class="btn btn-outline-secondary btn-sm" data-toggle title="{{ __('Show date picker') }}">
<i class="{{ calendar_icon }}"></i>
</button>
{# maybeempty has no distinct purpose, but it was here first #}
Expand Down
2 changes: 1 addition & 1 deletion templates/components/form/fields_macros.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,7 @@
{% if not options.include_field %}
{{ field }}
{% else %}
{% set id = options.id|length > 0 and options.id != '%id%' ? options.id : (name ~ '_' ~ options.rand) %}
{% set id = options.id|length > 0 and options.id != '%id%' ? options.id : (name|slug ~ '_' ~ options.rand) %}
{% set field = field|replace({'%id%': id}) %}
{% set add_field_html = options.add_field_html|length > 0 ? options.add_field_html : '' %}

Expand Down
9 changes: 8 additions & 1 deletion templates/components/pager.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
{# limit the number of adjacents links displayed #}
{% set adjacents = 2 %}
{% set skip_adjacents = false %}
{% set rand = rand|default(random()) %}

<div class="flex-grow-1 d-flex flex-wrap flex-md-nowrap align-items-center justify-content-between mb-2 search-pager">
{% if not short_display %}
Expand All @@ -77,8 +78,14 @@
'no_onchange': fluid_search|default(false),
'select_class': 'search-limit-dropdown',
'href': href|replace({'%start%': start}),
'additional_attributes': short_display ? {
'title': __('Rows per page'),
} : {
'aria-labelledby': 'list_limit' ~ rand ~ '_label',
},
'rand': rand,
} %}
{{ __('rows / page') }}
<span id="list_limit{{ rand }}_label">{{ __('rows / page') }}</span>
</span>
{% endif %}
<span class="search-limit {{ short_display ? "" : "d-block d-md-none" }}">
Expand Down
4 changes: 2 additions & 2 deletions templates/components/search/controls.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
</button>
{% if not user_pref('show_search_form') %}
{% set active_filter_class = is_filter_active ? "btn-active-search" : "btn-ghost-secondary" %}
<button class="btn btn-sm py-1 {{ active_filter_class }} btn-ghost-secondary dropdown-toggle" type="button"
<button class="btn btn-sm py-1 show-search-filters {{ active_filter_class }} btn-ghost-secondary dropdown-toggle" type="button"
data-bs-toggle="dropdown" data-bs-auto-close="outside">
<i class="ti ti-list-search"></i>
{% if active_search_name|length %}
Expand All @@ -82,7 +82,7 @@

{% if data['search']['as_map'] != 1 %}
{% set active_sort_class = active_sort ? "btn-active-sort" : "" %}
<button class="btn btn-sm py-1 {{ active_sort_class }} btn-ghost-secondary dropdown-toggle me-1 me-xl-2" type="button"
<button class="btn btn-sm py-1 show-search-sorts {{ active_sort_class }} btn-ghost-secondary dropdown-toggle me-1 me-xl-2" type="button"
data-bs-toggle="dropdown" data-bs-auto-close="outside">
<i class="ti ti-arrows-sort"></i>
{% set sort_names = (active_sort_name|length ? active_sort_name : __("Sort")) %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,5 @@
{% endif %}

{% if not display %}
<input type="text" class="form-control" size="13" name="{{ inputname }}" value="{{ value }}">
<input type="text" class="form-control" size="13" aria-label="{{ __('Criteria value') }}" name="{{ inputname }}" value="{{ value }}">
{% endif %}
2 changes: 1 addition & 1 deletion templates/pages/assistance/planning/filters.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
<h3>
{{ headings[filter_heading] }}
{% if filter_heading == 'plannings' %}
<a class="planning_link planning_add_filter" href="{{ path('/ajax/planning.php?action=add_planning_form') }}">
<a class="planning_link planning_add_filter" href="{{ path('/ajax/planning.php?action=add_planning_form') }}" title="{{ __('Add a calendar') }}">
<i class="ti ti-circle-plus"></i>
</a>
{% endif %}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
{# Colors not for groups #}
{% if filter_data.type != 'group_users' and filter_key != 'OnlyBgEvents' %}
<span class="color_input">
<input type="color" name="{{ filter_key }}_color" value="{{ color }}">
<input type="color" name="{{ filter_key }}_color" aria-label="{{ __('%s color')|format(title) }}" value="{{ color }}">
</span>
{% endif %}
{% if filter_data.type == 'group_users' %}
Expand Down
45 changes: 45 additions & 0 deletions tests/cypress/e2e/Planning/external_event.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2024 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/

describe('External event', () => {
beforeEach(() => {
cy.login();
cy.changeProfile('Super-Admin', true);
});
it('Accessibility - Form', () => {
cy.visit('/front/planning.php');
cy.disableAnimations();
cy.get('#planning_container .fc-slats td:not(.fc-axis)').first().click();
cy.get('.modal.show').injectAndCheckA11y();
});
});
44 changes: 44 additions & 0 deletions tests/cypress/e2e/Planning/planning_view.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2024 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/

describe('Planning view', () => {
beforeEach(() => {
cy.login();
cy.changeProfile('Super-Admin', true);
});
it('Accessibility', () => {
cy.visit('/front/planning.php');
cy.disableAnimations();
cy.get('#planning_container').injectAndCheckA11y();
});
});
55 changes: 55 additions & 0 deletions tests/cypress/e2e/page_layout.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2024 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/

describe('Page layout', () => {
beforeEach(() => {
cy.login();
cy.changeProfile('Super-Admin');
});
it('Accessibility', () => {
cy.visit('/front/computer.php');
cy.disableAnimations();
cy.get('aside.navbar.sidebar .nav-link.dropdown-toggle:visible').each(($el) => {
cy.wrap($el).click();
cy.get('aside.navbar.sidebar').injectAndCheckA11y();
});

cy.get('.navbar-nav.user-menu:visible').click();
cy.get('.navbar-nav.user-menu:visible .dropdown-menu').injectAndCheckA11y();

cy.get('.navbar-nav.user-menu:visible .dropdown-menu a.entity-dropdown-toggle').click();
cy.get('.navbar-nav.user-menu:visible .dropdown-menu a.entity-dropdown-toggle + .dropdown-menu').injectAndCheckA11y();

cy.get('header.navbar').injectAndCheckA11y();
});
});
49 changes: 49 additions & 0 deletions tests/cypress/e2e/search_engine.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* ---------------------------------------------------------------------
*
* GLPI - Gestionnaire Libre de Parc Informatique
*
* http://glpi-project.org
*
* @copyright 2015-2024 Teclib' and contributors.
* @copyright 2003-2014 by the INDEPNET Development Team.
* @licence https://www.gnu.org/licenses/gpl-3.0.html
*
* ---------------------------------------------------------------------
*
* LICENSE
*
* This file is part of GLPI.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* ---------------------------------------------------------------------
*/

describe('Search engine', () => {
beforeEach(() => {
cy.login();
cy.changeProfile('Super-Admin', true);
});
it('Accessibility', () => {
cy.visit('/front/computer.php');
cy.get('div.search_page').within(() => {
cy.root().injectAndCheckA11y();
cy.get('button.show-search-filters').click();
cy.get('div.search-form').injectAndCheckA11y();
cy.get('button.show-search-sorts').click();
cy.get('div.sort-container').injectAndCheckA11y();
});
});
});
5 changes: 4 additions & 1 deletion tests/cypress/support/cypress-axe-spec-utility.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@ function terminalLog(violations) {
id,
impact,
description,
nodes: nodes.length
nodes: JSON.stringify({
length: nodes.length,
targets: nodes.map(({ target }) => target).join(', ')
})
})
);

Expand Down
34 changes: 33 additions & 1 deletion tests/cypress/support/cypress-axe.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,14 @@ Cypress.Commands.add('injectAndCheckA11y', {prevSubject: 'optional'}, (subject)
['.alert'], // Default Bootstrap/Tabler alert colors don't meet contrast requirements at any level of WCAG.
['.nav-pills .nav-link.active'],
['span[aria-label]'], // aria-label attribute cannot be used on a span with no valid role attribute. Done by bootstrap.

// Below are items that do not need to be validated
['.sf-toolbar'], // Symfony profiler
['.flatpickr input[type="text"]'], // Input labels broken when using altInput option
['*[data-hasqtip]'], // Popper tooltips aren't created as labels for the elements they are attached to, but we use them instead of title attributes.
['aside.navbar.sidebar a.dropdown-item'], // Duplicate accesskeys
['.fancytree-container .fancytree-expander'], // Expand/collapse 'buttons' have no label
['.fancytree-container thead > tr > th'], // Fancytree adds an empty header row to the table
['.search-header .search-controls .show_export_menu'], // Wrapper element that triggers the tooltip is not valid
]
};
if (subject) {
Expand All @@ -65,3 +70,30 @@ Cypress.Commands.add('injectAndCheckA11y', {prevSubject: 'optional'}, (subject)
}
cy.checkA11y(context, null, terminalLog);
});

/**
* @memberof Cypress.Chainable.prototype
* @method disableAnimations
* @description Disable animations on the page
* @returns Chainable
*/
Cypress.Commands.add('disableAnimations', () => {
cy.get('body').invoke('append', Cypress.$(`
<style id="__cypress-animation-disabler">
*, *:before, *:after {
transition: none !important;
animation: none !important;
}
</style>
`));
});

/**
* @memberof Cypress.Chainable.prototype
* @method enableAnimations
* @description Enable animations on the page
* @returns Chainable
*/
Cypress.Commands.add('enableAnimations', () => {
Cypress.$('#__cypress-animation-disabler').remove();
});

0 comments on commit 484a265

Please sign in to comment.