Skip to content

Commit

Permalink
add Tabs keyboard unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
k-egor-smirnov committed Sep 23, 2022
1 parent ff35c58 commit a91f214
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 6 deletions.
6 changes: 2 additions & 4 deletions src/components/FocusTrap/FocusTrap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { useExternRef } from "../../hooks/useExternRef";
import { useGlobalEventListener } from "../../hooks/useGlobalEventListener";
import { useTimeout } from "../../hooks/useTimeout";
import {
FOCUSABLE_ELEMENTS_LIST,
FOCUSABLE_ELEMENTS_QUERY,
Keys,
pressedKey,
} from "../../lib/accessibility";
Expand All @@ -12,8 +12,6 @@ import { useIsomorphicLayoutEffect } from "../../lib/useIsomorphicLayoutEffect";
import { HasComponent, HasRootRef } from "../../types";
import { AppRootContext } from "../AppRoot/AppRootContext";

const FOCUSABLE_ELEMENTS: string = FOCUSABLE_ELEMENTS_LIST.join();

export interface FocusTrapProps
extends React.AllHTMLAttributes<HTMLElement>,
HasRootRef<HTMLElement>,
Expand Down Expand Up @@ -71,7 +69,7 @@ export const FocusTrap = ({
const nodes: HTMLElement[] = [];
Array.prototype.forEach.call(
// eslint-disable-next-line no-restricted-properties
ref.current.querySelectorAll(FOCUSABLE_ELEMENTS),
ref.current.querySelectorAll(FOCUSABLE_ELEMENTS_QUERY),
(focusableEl: Element) => {
const { display, visibility } = window!.getComputedStyle(focusableEl);

Expand Down
191 changes: 191 additions & 0 deletions src/components/Tabs/Tabs.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,197 @@
import { fireEvent, render, screen } from "@testing-library/react";
import { TabsItem } from "../TabsItem/TabsItem";
import { baselineComponent } from "../../testing/utils";
import { Tabs } from "./Tabs";
import { Group } from "../Group/Group";
import { ComponentProps, useState } from "react";

function TestTabs(props: { disabledKeys?: string[] }) {
const [currentTab, setCurrentTab] = useState("first");

return (
<div>
<Tabs>
<TabsItem
id="tab-first"
data-testid="first"
selected={currentTab === "first"}
onClick={() => setCurrentTab("first")}
aria-controls="tab-content-first"
disabled={props.disabledKeys?.includes("first")}
>
First
</TabsItem>
<TabsItem
id="tab-second"
data-testid="second"
aria-controls="tab-content-second"
onClick={() => setCurrentTab("second")}
selected={currentTab === "second"}
disabled={props.disabledKeys?.includes("second")}
>
Second
</TabsItem>
<TabsItem
id="tab-third"
data-testid="third"
aria-controls="tab-content-third"
onClick={() => setCurrentTab("third")}
selected={currentTab === "third"}
disabled={props.disabledKeys?.includes("third")}
>
Third
</TabsItem>
</Tabs>
{currentTab === "first" && (
<Group
role="tabpanel"
data-testid="content-first"
id="tab-content-first"
aria-labelledby="tab-first"
></Group>
)}
{currentTab === "second" && (
<Group
role="tabpanel"
data-testid="content-second"
id="tab-content-second"
aria-labelledby="tab-second"
></Group>
)}
{currentTab === "third" && (
<Group
role="tabpanel"
data-testid="content-third"
id="tab-content-third"
aria-labelledby="tab-third"
></Group>
)}
</div>
);
}

function isTabSelected(el: HTMLElement) {
return el.getAttribute("aria-selected") === "true";
}

function isTabFocused(el: HTMLElement) {
return document.activeElement === el;
}

function renderTestTabs(props: ComponentProps<typeof TestTabs> = {}) {
render(<TestTabs {...props} />);
screen.getByTestId("first").focus();
screen.getByTestId("first").click();
expect(screen.getByTestId("first").getAttribute("aria-selected")).toEqual(
"true"
);
expect(isTabSelected(screen.getByTestId("first"))).toBeTruthy();
}

function pressKey(key: string) {
if (!document.activeElement) {
return;
}

fireEvent.keyDown(document.activeElement, {
key,
});
}

describe("Tabs", () => {
baselineComponent(Tabs);

describe("Mouse handlers", () => {
it("select element on click", () => {
renderTestTabs();

fireEvent.click(screen.getByTestId("third"));

expect(screen.getByTestId("third").getAttribute("aria-selected")).toEqual(
"true"
);
expect(isTabSelected(screen.getByTestId("third"))).toBeTruthy();
});
it("doesn't select disabled element on click", () => {
renderTestTabs({ disabledKeys: ["third"] });

fireEvent.click(screen.getByTestId("third"));

expect(isTabSelected(screen.getByTestId("third"))).toBeFalsy();
});
});

describe("Keyboard handlers", () => {
it("doesn't focus previous element when first focused", () => {
renderTestTabs();
screen.getByTestId("first").focus();
pressKey("ArrowLeft");
expect(isTabFocused(screen.getByTestId("first"))).toBeTruthy();
});
it("doesn't focus next element when last focused", () => {
renderTestTabs();
screen.getByTestId("third").focus();
pressKey("ArrowRight");
expect(isTabFocused(screen.getByTestId("third"))).toBeTruthy();
});
it("focus next element with ArrowRight key", () => {
renderTestTabs();
screen.getByTestId("second").focus();
pressKey("ArrowRight");
expect(isTabFocused(screen.getByTestId("third"))).toBeTruthy();
});
it("focus previuos element with ArrowLeft key", () => {
renderTestTabs();
screen.getByTestId("second").focus();
pressKey("ArrowLeft");
expect(isTabFocused(screen.getByTestId("first"))).toBeTruthy();
});
it("focus first element with Home key", () => {
renderTestTabs();
screen.getByTestId("third").focus();
pressKey("Home");
expect(isTabFocused(screen.getByTestId("first"))).toBeTruthy();
});
it("focus last element with End key", () => {
renderTestTabs();
screen.getByTestId("first").focus();
pressKey("End");
expect(isTabFocused(screen.getByTestId("third"))).toBeTruthy();
});
it("select element with Space key", () => {
renderTestTabs();
screen.getByTestId("first").focus();
pressKey("ArrowRight");
pressKey("Space");
expect(isTabFocused(screen.getByTestId("second"))).toBeTruthy();
expect(isTabSelected(screen.getByTestId("second"))).toBeTruthy();
});
it("select element with Enter key", () => {
renderTestTabs();
screen.getByTestId("first").focus();
pressKey("ArrowRight");
pressKey("Enter");
expect(isTabFocused(screen.getByTestId("second"))).toBeTruthy();
expect(isTabSelected(screen.getByTestId("second"))).toBeTruthy();
});
it("skip disabled elements", () => {
renderTestTabs({ disabledKeys: ["second"] });
screen.getByTestId("first").focus();
pressKey("ArrowRight");
pressKey("Enter");
expect(isTabFocused(screen.getByTestId("third"))).toBeTruthy();
expect(isTabSelected(screen.getByTestId("second"))).toBeFalsy();
expect(isTabSelected(screen.getByTestId("third"))).toBeTruthy();
});
it("focus content with Down key", () => {
renderTestTabs();
screen.getByTestId("second").focus();
pressKey("Enter");
pressKey("ArrowDown");
expect(isTabSelected(screen.getByTestId("second"))).toBeTruthy();
expect(document.activeElement).toEqual(
screen.getByTestId("content-second")
);
});
});
});
4 changes: 2 additions & 2 deletions src/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { withAdaptivity, AdaptivityProps } from "../../hoc/withAdaptivity";
import { warnOnce } from "../../lib/warnOnce";
import { useGlobalEventListener } from "../../hooks/useGlobalEventListener";
import { useDOM } from "../../lib/dom";
import { pressedKey } from "../../lib/accessibility";
import { FOCUSABLE_ELEMENTS_QUERY, pressedKey } from "../../lib/accessibility";
import "./Tabs.css";

export interface TabsProps
Expand Down Expand Up @@ -78,7 +78,7 @@ const TabsComponent = ({

return Array.from(
// eslint-disable-next-line
tabsRef.current.querySelectorAll<HTMLDivElement>("[role=tab]")
tabsRef.current.querySelectorAll<HTMLDivElement>("[role=tab]:not([disabled])")
);
}

Expand Down
1 change: 1 addition & 0 deletions src/lib/accessibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const FOCUSABLE_ELEMENTS_LIST = [
"[contenteditable]",
'[tabindex]:not([tabindex="-1"])',
];
export const FOCUSABLE_ELEMENTS_QUERY: string = FOCUSABLE_ELEMENTS_LIST.join();

export enum Keys {
ENTER = "Enter",
Expand Down

0 comments on commit a91f214

Please sign in to comment.