From d36dd946d14af146dd30b8396e7e4805a973afdc Mon Sep 17 00:00:00 2001
From: Pierre-Yves Ayoul <105045667+pya35@users.noreply.github.com>
Date: Wed, 18 Dec 2024 16:59:44 +0100
Subject: [PATCH] auto-evaluation checklist Web (#596)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* ajout d'une checklist de tests pour les 11 critères web incontournables
---
src/_data/navigation.js | 3 +-
.../tests-concepteur-checklist-fr.json | 3 +
.../checklist/tests-web-checklist-fr.json | 155 +++++
src/assets/test-web-checklist.js | 581 ++++++++++++++++++
src/assets/test-web.js | 4 +-
src/fr/web/checklist-initiale.md | 32 +
6 files changed, 775 insertions(+), 3 deletions(-)
create mode 100644 src/assets/json/checklist/tests-concepteur-checklist-fr.json
create mode 100644 src/assets/json/checklist/tests-web-checklist-fr.json
create mode 100644 src/assets/test-web-checklist.js
create mode 100644 src/fr/web/checklist-initiale.md
diff --git a/src/_data/navigation.js b/src/_data/navigation.js
index fd1c9e51e..959711a44 100644
--- a/src/_data/navigation.js
+++ b/src/_data/navigation.js
@@ -129,7 +129,8 @@ module.exports = {
{ label: 'Développer', href: '/fr/web/developper/' },
{ label: 'Tester', href: '/fr/web/tester/' },
{ label: 'Auditer', href: '/fr/web/audit-wcag/' },
- { label: 'Boite à outils', href: '/fr/web/outils/' }
+ { label: 'Boite à outils', href: '/fr/web/outils/' },
+ { label: 'Incontournables', href: '/fr/web/checklist-initiale/' }
]
},
{
diff --git a/src/assets/json/checklist/tests-concepteur-checklist-fr.json b/src/assets/json/checklist/tests-concepteur-checklist-fr.json
new file mode 100644
index 000000000..3c5c1916a
--- /dev/null
+++ b/src/assets/json/checklist/tests-concepteur-checklist-fr.json
@@ -0,0 +1,3 @@
+[
+
+]
diff --git a/src/assets/json/checklist/tests-web-checklist-fr.json b/src/assets/json/checklist/tests-web-checklist-fr.json
new file mode 100644
index 000000000..e89710b13
--- /dev/null
+++ b/src/assets/json/checklist/tests-web-checklist-fr.json
@@ -0,0 +1,155 @@
+[{
+ "themes": "Contenu textuel",
+ "title": "Donner un titre aux pages",
+ "type": ["Test manuel - Inspecteur de code"],
+ "tests": [["Lancer l'inspecteur de code du navigateur et examiner le titre de page (<title>[titre de la page]</title>)."], ["Ou lire le titre présent dans l'onglet du navigateur."]],
+ "verifier": ["Le titre de page est renseigné.", "Le titre de page est unique et explicite le contenu de la page.", "Le titre de page reflète les modifications du contenu (exemple : erreur dans un formulaire, nombre et termes d'une recherche, étape dans un parcours, mise à jour asynchrone du contenu…)."],
+ "resultat": ["Chaque page possède un titre unique et descriptif du contenu, globalement du plus précis vers le plus général (exemple : [résumé du contenu de la page - nom du site])."],
+ "exception": "",
+ "raccourcis": "",
+ "moreInfo": "https://a11y-guidelines.orange.com/fr/web/developper/contenu-textuel/#donner-un-titre-aux-pages",
+ "profils": ["Checklist incontournables"],
+ "wcag": ["2.4.2"],
+ "ID": "testWebID-0",
+ "IDorigin": "testWebID-0"
+}, {
+ "themes": "Contenu textuel",
+ "title": "Donner des titres aux rubriques",
+ "type": ["Bookmarklet"],
+ "tests": ["Installer le bookmarklet Headings en le glissant dans la barre des favoris de votre navigateur puis l'exécuter."],
+ "verifier": ["Les titres sont pertinents, reflètent le contenu de la page et sont non vides.", "Les titres de niveaux sont hiérarchisés (balises <h1> à <h6>) de manière à refléter l'importance et la hiérarchie visible à l'écran (sur le principe d'une table des matières)."],
+ "resultat": ["Tous les contenus traités visuellement comme des titres possèdent une sémantique de titre (balises <h1> à <h6>).", "Les titres de niveaux sont hiérarchisés de manière à refléter leur poids sémantique."],
+ "exception": "
Ne pas tenir compte des titres masqués avec les attributs visibility:hidden, aria-hidden='true' ou display:none. Si un de ces titres masqués est susceptible d'apparaître, il faut refaire les observations sur la nouvelle structuration du titrage.
",
+ "raccourcis": "",
+ "moreInfo": "",
+ "profils": ["Checklist incontournables"],
+ "wcag": ["2.4.6", "1.3.1"],
+ "ID": "testWebID-1",
+ "IDorigin": "testWebID-1"
+}, {
+ "themes": "Contenu textuel",
+ "title": "Indiquer la langue principale",
+ "type": ["Test manuel - Inspecteur de code"],
+ "tests": ["Lancer l'inspecteur de code du navigateur.", "Examiner l'élément <html>."],
+ "verifier": ["L'attribut lang est présent.", "La valeur de l'attribut lang doit correspondre à la langue principale du document. Voir la liste des codes ISO 639-1."],
+ "resultat": [["Un attribut lang est présent dans l'élément <html> de la page."], ["La valeur de l'attribut lang correspond à la langue principale du document, exemple : <html lang='fr'>, <html lang='en-US'>."]],
+ "exception": "",
+ "raccourcis": "",
+ "moreInfo": "",
+ "profils": ["Checklist incontournables"],
+ "wcag": ["3.1.1"],
+ "ID": "testWebID-4",
+ "IDorigin": "testWebID-4"
+}, {
+ "themes": "Contenu non-textuel",
+ "title": "S'assurer que les images ont une alternative textuelle",
+ "type": ["Bookmarklet"],
+ "tests": ["Installer puis lancer le bookmarklet List Images ou l'inspecteur de code."],
+ "verifier": ["La valeur des attributs alt est pertinente."],
+ "resultat": ["La valeur de l'attribut alt dépend du type d'image :
Image lien : le contenu de l'attribut alt de chaque image-lien est pertinent par rapport à la cible du lien.
Image porteuse d'information : l'attribut alt de chaque image est pertinent par rapport au rôle de l'image dans la page.
Image contenant du texte : l'attribut alt reprend au moins le texte de l'image.
Image décorative : l'attribut alt est présent mais vide.
Image complexe dont le contenu du alt serait trop long (schémas, graphes…) : pour toute description d'image trop longue pour être mise dans un attribut alt, la description de l'image sous forme de texte html est soit présente dans la page, soit consultable par lien à proximité de l'image à décrire et pointant vers une page html contenant la description.
"],
+ "exception": "",
+ "raccourcis": "",
+ "moreInfo": "",
+ "profils": ["Checklist incontournables"],
+ "wcag": ["1.1.1"],
+ "ID": "testWebID-7",
+ "IDorigin": "testWebID-7"
+}, {
+ "themes": "Couleurs et contrastes",
+ "title": "Assurer un contraste suffisamment élevé entre texte et arrière-plan",
+ "type": ["Color Contrast Analyser"],
+ "tests": ["Installer et lancer Color Contrast Analyser."],
+ "verifier": ["Le contraste est suffisant pour les textes et les textes sous forme d'images."],
+ "resultat": [" Color Contrast Analyser affiche 'Conforme' pour les critères AA :
Texte normal : taille inférieure à 24px ou à 18,5px gras.
Grand texte : Taille supérieure ou égale à 24px ou à 18,5px gras.
Contenu non textuel : indicateurs de focus, graphiques, icones, liens non soulignés...
"],
+ "exception": ["Du texte qui fait partie d'un composant d'interface utilisateur inactif ou d'un logo, un nom de marque."],
+ "raccourcis": "",
+ "moreInfo": [["Lien Color Contrast Analyser : "],["Comment utiliser CCA : https://a11y-guidelines.orange.com/fr/web/outils/methodes-et-outils-de-test/mesurer-contraste-couleurs/"]],
+ "profils": ["Checklist incontournables"],
+ "wcag": ["1.4.3"],
+ "ID": "testWebID-16",
+ "IDorigin": "testWebID-16"
+}, {
+ "themes": "Couleurs et contrastes",
+ "title": "S'assurer que l'information n'est pas transmise uniquement par la couleur",
+ "type": ["Test manuel"],
+ "tests": ["Identifier les éléments utilisant de la couleur pour transmettre de l'information."],
+ "verifier": ["La couleur n'est pas le seul moyen de convoyer l'information : au moins un autre moyen visuel est disponible pour obtenir la même information."],
+ "resultat": ["L'information transmise par la couleur peut également être obtenue par un texte explicite.", "L'information transmise par la couleur est complétée par une autre information visuelle (exemple : icônes utilisant des couleurs et formes différentes).", "Cas particulier des liens dans du texte : s'ils ne sont pas soulignés, au focus clavier et au survol souris, fournir un autre moyen que la couleur pour les distinguer."],
+ "exception": "",
+ "raccourcis": "",
+ "moreInfo": "https://a11y-guidelines.orange.com/fr/web/developper/couleurs-et-contrastes/#ne-pas-utiliser-la-couleur-ou-linformation-sensorielle-comme-seule-source-dinformation",
+ "profils": ["Checklist incontournables"],
+ "wcag": ["1.4.1"],
+ "ID": "testWebID-18",
+ "IDorigin": "testWebID-18"
+}, {
+ "themes": "Navigation générale",
+ "title": "Permettre le contrôle des animations",
+ "type": ["Test manuel"],
+ "tests": ["Identifier tout contenu en mouvement, mis à jour automatiquement, clignotant ou en défilement, durant plus de 5 secondes et lancé automatiquement (exemple : un carrousel)."],
+ "verifier": [["Pour chaque contenu animé, un mécanisme est présent pour permettre à l'utilisateur de le mettre en pause ou de le masquer."], ["Ce mécanisme est accessible à la souris et à la navigation clavier."]],
+ "resultat": ["L'utilisateur peut mettre pause ou masquer les animations, les mouvements, les mises à jour ou les clignotements."],
+ "exception": "",
+ "raccourcis": "",
+ "moreInfo": "https://a11y-guidelines.orange.com/fr/web/developper/navigation-generale/#permettre-le-controle-des-animations",
+ "profils": ["Checklist incontournables"],
+ "wcag": ["2.2.2"],
+ "ID": "testWebID-37",
+ "IDorigin": "testWebID-37"
+}, {
+ "themes": "Navigation clavier",
+ "title": "Permettre l'utilisation de l'application au clavier",
+ "type": ["Navigation clavier"],
+ "tests": ["Parcourir la page au clavier à l'aide des touches Tab ou shift + Tab.","Utiliser tous les éléments interactifs (en tapant sur les touches Entrer, Espace pour les boutons/liens, et les flèches directionnelles pour certains composants : une série de boutons radio, un système d'onglets…)."],
+ "verifier": [["Tout ce qui peut être fait à la souris peut être fait au clavier et inversement."],["L'ordre de déplacement du focus conserve une logique de lecture et d'utilisation."],["Le focus ne reste pas bloqué sur une partie de la page (piège clavier)."],["L'indicateur de focus (outline) est visible sur tous les éléments interactifs et possède un contraste suffisant."]],
+ "resultat": ["Tous les éléments interactifs sont atteignables en naviguant au clavier.", "Tous les éléments interactifs sont utilisables depuis des interactions clavier."],
+ "exception": "",
+ "raccourcis": "",
+ "moreInfo": "https://a11y-guidelines.orange.com/fr/web/developper/navigation-clavier/#permettre-dutiliser-les-principales-fonctionnalites-de-lapplication-au-clavier",
+ "profils": ["Checklist incontournables"],
+ "wcag": ["2.1.1"],
+ "ID": "testWebID-38",
+ "IDorigin": "testWebID-38"
+}, {
+ "themes": "Mise en page",
+ "title": "Utiliser des tailles relatives et faire du web adaptatif (responsive)",
+ "type": ["Test manuel"],
+ "tests": ["Avec Firefox, à partir du menu \"Affichage\", sélectionner \"Zoom\" puis \"Agrandir uniquement le texte\" et activer un niveau de zoom à 200%."],
+ "verifier": ["Il n'y a ni contenus tronqués ou masqués ni fonctionnalités inutilisables."],
+ "resultat": ["Absence de contenus tronqués ou masqués et absence de fonctionnalités inutilisables."],
+ "exception": "",
+ "raccourcis": "",
+ "moreInfo": "https://a11y-guidelines.orange.com/fr/web/developper/mise-en-page/#utiliser-des-tailles-relatives-et-faire-du-web-adaptatif",
+ "profils": ["Checklist incontournables"],
+ "wcag": ["1.4.11"],
+ "ID": "testWebID-42",
+ "IDorigin": "testWebID-42"
+}, {
+ "themes": "Formulaires",
+ "title": "S'assurer qu'un nom accessible est associé à chaque champ de formulaire",
+ "type": ["Test manuel - Inspecteur de code"],
+ "tests": ["Utiliser l'inspecteur de code du navigateur sur l'onglet \"Accessibilité\"."],
+ "verifier": ["Chaque champ de formulaire doit posséder un nom accessible."],
+ "resultat": ["Chaque champ a au moins un nom accessible pertinent et contient au moins le texte de l'étiquette de champ visible à l'écran (un placeholder n'est pas conforme)."],
+ "exception": "Le nom accessible peut être caché visuellement lorsque le rôle d'un champ de formulaire est évident et non ambigu (exemple : champ de recherche accompagné d'un bouton en forme de loupe, case à cocher pour sélectionner une ligne dans un tableau de données).",
+ "raccourcis": "",
+ "moreInfo": "https://a11y-guidelines.orange.com/fr/web/developper/formulaires/#rendre-accessibles-les-champs-de-formulaire",
+ "profils": ["Checklist incontournables"],
+ "wcag": ["2.4.6", "3.3.2"],
+ "ID": "testWebID-47",
+ "IDorigin": "testWebID-47"
+}, {
+ "themes": "Formulaires",
+ "title": "S'assurer que les messages d'erreurs sont pertinents",
+ "type": ["Test manuel"],
+ "tests": ["Renseigner les formulaires avec des données erronées et des champs obligatoires laissés vides.", "Soumettre les formulaires."],
+ "verifier": ["Les messages d'erreurs sont présents, pertinents, et identifient les champs en erreur."],
+ "resultat": ["Les erreurs sont détectées et les messages sont pertinents."],
+ "exception": "",
+ "raccourcis": "",
+ "moreInfo": "https://a11y-guidelines.orange.com/fr/web/developper/formulaires/#detecter-identifier-les-erreurs-et-suggerer-des-corrections",
+ "profils": ["Checklist incontournables"],
+ "wcag": ["3.3.3"],
+ "ID": "testWebID-52",
+ "IDorigin": "testWebID-52"
+}]
diff --git a/src/assets/test-web-checklist.js b/src/assets/test-web-checklist.js
new file mode 100644
index 000000000..483bad864
--- /dev/null
+++ b/src/assets/test-web-checklist.js
@@ -0,0 +1,581 @@
+document.addEventListener("DOMContentLoaded", function (event) {
+ const lang = document.documentElement.getAttribute('lang')
+
+ if (!lang) {
+ throw new Error('A lang attribute must be set on the tag !')
+ }
+
+ const locales = {
+ 'en': {
+ process: "Process",
+ check: "To check",
+ conception: 'Design',
+ development: 'Development',
+ results: "Results",
+ justification: "Justification",
+ profiles: 'Profiles',
+ tools: 'Tools',
+ allTools: 'All tools',
+ allProfils: 'All profiles',
+ exceptions: 'Exceptions',
+ ongoingTests: 'ongoing tests',
+ noResults: 'No results match your selection',
+ withCurrentFilters: 'with current filters',
+ reinitFilters: 'Reinit filters',
+ filtersTitle: 'Go to filters'
+ },
+ 'fr': {
+ process: "Procédure",
+ check: "À vérifier",
+ conception: 'Conception',
+ development: 'Développement',
+ results: "Résultats",
+ justification: "Justification",
+ profiles: 'Profils ',
+ tools: 'Outils ',
+ allTools: 'Tous les outils',
+ allProfils: 'Tous les profils',
+ exceptions: 'Exceptions',
+ ongoingTests: 'tests en cours',
+ noResults: 'Aucun résultat ne correspond à votre sélection',
+ withCurrentFilters: 'dans les filtres en cours',
+ reinitFilters: 'Réinitialiser les filtres',
+ filtersTitle: 'Aller aux filtres'
+ }
+ }
+
+ function translate(key, to) {
+ const locale = to || lang
+
+ if (!locales.hasOwnProperty(locale)) {
+ throw new Error(`translate(): Translation's locale \`${locale}\` does not exist`)
+ }
+
+ if (!locales[locale].hasOwnProperty(key)) {
+ throw new Error(`translate(): Translation's key \`${key}\` does not exist for locale \`${locale}\``)
+ }
+
+ return locales[locale][key]
+ }
+
+ //requette XMLHttpRequest
+ function doXHR(url, callback) {
+ var oReq = new XMLHttpRequest();
+
+ oReq.onreadystatechange = function (event) {
+ if (this.readyState === XMLHttpRequest.DONE) {
+ if (this.status === 200) {
+ return callback(null, this.responseText);
+ } else {
+ return callback({errCode: this.status, errMsg: this.statusText});
+ }
+ }
+ };
+ oReq.open('GET', url, true);
+ oReq.send(null);
+ }
+
+ //appel des Json
+
+
+ doXHR('/assets/json/checklist/tests-web-checklist-' + lang + '.json', function (errFirst, responseFirst) {
+ if (errFirst) {
+ reqError();
+ }
+ return doXHR('/assets/json/checklist/tests-concepteur-checklist-' + lang + '.json', function (errSecond, responseSecond) {
+ if (errSecond) {
+ reqError();
+ }
+ return reqListener(responseFirst, responseSecond);
+ });
+
+ });
+
+
+ function reqError(err) {
+ let elrefTests = document.getElementById('refTests');
+ elrefTests.innerHTML = '
Erreur chargement ressource JSON
';
+ }
+
+ //on concatene les 2 jsons en les réorganisant par tests
+ function compareReorder(a, b) {
+
+ // si les titres sont identiques, on regroupe par titre
+ for (var i = 0; i < a.length; i++) {
+
+ let testA = a[i].title;
+
+
+ for (var j = 0; j < b.length; j++) {
+
+ var testB = b[j].title;
+
+ if (testA == testB) {
+ a.splice(i++, 0, b[j]);
+ b.splice(j, 1);
+
+ }
+
+ }
+
+ }
+
+ //sinon on regroupe les tests par themes
+ for (var i = 0; i < a.length; i++) {
+
+ let testC = a[i].themes;
+
+ for (var j = 0; j < b.length; j++) {
+
+ var testD = b[j].themes;
+
+ if (testC == testD) {
+ a.splice(i, 0, b[j]);
+ b.splice(j, 1);
+ }
+ }
+ }
+ return a;
+ }
+
+ // function encode(str){
+
+ // str=str.replace(/[\x26\x0A\<>'"^]/gi, function(r){return""+r.charCodeAt(0)+";"});
+ // str=str.replace(/\<code\>([\s\S]*?)\<\/code\>/g, '$1');
+
+ // return str;
+ // }
+
+
+ function formatHeading(str) {
+ str = str.toLowerCase();
+ str = str.replace(/é|è|ê/g, "e");
+ str = str.replace(/ /g, "-");
+
+ return str;
+ }
+
+ //supprimer les doublons dans les filtres
+ function delDoublon(arrCond, inputId) {
+ for (var i = 0; i < arrCond.length; i++) {
+ //for (let condition of arrCond) {
+ let condition = arrCond[i];
+ if (condition.name == inputId) {
+ let arrCondIndex = arrCond.indexOf(condition);
+ arrCond.splice(arrCondIndex, 1);
+ }
+ }
+ return arrCond;
+ }
+
+
+ function reqListener(responseFirst, responseSecond) {
+ var data = JSON.parse(responseFirst);
+ var data2 = JSON.parse(responseSecond);
+ var uniqueTypes = [];
+ var refTests = compareReorder(data, data2);
+
+ // Get the right menu
+ const toc = document.getElementById('toc');
+ let ul = toc.querySelector('ul');
+ const originalUl = ul.cloneNode(true);
+
+ let app = new function () {
+ // Récupération des données
+ //this.refTests = refTests;
+
+ this.UpdateTypes = function (allTypes, updatedTypes) {
+ let elrefTypes = [];
+
+ for (let i in updatedTypes) {
+ for (let j in updatedTypes[i].type) {
+ elrefTypes.push(updatedTypes[i].type[j]);
+ }
+ }
+ let uniqueUpdatedTypes = elrefTypes.filter(function (value, index, self) {
+ return self.indexOf(value) === index;
+ });
+
+ for (let i in allTypes) {
+ var elem = document.getElementById('type' + i);
+ elem.disabled = true;
+ var elemLabel = document.getElementById('labelType' + i);
+ elemLabel.classList.add("disabled");
+
+ }
+ for (let i in allTypes) {
+ for (let j in uniqueUpdatedTypes) {
+ if (allTypes[i] == uniqueUpdatedTypes[j]) {
+ var elem = document.getElementById('type' + i);
+ elem.disabled = false;
+ var elemLabel = document.getElementById('labelType' + i);
+ elemLabel.classList.remove("disabled");
+ }
+ }
+ }
+
+ };
+
+ this.UpdateFeedback = function (activeFilter, nbTests, isRefresh) {
+ let elBtnReinit = document.getElementById('reinit');
+ let elFeedback = document.getElementById('feedback');
+ let htmlFeedback = '';
+ let test = nbTests > 1 ? 'tests' : 'test'
+
+ if (activeFilter) {
+ elBtnReinit.classList.remove('hidden');
+ htmlFeedback = '