diff --git a/packages/alfa-rules/src/rules.ts b/packages/alfa-rules/src/rules.ts index 19730e49b7..d0bb8580fe 100644 --- a/packages/alfa-rules/src/rules.ts +++ b/packages/alfa-rules/src/rules.ts @@ -49,6 +49,7 @@ export { default as R48 } from "./sia-r48/rule"; export { default as R49 } from "./sia-r49/rule"; export { default as R50 } from "./sia-r50/rule"; export { default as R53 } from "./sia-r53/rule"; +export { default as R54 } from "./sia-r54/rule"; export { default as R55 } from "./sia-r55/rule"; export { default as R56 } from "./sia-r56/rule"; export { default as R57 } from "./sia-r57/rule"; diff --git a/packages/alfa-rules/src/sia-r54/rule.ts b/packages/alfa-rules/src/sia-r54/rule.ts new file mode 100644 index 0000000000..6f9976947e --- /dev/null +++ b/packages/alfa-rules/src/sia-r54/rule.ts @@ -0,0 +1,54 @@ +import { Rule, Diagnostic } from "@siteimprove/alfa-act"; +import { Node } from "@siteimprove/alfa-aria"; +import { Element } from "@siteimprove/alfa-dom"; +import { Predicate } from "@siteimprove/alfa-predicate"; +import { Err, Ok } from "@siteimprove/alfa-result"; +import { Page } from "@siteimprove/alfa-web"; + +import { isIgnored } from "../common/predicate"; +import { expectation } from "../common/expectation"; + +const { and, not } = Predicate; +const { isElement } = Element; + +export default Rule.Atomic.of({ + uri: "https://alfa.siteimprove.com/rules/sia-r54", + evaluate({ device, document }) { + return { + applicability() { + return document + .descendants({ flattened: true, nested: true }) + .filter(isElement) + .filter( + and(not(isIgnored(device)), (element) => + Node.from(element, device) + .attribute("aria-live") + .some((attribute) => attribute.value === "assertive") + ) + ); + }, + + expectations(target) { + return { + 1: expectation( + Node.from(target, device) + .attribute("aria-atomic") + .some((attribute) => attribute.value === "true"), + () => Outcomes.IsAtomic, + () => Outcomes.IsNotAtomic + ), + }; + }, + }; + }, +}); + +export namespace Outcomes { + export const IsAtomic = Ok.of( + Diagnostic.of("The assertive region is atomic") + ); + + export const IsNotAtomic = Err.of( + Diagnostic.of("The assertive region is not atomic") + ); +} diff --git a/packages/alfa-rules/test/sia-r54/rule.spec.tsx b/packages/alfa-rules/test/sia-r54/rule.spec.tsx new file mode 100644 index 0000000000..952215a25e --- /dev/null +++ b/packages/alfa-rules/test/sia-r54/rule.spec.tsx @@ -0,0 +1,72 @@ +import { test } from "@siteimprove/alfa-test"; + +import { h } from "@siteimprove/alfa-dom"; + +import R54, { Outcomes } from "../../src/sia-r54/rule"; + +import { evaluate } from "../common/evaluate"; +import { passed, failed, inapplicable } from "../common/outcome"; + +test("evaluate() passes an assertive and atomic element", async (t) => { + const target = ( +
+ Some words +
+ ); + + const document = h.document([target]); + + t.deepEqual(await evaluate(R54, { document }), [ + passed(R54, target, { + 1: Outcomes.IsAtomic, + }), + ]); +}); + +test("evaluate() fails an element which is assertive but not atomic", async (t) => { + const target =
Some words
; + + const document = h.document([target]); + + t.deepEqual(await evaluate(R54, { document }), [ + failed(R54, target, { + 1: Outcomes.IsNotAtomic, + }), + ]); +}); + +test("evaluate() fails an assertive element with an incorrect aria-atomic attribute", async (t) => { + const target = ( +
+ Some words +
+ ); + + const document = h.document([target]); + + t.deepEqual(await evaluate(R54, { document }), [ + failed(R54, target, { + 1: Outcomes.IsNotAtomic, + }), + ]); +}); + +test("evaluate() is inapplicable to elements that are not in the accessibility tree", async (t) => { + const target = ( + + ); + + const document = h.document([target]); + + t.deepEqual(await evaluate(R54, { document }), [inapplicable(R54)]); +}); + +test("evaluate() is inapplicable to an element which is not assertive", async (t) => { + const target =
Some words
; + + const document = h.document([target]); + + t.deepEqual(await evaluate(R54, { document }), [inapplicable(R54)]); +}); diff --git a/packages/alfa-rules/tsconfig.json b/packages/alfa-rules/tsconfig.json index 49a1867d77..3021cde04d 100644 --- a/packages/alfa-rules/tsconfig.json +++ b/packages/alfa-rules/tsconfig.json @@ -113,6 +113,7 @@ "src/sia-r5/rule.ts", "src/sia-r50/rule.ts", "src/sia-r53/rule.ts", + "src/sia-r54/rule.ts", "src/sia-r55/rule.ts", "src/sia-r56/rule.ts", "src/sia-r57/rule.ts", @@ -185,6 +186,7 @@ "test/sia-r45/rule.spec.tsx", "test/sia-r46/rule.spec.tsx", "test/sia-r53/rule.spec.tsx", + "test/sia-r54/rule.spec.tsx", "test/sia-r55/rule.spec.tsx", "test/sia-r56/rule.spec.tsx", "test/sia-r57/rule.spec.tsx",