From 2a21ffb5d735f31eee181234ecb9487fcdf4db79 Mon Sep 17 00:00:00 2001 From: Anthony Barnes Date: Thu, 25 Aug 2016 16:28:28 +1000 Subject: [PATCH] Initial commit of aria label detection and reporting. Both aria-labelledby and aria-label will be picked up as one of the required attributes on specified element types and checked for validity. This addresses #160 and paves the way for #159, #158, #157 and #156. Also references #162. --- HTMLCS.Util.js | 33 +++++++++++++++++ .../Sniffs/Principle1/Guideline1_3/1_3_1.js | 36 ++++++------------ .../Sniffs/Principle4/Guideline4_1/4_1_2.js | 35 +++++++++++------- Tests/WCAG2/4_1_2_Aria_Labels.html | 37 +++++++++++++++++++ 4 files changed, 104 insertions(+), 37 deletions(-) create mode 100644 Tests/WCAG2/4_1_2_Aria_Labels.html diff --git a/HTMLCS.Util.js b/HTMLCS.Util.js index 1abba105..0c71d218 100644 --- a/HTMLCS.Util.js +++ b/HTMLCS.Util.js @@ -149,6 +149,39 @@ HTMLCS.util = function() { }; + /** + * Returns true if the element has a valid aria label. + * + * @param {Node} element The element we are checking. + * + * @return {Boolean} + */ + self.hasValidAriaLabel = function(element) + { + var found = false; + if (element.hasAttribute('aria-labelledby') === true) { + // Checking aria-labelled by where the label exists AND it has text available + // to an accessibility API. + var labelledByIds = element.getAttribute('aria-labelledby').split(/\s+/); + labelledByIds.forEach(function(id) { + var elem = document.getElementById(id); + if (elem) { + var text = self.getElementTextContent(elem); + if (/^\s*$/.test(text) === false) { + found = true; + } + } + }); + } else if (element.hasAttribute('aria-label') === true) { + var text = element.getAttribute('aria-label'); + if (/^\s*$/.test(text) === false) { + found = true; + } + } + + return found; + }; + /** * Return the appropriate computed style object for an element. * diff --git a/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_3/1_3_1.js b/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_3/1_3_1.js index ceacf984..32bbf7d9 100755 --- a/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_3/1_3_1.js +++ b/Standards/WCAG2AAA/Sniffs/Principle1/Guideline1_3/1_3_1.js @@ -256,9 +256,8 @@ _global.HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1 = { } // Find an aria-label attribute. - var ariaLabel = element.getAttribute('aria-label'); - if (ariaLabel !== null) { - if ((/^\s*$/.test(ariaLabel) === true) && (needsLabel === true)) { + if (element.hasAttribute('aria-label') === true) { + if (HTMLCS.util.hasValidAriaLabel(element) === false) { HTMLCS.addMessage( HTMLCS.WARNING, element, @@ -270,28 +269,17 @@ _global.HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1 = { } } - // Find an aria-labelledby attribute. - var ariaLabelledBy = element.getAttribute('aria-labelledby'); - if (ariaLabelledBy && (/^\s*$/.test(ariaLabelledBy) === false)) { - var labelledByIds = ariaLabelledBy.split(/\s+/); - var ok = true; - - // First check that all of the IDs (space separated) are present and correct. - for (var x = 0; x < labelledByIds.length; x++) { - var labelledByElement = element.ownerDocument.querySelector('#' + labelledByIds[x]); - if (!labelledByElement) { - HTMLCS.addMessage( - HTMLCS.WARNING, - element, - 'This form control contains an aria-labelledby attribute, however it includes an ID "' + labelledByIds[x] + '" that does not exist on an element. The aria-labelledby attribute will be ignored for labelling test purposes.', - 'ARIA16,ARIA9' - ); - ok = false; - } - } - // We are all OK, add as a successful label technique. - if (ok === true) { + // Find an aria-labelledby attribute. + if (element.hasAttribute('aria-labelledby') === true) { + if (HTMLCS.util.hasValidAriaLabel(element) === false) { + HTMLCS.addMessage( + HTMLCS.WARNING, + element, + 'This form control contains an aria-labelledby attribute, however it includes an ID "' + element.getAttribute('aria-labelledby') + '" that does not exist on an element. The aria-labelledby attribute will be ignored for labelling test purposes.', + 'ARIA16,ARIA9' + ); + } else { addToLabelList('aria-labelledby'); } } diff --git a/Standards/WCAG2AAA/Sniffs/Principle4/Guideline4_1/4_1_2.js b/Standards/WCAG2AAA/Sniffs/Principle4/Guideline4_1/4_1_2.js index 71469568..7415c105 100755 --- a/Standards/WCAG2AAA/Sniffs/Principle4/Guideline4_1/4_1_2.js +++ b/Standards/WCAG2AAA/Sniffs/Principle4/Guideline4_1/4_1_2.js @@ -157,17 +157,17 @@ _global.HTMLCS_WCAG2AAA_Sniffs_Principle4_Guideline4_1_4_1_2 = { var warnings = []; var requiredNames = { - button: ['@title', '_content'], - fieldset: ['legend'], - input_button: ['@value'], - input_text: ['label', '@title'], - input_file: ['label', '@title'], - input_password: ['label', '@title'], - input_checkbox: ['label', '@title'], - input_radio: ['label', '@title'], - input_image: ['@alt', '@title'], - select: ['label', '@title'], - textarea: ['label', '@title'] + button: ['@title', '_content', '@aria-label', '@aria-labelledby'], + fieldset: ['legend', '@aria-label', '@aria-labelledby'], + input_button: ['@value', '@aria-label', '@aria-labelledby'], + input_text: ['label', '@title', '@aria-label', '@aria-labelledby'], + input_file: ['label', '@title', '@aria-label', '@aria-labelledby'], + input_password: ['label', '@title', '@aria-label', '@aria-labelledby'], + input_checkbox: ['label', '@title', '@aria-label', '@aria-labelledby'], + input_radio: ['label', '@title', '@aria-label', '@aria-labelledby'], + input_image: ['@alt', '@title', '@aria-label', '@aria-labelledby'], + select: ['label', '@title', '@aria-label', '@aria-labelledby'], + textarea: ['label', '@title', '@aria-label', '@aria-labelledby'] } var requiredValues = { @@ -218,12 +218,16 @@ _global.HTMLCS_WCAG2AAA_Sniffs_Principle4_Guideline4_1_4_1_2 = { // functions in SC 1.3.1. var hasLabel = HTMLCS_WCAG2AAA_Sniffs_Principle1_Guideline1_3_1_3_1.testLabelsOnInputs(element, top, true); if (hasLabel !== false) { - found = true; break; } } else if (requiredName.charAt(0) === '@') { // Attribute. requiredName = requiredName.substr(1, requiredName.length); + + if ((requiredName === 'aria-label' || requiredName === 'aria-labelledby') && HTMLCS.util.hasValidAriaLabel(element)) { + break; + } + if ((element.hasAttribute(requiredName) === true) && (/^\s*$/.test(element.getAttribute(requiredName)) === false)) { break; } @@ -300,6 +304,11 @@ _global.HTMLCS_WCAG2AAA_Sniffs_Principle4_Guideline4_1_4_1_2 = { } }//end if + // Check for valid aria labels. + if (valueFound === false) { + valuFound = HTMLCS.util.hasValidAriaLabel(element); + } + if (valueFound === false) { var msgNodeType = nodeName + ' element'; if (nodeName.substr(0, 6) === 'input_') { @@ -307,7 +316,7 @@ _global.HTMLCS_WCAG2AAA_Sniffs_Principle4_Guideline4_1_4_1_2 = { } var msg = 'This ' + msgNodeType + ' does not have a value available to an accessibility API.'; - + var builtAttr = ''; var warning = false; if (requiredValue === '_content') { diff --git a/Tests/WCAG2/4_1_2_Aria_Labels.html b/Tests/WCAG2/4_1_2_Aria_Labels.html new file mode 100644 index 00000000..da23c5bd --- /dev/null +++ b/Tests/WCAG2/4_1_2_Aria_Labels.html @@ -0,0 +1,37 @@ + + + +4.1.2 Aria Labels + + + +
+ + + + + + + +
Billing Address
+
+
Name
+ +
+ +
+ +