aria-valid-attr-value-evaluate.js 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import { validateAttrValue } from '../../commons/aria';
  2. import { getNodeAttributes } from '../../core/utils';
  3. /**
  4. * Check that each ARIA attribute on an element has a valid value.
  5. *
  6. * Valid ARIA attribute values are taken from the `ariaAttrs` standards object from an attributes `type` property.
  7. *
  8. * ##### Data:
  9. * <table class="props">
  10. * <thead>
  11. * <tr>
  12. * <th>Type</th>
  13. * <th>Description</th>
  14. * </tr>
  15. * </thead>
  16. * <tbody>
  17. * <tr>
  18. * <td><code>Mixed</code></td>
  19. * <td>Object with Strings `messageKey` and `needsReview` if `aria-current` or `aria-describedby` are invalid. Otherwise a list of all invalid ARIA attributes and their value</td>
  20. * </tr>
  21. * </tbody>
  22. * </table>
  23. *
  24. * @memberof checks
  25. * @return {Mixed} True if all ARIA attributes have a valid value. Undefined for invalid `aria-current` or `aria-describedby` values. False otherwise.
  26. */
  27. function ariaValidAttrValueEvaluate(node, options) {
  28. options = Array.isArray(options.value) ? options.value : [];
  29. let needsReview = '';
  30. let messageKey = '';
  31. const invalid = [];
  32. const aria = /^aria-/;
  33. const attrs = getNodeAttributes(node);
  34. const skipAttrs = ['aria-errormessage'];
  35. const preChecks = {
  36. // aria-controls should only check if element exists if the element
  37. // doesn't have aria-expanded=false or aria-selected=false (tabs)
  38. // @see https://github.com/dequelabs/axe-core/issues/1463
  39. 'aria-controls': () => {
  40. return (
  41. node.getAttribute('aria-expanded') !== 'false' &&
  42. node.getAttribute('aria-selected') !== 'false'
  43. );
  44. },
  45. // aria-current should mark as needs review if any value is used that is
  46. // not one of the valid values (since any value is treated as "true")
  47. 'aria-current': () => {
  48. if (!validateAttrValue(node, 'aria-current')) {
  49. needsReview = `aria-current="${node.getAttribute('aria-current')}"`;
  50. messageKey = 'ariaCurrent';
  51. }
  52. return;
  53. },
  54. // aria-owns should only check if element exists if the element
  55. // doesn't have aria-expanded=false (combobox)
  56. // @see https://github.com/dequelabs/axe-core/issues/1524
  57. 'aria-owns': () => {
  58. return node.getAttribute('aria-expanded') !== 'false';
  59. },
  60. // aria-describedby should not mark missing element as violation but
  61. // instead as needs review
  62. // @see https://github.com/dequelabs/axe-core/issues/1151
  63. 'aria-describedby': () => {
  64. if (!validateAttrValue(node, 'aria-describedby')) {
  65. needsReview = `aria-describedby="${node.getAttribute(
  66. 'aria-describedby'
  67. )}"`;
  68. messageKey = 'noId';
  69. }
  70. return;
  71. },
  72. // aria-labelledby should not mark missing element as violation but
  73. // instead as needs review
  74. // @see https://github.com/dequelabs/axe-core/issues/2621
  75. 'aria-labelledby': () => {
  76. if (!validateAttrValue(node, 'aria-labelledby')) {
  77. needsReview = `aria-labelledby="${node.getAttribute(
  78. 'aria-labelledby'
  79. )}"`;
  80. messageKey = 'noId';
  81. }
  82. }
  83. };
  84. for (let i = 0, l = attrs.length; i < l; i++) {
  85. const attr = attrs[i];
  86. const attrName = attr.name;
  87. // skip any attributes handled elsewhere
  88. if (
  89. !skipAttrs.includes(attrName) &&
  90. options.indexOf(attrName) === -1 &&
  91. aria.test(attrName) &&
  92. (preChecks[attrName] ? preChecks[attrName]() : true) &&
  93. !validateAttrValue(node, attrName)
  94. ) {
  95. invalid.push(`${attrName}="${attr.nodeValue}"`);
  96. }
  97. }
  98. if (needsReview) {
  99. this.data({
  100. messageKey,
  101. needsReview
  102. });
  103. return undefined;
  104. }
  105. if (invalid.length) {
  106. this.data(invalid);
  107. return false;
  108. }
  109. return true;
  110. }
  111. export default ariaValidAttrValueEvaluate;