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

Extended diagnostic for R14 #786

Merged
merged 5 commits into from
May 4, 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
93 changes: 78 additions & 15 deletions packages/alfa-rules/src/sia-r14/rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,21 @@ export default Rule.Atomic.of<Page, Element>({

expectations(target) {
const textContent = getPerceivableTextContent(target, device);
let name = "";

const accessibleNameIncludesTextContent = test(
hasAccessibleName(device, (accessibleName) =>
normalize(accessibleName.value).includes(textContent)
),
hasAccessibleName(device, (accessibleName) => {
name = normalize(accessibleName.value);
return name.includes(textContent);
}),
target
);

return {
1: expectation(
accessibleNameIncludesTextContent,
() => Outcomes.VisibleIsInName,
() => Outcomes.VisibleIsNotInName
() => Outcomes.VisibleIsInName(textContent, name),
() => Outcomes.VisibleIsNotInName(textContent, name)
),
};
},
Expand All @@ -83,15 +85,76 @@ function getPerceivableTextContent(element: Element, device: Device): string {
}

export namespace Outcomes {
export const VisibleIsInName = Ok.of(
Diagnostic.of(
`The visible text content of the element is included within its accessible name`
)
);
export const VisibleIsInName = (textContent: string, name: string) =>
Ok.of(
LabelAndName.of(
`The visible text content of the element is included within its accessible name`,
textContent,
name
)
);

export const VisibleIsNotInName = Err.of(
Diagnostic.of(
`The visible text content of the element is not included within its accessible name`
)
);
export const VisibleIsNotInName = (textContent: string, name: string) =>
Err.of(
LabelAndName.of(
`The visible text content of the element is not included within its accessible name`,
textContent,
name
)
);
}

class LabelAndName extends Diagnostic {
public static of(
message: string,
textContent: string = "",
name: string = ""
): LabelAndName {
return new LabelAndName(message, textContent, name);
}

private readonly _textContent: string;
private readonly _name: string;

private constructor(message: string, textContent: string, name: string) {
super(message);
this._textContent = textContent;
this._name = name;
}

public get textContent(): string {
return this._textContent;
}

public get name(): string {
return this._name;
}

public equals(value: LabelAndName): boolean;

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

public equals(value: unknown): boolean {
return (
value instanceof LabelAndName &&
value._message === this._message &&
value._textContent === this._textContent &&
value._name === this._name
);
}

public toJSON(): LabelAndName.JSON {
return {
...super.toJSON(),
textContent: this._textContent,
name: this._name,
};
}
}

namespace LabelAndName {
export interface JSON extends Diagnostic.JSON {
textContent: string;
name: string;
}
}
8 changes: 4 additions & 4 deletions packages/alfa-rules/test/sia-r14/rule.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ test(`evaluate() passes a <button> element whose perceivable text content

t.deepEqual(await evaluate(R14, { document }), [
passed(R14, target, {
1: Outcomes.VisibleIsInName,
1: Outcomes.VisibleIsInName("hello world", "hello world"),
}),
]);
});
Expand All @@ -28,7 +28,7 @@ test(`evaluate() passes a <button> element whose perceivable text content

t.deepEqual(await evaluate(R14, { document }), [
passed(R14, target, {
1: Outcomes.VisibleIsInName,
1: Outcomes.VisibleIsInName("hello", "hello world"),
}),
]);
});
Expand All @@ -41,7 +41,7 @@ test(`evaluate() fails a <button> element whose perceivable text content

t.deepEqual(await evaluate(R14, { document }), [
failed(R14, target, {
1: Outcomes.VisibleIsNotInName,
1: Outcomes.VisibleIsNotInName("hello world", "hello"),
}),
]);
});
Expand All @@ -57,7 +57,7 @@ test(`evaluate() ignores non-perceivable text content`, async (t) => {

t.deepEqual(await evaluate(R14, { document }), [
passed(R14, target, {
1: Outcomes.VisibleIsInName,
1: Outcomes.VisibleIsInName("hello", "hello"),
}),
]);
});
145 changes: 145 additions & 0 deletions packages/alfa-rules/test/sia-r42/rule.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { test } from "@siteimprove/alfa-test";

import { Document } from "@siteimprove/alfa-dom";

import R42, { Outcomes } from "../../src/sia-r42/rule";

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

test(`evaluates() passes an implicit listitem inside a list`, async (t) => {
const target = <li>Foo</li>;

const document = Document.of([<ul>{target}</ul>]);

t.deepEqual(await evaluate(R42, { document }), [
passed(R42, target, {
1: Outcomes.IsOwnedByContextRole,
}),
]);
});

test(`evaluates() passes an explicit listitem inside a list`, async (t) => {
const target = <div role="listitem">Foo</div>;

const document = Document.of([<div role="list">{target}</div>]);

t.deepEqual(await evaluate(R42, { document }), [
passed(R42, target, {
1: Outcomes.IsOwnedByContextRole,
}),
]);
});

test(`evaluates() fails an orphaned listitem`, async (t) => {
const target = <div role="listitem">Foo</div>;

const document = Document.of([target]);

t.deepEqual(await evaluate(R42, { document }), [
failed(R42, target, {
1: Outcomes.IsNotOwnedByContextRole,
}),
]);
});

test(`evaluates() skips through container nodes`, async (t) => {
const target = <div role="listitem">Foo</div>;

const document = Document.of([
<div role="list">
<div>{target}</div>
</div>,
]);

t.deepEqual(await evaluate(R42, { document }), [
passed(R42, target, {
1: Outcomes.IsOwnedByContextRole,
}),
]);
});

test(`evaluates() fails listitem in intermediate non-container nodes`, async (t) => {
const target = <div role="listitem">Foo</div>;

const document = Document.of([
<div role="list">
<div role="menu">{target}</div>
</div>,
]);

t.deepEqual(await evaluate(R42, { document }), [
failed(R42, target, {
1: Outcomes.IsNotOwnedByContextRole,
}),
]);
});

test(`evaluates() follows \`aria-owns\``, async (t) => {
const target = (
<div id="target" role="listitem">
Foo
</div>
);

const document = Document.of([
<div role="list" aria-owns="target"></div>,
target,
]);

t.deepEqual(await evaluate(R42, { document }), [
passed(R42, target, {
1: Outcomes.IsOwnedByContextRole,
}),
]);
});

test(`evaluates() passes a \`row\` inside a \`rowgroup\` inside a \`table\``, async (t) => {
const cell = <td>Foo</td>;
const row = <tr>{cell}</tr>;
const rowGroup = <tbody>{row}</tbody>;

const document = Document.of([<table>{rowGroup}</table>]);

t.deepEqual(await evaluate(R42, { document }), [
passed(R42, rowGroup, {
1: Outcomes.IsOwnedByContextRole,
}),
passed(R42, row, {
1: Outcomes.IsOwnedByContextRole,
}),
passed(R42, cell, {
1: Outcomes.IsOwnedByContextRole,
}),
]);
});

test(`evaluates() fails a \`row\` inside an orphaned \`rowgroup\``, async (t) => {
const row = <div role="row">Foo</div>;
const rowGroup = <div role="rowgroup">{row}</div>;

const document = Document.of([rowGroup]);

t.deepEqual(await evaluate(R42, { document }), [
failed(R42, rowGroup, {
1: Outcomes.IsNotOwnedByContextRole,
}),
failed(R42, row, {
1: Outcomes.IsNotOwnedByContextRole,
}),
]);
});

test(`evaluates() is inapplicable when on element whose role has no required parents`, async (t) => {
const document = Document.of([<h1>Header</h1>]);

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

test(`evaluates() is inapplicable on element that is not in the accessiblity tree`, async (t) => {
const document = Document.of([
<div role="listitem" style={{ display: "none" }}></div>,
]);

t.deepEqual(await evaluate(R42, { document }), [inapplicable(R42)]);
});
1 change: 1 addition & 0 deletions packages/alfa-rules/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@
"test/sia-r24/rule.spec.tsx",
"test/sia-r38/rule.spec.tsx",
"test/sia-r41/rule.spec.tsx",
"test/sia-r42/rule.spec.tsx",
"test/sia-r45/rule.spec.tsx",
"test/sia-r46/rule.spec.tsx",
"test/sia-r53/rule.spec.tsx",
Expand Down