diff --git a/README.md b/README.md index 0f6a737..c3b74de 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ | ❌ | aria-roledescription | https://dequeuniversity.com/rules/axe/4.4/aria-roledescription?application=RuleDescription | Ensure aria-roledescription is only used on elements with an implicit or explicit role | Serious | cat.aria, wcag2a, wcag412 | failure, needs review | | | ✅ | aria-roles | https://dequeuniversity.com/rules/axe/4.4/aria-roles?application=RuleDescription | Ensures all elements with a role attribute use a valid value | Minor, Serious, Critical | cat.aria, wcag2a, wcag412 | failure | [674b10](https://act-rules.github.io/rules/674b10) | | ❌ | aria-toggle-field-name | https://dequeuniversity.com/rules/axe/4.4/aria-toggle-field-name?application=RuleDescription | Ensures every ARIA toggle field has an accessible name | Moderate, Serious | cat.aria, wcag2a, wcag412, ACT | failure, needs review | [e086e5](https://act-rules.github.io/rules/e086e5) | -| ❌ | aria-tooltip-name | https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name?application=RuleDescription | Ensures every ARIA tooltip node has an accessible name | Serious | cat.aria, wcag2a, wcag412 | failure, needs review | | +| ✅ | aria-tooltip-name | https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name?application=RuleDescription | Ensures every ARIA tooltip node has an accessible name | Serious | cat.aria, wcag2a, wcag412 | failure, needs review | | | ✅ | aria-valid-attr-value | https://dequeuniversity.com/rules/axe/4.4/aria-valid-attr-value?application=RuleDescription | Ensures all ARIA attributes have valid values | Serious, Critical | cat.aria, wcag2a, wcag412 | failure, needs review | [6a7281](https://act-rules.github.io/rules/6a7281) | | ✅ | aria-valid-attr | https://dequeuniversity.com/rules/axe/4.4/aria-valid-attr?application=RuleDescription | Ensures attributes that begin with aria- are valid ARIA attributes | Critical | cat.aria, wcag2a, wcag412 | failure | [5f99a7](https://act-rules.github.io/rules/5f99a7) | | ✅ | audio-caption | https://dequeuniversity.com/rules/axe/4.4/audio-caption?application=RuleDescription | Ensures <audio> elements have captions | Critical | cat.time-and-media, wcag2a, wcag121, section508, section508.22.a | needs review | [2eb176](https://act-rules.github.io/rules/2eb176), [afb423](https://act-rules.github.io/rules/afb423) | diff --git a/src/rules/aria-tooltip-name.ts b/src/rules/aria-tooltip-name.ts new file mode 100644 index 0000000..a2ee52a --- /dev/null +++ b/src/rules/aria-tooltip-name.ts @@ -0,0 +1,48 @@ +import { AccessibilityError } from "../scanner"; +import { labelledByIsValid, querySelectorAll } from "../utils"; + +// Metadata +const id = "aria-tooltip-name"; +const text = "ARIA tooltip must have an accessible name"; +const url = `https://dequeuniversity.com/rules/axe/4.4/${id}?application=RuleDescription`; + +/** + * Make sure that a elements text is "visible" to a screenreader user. + * + * - Inner text that is discernible to screen reader users. + * - Non-empty aria-label attribute. + * - aria-labelledby pointing to element with text which is discernible to screen reader users. + */ +function hasAccessibleText(el: Element): boolean { + if (el.hasAttribute("aria-label")) { + return el.getAttribute("aria-label")!.trim() !== ""; + } + + if (!labelledByIsValid(el)) return false; + + if (el.getAttribute("title")) { + return el.getAttribute("title")!.trim() !== ""; + } + + if (el.textContent) { + return el.textContent.trim() !== ""; + } + + return true; +} + +export function ariaTooltipName(el: Element): AccessibilityError[] { + const errors = []; + const tooltips = querySelectorAll("[role=tooltip]", el); + if (el.matches("[role=tooltip]")) tooltips.push(el); + for (const tooltip of tooltips) { + if (!hasAccessibleText(tooltip)) { + errors.push({ + element: tooltip, + url, + text, + }); + } + } + return errors; +} diff --git a/tests/aria-tooltip-name.ts b/tests/aria-tooltip-name.ts new file mode 100644 index 0000000..1e8fe99 --- /dev/null +++ b/tests/aria-tooltip-name.ts @@ -0,0 +1,58 @@ +import { fixture, html, expect } from "@open-wc/testing"; +import { Scanner } from "../src/scanner"; +import { ariaTooltipName } from "../src/rules/aria-tooltip-name"; + +const scanner = new Scanner([ariaTooltipName]); + +const passes = [ + ``, + `
+ +
Hello world!
+
`, + ``, + ``, +]; + +const violations = [ + ``, + ``, + ``, + `
+ +
+
`, +]; + +describe("aria-tooltip-name", async function () { + for (const markup of passes) { + const el = await fixture(html`${markup}`); + it(el.outerHTML, async () => { + const results = (await scanner.scan(el)).map(({ text, url }) => { + return { text, url }; + }); + + expect(results).to.be.empty; + }); + } + + for await (const markup of violations) { + const el = await fixture(html`${markup}`); + it(el.outerHTML, async () => { + const results = (await scanner.scan(el)).map(({ text, url }) => { + return { text, url }; + }); + + expect(results).to.eql([ + { + text: "ARIA tooltip must have an accessible name", + url: "https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name?application=RuleDescription", + }, + ]); + }); + } +});