mv element.io @types __mocks__/ debian docker module_system/ playwright res src test webapp Dockerfile .dockerignore .eslintignore .stylelintrc.cjs babel.config.cjs recorder-worklet-loader.cjs .modernizr.json components.json config.json config.sample.json package.json project.json tsconfig.json tsconfig.module_system.json jest.config.ts playwright.config.ts webpack.config.ts build_config.sample.yaml apps/web/
mkdir apps/web/scripts
mv scripts/{cleanup.sh,ci_package.sh,copy-res.ts,deploy.py,package.sh} apps/web/scripts
And a couple of gitignore tweaks
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 Šimon Brandner <simon.bra.ag@gmail.com>
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mockPlatformPeg, unmockPlatformPeg } from "../../test-utils";
|
||||
|
||||
const PATH_TO_KEYBOARD_SHORTCUTS = "../../../src/accessibility/KeyboardShortcuts";
|
||||
const PATH_TO_KEYBOARD_SHORTCUT_UTILS = "../../../src/accessibility/KeyboardShortcutUtils";
|
||||
|
||||
const mockKeyboardShortcuts = (override: Record<string, any>) => {
|
||||
jest.doMock(PATH_TO_KEYBOARD_SHORTCUTS, () => {
|
||||
const original = jest.requireActual(PATH_TO_KEYBOARD_SHORTCUTS);
|
||||
return {
|
||||
...original,
|
||||
...override,
|
||||
};
|
||||
});
|
||||
};
|
||||
const getFile = async () => await import(PATH_TO_KEYBOARD_SHORTCUTS);
|
||||
const getUtils = async () => await import(PATH_TO_KEYBOARD_SHORTCUT_UTILS);
|
||||
|
||||
describe("KeyboardShortcutUtils", () => {
|
||||
beforeEach(() => {
|
||||
unmockPlatformPeg();
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
it("doesn't change KEYBOARD_SHORTCUTS when getting shortcuts", async () => {
|
||||
mockKeyboardShortcuts({
|
||||
KEYBOARD_SHORTCUTS: {
|
||||
Keybind1: {},
|
||||
Keybind2: {},
|
||||
},
|
||||
MAC_ONLY_SHORTCUTS: ["Keybind1"],
|
||||
DESKTOP_SHORTCUTS: ["Keybind2"],
|
||||
});
|
||||
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
||||
const utils = await getUtils();
|
||||
const file = await getFile();
|
||||
const copyKeyboardShortcuts = Object.assign({}, file.KEYBOARD_SHORTCUTS);
|
||||
|
||||
utils.getKeyboardShortcuts();
|
||||
expect(file.KEYBOARD_SHORTCUTS).toEqual(copyKeyboardShortcuts);
|
||||
utils.getKeyboardShortcutsForUI();
|
||||
expect(file.KEYBOARD_SHORTCUTS).toEqual(copyKeyboardShortcuts);
|
||||
});
|
||||
|
||||
describe("correctly filters shortcuts", () => {
|
||||
it("when on web and not on macOS", async () => {
|
||||
mockKeyboardShortcuts({
|
||||
KEYBOARD_SHORTCUTS: {
|
||||
Keybind1: {},
|
||||
Keybind2: {},
|
||||
Keybind3: { controller: { settingDisabled: true } },
|
||||
Keybind4: {},
|
||||
},
|
||||
MAC_ONLY_SHORTCUTS: ["Keybind1"],
|
||||
DESKTOP_SHORTCUTS: ["Keybind2"],
|
||||
});
|
||||
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(false) });
|
||||
expect((await getUtils()).getKeyboardShortcuts()).toEqual({ Keybind4: {} });
|
||||
});
|
||||
|
||||
it("when on desktop", async () => {
|
||||
mockKeyboardShortcuts({
|
||||
KEYBOARD_SHORTCUTS: {
|
||||
Keybind1: {},
|
||||
Keybind2: {},
|
||||
},
|
||||
MAC_ONLY_SHORTCUTS: [],
|
||||
DESKTOP_SHORTCUTS: ["Keybind2"],
|
||||
});
|
||||
mockPlatformPeg({ overrideBrowserShortcuts: jest.fn().mockReturnValue(true) });
|
||||
expect((await getUtils()).getKeyboardShortcuts()).toEqual({ Keybind1: {}, Keybind2: {} });
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
* Copyright 2024 The Matrix.org Foundation C.I.C.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { render, screen, waitFor } from "jest-matrix-react";
|
||||
import React from "react";
|
||||
|
||||
import { Landmark, LandmarkNavigation } from "../../../src/accessibility/LandmarkNavigation";
|
||||
import defaultDispatcher from "../../../src/dispatcher/dispatcher";
|
||||
|
||||
describe("KeyboardLandmarkUtils", () => {
|
||||
it("Landmarks are cycled through correctly without an opened room", () => {
|
||||
render(
|
||||
<div>
|
||||
<div tabIndex={0} className="mx_SpaceButton_active" data-testid="mx_SpaceButton_active">
|
||||
SPACE_BUTTON
|
||||
</div>
|
||||
<div tabIndex={0} id="room-list-search-button" data-testid="mx_RoomListSearch_search">
|
||||
ROOM_SEARCH
|
||||
</div>
|
||||
<div tabIndex={0} className="mx_RoomListItemView" data-testid="mx_RoomListItemView">
|
||||
ROOM_TILE
|
||||
</div>
|
||||
<div tabIndex={0} className="mx_HomePage" data-testid="mx_HomePage">
|
||||
HOME_PAGE
|
||||
</div>
|
||||
</div>,
|
||||
);
|
||||
// ACTIVE_SPACE_BUTTON <-> ROOM_SEARCH <-> ROOM_LIST <-> HOME <-> ACTIVE_SPACE_BUTTON
|
||||
// ACTIVE_SPACE_BUTTON -> ROOM_SEARCH
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.ACTIVE_SPACE_BUTTON);
|
||||
expect(screen.getByTestId("mx_RoomListSearch_search")).toHaveFocus();
|
||||
|
||||
// ROOM_SEARCH -> ROOM_LIST
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.ROOM_SEARCH);
|
||||
expect(screen.getByTestId("mx_RoomListItemView")).toHaveFocus();
|
||||
|
||||
// ROOM_LIST -> HOME_PAGE
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.ROOM_LIST);
|
||||
expect(screen.getByTestId("mx_HomePage")).toHaveFocus();
|
||||
|
||||
// HOME_PAGE -> ACTIVE_SPACE_BUTTON
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.MESSAGE_COMPOSER_OR_HOME);
|
||||
expect(screen.getByTestId("mx_SpaceButton_active")).toHaveFocus();
|
||||
|
||||
// HOME_PAGE <- ACTIVE_SPACE_BUTTON
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.ACTIVE_SPACE_BUTTON, true);
|
||||
expect(screen.getByTestId("mx_HomePage")).toHaveFocus();
|
||||
|
||||
// ROOM_LIST <- HOME_PAGE
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.MESSAGE_COMPOSER_OR_HOME, true);
|
||||
expect(screen.getByTestId("mx_RoomListItemView")).toHaveFocus();
|
||||
|
||||
// ROOM_SEARCH <- ROOM_LIST
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.ROOM_LIST, true);
|
||||
expect(screen.getByTestId("mx_RoomListSearch_search")).toHaveFocus();
|
||||
|
||||
// ACTIVE_SPACE_BUTTON <- ROOM_SEARCH
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.ROOM_SEARCH, true);
|
||||
expect(screen.getByTestId("mx_SpaceButton_active")).toHaveFocus();
|
||||
});
|
||||
|
||||
it("Landmarks are cycled through correctly with an opened room", async () => {
|
||||
const callback = jest.fn();
|
||||
defaultDispatcher.register(callback);
|
||||
render(
|
||||
<div>
|
||||
<div tabIndex={0} className="mx_SpaceButton_active" data-testid="mx_SpaceButton_active">
|
||||
SPACE_BUTTON
|
||||
</div>
|
||||
<div tabIndex={0} id="room-list-search-button" data-testid="mx_RoomListSearch_search">
|
||||
ROOM_SEARCH
|
||||
</div>
|
||||
<div tabIndex={0} className="mx_RoomListItemView_selected" data-testid="mx_RoomListItemView_selected">
|
||||
ROOM_TILE
|
||||
</div>
|
||||
<div tabIndex={0} className="mx_Room" data-testid="mx_Room">
|
||||
ROOM
|
||||
<div tabIndex={0} className="mx_MessageComposer">
|
||||
COMPOSER
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
);
|
||||
// ACTIVE_SPACE_BUTTON <-> ROOM_SEARCH <-> ROOM_LIST <-> MESSAGE_COMPOSER <-> ACTIVE_SPACE_BUTTON
|
||||
// ACTIVE_SPACE_BUTTON -> ROOM_SEARCH
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.ACTIVE_SPACE_BUTTON);
|
||||
expect(screen.getByTestId("mx_RoomListSearch_search")).toHaveFocus();
|
||||
|
||||
// ROOM_SEARCH -> ROOM_LIST
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.ROOM_SEARCH);
|
||||
expect(screen.getByTestId("mx_RoomListItemView_selected")).toHaveFocus();
|
||||
|
||||
// ROOM_LIST -> MESSAGE_COMPOSER
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.ROOM_LIST);
|
||||
await waitFor(() => expect(callback).toHaveBeenCalledTimes(1));
|
||||
|
||||
// MESSAGE_COMPOSER -> ACTIVE_SPACE_BUTTON
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.MESSAGE_COMPOSER_OR_HOME);
|
||||
expect(screen.getByTestId("mx_SpaceButton_active")).toHaveFocus();
|
||||
|
||||
// MESSAGE_COMPOSER <- ACTIVE_SPACE_BUTTON
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.ACTIVE_SPACE_BUTTON, true);
|
||||
await waitFor(() => expect(callback).toHaveBeenCalledTimes(2));
|
||||
|
||||
// ROOM_LIST <- MESSAGE_COMPOSER
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.MESSAGE_COMPOSER_OR_HOME, true);
|
||||
expect(screen.getByTestId("mx_RoomListItemView_selected")).toHaveFocus();
|
||||
|
||||
// ROOM_SEARCH <- ROOM_LIST
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.ROOM_LIST, true);
|
||||
expect(screen.getByTestId("mx_RoomListSearch_search")).toHaveFocus();
|
||||
|
||||
// ACTIVE_SPACE_BUTTON <- ROOM_SEARCH
|
||||
LandmarkNavigation.findAndFocusNextLandmark(Landmark.ROOM_SEARCH, true);
|
||||
expect(screen.getByTestId("mx_SpaceButton_active")).toHaveFocus();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,435 @@
|
||||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2020, 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React, { type HTMLAttributes } from "react";
|
||||
import { act, render } from "jest-matrix-react";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import {
|
||||
type IState,
|
||||
reducer,
|
||||
RovingTabIndexProvider,
|
||||
RovingTabIndexWrapper,
|
||||
Type,
|
||||
useRovingTabIndex,
|
||||
} from "../../../src/accessibility/RovingTabIndex";
|
||||
|
||||
const Button = (props: HTMLAttributes<HTMLButtonElement>) => {
|
||||
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLButtonElement>();
|
||||
return <button {...props} onFocus={onFocus} tabIndex={isActive ? 0 : -1} ref={ref} />;
|
||||
};
|
||||
|
||||
const checkTabIndexes = (buttons: NodeListOf<HTMLElement>, expectations: number[]) => {
|
||||
expect([...buttons].map((b) => b.tabIndex)).toStrictEqual(expectations);
|
||||
};
|
||||
|
||||
const createButtonElement = (text: string): HTMLButtonElement => {
|
||||
const button = document.createElement("button");
|
||||
button.textContent = text;
|
||||
return button;
|
||||
};
|
||||
|
||||
// give the buttons keys for the fibre reconciler to not treat them all as the same
|
||||
const button1 = <Button key={1}>a</Button>;
|
||||
const button2 = <Button key={2}>b</Button>;
|
||||
const button3 = <Button key={3}>c</Button>;
|
||||
const button4 = <Button key={4}>d</Button>;
|
||||
|
||||
// mock offsetParent
|
||||
Object.defineProperty(HTMLElement.prototype, "offsetParent", {
|
||||
get() {
|
||||
return this.parentNode;
|
||||
},
|
||||
});
|
||||
|
||||
describe("RovingTabIndex", () => {
|
||||
it("RovingTabIndexProvider renders children as expected", () => {
|
||||
const { container } = render(
|
||||
<RovingTabIndexProvider>
|
||||
{() => (
|
||||
<div>
|
||||
<span>Test</span>
|
||||
</div>
|
||||
)}
|
||||
</RovingTabIndexProvider>,
|
||||
);
|
||||
expect(container.textContent).toBe("Test");
|
||||
expect(container.innerHTML).toBe("<div><span>Test</span></div>");
|
||||
});
|
||||
|
||||
it("RovingTabIndexProvider works as expected with useRovingTabIndex", () => {
|
||||
const { container, rerender } = render(
|
||||
<RovingTabIndexProvider>
|
||||
{() => (
|
||||
<React.Fragment>
|
||||
{button1}
|
||||
{button2}
|
||||
{button3}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</RovingTabIndexProvider>,
|
||||
);
|
||||
|
||||
// should begin with 0th being active
|
||||
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
|
||||
|
||||
// focus on 2nd button and test it is the only active one
|
||||
act(() => container.querySelectorAll("button")[2].focus());
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, -1, 0]);
|
||||
|
||||
// focus on 1st button and test it is the only active one
|
||||
act(() => container.querySelectorAll("button")[1].focus());
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, 0, -1]);
|
||||
|
||||
// check that the active button does not change even on an explicit blur event
|
||||
act(() => container.querySelectorAll("button")[1].blur());
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, 0, -1]);
|
||||
|
||||
// update the children, it should remain on the same button
|
||||
rerender(
|
||||
<RovingTabIndexProvider>
|
||||
{() => (
|
||||
<React.Fragment>
|
||||
{button1}
|
||||
{button4}
|
||||
{button2}
|
||||
{button3}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</RovingTabIndexProvider>,
|
||||
);
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, -1, 0, -1]);
|
||||
|
||||
// update the children, remove the active button, it should move to the next one
|
||||
rerender(
|
||||
<RovingTabIndexProvider>
|
||||
{() => (
|
||||
<React.Fragment>
|
||||
{button1}
|
||||
{button4}
|
||||
{button3}
|
||||
</React.Fragment>
|
||||
)}
|
||||
</RovingTabIndexProvider>,
|
||||
);
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, -1, 0]);
|
||||
});
|
||||
|
||||
it("RovingTabIndexProvider provides a ref to the dom element", () => {
|
||||
const nodeRef = React.createRef<HTMLButtonElement>();
|
||||
const MyButton = (props: HTMLAttributes<HTMLButtonElement>) => {
|
||||
const [onFocus, isActive, ref] = useRovingTabIndex<HTMLButtonElement>(nodeRef);
|
||||
return <button {...props} onFocus={onFocus} tabIndex={isActive ? 0 : -1} ref={ref} />;
|
||||
};
|
||||
const { container } = render(
|
||||
<RovingTabIndexProvider>
|
||||
{() => (
|
||||
<React.Fragment>
|
||||
<MyButton />
|
||||
</React.Fragment>
|
||||
)}
|
||||
</RovingTabIndexProvider>,
|
||||
);
|
||||
// nodeRef should point to button
|
||||
expect(nodeRef.current).toBe(container.querySelector("button"));
|
||||
});
|
||||
|
||||
it("RovingTabIndexProvider works as expected with RovingTabIndexWrapper", () => {
|
||||
const { container } = render(
|
||||
<RovingTabIndexProvider>
|
||||
{() => (
|
||||
<React.Fragment>
|
||||
{button1}
|
||||
{button2}
|
||||
<RovingTabIndexWrapper>
|
||||
{({ onFocus, isActive, ref }) => (
|
||||
<button onFocus={onFocus} tabIndex={isActive ? 0 : -1} ref={ref}>
|
||||
.
|
||||
</button>
|
||||
)}
|
||||
</RovingTabIndexWrapper>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</RovingTabIndexProvider>,
|
||||
);
|
||||
|
||||
// should begin with 0th being active
|
||||
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
|
||||
|
||||
// focus on 2nd button and test it is the only active one
|
||||
act(() => container.querySelectorAll("button")[2].focus());
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, -1, 0]);
|
||||
});
|
||||
|
||||
describe("reducer functions as expected", () => {
|
||||
it("SetFocus works as expected", () => {
|
||||
const node1 = createButtonElement("Button 1");
|
||||
const node2 = createButtonElement("Button 2");
|
||||
expect(
|
||||
reducer(
|
||||
{
|
||||
activeNode: node1,
|
||||
nodes: [node1, node2],
|
||||
},
|
||||
{
|
||||
type: Type.SetFocus,
|
||||
payload: {
|
||||
node: node2,
|
||||
},
|
||||
},
|
||||
),
|
||||
).toStrictEqual({
|
||||
activeNode: node2,
|
||||
nodes: [node1, node2],
|
||||
});
|
||||
});
|
||||
|
||||
it("Unregister works as expected", () => {
|
||||
const button1 = createButtonElement("Button 1");
|
||||
const button2 = createButtonElement("Button 2");
|
||||
const button3 = createButtonElement("Button 3");
|
||||
const button4 = createButtonElement("Button 4");
|
||||
|
||||
let state: IState = {
|
||||
nodes: [button1, button2, button3, button4],
|
||||
};
|
||||
|
||||
state = reducer(state, {
|
||||
type: Type.Unregister,
|
||||
payload: {
|
||||
node: button2,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
nodes: [button1, button3, button4],
|
||||
});
|
||||
|
||||
state = reducer(state, {
|
||||
type: Type.Unregister,
|
||||
payload: {
|
||||
node: button3,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
nodes: [button1, button4],
|
||||
});
|
||||
|
||||
state = reducer(state, {
|
||||
type: Type.Unregister,
|
||||
payload: {
|
||||
node: button4,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
nodes: [button1],
|
||||
});
|
||||
|
||||
state = reducer(state, {
|
||||
type: Type.Unregister,
|
||||
payload: {
|
||||
node: button1,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
nodes: [],
|
||||
});
|
||||
});
|
||||
|
||||
it("Register works as expected", () => {
|
||||
const ref1 = React.createRef<HTMLElement>();
|
||||
const ref2 = React.createRef<HTMLElement>();
|
||||
const ref3 = React.createRef<HTMLElement>();
|
||||
const ref4 = React.createRef<HTMLElement>();
|
||||
|
||||
render(
|
||||
<React.Fragment>
|
||||
<span ref={ref1} />
|
||||
<span ref={ref2} />
|
||||
<span ref={ref3} />
|
||||
<span ref={ref4} />
|
||||
</React.Fragment>,
|
||||
);
|
||||
|
||||
let state: IState = {
|
||||
nodes: [],
|
||||
};
|
||||
|
||||
state = reducer(state, {
|
||||
type: Type.Register,
|
||||
payload: {
|
||||
node: ref1.current!,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
activeNode: ref1.current,
|
||||
nodes: [ref1.current],
|
||||
});
|
||||
|
||||
state = reducer(state, {
|
||||
type: Type.Register,
|
||||
payload: {
|
||||
node: ref2.current!,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
activeNode: ref1.current,
|
||||
nodes: [ref1.current, ref2.current],
|
||||
});
|
||||
|
||||
state = reducer(state, {
|
||||
type: Type.Register,
|
||||
payload: {
|
||||
node: ref3.current!,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
activeNode: ref1.current,
|
||||
nodes: [ref1.current, ref2.current, ref3.current],
|
||||
});
|
||||
|
||||
state = reducer(state, {
|
||||
type: Type.Register,
|
||||
payload: {
|
||||
node: ref4.current!,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
activeNode: ref1.current,
|
||||
nodes: [ref1.current, ref2.current, ref3.current, ref4.current],
|
||||
});
|
||||
|
||||
// test that the automatic focus switch works for unmounting
|
||||
state = reducer(state, {
|
||||
type: Type.SetFocus,
|
||||
payload: {
|
||||
node: ref2.current!,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
activeNode: ref2.current,
|
||||
nodes: [ref1.current, ref2.current, ref3.current, ref4.current],
|
||||
});
|
||||
|
||||
state = reducer(state, {
|
||||
type: Type.Unregister,
|
||||
payload: {
|
||||
node: ref2.current!,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
activeNode: ref3.current,
|
||||
nodes: [ref1.current, ref3.current, ref4.current],
|
||||
});
|
||||
|
||||
// test that the insert into the middle works as expected
|
||||
state = reducer(state, {
|
||||
type: Type.Register,
|
||||
payload: {
|
||||
node: ref2.current!,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
activeNode: ref3.current,
|
||||
nodes: [ref1.current, ref2.current, ref3.current, ref4.current],
|
||||
});
|
||||
|
||||
// test that insertion at the edges works
|
||||
state = reducer(state, {
|
||||
type: Type.Unregister,
|
||||
payload: {
|
||||
node: ref1.current!,
|
||||
},
|
||||
});
|
||||
state = reducer(state, {
|
||||
type: Type.Unregister,
|
||||
payload: {
|
||||
node: ref4.current!,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
activeNode: ref3.current,
|
||||
nodes: [ref2.current, ref3.current],
|
||||
});
|
||||
|
||||
state = reducer(state, {
|
||||
type: Type.Register,
|
||||
payload: {
|
||||
node: ref1.current!,
|
||||
},
|
||||
});
|
||||
|
||||
state = reducer(state, {
|
||||
type: Type.Register,
|
||||
payload: {
|
||||
node: ref4.current!,
|
||||
},
|
||||
});
|
||||
expect(state).toStrictEqual({
|
||||
activeNode: ref3.current,
|
||||
nodes: [ref1.current, ref2.current, ref3.current, ref4.current],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("handles arrow keys", () => {
|
||||
it("should handle up/down arrow keys work when handleUpDown=true", async () => {
|
||||
const { container } = render(
|
||||
<RovingTabIndexProvider handleUpDown>
|
||||
{({ onKeyDownHandler }) => (
|
||||
<div onKeyDown={onKeyDownHandler}>
|
||||
{button1}
|
||||
{button2}
|
||||
{button3}
|
||||
</div>
|
||||
)}
|
||||
</RovingTabIndexProvider>,
|
||||
);
|
||||
|
||||
act(() => container.querySelectorAll("button")[0].focus());
|
||||
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
|
||||
|
||||
await userEvent.keyboard("[ArrowDown]");
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, 0, -1]);
|
||||
|
||||
await userEvent.keyboard("[ArrowDown]");
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, -1, 0]);
|
||||
|
||||
await userEvent.keyboard("[ArrowUp]");
|
||||
checkTabIndexes(container.querySelectorAll("button"), [-1, 0, -1]);
|
||||
|
||||
await userEvent.keyboard("[ArrowUp]");
|
||||
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
|
||||
|
||||
// Does not loop without
|
||||
await userEvent.keyboard("[ArrowUp]");
|
||||
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
|
||||
});
|
||||
|
||||
it("should call scrollIntoView if specified", async () => {
|
||||
const { container } = render(
|
||||
<RovingTabIndexProvider handleUpDown scrollIntoView>
|
||||
{({ onKeyDownHandler }) => (
|
||||
<div onKeyDown={onKeyDownHandler}>
|
||||
{button1}
|
||||
{button2}
|
||||
{button3}
|
||||
</div>
|
||||
)}
|
||||
</RovingTabIndexProvider>,
|
||||
);
|
||||
|
||||
act(() => container.querySelectorAll("button")[0].focus());
|
||||
checkTabIndexes(container.querySelectorAll("button"), [0, -1, -1]);
|
||||
|
||||
const button = container.querySelectorAll("button")[1];
|
||||
const mock = jest.spyOn(button, "scrollIntoView");
|
||||
await userEvent.keyboard("[ArrowDown]");
|
||||
expect(mock).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user