Skip to content

Commit

Permalink
SIA R65: accept different border as focus indicator (#819)
Browse files Browse the repository at this point in the history
* Remove duplicate test on border-style
* Accept difference in border-* as focus indicator
* Add some tests
  • Loading branch information
Jym77 authored Jun 1, 2021
1 parent 8f0338d commit 3b282b6
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 5 deletions.
3 changes: 0 additions & 3 deletions packages/alfa-rules/src/common/predicate/has-border.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@ export function hasBorder(
return sides.some(
(side) =>
style.computed(`border-${side}-width` as const).none(Length.isZero) &&
style
.computed(`border-${side}-style` as const)
.none((style) => style.value === "none") &&
style
.computed(`border-${side}-color` as const)
.none((color) => color.type === "color" && Color.isTransparent(color))
Expand Down
59 changes: 57 additions & 2 deletions packages/alfa-rules/src/sia-r65/rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { Page } from "@siteimprove/alfa-web";
import { expectation } from "../common/expectation";

import {
hasBorder,
hasBoxShadow,
hasOutline,
hasTextDecoration,
Expand All @@ -22,7 +23,7 @@ import { Question } from "../common/question";

const { isElement } = Element;
const { isKeyword } = Keyword;
const { or, xor } = Predicate;
const { or, test, xor } = Predicate;

export default Rule.Atomic.of<Page, Element, Question>({
uri: "https://alfa.siteimprove.com/rules/sia-r65",
Expand Down Expand Up @@ -99,7 +100,9 @@ function hasFocusIndicator(device: Device): Predicate<Element> {
xor(hasBoxShadow(device), hasBoxShadow(device, withFocus)),
// These properties are essentially always set, so any difference in the color is good enough.
hasDifferentColors(device, withFocus),
hasDifferentBackgroundColors(device, withFocus)
hasDifferentBackgroundColors(device, withFocus),
// Any difference in border is accepted
hasDifferentBorder(device, withFocus)
)
);
};
Expand Down Expand Up @@ -148,3 +151,55 @@ function hasDifferentBackgroundColors(
return !color1.equals(color2);
};
}

function hasDifferentBorder(
device: Device,
context1: Context = Context.empty(),
context2: Context = Context.empty()
): Predicate<Element> {
return function hasDifferentBorder(element: Element): boolean {
const style1 = Style.from(element, device, context1);
const style2 = Style.from(element, device, context2);

// If 0 or 1 has border, the answer is easy.
const hasBorder1 = test(hasBorder(device, context1), element);
const hasBorder2 = test(hasBorder(device, context2), element);

if (hasBorder1 !== hasBorder2) {
// only one has border
return true;
}

if (!hasBorder1 && !hasBorder2) {
// none has border
return false;
}

// They both have border, we need to dig the values

// We consider any difference in any of the border-* longhand as enough
for (const side of ["top", "right", "bottom", "left"] as const) {
for (const effect of ["color", "style", "width"] as const) {
const longhand = `border-${side}-${effect}` as const;

const border1 = style1.computed(longhand);
const border2 = style2.computed(longhand);

// We avoid keyword resolution for color,
// but we need it for style. The none=hidden conflict has been solved
// by hasBorder so any difference in style is enough.
if (
!(
(effect === "color" &&
(isKeyword(border1) || isKeyword(border2))) ||
border1.equals(border2)
)
) {
return true;
}
}
}

return false;
};
}
55 changes: 55 additions & 0 deletions packages/alfa-rules/test/sia-r65/rule.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -365,3 +365,58 @@ test(`evaluate() passes an <a> element that removes the default focus outline
}),
]);
});

test(`evaluate() passes an <a> element that removes the default focus outline
and applies a border on focus`, async (t) => {
const target = <a href="#">Link</a>;

const document = Document.of(
[target, <button />],
[
h.sheet([
h.rule.style("a:focus", {
outline: "none",
border: "solid 1px",
}),
]),
]
);

t.deepEqual(await evaluate(R65, { document }), [
passed(R65, target, {
1: Outcomes.HasFocusIndicator,
}),
passed(R65, <button />, {
1: Outcomes.HasFocusIndicator,
}),
]);
});

test(`evaluate() passes an <a> element that removes the default focus outline
and changes border color on focus`, async (t) => {
const target = <a href="#">Link</a>;

const document = Document.of(
[target, <button />],
[
h.sheet([
h.rule.style("a", {
border: "solid 1px black",
}),
h.rule.style("a:focus", {
outline: "none",
border: "solid 1px red",
}),
]),
]
);

t.deepEqual(await evaluate(R65, { document }), [
passed(R65, target, {
1: Outcomes.HasFocusIndicator,
}),
passed(R65, <button />, {
1: Outcomes.HasFocusIndicator,
}),
]);
});

0 comments on commit 3b282b6

Please sign in to comment.