-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Refactor normalize * Mark members of Group as readonly * Write R56 * Add extended diagnostic
- Loading branch information
Showing
9 changed files
with
257 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export function normalize(input: string): string { | ||
return input.trim().toLowerCase().replace(/\s+/g, " "); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,166 @@ | ||
import { Diagnostic, Rule } from "@siteimprove/alfa-act"; | ||
import { Node, Role } from "@siteimprove/alfa-aria"; | ||
import { Array } from "@siteimprove/alfa-array"; | ||
import { Element, Namespace } from "@siteimprove/alfa-dom"; | ||
import { Iterable } from "@siteimprove/alfa-iterable"; | ||
import { List } from "@siteimprove/alfa-list"; | ||
import { Map } from "@siteimprove/alfa-map"; | ||
import { Option } from "@siteimprove/alfa-option"; | ||
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 { Group } from "../common/group"; | ||
import { normalize } from "../common/normalize"; | ||
|
||
import { hasRole, isIgnored } from "../common/predicate"; | ||
|
||
const { and, equals, not } = Predicate; | ||
const { hasNamespace } = Element; | ||
|
||
export default Rule.Atomic.of<Page, Group<Element>>({ | ||
uri: "https://siteimprove.github.io/sanshikan/rules/sia-r56.html", | ||
evaluate({ device, document }) { | ||
return { | ||
applicability() { | ||
return document | ||
.descendants({ flattened: true, nested: true }) | ||
.filter(Element.isElement) | ||
.filter( | ||
and( | ||
hasNamespace(equals(Namespace.HTML)), | ||
not(isIgnored(device)), | ||
hasRole(device, (role) => role.is("landmark")) | ||
) | ||
) | ||
.reduce((groups, landmark) => { | ||
// since we already have filtered by having a landmark role, we can | ||
// safely get the role. | ||
const role = Node.from(landmark, device).role.get()!; | ||
|
||
groups = groups.set( | ||
role, | ||
groups | ||
.get(role) | ||
.getOrElse(() => List.empty<Element>()) | ||
.append(landmark) | ||
); | ||
|
||
return groups; | ||
}, Map.empty<Role, List<Element>>()) | ||
.filter((elements) => elements.size > 1) | ||
.map(Group.of) | ||
.values(); | ||
}, | ||
|
||
expectations(target) { | ||
// empty groups have been filtered out already, so we can safely get the | ||
// first element | ||
const role = Node.from( | ||
Iterable.first(target).get()!, | ||
device | ||
).role.get()!.name; | ||
|
||
const byNames = [...target] | ||
.reduce((groups, landmark) => { | ||
const name = Node.from(landmark, device).name.map((name) => | ||
normalize(name.value) | ||
); | ||
groups = groups.set( | ||
name, | ||
groups | ||
.get(name) | ||
.getOrElse(() => List.empty<Element>()) | ||
.append(landmark) | ||
); | ||
|
||
return groups; | ||
}, Map.empty<Option<string>, List<Element>>()) | ||
.filter((landmarks) => landmarks.size > 1); | ||
|
||
return { | ||
1: expectation( | ||
byNames.size === 0, | ||
() => Outcomes.differentNames(role), | ||
() => Outcomes.sameNames(role, byNames.values()) | ||
), | ||
}; | ||
}, | ||
}; | ||
}, | ||
}); | ||
|
||
export namespace Outcomes { | ||
export const differentNames = (role: Role.Name) => | ||
Ok.of(Diagnostic.of(`No two \`${role}\` have the same name.`)); | ||
|
||
export const sameNames = ( | ||
role: Role.Name, | ||
errors: Iterable<Iterable<Element>> | ||
) => | ||
Err.of(SameNames.of(`Some \`${role}\` have the same name.`, role, errors)); | ||
} | ||
|
||
class SameNames extends Diagnostic implements Iterable<List<Element>> { | ||
public static of( | ||
message: string, | ||
role: Role.Name = "none", | ||
errors: Iterable<Iterable<Element>> = [] | ||
): SameNames { | ||
return new SameNames(message, role, Array.from(errors).map(List.from)); | ||
} | ||
|
||
private readonly _role: Role.Name; | ||
private readonly _errors: ReadonlyArray<List<Element>>; | ||
|
||
private constructor( | ||
message: string, | ||
role: Role.Name, | ||
errors: ReadonlyArray<List<Element>> | ||
) { | ||
super(message); | ||
this._role = role; | ||
this._errors = errors; | ||
} | ||
|
||
public get role(): Role.Name { | ||
return this._role; | ||
} | ||
|
||
public *[Symbol.iterator](): Iterator<List<Element>> { | ||
yield* this._errors; | ||
} | ||
|
||
public equals(value: SameNames): boolean; | ||
|
||
public equals(value: unknown): value is this; | ||
|
||
public equals(value: unknown): boolean { | ||
return ( | ||
value instanceof SameNames && | ||
value._message === this._message && | ||
value._role === this._role && | ||
value._errors.every((list, idx) => list.equals(this._errors[idx])) | ||
); | ||
} | ||
|
||
public toJSON(): SameNames.JSON { | ||
return { | ||
...super.toJSON(), | ||
role: this._role, | ||
errors: Array.toJSON(this._errors), | ||
}; | ||
} | ||
} | ||
|
||
namespace SameNames { | ||
export interface JSON extends Diagnostic.JSON { | ||
role: string; | ||
errors: Array<List.JSON<Element>>; | ||
} | ||
|
||
export function isSameNames(value: unknown): value is SameNames { | ||
return value instanceof SameNames; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { test } from "@siteimprove/alfa-test"; | ||
|
||
import { Document } from "@siteimprove/alfa-dom"; | ||
|
||
import R56, { Outcomes } from "../../src/sia-r56/rule"; | ||
|
||
import { evaluate } from "../common/evaluate"; | ||
import { passed, failed, inapplicable } from "../common/outcome"; | ||
|
||
import { Group } from "../../src/common/group"; | ||
|
||
test("evaluate() passes when same landmarks have different names", async (t) => { | ||
const author = <aside aria-label="About the author" id="author" />; | ||
const book = <aside aria-label="About the book" id="book" />; | ||
|
||
const document = Document.of([author, book]); | ||
const target = Group.of([author, book]); | ||
|
||
t.deepEqual(await evaluate(R56, { document }), [ | ||
passed(R56, target, { 1: Outcomes.differentNames("complementary") }), | ||
]); | ||
}); | ||
|
||
test("evaluate() fails when same landmarks have same names", async (t) => { | ||
const aside1 = <aside aria-label="More information" id="author" />; | ||
const aside2 = <aside aria-label="More information" id="book" />; | ||
|
||
const document = Document.of([aside1, aside2]); | ||
const target = Group.of([aside1, aside2]); | ||
|
||
t.deepEqual(await evaluate(R56, { document }), [ | ||
failed(R56, target, { 1: Outcomes.sameNames("complementary", [target]) }), | ||
]); | ||
}); | ||
|
||
test("evaluate() fails when same landmarks have no names", async (t) => { | ||
const aside1 = <aside id="author" />; | ||
const aside2 = <aside id="book" />; | ||
|
||
const document = Document.of([aside1, aside2]); | ||
const target = Group.of([aside1, aside2]); | ||
|
||
t.deepEqual(await evaluate(R56, { document }), [ | ||
failed(R56, target, { 1: Outcomes.sameNames("complementary", [target]) }), | ||
]); | ||
}); | ||
|
||
test("evaluate() is inapplicable when only different landmarks exist", async (t) => { | ||
const aside = <aside />; | ||
const nav = <nav />; | ||
|
||
const document = Document.of([aside, nav]); | ||
|
||
t.deepEqual(await evaluate(R56, { document }), [inapplicable(R56)]); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters