Skip to content

Commit 3a931e9

Browse files
Copilotkoddsson
andauthored
Add tests for aria-tooltip-name and fix hasAccessibleText logic bugs (#201)
* Initial plan * Add comprehensive tests for aria-tooltip-name and fix implementation bugs Co-authored-by: koddsson <[email protected]> * Fix export pattern to use default export for consistency Co-authored-by: koddsson <[email protected]> * Remove query parameters from URLs in aria-tooltip-name rule and tests Co-authored-by: koddsson <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: koddsson <[email protected]>
1 parent b180da7 commit 3a931e9

File tree

3 files changed

+206
-5
lines changed

3 files changed

+206
-5
lines changed

src/rules/aria-tooltip-name.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,22 @@ function hasAccessibleText(element: Element): boolean {
1818
return element.getAttribute("aria-label")!.trim() !== "";
1919
}
2020

21-
if (!labelledByIsValid(element)) return false;
21+
if (element.hasAttribute("aria-labelledby")) {
22+
return labelledByIsValid(element);
23+
}
2224

23-
if (element.getAttribute("title")) {
25+
if (element.hasAttribute("title")) {
2426
return element.getAttribute("title")!.trim() !== "";
2527
}
2628

2729
if (element.textContent) {
2830
return element.textContent.trim() !== "";
2931
}
3032

31-
return true;
33+
return false;
3234
}
3335

34-
export function ariaTooltipName(element: Element): AccessibilityError[] {
36+
export default function (element: Element): AccessibilityError[] {
3537
const errors = [];
3638
const tooltips = querySelectorAll("[role=tooltip]", element);
3739
if (element.matches("[role=tooltip]")) tooltips.push(element);

src/scanner.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import ariaRequiredChildren from "./rules/aria-required-children";
1111
import ariaRequiredParent from "./rules/aria-required-parent";
1212
import ariaRoledescription from "./rules/aria-roledescription";
1313
import ariaToggleFieldName from "./rules/aria-toggle-field-name";
14-
import { ariaTooltipName } from "./rules/aria-tooltip-name";
14+
import ariaTooltipName from "./rules/aria-tooltip-name";
1515
import ariaValidAttr from "./rules/aria-valid-attr";
1616
import { ariaValidAttrValue } from "./rules/aria-valid-attr-value";
1717
import metaViewport from "./rules/meta-viewport";

tests/aria-tooltip-name.ts

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
import { fixture, html, expect } from "@open-wc/testing";
2+
import { Scanner } from "../src/scanner";
3+
import ariaTooltipName from "../src/rules/aria-tooltip-name";
4+
5+
const scanner = new Scanner([ariaTooltipName]);
6+
7+
describe("aria-tooltip-name", function () {
8+
it("tooltip with aria-label passes", async () => {
9+
const container = await fixture(html`
10+
<div role="tooltip" aria-label="Additional information">
11+
Tooltip content
12+
</div>
13+
`);
14+
const results = await scanner.scan(container);
15+
expect(results).to.be.empty;
16+
});
17+
18+
it("tooltip with aria-labelledby passes", async () => {
19+
const container = await fixture(html`
20+
<div>
21+
<p id="tooltipLabel">Help text</p>
22+
<div role="tooltip" aria-labelledby="tooltipLabel">
23+
Tooltip content
24+
</div>
25+
</div>
26+
`);
27+
const results = await scanner.scan(container);
28+
expect(results).to.be.empty;
29+
});
30+
31+
it("tooltip with title attribute passes", async () => {
32+
const container = await fixture(html`
33+
<div role="tooltip" title="Tooltip name">
34+
Tooltip content
35+
</div>
36+
`);
37+
const results = await scanner.scan(container);
38+
expect(results).to.be.empty;
39+
});
40+
41+
it("tooltip with text content passes", async () => {
42+
const container = await fixture(html`
43+
<div role="tooltip">Helpful information</div>
44+
`);
45+
const results = await scanner.scan(container);
46+
expect(results).to.be.empty;
47+
});
48+
49+
it("tooltip without accessible name fails", async () => {
50+
const container = await fixture(html` <div role="tooltip"></div> `);
51+
const results = (await scanner.scan(container)).map(({ text, url }) => {
52+
return { text, url };
53+
});
54+
expect(results).to.eql([
55+
{
56+
text: "ARIA tooltip must have an accessible name",
57+
url: "https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name",
58+
},
59+
]);
60+
});
61+
62+
it("tooltip with empty aria-label fails", async () => {
63+
const container = await fixture(html`
64+
<div role="tooltip" aria-label=" ">Content</div>
65+
`);
66+
const results = (await scanner.scan(container)).map(({ text, url }) => {
67+
return { text, url };
68+
});
69+
expect(results).to.eql([
70+
{
71+
text: "ARIA tooltip must have an accessible name",
72+
url: "https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name",
73+
},
74+
]);
75+
});
76+
77+
it("tooltip with non-existing aria-labelledby fails", async () => {
78+
const container = await fixture(html`
79+
<div role="tooltip" aria-labelledby="non-existing-id">Content</div>
80+
`);
81+
const results = (await scanner.scan(container)).map(({ text, url }) => {
82+
return { text, url };
83+
});
84+
expect(results).to.eql([
85+
{
86+
text: "ARIA tooltip must have an accessible name",
87+
url: "https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name",
88+
},
89+
]);
90+
});
91+
92+
it("tooltip with empty title fails", async () => {
93+
const container = await fixture(html`
94+
<div role="tooltip" title=" ">Content</div>
95+
`);
96+
const results = (await scanner.scan(container)).map(({ text, url }) => {
97+
return { text, url };
98+
});
99+
expect(results).to.eql([
100+
{
101+
text: "ARIA tooltip must have an accessible name",
102+
url: "https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name",
103+
},
104+
]);
105+
});
106+
107+
it("tooltip with only whitespace content fails", async () => {
108+
const container = await fixture(html` <div role="tooltip"> </div> `);
109+
const results = (await scanner.scan(container)).map(({ text, url }) => {
110+
return { text, url };
111+
});
112+
expect(results).to.eql([
113+
{
114+
text: "ARIA tooltip must have an accessible name",
115+
url: "https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name",
116+
},
117+
]);
118+
});
119+
120+
it("multiple tooltips with mixed accessible names", async () => {
121+
const container = await fixture(html`
122+
<div>
123+
<div role="tooltip" aria-label="Valid tooltip">First tooltip</div>
124+
<div role="tooltip"></div>
125+
<div role="tooltip"></div>
126+
</div>
127+
`);
128+
const results = (await scanner.scan(container)).map(({ text, url }) => {
129+
return { text, url };
130+
});
131+
expect(results).to.have.lengthOf(2);
132+
expect(results[0]).to.eql({
133+
text: "ARIA tooltip must have an accessible name",
134+
url: "https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name",
135+
});
136+
expect(results[1]).to.eql({
137+
text: "ARIA tooltip must have an accessible name",
138+
url: "https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name",
139+
});
140+
});
141+
142+
it("nested tooltips are each checked separately", async () => {
143+
const container = await fixture(html`
144+
<div role="tooltip" aria-label="Outer tooltip">
145+
<div role="tooltip"></div>
146+
</div>
147+
`);
148+
const results = (await scanner.scan(container)).map(({ text, url }) => {
149+
return { text, url };
150+
});
151+
expect(results).to.have.lengthOf(1);
152+
expect(results[0]).to.eql({
153+
text: "ARIA tooltip must have an accessible name",
154+
url: "https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name",
155+
});
156+
});
157+
158+
it("element without tooltip role is ignored", async () => {
159+
const container = await fixture(html`<div>Regular div content</div>`);
160+
const results = await scanner.scan(container);
161+
expect(results).to.be.empty;
162+
});
163+
164+
it("tooltip with aria-labelledby pointing to empty element fails", async () => {
165+
const container = await fixture(html`
166+
<div>
167+
<p id="emptyLabel"></p>
168+
<div role="tooltip" aria-labelledby="emptyLabel">Content</div>
169+
</div>
170+
`);
171+
const results = (await scanner.scan(container)).map(({ text, url }) => {
172+
return { text, url };
173+
});
174+
expect(results).to.eql([
175+
{
176+
text: "ARIA tooltip must have an accessible name",
177+
url: "https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name",
178+
},
179+
]);
180+
});
181+
182+
it("tooltip with aria-labelledby pointing to whitespace-only element fails", async () => {
183+
const container = await fixture(html`
184+
<div>
185+
<p id="whitespaceLabel"> </p>
186+
<div role="tooltip" aria-labelledby="whitespaceLabel">Content</div>
187+
</div>
188+
`);
189+
const results = (await scanner.scan(container)).map(({ text, url }) => {
190+
return { text, url };
191+
});
192+
expect(results).to.eql([
193+
{
194+
text: "ARIA tooltip must have an accessible name",
195+
url: "https://dequeuniversity.com/rules/axe/4.4/aria-tooltip-name",
196+
},
197+
]);
198+
});
199+
});

0 commit comments

Comments
 (0)