Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement SIA-R70 #870

Merged
merged 9 commits into from
Aug 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>;
elenamongelli marked this conversation as resolved.
Show resolved Hide resolved

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

public get errors(): ReadonlyArray<Element> {
return this._errors;
}
elenamongelli marked this conversation as resolved.
Show resolved Hide resolved

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;
}
elenamongelli marked this conversation as resolved.
Show resolved Hide resolved

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