Skip to content

Commit

Permalink
feat: Allow mocking getComputedStyle (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
eps1lon authored Nov 28, 2019
1 parent ea31cc8 commit eb86842
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 11 deletions.
11 changes: 11 additions & 0 deletions .changeset/lemon-pets-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"dom-accessibility-api": minor
---

Add option to mock window.getComputedStyle

This option has two use cases in mind:

1. fake the style and assume everything is visible.
This increases performance (window.getComputedStyle) is expensive) by not distinguishing between various levels of visual impairments. If one can't see the name with a screen reader then neither will a sighted user
2. Wrap a cache provider around `window.getComputedStyle`. We don't implement any because the returned `CSSStyleDeclaration` is only live in a browser. `jsdom` does not implement live declarations.
37 changes: 37 additions & 0 deletions sources/__tests__/accessible-name.js
Original file line number Diff line number Diff line change
Expand Up @@ -358,3 +358,40 @@ describe("prohibited naming", () => {
}
);
});

describe("options.getComputedStyle", () => {
beforeEach(() => {
jest.spyOn(window, "getComputedStyle");
});

afterEach(() => {
jest.restoreAllMocks();
});

it("uses window.getComputedStyle by default", () => {
const container = renderIntoDocument("<button>test</button>");

computeAccessibleName(container.querySelector("button"));

// also mixing in a regression test for the number of calls
expect(window.getComputedStyle).toHaveBeenCalledTimes(4);
});

it("can be mocked with a fake", () => {
const container = renderIntoDocument("<button>test</button>");

const name = computeAccessibleName(container.querySelector("button"), {
getComputedStyle: () => {
const declaration = new CSSStyleDeclaration();
declaration.content = "'foo'";
declaration.display = "block";
declaration.visibility = "visible";

return declaration;
}
});

expect(name).toEqual("foo test foo");
expect(window.getComputedStyle).not.toHaveBeenCalled();
});
});
58 changes: 47 additions & 11 deletions sources/accessible-name.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,33 @@ type FlatString = string & {
__flat: true;
};

/**
* interface for an options-bag where `window.getComputedStyle` can be mocked
*/
interface GetComputedStyleOptions {
getComputedStyle?: typeof window.getComputedStyle;
}
/**
* Small utility that handles all the JS quirks with `this` which is important
* if no mock is provided.
* @param element
* @param options - These are not optional to prevent accidentally calling it without options in `computeAccessibleName`
*/
function createGetComputedStyle(
element: Element,
options: GetComputedStyleOptions
): typeof window.getComputedStyle {
const window = safeWindow(element);
const {
// This might be overengineered. I don't know what happens if I call
// window.getComputedStyle(elementFromAnotherWindow) or if I don't bind it
// the type declarations don't require a `this`
getComputedStyle = window.getComputedStyle.bind(window)
} = options;

return getComputedStyle;
}

/**
*
* @param {string} string -
Expand Down Expand Up @@ -48,10 +75,14 @@ function prohibitsNaming(node: Node): boolean {

/**
*
* @param {Node} node -
* @param node -
* @param options - These are not optional to prevent accidentally calling it without options in `computeAccessibleName`
* @returns {boolean} -
*/
function isHidden(node: Node): node is Element {
function isHidden(
node: Node,
options: GetComputedStyleOptions
): node is Element {
if (!isElement(node)) {
return false;
}
Expand All @@ -63,7 +94,7 @@ function isHidden(node: Node): node is Element {
return true;
}

const style = safeWindow(node).getComputedStyle(node);
const style = createGetComputedStyle(node, options)(node);
return (
style.getPropertyValue("display") === "none" ||
style.getPropertyValue("visibility") === "hidden"
Expand Down Expand Up @@ -252,9 +283,13 @@ function getTextualContent(declaration: CSSStyleDeclaration): string {
/**
* implements https://w3c.github.io/accname/#mapping_additional_nd_te
* @param root
* @param context
* @param [options]
* @parma [options.getComputedStyle] - mock window.getComputedStyle. Needs `content`, `display` and `visibility`
*/
export function computeAccessibleName(root: Element): string {
export function computeAccessibleName(
root: Element,
options: GetComputedStyleOptions = {}
): string {
const consultedNodes = new Set<Node>();

if (prohibitsNaming(root)) {
Expand All @@ -268,7 +303,10 @@ export function computeAccessibleName(root: Element): string {
): string {
let accumulatedText = "";
if (isElement(node)) {
const pseudoBefore = safeWindow(node).getComputedStyle(node, "::before");
const pseudoBefore = createGetComputedStyle(node, options)(
node,
"::before"
);
const beforeContent = getTextualContent(pseudoBefore);
accumulatedText = `${beforeContent} ${accumulatedText}`;
}
Expand All @@ -282,15 +320,13 @@ export function computeAccessibleName(root: Element): string {
// TODO: Unclear why display affects delimiter
const display =
isElement(node) &&
safeWindow(node)
.getComputedStyle(node)
.getPropertyValue("display");
createGetComputedStyle(node, options)(node).getPropertyValue("display");
const separator = display !== "inline" ? " " : "";
accumulatedText += `${separator}${result}`;
}

if (isElement(node)) {
const pseudoAfter = safeWindow(node).getComputedStyle(node, ":after");
const pseudoAfter = createGetComputedStyle(node, options)(node, ":after");
const afterContent = getTextualContent(pseudoAfter);
accumulatedText = `${accumulatedText} ${afterContent}`;
}
Expand Down Expand Up @@ -381,7 +417,7 @@ export function computeAccessibleName(root: Element): string {
}

// 2A
if (isHidden(current) && !context.isReferenced) {
if (isHidden(current, options) && !context.isReferenced) {
consultedNodes.add(current);
return "" as FlatString;
}
Expand Down

0 comments on commit eb86842

Please sign in to comment.