Skip to content

Commit f63750f

Browse files
Merge pull request ember-cli#2723 from johanrd/fix/aria-orientation-undefined-token
fix(template-no-invalid-aria-attributes): absorb allowundefined handling into validateByType
2 parents ecb3aeb + 3be7dd4 commit f63750f

2 files changed

Lines changed: 79 additions & 9 deletions

File tree

lib/rules/template-no-invalid-aria-attributes.js

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,22 @@ function isNumeric(value) {
1111
return !Number.isNaN(Number(value));
1212
}
1313

14-
function isValidAriaValue(attrName, value) {
15-
const attrDef = aria.get(attrName);
16-
if (!attrDef) {
17-
return true;
18-
}
14+
// In aria-query 5.3.2, `allowundefined: true` is set only on the four
15+
// boolean-like ARIA state attributes — `aria-expanded`, `aria-hidden`,
16+
// `aria-grabbed`, `aria-selected` — whose WAI-ARIA 1.2 value tables list
17+
// the literal string `"undefined"` as a spec-valid value meaning "state
18+
// is not applicable" (e.g. https://www.w3.org/TR/wai-aria-1.2/#aria-expanded).
19+
// The flag is nominally type-agnostic, but in practice this function only
20+
// green-lights `"undefined"` for that boolean-like subset; no non-boolean
21+
// ARIA attribute in aria-query currently sets `allowundefined`.
22+
function allowsUndefinedLiteral(attrDef, value) {
23+
return value === 'undefined' && Boolean(attrDef.allowundefined);
24+
}
1925

20-
if (value === 'undefined') {
21-
return Boolean(attrDef.allowundefined);
26+
function validateByType(attrDef, value) {
27+
if (allowsUndefinedLiteral(attrDef, value)) {
28+
return true;
2229
}
23-
2430
switch (attrDef.type) {
2531
case 'boolean': {
2632
return isBoolean(value);
@@ -45,7 +51,9 @@ function isValidAriaValue(attrName, value) {
4551
return isNumeric(value) && !isBoolean(value);
4652
}
4753
case 'token': {
48-
// aria-query stores boolean values as actual booleans, convert for comparison
54+
// aria-query stores boolean values as actual booleans; stringify for comparison.
55+
// The string literal 'undefined' that appears in some values arrays (e.g.
56+
// aria-orientation) passes through this check naturally — no special-casing.
4957
const permittedValues = attrDef.values.map((v) =>
5058
typeof v === 'boolean' ? v.toString() : v
5159
);
@@ -60,6 +68,14 @@ function isValidAriaValue(attrName, value) {
6068
}
6169
}
6270

71+
function isValidAriaValue(attrName, value) {
72+
const attrDef = aria.get(attrName);
73+
if (!attrDef) {
74+
return true;
75+
}
76+
return validateByType(attrDef, value);
77+
}
78+
6379
function getExpectedTypeDescription(attrName) {
6480
const attrDef = aria.get(attrName);
6581
if (!attrDef) {

tests/lib/rules/template-no-invalid-aria-attributes.js

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,24 @@ ruleTester.run('template-no-invalid-aria-attributes', rule, {
3131
'<template><CustomComponent @ariaRequired={{this.ariaRequired}} aria-errormessage="errorId" /></template>',
3232
'<template><button type="submit" aria-disabled={{this.isDisabled}}>Submit</button></template>',
3333
'<template><div role="textbox" aria-sort={{if this.hasCustomSort "other" "ascending"}}></div></template>',
34+
// Boolean-type attributes with allowundefined: true per aria-query — the
35+
// string "undefined" is spec-valid (WAI-ARIA 1.2 value tables for these
36+
// attrs list true/false/undefined). All 4 share the same code path.
3437
'<template><div role="combobox" aria-expanded="undefined"></div></template>',
38+
'<template><div aria-hidden="undefined"></div></template>',
39+
'<template><div aria-grabbed="undefined" draggable="true"></div></template>',
40+
'<template><div role="option" aria-selected="undefined"></div></template>',
41+
42+
// Token-type aria-orientation lists "undefined" in its values array;
43+
// passes the natural token check (no special-casing needed).
44+
'<template><div role="slider" aria-orientation="undefined"></div></template>',
45+
'<template><div role="slider" aria-orientation="horizontal"></div></template>',
46+
47+
// aria-pressed is tristate WITHOUT allowundefined — string "undefined"
48+
// is NOT accepted. Explicit valid values still work.
49+
'<template><button aria-pressed="true">Toggle</button></template>',
50+
'<template><button aria-pressed="false">Toggle</button></template>',
51+
'<template><button aria-pressed="mixed">Toggle</button></template>',
3552
'<template><button aria-label={{if @isNew (t "actions.add") (t "actions.edit")}}></button></template>',
3653
],
3754
invalid: [
@@ -121,6 +138,18 @@ ruleTester.run('template-no-invalid-aria-attributes', rule, {
121138
output: null,
122139
errors: [{ messageId: 'invalidAriaAttributeValue' }],
123140
},
141+
{
142+
code: '<template><div role="slider" aria-orientation="sideways"></div></template>',
143+
output: null,
144+
errors: [{ messageId: 'invalidAriaAttributeValue' }],
145+
},
146+
// aria-pressed is tristate WITHOUT allowundefined — string "undefined"
147+
// is spec-invalid here (aria-query doesn't mark it allowundefined).
148+
{
149+
code: '<template><button aria-pressed="undefined">Toggle</button></template>',
150+
output: null,
151+
errors: [{ messageId: 'invalidAriaAttributeValue' }],
152+
},
124153
],
125154
});
126155

@@ -149,7 +178,21 @@ hbsRuleTester.run('template-no-invalid-aria-attributes', rule, {
149178
'<CustomComponent @ariaRequired={{this.ariaRequired}} aria-errormessage="errorId" />',
150179
'<button type="submit" aria-disabled={{this.isDisabled}}>Submit</button>',
151180
'<div role="textbox" aria-sort={{if this.hasCustomSort "other" "ascending"}}></div>',
181+
// Boolean-type attrs with allowundefined (spec-valid "undefined" literal):
152182
'<div role="combobox" aria-expanded="undefined"></div>',
183+
'<div aria-hidden="undefined"></div>',
184+
'<div aria-grabbed="undefined" draggable="true"></div>',
185+
'<div role="option" aria-selected="undefined"></div>',
186+
187+
// Token-type aria-orientation — "undefined" passes via values list:
188+
'<div role="slider" aria-orientation="undefined"></div>',
189+
'<div role="slider" aria-orientation="horizontal"></div>',
190+
191+
// aria-pressed is tristate WITHOUT allowundefined; valid values:
192+
'<button aria-pressed="true">Toggle</button>',
193+
'<button aria-pressed="false">Toggle</button>',
194+
'<button aria-pressed="mixed">Toggle</button>',
195+
153196
'<button aria-label={{if @isNew (t "actions.add") (t "actions.edit")}}></button>',
154197
],
155198
invalid: [
@@ -223,5 +266,16 @@ hbsRuleTester.run('template-no-invalid-aria-attributes', rule, {
223266
output: null,
224267
errors: [{ messageId: 'invalidAriaAttributeValue' }],
225268
},
269+
{
270+
code: '<div role="slider" aria-orientation="sideways"></div>',
271+
output: null,
272+
errors: [{ messageId: 'invalidAriaAttributeValue' }],
273+
},
274+
// aria-pressed has no allowundefined — "undefined" is spec-invalid here.
275+
{
276+
code: '<button aria-pressed="undefined">Toggle</button>',
277+
output: null,
278+
errors: [{ messageId: 'invalidAriaAttributeValue' }],
279+
},
226280
],
227281
});

0 commit comments

Comments
 (0)