feat: show call participants in room list (Discord-style)
Docker / Docker Buildx (push) Has been cancelled
Build Debian package / Build package (release) Has been cancelled
Build and Deploy / prepare (release) Has been cancelled
Deploy release / Deploy to Cloudflare Pages (release) Has been cancelled
Build and Deploy / Trigger Pro pipeline (release) Has been cancelled
Build and Deploy / Windows arm64 (release) Has been cancelled
Build and Deploy / Windows x64 (release) Has been cancelled
Build and Deploy / macOS (release) Has been cancelled
Build and Deploy / Linux amd64 (sqlcipher static) (release) Has been cancelled
Build and Deploy / Linux arm64 (sqlcipher static) (release) Has been cancelled
Build and Deploy / ${{ needs.prepare.outputs.deploy == 'true' && 'Deploy' || 'Deploy (dry-run)' }} (release) Has been cancelled
Build and Deploy / Deploy builds to ESS (release) Has been cancelled
Docker / Docker Buildx (push) Has been cancelled
Build Debian package / Build package (release) Has been cancelled
Build and Deploy / prepare (release) Has been cancelled
Deploy release / Deploy to Cloudflare Pages (release) Has been cancelled
Build and Deploy / Trigger Pro pipeline (release) Has been cancelled
Build and Deploy / Windows arm64 (release) Has been cancelled
Build and Deploy / Windows x64 (release) Has been cancelled
Build and Deploy / macOS (release) Has been cancelled
Build and Deploy / Linux amd64 (sqlcipher static) (release) Has been cancelled
Build and Deploy / Linux arm64 (sqlcipher static) (release) Has been cancelled
Build and Deploy / ${{ needs.prepare.outputs.deploy == 'true' && 'Deploy' || 'Deploy (dry-run)' }} (release) Has been cancelled
Build and Deploy / Deploy builds to ESS (release) Has been cancelled
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,96 @@
|
||||
/*
|
||||
Copyright 2026 Element Creations Ltd.
|
||||
|
||||
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 from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
import { RovingAction, type RovingTabIndexProviderProps } from "@element-hq/web-shared-components";
|
||||
|
||||
import * as KeyBindingsManagerModule from "../../../src/KeyBindingsManager";
|
||||
import { KeyBindingAction } from "../../../src/accessibility/KeyboardShortcuts";
|
||||
import { RovingTabIndexProvider } from "../../../src/accessibility/RovingTabIndex";
|
||||
|
||||
jest.mock("@element-hq/web-shared-components", () => {
|
||||
const actual = jest.requireActual("@element-hq/web-shared-components");
|
||||
const mockSharedRovingTabIndexProvider = jest.fn(({ children }: RovingTabIndexProviderProps) => {
|
||||
return <>{children({ onDragEndHandler: jest.fn(), onKeyDownHandler: jest.fn() })}</>;
|
||||
});
|
||||
|
||||
return {
|
||||
__mockSharedRovingTabIndexProvider: mockSharedRovingTabIndexProvider,
|
||||
...actual,
|
||||
RovingTabIndexProvider: mockSharedRovingTabIndexProvider,
|
||||
};
|
||||
});
|
||||
|
||||
const getMockSharedRovingTabIndexProvider = (): jest.Mock => {
|
||||
return jest.requireMock("@element-hq/web-shared-components").__mockSharedRovingTabIndexProvider as jest.Mock;
|
||||
};
|
||||
|
||||
const getInjectedGetAction = (): NonNullable<RovingTabIndexProviderProps["getAction"]> => {
|
||||
const mockSharedRovingTabIndexProvider = getMockSharedRovingTabIndexProvider();
|
||||
expect(mockSharedRovingTabIndexProvider).toHaveBeenCalled();
|
||||
const getAction = (mockSharedRovingTabIndexProvider.mock.calls.at(-1)![0] as RovingTabIndexProviderProps).getAction;
|
||||
expect(getAction).toBeDefined();
|
||||
return getAction!;
|
||||
};
|
||||
|
||||
describe("RovingTabIndex adapter", () => {
|
||||
beforeEach(() => {
|
||||
const mockSharedRovingTabIndexProvider = getMockSharedRovingTabIndexProvider();
|
||||
mockSharedRovingTabIndexProvider.mockClear();
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
|
||||
it.each([
|
||||
[KeyBindingAction.ArrowDown, RovingAction.ArrowDown],
|
||||
[KeyBindingAction.ArrowUp, RovingAction.ArrowUp],
|
||||
[KeyBindingAction.ArrowRight, RovingAction.ArrowRight],
|
||||
[KeyBindingAction.ArrowLeft, RovingAction.ArrowLeft],
|
||||
[KeyBindingAction.Home, RovingAction.Home],
|
||||
[KeyBindingAction.End, RovingAction.End],
|
||||
[KeyBindingAction.Tab, RovingAction.Tab],
|
||||
])("maps %s to %s", (accessibilityAction, expectedRovingAction) => {
|
||||
const manager = new KeyBindingsManagerModule.KeyBindingsManager();
|
||||
jest.spyOn(KeyBindingsManagerModule, "getKeyBindingsManager").mockReturnValue(manager);
|
||||
jest.spyOn(manager, "getAccessibilityAction").mockReturnValue(accessibilityAction);
|
||||
|
||||
render(<RovingTabIndexProvider>{() => null}</RovingTabIndexProvider>);
|
||||
|
||||
const getAction = getInjectedGetAction();
|
||||
expect(getAction({ key: "irrelevant" } as React.KeyboardEvent)).toBe(expectedRovingAction);
|
||||
});
|
||||
|
||||
it("returns undefined when there is no matching accessibility action", () => {
|
||||
const manager = new KeyBindingsManagerModule.KeyBindingsManager();
|
||||
jest.spyOn(KeyBindingsManagerModule, "getKeyBindingsManager").mockReturnValue(manager);
|
||||
jest.spyOn(manager, "getAccessibilityAction").mockReturnValue(undefined);
|
||||
|
||||
render(<RovingTabIndexProvider>{() => null}</RovingTabIndexProvider>);
|
||||
|
||||
const getAction = getInjectedGetAction();
|
||||
expect(getAction({ key: "x" } as React.KeyboardEvent)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("forwards provider props to shared-components", () => {
|
||||
const onKeyDown = jest.fn();
|
||||
|
||||
render(
|
||||
<RovingTabIndexProvider handleHomeEnd handleLoop handleUpDown onKeyDown={onKeyDown} scrollIntoView>
|
||||
{() => null}
|
||||
</RovingTabIndexProvider>,
|
||||
);
|
||||
|
||||
const mockSharedRovingTabIndexProvider = getMockSharedRovingTabIndexProvider();
|
||||
const props = mockSharedRovingTabIndexProvider.mock.calls.at(-1)![0] as RovingTabIndexProviderProps;
|
||||
expect(props.handleHomeEnd).toBe(true);
|
||||
expect(props.handleLoop).toBe(true);
|
||||
expect(props.handleUpDown).toBe(true);
|
||||
expect(props.onKeyDown).toBe(onKeyDown);
|
||||
expect(props.scrollIntoView).toBe(true);
|
||||
expect(props.getAction).toEqual(expect.any(Function));
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user