Skip to content

Commit cb08476

Browse files
committed
test: add Phase 3 audit fixture translating alt-text peer cases
Translates 41 cases from peer-plugin rules: - jsx-a11y alt-text - vuejs-accessibility alt-text - lit-a11y alt-text Fixture documents parity after this fix: - Empty-string aria-label/aria-labelledby on <object>, <area>, and <input type=image> is now flagged (reusing existing objectMissing / areaMissing / inputImage messageIds). Remaining divergences (<img alt role=presentation> accepting non-empty alt in jsx-a11y, <img aria-label> without alt) are annotated inline.
1 parent 7488d8d commit cb08476

1 file changed

Lines changed: 190 additions & 0 deletions

File tree

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Audit fixture — peer-plugin parity for `ember/template-require-valid-alt-text`.
2+
// See docs/audit-a11y-behavior.md for the summary of divergences.
3+
//
4+
// Source files:
5+
// - context/eslint-plugin-jsx-a11y-main/__tests__/src/rules/alt-text-test.js
6+
// - context/eslint-plugin-vuejs-accessibility-main/src/rules/__tests__/alt-text.test.ts
7+
// - context/eslint-plugin-lit-a11y/tests/lib/rules/alt-text.js
8+
9+
'use strict';
10+
11+
const rule = require('../../../lib/rules/template-require-valid-alt-text');
12+
const RuleTester = require('eslint').RuleTester;
13+
14+
const ruleTester = new RuleTester({
15+
parser: require.resolve('ember-eslint-parser'),
16+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
17+
});
18+
19+
ruleTester.run('audit:alt-text (gts)', rule, {
20+
valid: [
21+
// === Upstream parity (valid in jsx-a11y + ours) ===
22+
'<template><img alt="foo" /></template>',
23+
'<template><img alt="" /></template>',
24+
'<template><img alt=" " /></template>',
25+
'<template><img alt="" role="presentation" /></template>',
26+
'<template><img alt="" role="none" /></template>',
27+
// DIVERGENCE — moved to invalid below:
28+
// '<template><img alt="this is lit..." role="presentation" /></template>',
29+
'<template><img alt={{@dynamicAlt}} /></template>',
30+
// object with label/children
31+
'<template><object aria-label="foo" /></template>',
32+
'<template><object aria-labelledby="id1" /></template>',
33+
'<template><object>Foo</object></template>',
34+
'<template><object title="An object" /></template>',
35+
// area with label
36+
'<template><area aria-label="foo" /></template>',
37+
'<template><area aria-labelledby="id1" /></template>',
38+
'<template><area alt="foo" /></template>',
39+
// input[type=image]
40+
'<template><input type="image" alt="foo" /></template>',
41+
'<template><input type="image" aria-label="foo" /></template>',
42+
'<template><input type="image" aria-labelledby="id1" /></template>',
43+
44+
// === DIVERGENCE — aria-label/aria-labelledby on <img> without alt ===
45+
// jsx-a11y: VALID — `<img aria-label="foo" />` is accepted.
46+
// vue-a11y: VALID — same.
47+
// Our rule: INVALID — requires `alt` attribute on <img>, full stop.
48+
// Spec reading: the HTML spec mandates alt on <img>. WAI-ARIA accepts
49+
// aria-label/aria-labelledby as alternative accessible-name sources. The
50+
// two specs disagree; we side with HTML-strict.
51+
// No valid test here — we flag; see invalid section.
52+
53+
// === Edge cases we handle ===
54+
// alt === src (we flag)
55+
// numeric alt (we flag)
56+
// redundant words (we flag)
57+
],
58+
invalid: [
59+
// === Upstream parity (invalid in jsx-a11y + ours) ===
60+
{
61+
code: '<template><img /></template>',
62+
output: null,
63+
errors: [{ messageId: 'imgMissing' }],
64+
},
65+
{
66+
code: '<template><input type="image" /></template>',
67+
output: null,
68+
errors: [{ messageId: 'inputImage' }],
69+
},
70+
{
71+
code: '<template><object /></template>',
72+
output: null,
73+
errors: [{ messageId: 'objectMissing' }],
74+
},
75+
{
76+
code: '<template><area /></template>',
77+
output: null,
78+
errors: [{ messageId: 'areaMissing' }],
79+
},
80+
81+
// === DIVERGENCE — <img aria-label> without alt ===
82+
// jsx-a11y: VALID. Ours: INVALID (imgMissing).
83+
// Behavior captured here; potential false positive per WAI-ARIA.
84+
{
85+
code: '<template><img aria-label="foo" /></template>',
86+
output: null,
87+
errors: [{ messageId: 'imgMissing' }],
88+
},
89+
{
90+
code: '<template><img aria-labelledby="id1" /></template>',
91+
output: null,
92+
errors: [{ messageId: 'imgMissing' }],
93+
},
94+
95+
// === DIVERGENCE — non-empty alt with role=presentation on img ===
96+
// jsx-a11y: VALID — accepts `<img alt="this is lit..." role="presentation" />`.
97+
// Ours: INVALID — imgRolePresentation. We're spec-strict: if role is
98+
// "none"/"presentation", the image is decorative and alt should be empty.
99+
{
100+
code: '<template><img alt="this is lit..." role="presentation" /></template>',
101+
output: null,
102+
errors: [{ messageId: 'imgRolePresentation' }],
103+
},
104+
105+
// === Parity — empty-string aria-label/aria-labelledby ===
106+
// jsx-a11y / vuejs-accessibility flag empty-string fallbacks on the
107+
// "accessible-name-required" elements (<object>, <area>, <input
108+
// type=image>). Our rule now reuses the existing messageIds.
109+
{
110+
code: '<template><object aria-label="" /></template>',
111+
output: null,
112+
errors: [{ messageId: 'objectMissing' }],
113+
},
114+
{
115+
code: '<template><object aria-labelledby="" /></template>',
116+
output: null,
117+
errors: [{ messageId: 'objectMissing' }],
118+
},
119+
{
120+
code: '<template><area aria-label="" /></template>',
121+
output: null,
122+
errors: [{ messageId: 'areaMissing' }],
123+
},
124+
{
125+
code: '<template><area aria-labelledby="" /></template>',
126+
output: null,
127+
errors: [{ messageId: 'areaMissing' }],
128+
},
129+
{
130+
code: '<template><input type="image" aria-label="" /></template>',
131+
output: null,
132+
errors: [{ messageId: 'inputImage' }],
133+
},
134+
{
135+
code: '<template><input type="image" aria-labelledby="" /></template>',
136+
output: null,
137+
errors: [{ messageId: 'inputImage' }],
138+
},
139+
],
140+
});
141+
142+
const hbsRuleTester = new RuleTester({
143+
parser: require.resolve('ember-eslint-parser/hbs'),
144+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
145+
});
146+
147+
hbsRuleTester.run('audit:alt-text (hbs)', rule, {
148+
valid: [
149+
'<img alt="foo" />',
150+
'<img alt="" />',
151+
'<img alt="" role="presentation" />',
152+
'<object aria-label="foo" />',
153+
'<area aria-label="foo" />',
154+
'<input type="image" aria-label="foo" />',
155+
],
156+
invalid: [
157+
{
158+
code: '<img />',
159+
output: null,
160+
errors: [{ messageId: 'imgMissing' }],
161+
},
162+
{
163+
code: '<input type="image" />',
164+
output: null,
165+
errors: [{ messageId: 'inputImage' }],
166+
},
167+
{
168+
code: '<object />',
169+
output: null,
170+
errors: [{ messageId: 'objectMissing' }],
171+
},
172+
{
173+
code: '<area />',
174+
output: null,
175+
errors: [{ messageId: 'areaMissing' }],
176+
},
177+
// DIVERGENCE captured — we flag img-with-aria-label (jsx-a11y/vue-a11y don't)
178+
{
179+
code: '<img aria-label="foo" />',
180+
output: null,
181+
errors: [{ messageId: 'imgMissing' }],
182+
},
183+
// Parity — empty-string label on accessible-name-required elements.
184+
{
185+
code: '<object aria-label="" />',
186+
output: null,
187+
errors: [{ messageId: 'objectMissing' }],
188+
},
189+
],
190+
});

0 commit comments

Comments
 (0)