Skip to content

Commit 73fa278

Browse files
authored
Move room name, avatar, and topic to IOpts. (#30981)
1 parent a9bb046 commit 73fa278

File tree

7 files changed

+380
-32
lines changed

7 files changed

+380
-32
lines changed

src/components/structures/SpaceRoomView.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -329,8 +329,8 @@ const SpaceSetupFirstRooms: React.FC<{
329329
return createRoom(space.client, {
330330
createOpts: {
331331
preset: isPublic ? Preset.PublicChat : Preset.PrivateChat,
332-
name,
333332
},
333+
name,
334334
spinner: false,
335335
encryption: false,
336336
andView: false,
@@ -423,7 +423,7 @@ const SpaceSetupPublicShare: React.FC<ISpaceSetupPublicShareProps> = ({
423423
<div className="mx_SpaceRoomView_publicShare">
424424
<h1>
425425
{_t("create_space|share_heading", {
426-
name: justCreatedOpts?.createOpts?.name || space.name,
426+
name: justCreatedOpts?.name || space.name,
427427
})}
428428
</h1>
429429
<div className="mx_SpaceRoomView_description">{_t("create_space|share_description")}</div>
@@ -449,7 +449,7 @@ const SpaceSetupPrivateScope: React.FC<{
449449
<h1>{_t("create_space|private_personal_heading")}</h1>
450450
<div className="mx_SpaceRoomView_description">
451451
{_t("create_space|private_personal_description", {
452-
name: justCreatedOpts?.createOpts?.name || space.name,
452+
name: justCreatedOpts?.name || space.name,
453453
})}
454454
</div>
455455

@@ -686,7 +686,7 @@ export default class SpaceRoomView extends React.PureComponent<IProps, IState> {
686686
<SpaceSetupFirstRooms
687687
space={this.props.space}
688688
title={_t("create_space|setup_rooms_community_heading", {
689-
spaceName: this.props.justCreatedOpts?.createOpts?.name || this.props.space.name,
689+
spaceName: this.props.justCreatedOpts?.name || this.props.space.name,
690690
})}
691691
description={
692692
<>

src/components/views/dialogs/CreateRoomDialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
126126
const opts: IOpts = {};
127127
const createOpts: IOpts["createOpts"] = (opts.createOpts = {});
128128
opts.roomType = this.props.type;
129-
createOpts.name = this.state.name;
129+
opts.name = this.state.name;
130130

131131
if (this.state.joinRule === JoinRule.Public) {
132132
createOpts.visibility = Visibility.Public;
@@ -139,7 +139,7 @@ export default class CreateRoomDialog extends React.Component<IProps, IState> {
139139
}
140140

141141
if (this.state.topic) {
142-
createOpts.topic = this.state.topic;
142+
opts.topic = this.state.topic;
143143
}
144144
if (this.state.noFederate) {
145145
createOpts.creation_content = { "m.federate": false };

src/createRoom.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,19 @@ import { ElementCallEventType, ElementCallMemberEventType } from "./call-types";
4949

5050
export interface IOpts {
5151
dmUserId?: string;
52-
createOpts?: ICreateRoomOpts;
52+
/**
53+
* The name of the room to be created.
54+
*/
55+
name?: string;
56+
/**
57+
* The topic for the room.
58+
*/
59+
topic?: string;
60+
/**
61+
* Additional options to pass to the room creation API.
62+
* Note: "name", "topic", and "avatar" should be set via their respective properties in IOpts.
63+
*/
64+
createOpts?: Omit<ICreateRoomOpts, "name" | "topic" | "avatar">;
5365
spinner?: boolean;
5466
guestAccess?: boolean;
5567
encryption?: boolean;
@@ -251,6 +263,14 @@ export default async function createRoom(client: MatrixClient, opts: IOpts): Pro
251263
});
252264
}
253265

266+
if (opts.name) {
267+
createOpts.name = opts.name;
268+
}
269+
270+
if (opts.topic) {
271+
createOpts.topic = opts.topic;
272+
}
273+
254274
if (opts.avatar) {
255275
let url = opts.avatar;
256276
if (opts.avatar instanceof File) {

test/unit-tests/components/structures/SpaceRoomView-test.tsx

Lines changed: 176 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ Please see LICENSE files in the repository root for full details.
77

88
import React from "react";
99
import { mocked, type MockedObject } from "jest-mock";
10-
import { type MatrixClient, MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
11-
import { render, cleanup, screen, fireEvent } from "jest-matrix-react";
10+
import { type MatrixClient, MatrixEvent, Preset, Room } from "matrix-js-sdk/src/matrix";
11+
import { render, cleanup, screen, fireEvent, waitFor, act } from "jest-matrix-react";
1212

1313
import { stubClient, mockPlatformPeg, unmockPlatformPeg, withClientContextRenderOptions } from "../../../test-utils";
1414
import { RightPanelPhases } from "../../../../src/stores/right-panel/RightPanelStorePhases";
@@ -17,6 +17,8 @@ import ResizeNotifier from "../../../../src/utils/ResizeNotifier.ts";
1717
import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalinks.ts";
1818
import RightPanelStore from "../../../../src/stores/right-panel/RightPanelStore.ts";
1919
import DMRoomMap from "../../../../src/utils/DMRoomMap.ts";
20+
import { type IOpts } from "../../../../src/createRoom.ts";
21+
import SpaceStore from "../../../../src/stores/spaces/SpaceStore.ts";
2022

2123
describe("SpaceRoomView", () => {
2224
let cli: MockedObject<MatrixClient>;
@@ -86,7 +88,7 @@ describe("SpaceRoomView", () => {
8688
cleanup();
8789
});
8890

89-
const renderSpaceRoomView = async (): Promise<ReturnType<typeof render>> => {
91+
const renderSpaceRoomView = async (justCreatedOpts?: IOpts): Promise<ReturnType<typeof render>> => {
9092
const resizeNotifier = new ResizeNotifier();
9193
const permalinkCreator = new RoomPermalinkCreator(space);
9294

@@ -97,6 +99,7 @@ describe("SpaceRoomView", () => {
9799
permalinkCreator={permalinkCreator}
98100
onJoinButtonClicked={jest.fn()}
99101
onRejectButtonClicked={jest.fn()}
102+
justCreatedOpts={justCreatedOpts}
100103
/>,
101104
withClientContextRenderOptions(cli),
102105
);
@@ -113,5 +116,175 @@ describe("SpaceRoomView", () => {
113116

114117
expect(spy).toHaveBeenCalledWith({ phase: RightPanelPhases.MemberList });
115118
});
119+
120+
it("shows SpaceLandingAddButton context menu when Add button is clicked", async () => {
121+
await renderSpaceRoomView();
122+
await expect(screen.findByText("Welcome to")).resolves.toBeVisible();
123+
124+
const addButton = screen.getByRole("button", { name: /add/i });
125+
fireEvent.click(addButton);
126+
127+
expect(await screen.findByText(/new room/i)).toBeInTheDocument();
128+
expect(screen.getByText(/add existing room/i)).toBeInTheDocument();
129+
});
130+
});
131+
132+
describe("Spaces: creating a new community space", () => {
133+
it("asks what topics you want to discuss, creates rooms for them and offers to share", async () => {
134+
cli.createRoom.mockResolvedValueOnce({ room_id: "room1" }).mockResolvedValueOnce({ room_id: "room2" });
135+
SpaceStore.instance.addRoomToSpace = jest.fn();
136+
137+
// Given we are creating a space
138+
const view = await renderSpaceRoomView({
139+
createOpts: { preset: Preset.PublicChat },
140+
name: "My MySpace Space",
141+
});
142+
143+
// Then we are asked what topics we want
144+
expect(
145+
view.getByRole("heading", { name: "What are some things you want to discuss in My MySpace Space?" }),
146+
).toBeInTheDocument();
147+
148+
// And some defaults are suggested
149+
expect(view.getByPlaceholderText(/general/i)).toBeInTheDocument();
150+
expect(view.getByPlaceholderText(/random/i)).toBeInTheDocument();
151+
expect(view.getByPlaceholderText(/support/i)).toBeInTheDocument();
152+
153+
// When we enter some room names
154+
const input1 = view.getAllByRole("textbox")[0];
155+
const input2 = view.getAllByRole("textbox")[1];
156+
fireEvent.change(input1, { target: { value: "Room 1" } });
157+
fireEvent.change(input2, { target: { value: "Room 2" } });
158+
159+
// And click "Continue"
160+
const button = view.getByRole("button", { name: "Continue" });
161+
fireEvent.click(button);
162+
163+
// Then we create 2 rooms
164+
await waitFor(() => {
165+
expect(cli.createRoom).toHaveBeenCalledTimes(2);
166+
});
167+
168+
// And offer the user to share this space
169+
await waitFor(() =>
170+
expect(view.getByRole("heading", { name: "Share My MySpace Space" })).toBeInTheDocument(),
171+
);
172+
expect(view.getByRole("button", { name: /Share invite link/ })).toBeInTheDocument();
173+
174+
// And allow them to continue to the first room
175+
expect(view.getByRole("button", { name: "Go to my first room" })).toBeInTheDocument();
176+
});
177+
178+
it("shows 'Skip for now' when all fields are empty, 'Continue' when any field is filled", async () => {
179+
// Given we are creating a space
180+
const view = await renderSpaceRoomView({
181+
createOpts: { preset: Preset.PublicChat },
182+
});
183+
184+
// When we clear all the topics
185+
view.getAllByRole("textbox").forEach((input) => fireEvent.change(input, { target: { value: "" } }));
186+
187+
// Then the button reads "Skip for now"
188+
expect(view.getByRole("button", { name: "Skip for now" })).toBeVisible();
189+
190+
// But when we enter a topic
191+
fireEvent.change(view.getAllByRole("textbox")[0], { target: { value: "Room" } });
192+
193+
// Then the button says "Continue"
194+
expect(view.getByRole("button", { name: "Continue" })).toBeVisible();
195+
});
196+
197+
it("shows error message if room creation fails", async () => {
198+
// Given we are creating a space
199+
const view = await renderSpaceRoomView({
200+
createOpts: { preset: Preset.PublicChat },
201+
});
202+
203+
// And when we create a room it will fail
204+
cli.createRoom.mockRejectedValue(new Error("fail"));
205+
206+
// When we create the space
207+
fireEvent.change(view.getAllByRole("textbox")[0], { target: { value: "Room A" } });
208+
fireEvent.click(view.getByRole("button", { name: "Continue" }));
209+
210+
// Then we display an error message because it failed
211+
await waitFor(() => {
212+
expect(
213+
view.getByText((content) => content.toLowerCase().includes("failed to create initial space rooms")),
214+
).toBeInTheDocument();
215+
});
216+
});
217+
218+
it("disables button and shows 'Creating rooms' while busy", async () => {
219+
// Given we are creating a space
220+
const view = await renderSpaceRoomView({
221+
createOpts: { preset: Preset.PublicChat },
222+
});
223+
224+
// And creating a room will be slow
225+
cli.createRoom.mockImplementation(
226+
() =>
227+
new Promise(() => {
228+
// This promise never resolves
229+
}),
230+
);
231+
232+
// When we create the space
233+
fireEvent.change(view.getAllByRole("textbox")[0], { target: { value: "Room A" } });
234+
fireEvent.click(view.getByRole("button", { name: "Continue" }));
235+
236+
// Then the "Creating rooms..." message is displayed
237+
const button = view.getByRole("button");
238+
expect(button).toBeDisabled();
239+
expect(button).toHaveValue("Creating rooms…"); // Note the ellipsis
240+
});
241+
});
242+
243+
describe("Spaces: creating a new private space", () => {
244+
it("creates rooms inside a private space for a team", async () => {
245+
cli.createRoom.mockResolvedValueOnce({ room_id: "room1" }).mockResolvedValueOnce({ room_id: "room2" });
246+
SpaceStore.instance.addRoomToSpace = jest.fn();
247+
248+
// When I create a private space
249+
const view = await renderSpaceRoomView({
250+
createOpts: { preset: Preset.PrivateChat },
251+
name: "Private space",
252+
topic: "a private space for team A",
253+
});
254+
255+
// Then I am asked whether it's individual or team
256+
expect(view.getByRole("heading", { name: "Who are you working with?" })).toBeInTheDocument();
257+
258+
// And when I say team
259+
act(() =>
260+
view
261+
.getByRole("button", {
262+
name: "Me and my teammates A private space for you and your teammates",
263+
})
264+
.click(),
265+
);
266+
267+
// Then I am asked what rooms to create
268+
expect(view.getByRole("heading", { name: "What projects are your team working on?" })).toBeInTheDocument();
269+
270+
expect(view.getByPlaceholderText(/general/i)).toBeInTheDocument();
271+
expect(view.getByPlaceholderText(/random/i)).toBeInTheDocument();
272+
expect(view.getByPlaceholderText(/support/i)).toBeInTheDocument();
273+
274+
// And when I enter some room names
275+
const input1 = view.getAllByRole("textbox")[0];
276+
const input2 = view.getAllByRole("textbox")[1];
277+
fireEvent.change(input1, { target: { value: "Room 1" } });
278+
fireEvent.change(input2, { target: { value: "Room 2" } });
279+
280+
// And click "Continue"
281+
const button = view.getByRole("button", { name: "Continue" });
282+
fireEvent.click(button);
283+
284+
// Then the rooms are created
285+
await waitFor(() => {
286+
expect(cli.createRoom).toHaveBeenCalledTimes(2);
287+
});
288+
});
116289
});
117290
});

0 commit comments

Comments
 (0)