Skip to content

Commit

Permalink
Implement SIA-R70 (#870)
Browse files Browse the repository at this point in the history
* Copy implementation of R70

* Update documentation

* Comments implemented

* Update alfa-rules.api.md

* Update rule.ts

* Update alfa-rules.api.md

Co-authored-by: elenamongelli <[email protected]>
  • Loading branch information
Jym77 and elenamongelli authored Aug 24, 2021
1 parent 3ea3457 commit 170bb71
Show file tree
Hide file tree
Showing 5 changed files with 258 additions and 7 deletions.
17 changes: 10 additions & 7 deletions docs/review/api/alfa-rules.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ const _default_65: Rule.Atomic<Page, Element, never, Element>;
const _default_66: Rule.Atomic<Page, Text, Question, Text>;

// @public (undocumented)
const _default_67: Rule.Atomic<Page, Element, never, Element>;
const _default_67: Rule.Atomic<Page, Document, never, Document>;

// @public (undocumented)
const _default_68: Rule.Atomic<Page, Element, never, Element>;
Expand All @@ -237,16 +237,16 @@ const _default_72: Rule.Atomic<Page, Element, never, Element>;
const _default_73: Rule.Atomic<Page, Element, never, Element>;

// @public (undocumented)
const _default_74: Rule.Atomic<Page, Group<Element>, Question, Group<Element>>;
const _default_74: Rule.Atomic<Page, Element, never, Element>;

// @public (undocumented)
const _default_75: Rule.Atomic<Page, Element, Question, Node>;
const _default_75: Rule.Atomic<Page, Group<Element>, Question, Group<Element>>;

// @public (undocumented)
const _default_76: Rule.Atomic<Page, Text, never, Text>;
const _default_76: Rule.Atomic<Page, Element, Question, Node>;

// @public (undocumented)
const _default_77: Rule.Atomic<Page, Element, never, Element>;
const _default_77: Rule.Atomic<Page, Text, never, Text>;

// @public (undocumented)
const _default_78: Rule.Atomic<Page, Element, never, Element>;
Expand All @@ -258,10 +258,10 @@ const _default_79: Rule.Atomic<Page, Element, never, Element>;
const _default_8: Rule.Atomic<Page, Element, never, Element>;

// @public (undocumented)
const _default_80: Rule.Atomic<Page, Document, Question, Document>;
const _default_80: Rule.Atomic<Page, Element, never, Element>;

// @public (undocumented)
const _default_81: Rule.Atomic<Page, Element, never, Element>;
const _default_81: Rule.Atomic<Page, Document, Question, Document>;

// @public (undocumented)
const _default_82: Rule.Atomic<Page, Element, never, Element>;
Expand All @@ -281,6 +281,9 @@ const _default_86: Rule.Atomic<Page, Element, never, Element>;
// @public (undocumented)
const _default_87: Rule.Atomic<Page, Element, never, Element>;

// @public (undocumented)
const _default_88: Rule.Atomic<Page, Element, never, Element>;

// @public (undocumented)
const _default_9: Rule.Atomic<Page, Element, never, Element>;

Expand Down
1 change: 1 addition & 0 deletions packages/alfa-rules/src/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ export { default as R66 } from "./sia-r66/rule";
export { default as R67 } from "./sia-r67/rule";
export { default as R68 } from "./sia-r68/rule";
export { default as R69 } from "./sia-r69/rule";
export { default as R70 } from "./sia-r70/rule";
export { default as R71 } from "./sia-r71/rule";
export { default as R72 } from "./sia-r72/rule";
export { default as R73 } from "./sia-r73/rule";
Expand Down
138 changes: 138 additions & 0 deletions packages/alfa-rules/src/sia-r70/rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { Rule, Diagnostic } from "@siteimprove/alfa-act";
import { Array } from "@siteimprove/alfa-array";
import { Document, Element, Namespace } from "@siteimprove/alfa-dom";
import { Iterable } from "@siteimprove/alfa-iterable";
import { Predicate } from "@siteimprove/alfa-predicate";
import { Err, Ok } from "@siteimprove/alfa-result";
import { Page } from "@siteimprove/alfa-web";

import { expectation } from "../common/expectation";
import { hasChild, isRendered, isDocumentElement } from "../common/predicate";

const { isElement, hasName, hasNamespace } = Element;
const { and, test } = Predicate;

const isDeprecated = hasName(
"acronym",
"applet",
"basefont",
"bgsound",
"big",
"blink",
"center",
"content",
"dir",
"font",
"frame",
"frameset",
"hgroup",
"image",
"keygen",
"marquee",
"menuitem",
"nobr",
"noembed",
"noframes",
"plaintext",
"rb",
"rtc",
"shadow",
"spacer",
"strike",
"tt",
"xmp"
);

export default Rule.Atomic.of<Page, Document>({
uri: "https://alfa.siteimprove.com/rules/sia-r70",
evaluate({ device, document }) {
return {
applicability() {
return test(hasChild(isDocumentElement), document) ? [document] : [];
},

expectations(target) {
const deprecatedElements = target
.descendants({ flattened: true, nested: true })
.filter(isElement)
.filter(
and(hasNamespace(Namespace.HTML), isDeprecated, isRendered(device))
);

return {
1: expectation(
deprecatedElements.isEmpty(),
() => Outcomes.HasNoDeprecatedElement,
() => Outcomes.HasDeprecatedElements(deprecatedElements)
),
};
},
};
},
});

export namespace Outcomes {
export const HasNoDeprecatedElement = Ok.of(
Diagnostic.of(`The document doesn't contain any deprecated elements`)
);

export const HasDeprecatedElements = (errors: Iterable<Element>) =>
Err.of(
DeprecatedElements.of(`The document contains deprecated elements`, errors)
);
}

class DeprecatedElements extends Diagnostic implements Iterable<Element> {
public static of(
message: string,
errors: Iterable<Element> = []
): DeprecatedElements {
return new DeprecatedElements(message, Array.from(errors));
}

private _errors: ReadonlyArray<Element>;

private constructor(message: string, errors: Array<Element>) {
super(message);
this._errors = errors;
}

public get errors(): ReadonlyArray<Element> {
return this._errors;
}

public equals(value: DeprecatedElements): boolean;

public equals(value: unknown): value is this;

public equals(value: unknown): boolean {
return (
value instanceof DeprecatedElements &&
value._message === this._message &&
Array.equals(value._errors, this._errors)
);
}

public *[Symbol.iterator](): Iterator<Element> {
yield* this._errors;
}

public toJSON(): DeprecatedElements.JSON {
return {
...super.toJSON(),
errors: this._errors.map((element) => element.path()),
};
}
}

namespace DeprecatedElements {
export interface JSON extends Diagnostic.JSON {
errors: Array<string>;
}

export function isDeprecatedElements(
value: unknown
): value is DeprecatedElements {
return value instanceof DeprecatedElements;
}
}
107 changes: 107 additions & 0 deletions packages/alfa-rules/test/sia-r70/rule.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { h } from "@siteimprove/alfa-dom";
import { test } from "@siteimprove/alfa-test";

import R70, { Outcomes } from "../../src/sia-r70/rule";

import { evaluate } from "../common/evaluate";
import { passed, failed, inapplicable } from "../common/outcome";

test("evaluate() passes a page with no deprecated / obsolete elements ", async (t) => {
const target = (
<html>
<p>Lorem ipsum.</p>
</html>
);

const document = h.document([target]);

t.deepEqual(await evaluate(R70, { document }), [
passed(R70, document, {
1: Outcomes.HasNoDeprecatedElement,
}),
]);
});

test("evaluate() passes a page with a deprecated but not rendered element", async (t) => {
const target = (
<html>
<p>
Lorem <blink hidden>ipsum</blink>
</p>
</html>
);

const document = h.document([target]);

t.deepEqual(await evaluate(R70, { document }), [
passed(R70, document, {
1: Outcomes.HasNoDeprecatedElement,
}),
]);
});

test("evaluate() fails a page with deprecated and rendered element", async (t) => {
const blink = <blink>not</blink>;
const target = (
<html>
<p>Schrödinger's cat is {blink} dead.</p>
</html>
);

const document = h.document([target]);

t.deepEqual(await evaluate(R70, { document }), [
failed(R70, document, {
1: Outcomes.HasDeprecatedElements([blink]),
}),
]);
});

test("evaluate() fails a page with deprecated visible element", async (t) => {
const blink = <blink aria-hidden="true">not</blink>;
const target = (
<html>
<p>Schrödinger's cat is {blink} dead.</p>
</html>
);

const document = h.document([target]);

t.deepEqual(await evaluate(R70, { document }), [
failed(R70, document, {
1: Outcomes.HasDeprecatedElements([blink]),
}),
]);
});

test("evaluate() fails a page with two deprecated elements in the accessibility tree", async (t) => {
const menuitem1 = <menuitem role="menuitem">Foo</menuitem>;
const menuitem2 = <menuitem role="menuitem">Bar</menuitem>;
const target = (
<html>
<ul role="menu" style={{ position: "absolute", left: "-9999px" }}>
{menuitem1}
{menuitem2}
</ul>
</html>
);

const document = h.document([target]);

t.deepEqual(await evaluate(R70, { document }), [
failed(R70, document, {
1: Outcomes.HasDeprecatedElements([menuitem1, menuitem2]),
}),
]);
});

test("evaluate() is inapplicable to non-HTML documents", async (t) => {
const document = h.document([
<svg xmlns="http://www.w3.org/2000/svg">
<title>This is a circle</title>
<circle cx="150" cy="75" r="50" fill="green"></circle>
</svg>,
]);

t.deepEqual(await evaluate(R70, { document }), [inapplicable(R70)]);
});
2 changes: 2 additions & 0 deletions packages/alfa-rules/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
"src/sia-r67/rule.ts",
"src/sia-r68/rule.ts",
"src/sia-r69/rule.ts",
"src/sia-r70/rule.ts",
"src/sia-r7/rule.ts",
"src/sia-r71/rule.ts",
"src/sia-r72/rule.ts",
Expand Down Expand Up @@ -212,6 +213,7 @@
"test/sia-r67/rule.spec.tsx",
"test/sia-r68/rule.spec.tsx",
"test/sia-r69/rule.spec.tsx",
"test/sia-r70/rule.spec.tsx",
"test/sia-r71/rule.spec.tsx",
"test/sia-r72/rule.spec.tsx",
"test/sia-r73/rule.spec.tsx",
Expand Down

0 comments on commit 170bb71

Please sign in to comment.