Files
Michael TelatynskiandGitHub 4aa1a3549e Enable oxlint restriction ruleset (#34307)
* Remove stale max-len disablements

* Remove stale camelCase & naming-convention disablements

* Remove stale ban-ts-comment disablements

* Remove stale no-var disablements

* Remove stale no-empty-property disablements

* Remove stale react rule disablements

* Remove stale no-constant-condition disablements

* Remove stale no-unused-vars disablements

* Remove stale disablements for disabled rules

* fixup camelcase

* Remove dead code

* Tidy code

* Tweak oxlint config

* Use oxlint to apply jsx/tsx extension consistently

* Fix import

* Fix imports

* Rename affected snapshots

* Update more imports

* Enable restriction ruleset

* Make code comply with new rules

* Make code comply with react/button-has-type

* Make code comply with typescript/non-nullable-type-assertion-style

* Comply with node/no-process-env

* Comply with unicorn/prefer-node-protocol

* Comply with unicorn/import-style

* Comply with unicorn/no-process-exit

* Comply with no-proto

* Comply with node/handle-callback-err

* Comply with import/no-commonjs

* Comply with node/no-path-concat

* Comply with unicorn/no-length-as-slice-end

* Comply with unicorn/no-document-cookie

* Comply with unicorn/prefer-module

* Comply with typescript/prefer-literal-enum-member

* Comply with jsx-a11y/anchor-ambiguous-text

* Tweak oxlint config

* Fix resolves

* Iterate

* Iterate

* Iterate

* Iterate

* Iterate

* Iterate

* Iterate
2026-08-04 09:15:07 +00:00

214 lines
8.3 KiB
TypeScript

/*
Copyright 2026 Element Creations Ltd.
Copyright 2024 New Vector Ltd.
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 React from "react";
import { render, screen, fireEvent, act, cleanup, waitFor, within } from "jest-matrix-react";
import { mocked } from "jest-mock";
import { type MatrixClient, type Room } from "matrix-js-sdk/src/matrix";
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
import { MetaSpace, type SpaceKey } from "../../../../../src/stores/spaces";
import { shouldShowComponent } from "../../../../../src/customisations/helpers/UIComponents";
import { UIComponent } from "../../../../../src/settings/UIFeature";
import { mkStubRoom, wrapInMatrixClientContext, wrapInSdkContext } from "../../../../test-utils";
import { TestSDKContext } from "../../../TestSDKContext.ts";
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
import { type SpaceNotificationState } from "../../../../../src/stores/notifications/SpaceNotificationState";
import SettingsStore from "../../../../../src/settings/SettingsStore";
import UnwrappedSpacePanel from "../../../../../src/components/views/spaces/SpacePanel";
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
import { Action } from "../../../../../src/dispatcher/actions";
// DND test utilities based on
// https://github.com/colinrobertbrooks/react-beautiful-dnd-test-utils/issues/18#issuecomment-1373388693
enum Keys {
SPACE = 32,
ARROW_LEFT = 37,
ARROW_UP = 38,
ARROW_RIGHT = 39,
ARROW_DOWN = 40,
}
/* oxlint-disable typescript/prefer-literal-enum-member */
enum DragDirection {
LEFT = Keys.ARROW_LEFT,
UP = Keys.ARROW_UP,
RIGHT = Keys.ARROW_RIGHT,
DOWN = Keys.ARROW_DOWN,
}
/* oxlint-enable typescript/prefer-literal-enum-member */
// taken from https://github.com/hello-pangea/dnd/blob/main/test/unit/integration/util/controls.ts#L20
const createTransitionEndEvent = (): Event => {
const event = new Event("transitionend", {
bubbles: true,
cancelable: true,
}) as TransitionEvent;
// cheating and adding property to event as
// TransitionEvent constructor does not exist.
// This is needed because of the following check
// https://github.com/atlassian/react-beautiful-dnd/blob/master/src/view/draggable/draggable.jsx#L130
(event as any).propertyName = "transform";
return event;
};
const pickUp = async (element: HTMLElement) => {
fireEvent.keyDown(element, {
keyCode: Keys.SPACE,
});
await screen.findByText(/You have lifted an item/i);
act(() => {
jest.runOnlyPendingTimers();
});
};
const move = async (element: HTMLElement, direction: DragDirection) => {
fireEvent.keyDown(element, {
keyCode: direction,
});
await screen.findByText(/(You have moved the item | has been combined with)/i);
};
const drop = async (element: HTMLElement) => {
fireEvent.keyDown(element, {
keyCode: Keys.SPACE,
});
fireEvent(element.parentElement!, createTransitionEndEvent());
await screen.findByText(/You have dropped the item/i);
};
jest.mock("../../../../../src/stores/spaces/SpaceStore", () => {
const EventEmitter = jest.requireActual("events");
class MockSpaceStore extends EventEmitter {
invitedSpaces: SpaceKey[] = [];
enabledMetaSpaces: MetaSpace[] = [];
spacePanelSpaces: string[] = [];
activeSpace: SpaceKey = "!space1";
getChildSpaces = () => [] as Room[];
getNotificationState = () => null as SpaceNotificationState | null;
setActiveSpace = jest.fn();
moveRootSpace = jest.fn();
start = jest.fn();
}
return MockSpaceStore;
});
jest.mock("../../../../../src/customisations/helpers/UIComponents", () => ({
shouldShowComponent: jest.fn(),
}));
describe("<SpacePanel />", () => {
const mockClient = {
getUserId: jest.fn().mockReturnValue("@test:test"),
getSafeUserId: jest.fn().mockReturnValue("@test:test"),
getClientWellKnown: jest.fn(),
mxcUrlToHttp: jest.fn(),
getRoom: jest.fn(),
isGuest: jest.fn(),
getAccountData: jest.fn(),
on: jest.fn(),
off: jest.fn(),
removeListener: jest.fn(),
isVersionSupported: jest.fn().mockResolvedValue(true),
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(false),
getAuthMetadata: jest.fn().mockRejectedValue(new Error("Legacy auth")),
} as unknown as MatrixClient;
const sdkContext = new TestSDKContext();
const SpacePanel = wrapInSdkContext(wrapInMatrixClientContext(UnwrappedSpacePanel), sdkContext);
beforeAll(() => {
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);
jest.spyOn(MatrixClientPeg, "safeGet").mockReturnValue(mockClient);
sdkContext._client = mockClient;
});
beforeEach(() => {
sdkContext.spaceStore.enabledMetaSpaces.push(MetaSpace.Home, MetaSpace.Orphans, MetaSpace.VideoRooms);
mocked(shouldShowComponent).mockClear().mockReturnValue(true);
});
afterEach(() => {
cleanup();
});
it("should show all activated MetaSpaces in the correct order", async () => {
const originalGetValue = SettingsStore.getValue;
const spySettingsStore = jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) => {
return setting === "feature_video_rooms" ? true : originalGetValue(setting);
});
render(<SpacePanel />);
// Inspect the order of the rendered MetaSpaces, excluding the "Create a space" button.
const tree = screen.getByRole("tree", { name: "Spaces" });
const spaceButtons = within(tree)
.getAllByRole("treeitem")
.filter((el) => within(el).queryByRole("button", { name: "Create a space" }) === null);
const metaSpaceLabels = Array.from(spaceButtons).map((li) =>
within(li)
.getByRole("button", { name: /^(?!Options$).*/ }) // filter out the 'options' buttons within the buttons
.getAttribute("aria-label"),
);
expect(metaSpaceLabels).toEqual(["Home", "Other rooms", "Conferences"]);
spySettingsStore.mockRestore();
});
describe("create new space button", () => {
it("renders create space button when UIComponent.CreateSpaces component should be shown", () => {
render(<SpacePanel />);
screen.getByTestId("create-space-button");
});
it("does not render create space button when UIComponent.CreateSpaces component should not be shown", () => {
mocked(shouldShowComponent).mockReturnValue(false);
render(<SpacePanel />);
expect(shouldShowComponent).toHaveBeenCalledWith(UIComponent.CreateSpaces);
expect(screen.queryByTestId("create-space-button")).toBeFalsy();
});
it("opens context menu on create space button click", () => {
render(<SpacePanel />);
fireEvent.click(screen.getByTestId("create-space-button"));
screen.getByTestId("create-space-button");
});
});
it("should allow rearranging via drag and drop", async () => {
(sdkContext.spaceStore.spacePanelSpaces as any) = [
mkStubRoom("!room1:server", "Room 1", mockClient),
mkStubRoom("!room2:server", "Room 2", mockClient),
mkStubRoom("!room3:server", "Room 3", mockClient),
];
DMRoomMap.makeShared(mockClient);
jest.useFakeTimers();
const { getByLabelText } = render(<SpacePanel />);
const room1 = getByLabelText("Room 1");
await pickUp(room1);
await move(room1, DragDirection.DOWN);
await drop(room1);
expect(sdkContext.spaceStore.moveRootSpace).toHaveBeenCalledWith(0, 1);
});
it("should be able to open the user menu via dispatcher", async () => {
const { baseElement } = render(<SpacePanel />);
defaultDispatcher.dispatch({ action: Action.ToggleUserMenu });
await waitFor(() => {
// Menu exists outside the component due to Portals, so select it manually.
expect(baseElement.querySelector("div[aria-label='User menu']")).toBeInTheDocument();
});
});
});