mirror of
https://github.com/vector-im/element-web.git
synced 2026-03-04 04:52:04 +01:00
Move the upgraderoom command into its own subdirectory of slash-commands (#31941)
* Move the code for running the upgrade command into its subdir * Replace use of deprecated currentState property in runUpgradeRoomCommand * Move upgraderoom command options into their own file * Move upgraderoom tests into their own file and add a couple
This commit is contained in:
parent
a1be203683
commit
6d7def7b10
@ -44,9 +44,7 @@ import { UIComponent, UIFeature } from "./settings/UIFeature";
|
||||
import { CHAT_EFFECTS } from "./effects";
|
||||
import LegacyCallHandler from "./LegacyCallHandler";
|
||||
import { guessAndSetDMRoom } from "./Rooms";
|
||||
import { upgradeRoom } from "./utils/RoomUpgrade";
|
||||
import DevtoolsDialog from "./components/views/dialogs/DevtoolsDialog";
|
||||
import RoomUpgradeWarningDialog from "./components/views/dialogs/RoomUpgradeWarningDialog";
|
||||
import InfoDialog from "./components/views/dialogs/InfoDialog";
|
||||
import SlashCommandHelpDialog from "./components/views/dialogs/SlashCommandHelpDialog";
|
||||
import { shouldShowComponent } from "./customisations/helpers/UIComponents";
|
||||
@ -61,7 +59,7 @@ import { CommandCategories } from "./slash-commands/interface";
|
||||
import { Command } from "./slash-commands/command";
|
||||
import { goto, join } from "./slash-commands/join";
|
||||
import { manuallyVerifyDevice } from "./components/views/dialogs/ManualDeviceKeyVerificationDialog";
|
||||
import { parseUpgradeRoomArgs } from "./slash-commands/upgraderoom/parseUpgradeRoomArgs";
|
||||
import upgraderoom from "./slash-commands/upgraderoom/upgraderoom";
|
||||
|
||||
export { CommandCategories, Command };
|
||||
|
||||
@ -145,52 +143,7 @@ export const Commands = [
|
||||
},
|
||||
category: CommandCategories.messages,
|
||||
}),
|
||||
new Command({
|
||||
command: "upgraderoom",
|
||||
args: "<new_version> [<additional-creator-user-id> ...]",
|
||||
description: _td("slash_command|upgraderoom"),
|
||||
isEnabled: (cli) => !isCurrentLocalRoom(cli),
|
||||
runFn: function (cli, roomId, threadId, args) {
|
||||
if (!args) {
|
||||
return reject(this.getUsage());
|
||||
}
|
||||
const parsedArgs = parseUpgradeRoomArgs(args);
|
||||
if (parsedArgs) {
|
||||
const room = cli.getRoom(roomId);
|
||||
if (!room?.currentState.mayClientSendStateEvent("m.room.tombstone", cli)) {
|
||||
return reject(new UserFriendlyError("slash_command|upgraderoom_permission_error"));
|
||||
}
|
||||
|
||||
const { finished } = Modal.createDialog(
|
||||
RoomUpgradeWarningDialog,
|
||||
{ roomId: roomId, targetVersion: parsedArgs.targetVersion },
|
||||
/*className=*/ undefined,
|
||||
/*isPriority=*/ false,
|
||||
/*isStatic=*/ true,
|
||||
);
|
||||
|
||||
return success(
|
||||
finished.then(async ([resp]): Promise<void> => {
|
||||
if (!resp?.continue) return;
|
||||
await upgradeRoom(
|
||||
room,
|
||||
parsedArgs.targetVersion,
|
||||
resp.invite,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
undefined,
|
||||
false,
|
||||
parsedArgs.additionalCreators,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
return reject(this.getUsage());
|
||||
},
|
||||
category: CommandCategories.admin,
|
||||
renderingTypes: [TimelineRenderingType.Room],
|
||||
}),
|
||||
upgraderoom,
|
||||
new Command({
|
||||
command: "jumptodate",
|
||||
args: "<YYYY-MM-DD>",
|
||||
|
||||
64
src/slash-commands/upgraderoom/runUpgradeRoomCommand.ts
Normal file
64
src/slash-commands/upgraderoom/runUpgradeRoomCommand.ts
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 { EventTimeline, type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import Modal from "../../Modal";
|
||||
import RoomUpgradeWarningDialog from "../../components/views/dialogs/RoomUpgradeWarningDialog";
|
||||
import { type Command } from "../command";
|
||||
import { UserFriendlyError } from "../../languageHandler";
|
||||
import { parseUpgradeRoomArgs } from "./parseUpgradeRoomArgs";
|
||||
import { reject, success } from "../utils";
|
||||
import { type RunResult } from "../interface";
|
||||
import { upgradeRoom } from "../../utils/RoomUpgrade";
|
||||
|
||||
export function runUpgradeRoomCommand(
|
||||
command: Command,
|
||||
cli: MatrixClient,
|
||||
roomId: string,
|
||||
_threadId: string | null,
|
||||
args?: string,
|
||||
): RunResult {
|
||||
if (!args) {
|
||||
return reject(command.getUsage());
|
||||
}
|
||||
const parsedArgs = parseUpgradeRoomArgs(args);
|
||||
if (parsedArgs) {
|
||||
const room = cli.getRoom(roomId);
|
||||
if (
|
||||
!room?.getLiveTimeline().getState(EventTimeline.FORWARDS)?.mayClientSendStateEvent("m.room.tombstone", cli)
|
||||
) {
|
||||
return reject(new UserFriendlyError("slash_command|upgraderoom_permission_error"));
|
||||
}
|
||||
|
||||
const { finished } = Modal.createDialog(
|
||||
RoomUpgradeWarningDialog,
|
||||
{ roomId: roomId, targetVersion: parsedArgs.targetVersion },
|
||||
/*className=*/ undefined,
|
||||
/*isPriority=*/ false,
|
||||
/*isStatic=*/ true,
|
||||
);
|
||||
|
||||
return success(
|
||||
finished.then(async ([resp]): Promise<void> => {
|
||||
if (!resp?.continue) return;
|
||||
await upgradeRoom(
|
||||
room,
|
||||
parsedArgs.targetVersion,
|
||||
resp.invite,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
undefined,
|
||||
false,
|
||||
parsedArgs.additionalCreators,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
return reject(command.getUsage());
|
||||
}
|
||||
29
src/slash-commands/upgraderoom/upgraderoom.ts
Normal file
29
src/slash-commands/upgraderoom/upgraderoom.ts
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { type MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { _td } from "../../languageHandler";
|
||||
import { isCurrentLocalRoom } from "../utils";
|
||||
import { runUpgradeRoomCommand } from "./runUpgradeRoomCommand";
|
||||
import { Command } from "../command";
|
||||
import { CommandCategories, type RunResult } from "../interface";
|
||||
import { TimelineRenderingType } from "../../contexts/RoomContext";
|
||||
|
||||
const upgraderoom = new Command({
|
||||
command: "upgraderoom",
|
||||
args: "<new_version> [<additional-creator-user-id> ...]",
|
||||
description: _td("slash_command|upgraderoom"),
|
||||
isEnabled: (cli: MatrixClient) => !isCurrentLocalRoom(cli),
|
||||
runFn: function (cli: MatrixClient, roomId: string, threadId: string | null, args?: string): RunResult {
|
||||
return runUpgradeRoomCommand(this, cli, roomId, threadId, args);
|
||||
},
|
||||
category: CommandCategories.admin,
|
||||
renderingTypes: [TimelineRenderingType.Room],
|
||||
});
|
||||
|
||||
export default upgraderoom;
|
||||
@ -22,10 +22,6 @@ import { warnSelfDemote } from "../../src/components/views/right_panel/UserInfo"
|
||||
import dispatcher from "../../src/dispatcher/dispatcher";
|
||||
import QuestionDialog from "../../src/components/views/dialogs/QuestionDialog";
|
||||
import ErrorDialog from "../../src/components/views/dialogs/ErrorDialog";
|
||||
import RoomUpgradeWarningDialog, {
|
||||
type IFinishedOpts,
|
||||
} from "../../src/components/views/dialogs/RoomUpgradeWarningDialog";
|
||||
import { parseUpgradeRoomArgs } from "../../src/slash-commands/upgraderoom/parseUpgradeRoomArgs";
|
||||
|
||||
jest.mock("../../src/components/views/right_panel/UserInfo");
|
||||
|
||||
@ -123,67 +119,6 @@ describe("SlashCommands", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("/upgraderoom", () => {
|
||||
beforeEach(() => {
|
||||
command = findCommand("upgraderoom")!;
|
||||
setCurrentRoom();
|
||||
});
|
||||
|
||||
it("should be enabled by default", () => {
|
||||
expect(command.isEnabled(client, roomId)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return usage if given no args", () => {
|
||||
expect(command.run(client, roomId, null, undefined).error).toBe(command.getUsage());
|
||||
expect(command.run(client, roomId, null, "").error).toBe(command.getUsage());
|
||||
});
|
||||
|
||||
it("should accept arguments of a room version with no additional creators", () => {
|
||||
expect(parseUpgradeRoomArgs("12")).toEqual({ targetVersion: "12" });
|
||||
});
|
||||
|
||||
it("should accept arguments of a room version and additional creators", () => {
|
||||
expect(parseUpgradeRoomArgs("13 @u:s.co")).toEqual({
|
||||
targetVersion: "13",
|
||||
additionalCreators: ["@u:s.co"],
|
||||
});
|
||||
|
||||
expect(parseUpgradeRoomArgs("14 @u:s.co @v:s.co @w:z.uk")).toEqual({
|
||||
targetVersion: "14",
|
||||
additionalCreators: ["@u:s.co", "@v:s.co", "@w:z.uk"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should upgrade the room when given valid arguments", async () => {
|
||||
// Given we mock out creating dialogs and upgrading rooms
|
||||
const createDialog = jest.spyOn(Modal, "createDialog");
|
||||
const upgradeRoom = jest.fn().mockResolvedValue({ replacement_room: "!newroom" });
|
||||
const resp: IFinishedOpts = { continue: true, invite: false };
|
||||
createDialog.mockReturnValue({
|
||||
finished: Promise.resolve([resp]),
|
||||
close: jest.fn(),
|
||||
});
|
||||
client.upgradeRoom = upgradeRoom;
|
||||
|
||||
// When we run a room upgrade
|
||||
const result = command.run(client, roomId, null, "12 @foo:bar.com @baz:qux.uk");
|
||||
expect(result.promise).toBeDefined();
|
||||
await result.promise;
|
||||
|
||||
// Then we warned the user
|
||||
expect(createDialog).toHaveBeenCalledWith(
|
||||
RoomUpgradeWarningDialog,
|
||||
{ roomId: "!room:example.com", targetVersion: "12" },
|
||||
undefined,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
|
||||
// And when they said yes, we called into upgradeRoom
|
||||
expect(upgradeRoom).toHaveBeenCalledWith("!room:example.com", "12", ["@foo:bar.com", "@baz:qux.uk"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("/op", () => {
|
||||
beforeEach(() => {
|
||||
command = findCommand("op")!;
|
||||
|
||||
136
test/unit-tests/slash-commands/upgraderoom-test.tsx
Normal file
136
test/unit-tests/slash-commands/upgraderoom-test.tsx
Normal file
@ -0,0 +1,136 @@
|
||||
/*
|
||||
* 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 { mocked } from "jest-mock";
|
||||
import { type MatrixClient, Room } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import RoomUpgradeWarningDialog, {
|
||||
type IFinishedOpts,
|
||||
} from "../../../src/components/views/dialogs/RoomUpgradeWarningDialog";
|
||||
import { type Command, Commands } from "../../../src/SlashCommands";
|
||||
import { SdkContextClass } from "../../../src/contexts/SDKContext";
|
||||
import { createTestClient } from "../../test-utils";
|
||||
import { parseUpgradeRoomArgs } from "../../../src/slash-commands/upgraderoom/parseUpgradeRoomArgs";
|
||||
import Modal from "../../../src/Modal";
|
||||
|
||||
describe("/upgraderoom", () => {
|
||||
const roomId = "!room:example.com";
|
||||
|
||||
function findCommand(cmd: string): Command | undefined {
|
||||
return Commands.find((command: Command) => command.command === cmd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up an upgraderoom test.
|
||||
*
|
||||
* @param continueUpgrade if true, simulates the user clicking Continue in
|
||||
* the "Upgrade Room" dialog. If false, simulates the
|
||||
* user clicking Cancel.
|
||||
*/
|
||||
function setUp(continueUpgrade: boolean): {
|
||||
command: Command;
|
||||
client: MatrixClient;
|
||||
createDialog: unknown;
|
||||
upgradeRoom: unknown;
|
||||
} {
|
||||
jest.clearAllMocks();
|
||||
|
||||
const command = findCommand("upgraderoom")!;
|
||||
const client = createTestClient();
|
||||
|
||||
jest.spyOn(SdkContextClass.instance.roomViewStore, "getRoomId");
|
||||
mocked(SdkContextClass.instance.roomViewStore.getRoomId).mockReturnValue(roomId);
|
||||
mocked(client.getRoom).mockImplementation((rId: string): Room | null => {
|
||||
if (rId === roomId) return new Room(roomId, client, client.getSafeUserId());
|
||||
return null;
|
||||
});
|
||||
|
||||
const createDialog = jest.spyOn(Modal, "createDialog");
|
||||
const upgradeRoom = jest.fn().mockResolvedValue({ replacement_room: "!newroom" });
|
||||
const resp: IFinishedOpts = { continue: continueUpgrade, invite: false };
|
||||
createDialog.mockReturnValue({
|
||||
finished: Promise.resolve([resp]),
|
||||
close: jest.fn(),
|
||||
});
|
||||
client.upgradeRoom = upgradeRoom;
|
||||
|
||||
return { command, client, createDialog, upgradeRoom };
|
||||
}
|
||||
|
||||
it("should be enabled by default", () => {
|
||||
const { command, client } = setUp(false);
|
||||
expect(command.isEnabled(client, roomId)).toBe(true);
|
||||
});
|
||||
|
||||
it("should return usage if given no args", () => {
|
||||
const { command, client } = setUp(false);
|
||||
|
||||
expect(command.run(client, roomId, null, undefined).error).toBe(command.getUsage());
|
||||
expect(command.run(client, roomId, null, "").error).toBe(command.getUsage());
|
||||
expect(command.run(client, roomId, null, " ").error).toBe(command.getUsage());
|
||||
});
|
||||
|
||||
it("should accept arguments of a room version with no additional creators", () => {
|
||||
expect(parseUpgradeRoomArgs("12")).toEqual({ targetVersion: "12" });
|
||||
});
|
||||
|
||||
it("should accept arguments of a room version and additional creators", () => {
|
||||
expect(parseUpgradeRoomArgs("13 @u:s.co")).toEqual({
|
||||
targetVersion: "13",
|
||||
additionalCreators: ["@u:s.co"],
|
||||
});
|
||||
|
||||
expect(parseUpgradeRoomArgs("14 @u:s.co @v:s.co @w:z.uk")).toEqual({
|
||||
targetVersion: "14",
|
||||
additionalCreators: ["@u:s.co", "@v:s.co", "@w:z.uk"],
|
||||
});
|
||||
});
|
||||
|
||||
it("should upgrade the room when given valid arguments", async () => {
|
||||
// Given the user clicks continue in the Upgrade Room dialog
|
||||
const { command, client, createDialog, upgradeRoom } = setUp(true);
|
||||
|
||||
// When we type /upgraderoom ...
|
||||
const result = command.run(client, roomId, null, "12 @foo:bar.com @baz:qux.uk");
|
||||
expect(result.promise).toBeDefined();
|
||||
await result.promise;
|
||||
|
||||
// Then we warned the user
|
||||
expect(createDialog).toHaveBeenCalledWith(
|
||||
RoomUpgradeWarningDialog,
|
||||
{ roomId: "!room:example.com", targetVersion: "12" },
|
||||
undefined,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
|
||||
// And when they said yes, we called into upgradeRoom
|
||||
expect(upgradeRoom).toHaveBeenCalledWith("!room:example.com", "12", ["@foo:bar.com", "@baz:qux.uk"]);
|
||||
});
|
||||
|
||||
it("should not upgrade the room if the user changes their mind", async () => {
|
||||
// Given the user cancels the upgrade dialog
|
||||
const { command, client, createDialog, upgradeRoom } = setUp(false);
|
||||
|
||||
// When we type /upgraderoom ...
|
||||
const result = command.run(client, roomId, null, "12 @foo:bar.com @baz:qux.uk");
|
||||
expect(result.promise).toBeDefined();
|
||||
await result.promise;
|
||||
|
||||
// Then we warned the user
|
||||
expect(createDialog).toHaveBeenCalledWith(
|
||||
RoomUpgradeWarningDialog,
|
||||
{ roomId: "!room:example.com", targetVersion: "12" },
|
||||
undefined,
|
||||
false,
|
||||
true,
|
||||
);
|
||||
|
||||
// And when they said no, we did not call into upgradeRoom
|
||||
expect(upgradeRoom).not.toHaveBeenCalledWith("!room:example.com", "12", ["@foo:bar.com", "@baz:qux.uk"]);
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user