import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import * as React from "react";
import {
useHotkeys,
useObserver,
useRovingFocus,
useSelections,
useTraits,
} from ".";
import { createFileTree } from "./file-tree";
import type { Dir } from "./file-tree";
import { getNodesFromMockFs, waitForTree } from "./test/utils";
describe("useHotkeys()", () => {
let fileTree = createFileTree(getNodesFromMockFs);
afterEach(() => {
fileTree = createFileTree(getNodesFromMockFs);
});
beforeAll(() => {
// this is here to silence a warning temporarily
// we'll fix it in the next exercise
jest.spyOn(console, "error").mockImplementation(() => {});
});
afterAll(() => {
// @ts-expect-error
console.error.mockRestore();
});
const createComponent = () => {
const element = document.createElement("div");
document.body.appendChild(element);
element.tabIndex = 0;
element.style.paddingTop = "0";
element.style.paddingBottom = "0";
Object.defineProperties(element, {
clientHeight: {
get: () => 24,
},
});
const windowRef = { current: element };
return [
element,
() => {
const [, forceUpdate] = React.useState({});
const rovingFocus = useRovingFocus(fileTree);
const selections = useSelections(fileTree);
const traits = useTraits(fileTree, ["selected"]);
useHotkeys(fileTree, {
windowRef,
rovingFocus,
selections,
});
useObserver(rovingFocus.didChange, () => {
forceUpdate({});
});
useObserver(selections.didChange, (nodeIds) => {
traits.set("selected", [...nodeIds]);
forceUpdate({});
});
return (
{fileTree.visibleNodes.map((nodeId, index) => (
))}
);
},
] as const;
};
it('should traverse the file tree with the "up" and "down" keys', async () => {
await waitForTree(fileTree);
const [element, Component] = createComponent();
render(, { container: element });
fireEvent.keyDown(element, { key: "ArrowDown" });
expect(document.activeElement as any).toBe(
document.getElementById("exploration-0")
);
fireEvent.keyDown(element, { key: "ArrowDown" });
expect(document.activeElement as any).toBe(
document.getElementById("exploration-1")
);
fireEvent.keyDown(element, { key: "ArrowUp" });
expect(document.activeElement as any).toBe(
document.getElementById("exploration-0")
);
});
it('should open directory when "right" key is pressed and close directory when "left" key is pressed', async () => {
await waitForTree(fileTree);
const dir = fileTree.getById(fileTree.visibleNodes[0]) as Dir;
const [element, Component] = createComponent();
render(, { container: element });
fireEvent.keyDown(element, { key: "ArrowDown" });
fireEvent.keyDown(element, { key: "ArrowRight" });
expect(document.activeElement as any).toBe(
document.getElementById("exploration-0")
);
expect(dir.expanded).toBe(true);
expect(document.activeElement as any).toBe(
document.getElementById("exploration-0")
);
await waitFor(() => {
fireEvent.keyDown(element, { key: "ArrowRight" });
expect(document.activeElement as any).toBe(
document.getElementById("exploration-1")
);
});
await waitFor(() => {
fireEvent.keyDown(element, { key: "ArrowLeft" });
expect(dir.expanded).toBe(false);
expect(document.activeElement as any).toBe(
document.getElementById("exploration-0")
);
});
});
it('should expand a directory when the "space" key is pressed on a directory', async () => {
await waitForTree(fileTree);
const dir = fileTree.getById(fileTree.visibleNodes[0]) as Dir;
const [element, Component] = createComponent();
render(, { container: element });
fireEvent.keyDown(element, { key: "ArrowDown" });
fireEvent.keyDown(element, { key: " " });
expect(dir.expanded).toBe(true);
expect(document.activeElement as any).toBe(
document.getElementById("exploration-0")
);
fireEvent.keyDown(element, { key: " " });
expect(dir.expanded).toBe(false);
});
it('should select when the "space" key is pressed on a file', async () => {
await waitForTree(fileTree);
const [element, Component] = createComponent();
render(, { container: element });
fireEvent.keyDown(element, { key: "End" });
fireEvent.keyDown(element, { key: " " });
await waitFor(() => {
expect(document.activeElement as any).toHaveClass("selected");
});
});
it('should focus the first node when the "Home" key is pressed', async () => {
await waitForTree(fileTree);
const [element, Component] = createComponent();
render(, { container: element });
fireEvent.keyDown(element, { key: "Home" });
expect(document.activeElement as any).toBe(
document.getElementById("exploration-0")
);
});
it('should clear selections and focus the container when the "Escape" key is pressed', async () => {
await waitForTree(fileTree);
const [element, Component] = createComponent();
render(, { container: element });
fireEvent.keyDown(element, { key: "End" });
fireEvent.keyDown(element, { key: " " });
const focusedElement = document.activeElement;
expect(focusedElement as any).toHaveClass("selected");
fireEvent.keyDown(element, { key: "Escape" });
expect(focusedElement as any).not.toHaveClass("selected");
await waitFor(() => {
expect(document.activeElement as any).toBe(element);
});
});
it("should be a noop if the focused element is an input", () => {
const [element, Component] = createComponent();
render(, { container: element });
const input = screen.getByRole("textbox", { name: "input" });
input.focus();
fireEvent.keyDown(input, { key: "ArrowDown" });
fireEvent.keyDown(input, { key: "ArrowUp" });
fireEvent.keyDown(input, { key: "ArrowRight" });
fireEvent.keyDown(input, { key: "ArrowLeft" });
fireEvent.keyDown(input, { key: "Home" });
fireEvent.keyDown(input, { key: "End" });
fireEvent.keyDown(input, { key: "Escape" });
fireEvent.keyDown(input, { key: " " });
expect(document.activeElement as any).toBe(input);
});
it("should be a noop if the focused element is an textarea", () => {
const [element, Component] = createComponent();
render(, { container: element });
const textarea = screen.getByRole("textbox", { name: "textarea" });
textarea.focus();
fireEvent.keyDown(textarea, { key: "ArrowDown" });
fireEvent.keyDown(textarea, { key: "ArrowUp" });
fireEvent.keyDown(textarea, { key: "ArrowRight" });
fireEvent.keyDown(textarea, { key: "ArrowLeft" });
fireEvent.keyDown(textarea, { key: "Home" });
fireEvent.keyDown(textarea, { key: "End" });
fireEvent.keyDown(textarea, { key: "Escape" });
fireEvent.keyDown(textarea, { key: " " });
expect(document.activeElement as any).toBe(textarea);
});
});