Skip to content

Commit

Permalink
- expanded applicability to include many more type="" attribute value…
Browse files Browse the repository at this point in the history
…s. inspired act-rules/act-rules.github.io#2123 (comment)

- added corresponding unit tests.  they pass.
  • Loading branch information
dan-tripp-siteimprove committed Aug 13, 2024
1 parent 0b753b7 commit 27fbddc
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 32 deletions.
27 changes: 14 additions & 13 deletions packages/alfa-rules/src/sia-r8/rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ export default Rule.Atomic.of<Page, Element>({
"switch",
"textbox",
),
and(hasName("input"), hasInputType("password")),
and(hasName("input"), or(hasInputType("password"), hasInputType("color"),
hasInputType("date"), hasInputType("datetime-local"), hasInputType("file"),
hasInputType("month"), hasInputType("time"), hasInputType("week"))),
),
isIncludedInTheAccessibilityTree(device),
),
Expand All @@ -57,16 +59,17 @@ export default Rule.Atomic.of<Page, Element>({
return {
1: expectation(
hasNonEmptyAccessibleName(device)(target),
() => Outcomes.HasName(roleName),
() => Outcomes.HasNoName(roleName),
() => Outcomes.FormFieldWithAriaRoleHasName(roleName),
() => Outcomes.FormFieldWithAriaRoleHasNoName(roleName),
),
};
} else {
const type = target.attribute("type").map(attr => attr.value).getOr("");
return {
1: expectation(
hasNonEmptyAccessibleName(device)(target),
() => Outcomes.InputPasswordElementHasName(),
() => Outcomes.InputPasswordElementHasNoName(),
() => Outcomes.InputElementWithNoAriaRoleHasName(type),
() => Outcomes.InputElementWithNoAriaRoleHasNoName(type),
),
};
}
Expand All @@ -79,19 +82,17 @@ export default Rule.Atomic.of<Page, Element>({
* @public
*/
export namespace Outcomes {
export const HasName = (role: Role.Name) =>
export const FormFieldWithAriaRoleHasName = (role: Role.Name) =>
Ok.of(WithRole.of(`The form field has an accessible name`, role));

export const HasNoName = (role: Role.Name) =>
export const FormFieldWithAriaRoleHasNoName = (role: Role.Name) =>
Err.of(
WithRole.of(`The form field does not have an accessible name`, role),
);

export const InputPasswordElementHasName = () =>
Ok.of(Diagnostic.of(`The password form field has an accessible name`));
export const InputElementWithNoAriaRoleHasName = (typeAttribValue: string) =>
Ok.of(Diagnostic.of(`The type="${typeAttribValue}" form field has an accessible name`));

export const InputPasswordElementHasNoName = () =>
Err.of(
Diagnostic.of(`The password form field does not have an accessible name`),
);
export const InputElementWithNoAriaRoleHasNoName = (typeAttribValue: string) =>
Err.of(Diagnostic.of(`The type="${typeAttribValue}" form field does not have an accessible name`));
}
65 changes: 46 additions & 19 deletions packages/alfa-rules/test/sia-r8/rule.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ test("evaluate() passes an input element with implicit label", async (t) => {

t.deepEqual(await evaluate(R8, { document }), [
passed(R8, target, {
1: Outcomes.HasName("textbox"),
1: Outcomes.FormFieldWithAriaRoleHasName("textbox"),
}),
]);
});
Expand All @@ -32,7 +32,7 @@ test("evaluate() passes an input element with aria-label", async (t) => {

t.deepEqual(await evaluate(R8, { document }), [
passed(R8, target, {
1: Outcomes.HasName("textbox"),
1: Outcomes.FormFieldWithAriaRoleHasName("textbox"),
}),
]);
});
Expand All @@ -53,7 +53,7 @@ test("evaluate() passes a select element with explicit label", async (t) => {

t.deepEqual(await evaluate(R8, { document }), [
passed(R8, target, {
1: Outcomes.HasName("listbox"),
1: Outcomes.FormFieldWithAriaRoleHasName("listbox"),
}),
]);
});
Expand All @@ -67,7 +67,7 @@ test("evaluate() passes a textarea element with aria-labelledby", async (t) => {

t.deepEqual(await evaluate(R8, { document }), [
passed(R8, target, {
1: Outcomes.HasName("textbox"),
1: Outcomes.FormFieldWithAriaRoleHasName("textbox"),
}),
]);
});
Expand All @@ -81,7 +81,7 @@ test("evaluate() passes a input element with placeholder attribute", async (t) =

t.deepEqual(await evaluate(R8, { document }), [
passed(R8, target, {
1: Outcomes.HasName("textbox"),
1: Outcomes.FormFieldWithAriaRoleHasName("textbox"),
}),
]);
});
Expand All @@ -100,7 +100,7 @@ test(`evaluate() passes a div element with explicit combobox role and an

t.deepEqual(await evaluate(R8, { document }), [
passed(R8, target, {
1: Outcomes.HasName(role),
1: Outcomes.FormFieldWithAriaRoleHasName(role),
}),
]);
});
Expand All @@ -112,7 +112,7 @@ test("evaluate() fails a input element without accessible name", async (t) => {

t.deepEqual(await evaluate(R8, { document }), [
failed(R8, target, {
1: Outcomes.HasNoName("textbox"),
1: Outcomes.FormFieldWithAriaRoleHasNoName("textbox"),
}),
]);
});
Expand All @@ -124,7 +124,7 @@ test("evaluate() fails a input element with empty aria-label", async (t) => {

t.deepEqual(await evaluate(R8, { document }), [
failed(R8, target, {
1: Outcomes.HasNoName("textbox"),
1: Outcomes.FormFieldWithAriaRoleHasNoName("textbox"),
}),
]);
});
Expand All @@ -143,7 +143,7 @@ test(`evaluate() fails a select element with aria-labelledby pointing to an

t.deepEqual(await evaluate(R8, { document }), [
failed(R8, target, {
1: Outcomes.HasNoName("listbox"),
1: Outcomes.FormFieldWithAriaRoleHasNoName("listbox"),
}),
]);
});
Expand All @@ -163,7 +163,7 @@ test("evaluate() fails a textbox with no accessible name", async (t) => {

t.deepEqual(await evaluate(R8, { document }), [
failed(R8, target, {
1: Outcomes.HasNoName(role),
1: Outcomes.FormFieldWithAriaRoleHasNoName(role),
}),
]);
});
Expand Down Expand Up @@ -206,7 +206,7 @@ test(`evaluate() fails an input element with type=password which is disabled

t.deepEqual(await evaluate(R8, { document }), [
failed(R8, target, {
1: Outcomes.InputPasswordElementHasNoName(),
1: Outcomes.InputElementWithNoAriaRoleHasNoName("password"),
}),
]);
});
Expand All @@ -225,7 +225,7 @@ test("evaluate() passes an input element with type=password and implicit label",

t.deepEqual(await evaluate(R8, { document }), [
passed(R8, target, {
1: Outcomes.InputPasswordElementHasName(),
1: Outcomes.InputElementWithNoAriaRoleHasName("password"),
}),
]);
});
Expand All @@ -237,7 +237,7 @@ test("evaluate() passes an input element with type=password and aria-label", asy

t.deepEqual(await evaluate(R8, { document }), [
passed(R8, target, {
1: Outcomes.InputPasswordElementHasName(),
1: Outcomes.InputElementWithNoAriaRoleHasName("password"),
}),
]);
});
Expand All @@ -251,7 +251,7 @@ test("evaluate() passes an input element with type=password and explicit label",

t.deepEqual(await evaluate(R8, { document }), [
passed(R8, target, {
1: Outcomes.InputPasswordElementHasName(),
1: Outcomes.InputElementWithNoAriaRoleHasName("password"),
}),
]);
});
Expand All @@ -265,7 +265,7 @@ test("evaluate() passes an input element with type=password and aria-labelledby"

t.deepEqual(await evaluate(R8, { document }), [
passed(R8, target, {
1: Outcomes.InputPasswordElementHasName(),
1: Outcomes.InputElementWithNoAriaRoleHasName("password"),
}),
]);
});
Expand All @@ -277,7 +277,7 @@ test("evaluate() passes an input element with type=password and placeholder attr

t.deepEqual(await evaluate(R8, { document }), [
passed(R8, target, {
1: Outcomes.InputPasswordElementHasName(),
1: Outcomes.InputElementWithNoAriaRoleHasName("password"),
}),
]);
});
Expand All @@ -289,7 +289,7 @@ test("evaluate() fails an input element with type=password and empty aria-label"

t.deepEqual(await evaluate(R8, { document }), [
failed(R8, target, {
1: Outcomes.InputPasswordElementHasNoName(),
1: Outcomes.InputElementWithNoAriaRoleHasNoName("password"),
}),
]);
});
Expand All @@ -304,7 +304,7 @@ test(`evaluate() fails an input element with type=password and aria-labelledby p

t.deepEqual(await evaluate(R8, { document }), [
failed(R8, target, {
1: Outcomes.InputPasswordElementHasNoName(),
1: Outcomes.InputElementWithNoAriaRoleHasNoName("password"),
}),
]);
});
Expand All @@ -325,4 +325,31 @@ test(`evaluate() is inapplicable for an element with type=password and which
const document = h.document([target]);

t.deepEqual(await evaluate(R8, { document }), [inapplicable(R8)]);
});
});

test(`evaluate() fails for input elements with various types which give it no ARIA
role and which have no accessible name`, async (t) => {
const targets = [<input type="color"/>, <input type="date"/>, <input type="datetime-local"/>,
<input type="file"/>, <input type="month"/>, <input type="time"/>,
<input type="week"/>];
const document = h.document(targets)
t.deepEqual(await evaluate(R8, { document }),
targets.map(target => failed(R8, target, {
1: Outcomes.InputElementWithNoAriaRoleHasNoName(target.attribute("type")
.map(attr => attr.value).getOr("")) })),
);
});

test(`evaluate() passes for input elements with various types which give it no ARIA
role and which have an aria-label`, async (t) => {
const targets = [<input type="color" aria-label="x"/>, <input type="date" aria-label="x"/>,
<input type="datetime-local" aria-label="x"/>, <input type="file" aria-label="x"/>,
<input type="month" aria-label="x"/>, <input type="time" aria-label="x"/>,
<input type="week" aria-label="x"/>];
const document = h.document(targets)
t.deepEqual(await evaluate(R8, { document }),
targets.map(target => passed(R8, target, {
1: Outcomes.InputElementWithNoAriaRoleHasName(target.attribute("type")
.map(attr => attr.value).getOr("")) })),
);
});

0 comments on commit 27fbddc

Please sign in to comment.