Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Allow requesting to join knock rooms via spotlight #11482

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 32 additions & 4 deletions src/components/views/dialogs/spotlight/SpotlightDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import {
RoomType,
Room,
HierarchyRoom,
JoinRule,
} from "matrix-js-sdk/src/matrix";
import { normalize } from "matrix-js-sdk/src/utils";
import React, { ChangeEvent, RefObject, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
Expand Down Expand Up @@ -276,6 +277,10 @@ const roomAriaUnreadLabel = (room: Room, notification: RoomNotificationState): s
}
};

const canAskToJoin = (joinRule?: JoinRule): boolean => {
return SettingsStore.getValue("feature_ask_to_join") && JoinRule.Knock === joinRule;
};

interface IDirectoryOpts {
limit: number;
query: string;
Expand Down Expand Up @@ -514,7 +519,14 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
}, [results, filter]);

const viewRoom = (
room: { roomId: string; roomAlias?: string; autoJoin?: boolean; shouldPeek?: boolean; viaServers?: string[] },
room: {
roomId: string;
roomAlias?: string;
autoJoin?: boolean;
shouldPeek?: boolean;
viaServers?: string[];
joinRule?: IPublicRoomsChunkRoom["join_rule"];
},
persist = false,
viaKeyboard = false,
): void => {
Expand All @@ -538,10 +550,15 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
metricsViaKeyboard: viaKeyboard,
room_id: room.roomId,
room_alias: room.roomAlias,
auto_join: room.autoJoin,
auto_join: room.autoJoin && !canAskToJoin(room.joinRule),
should_peek: room.shouldPeek,
via_servers: room.viaServers,
});

if (canAskToJoin(room.joinRule)) {
defaultDispatcher.dispatch({ action: Action.PromptAskToJoin });
}

onFinished();
};

Expand Down Expand Up @@ -647,12 +664,15 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
}
if (isPublicRoomResult(result)) {
const clientRoom = cli.getRoom(result.publicRoom.room_id);
const joinRule = result.publicRoom.join_rule;
// Element Web currently does not allow guests to join rooms, so we
// instead show them view buttons for all rooms. If the room is not
// world readable, a modal will appear asking you to register first. If
// it is readable, the preview appears as normal.
const showViewButton =
clientRoom?.getMyMembership() === "join" || result.publicRoom.world_readable || cli.isGuest();
clientRoom?.getMyMembership() === "join" ||
(result.publicRoom.world_readable && !canAskToJoin(joinRule)) ||
cli.isGuest();

const listener = (ev: ButtonEvent): void => {
ev.stopPropagation();
Expand All @@ -665,12 +685,20 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
autoJoin: !result.publicRoom.world_readable && !cli.isGuest(),
shouldPeek: result.publicRoom.world_readable || cli.isGuest(),
viaServers: config ? [config.roomServer] : undefined,
joinRule,
},
true,
ev.type !== "click",
);
};

let buttonLabel;
if (showViewButton) {
buttonLabel = _t("action|view");
} else {
buttonLabel = canAskToJoin(joinRule) ? _t("action|ask_to_join") : _t("action|join");
}

return (
<Option
id={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}`}
Expand All @@ -683,7 +711,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
onClick={listener}
tabIndex={-1}
>
{showViewButton ? _t("action|view") : _t("action|join")}
{buttonLabel}
</AccessibleButton>
}
aria-labelledby={`mx_SpotlightDialog_button_result_${result.publicRoom.room_id}_name`}
Expand Down
1 change: 1 addition & 0 deletions src/i18n/strings/en_EN.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
"resend": "Resend",
"next": "Next",
"view": "View",
"ask_to_join": "Ask to join",
"forward": "Forward",
"copy_link": "Copy link",
"logout": "Logout",
Expand Down
73 changes: 73 additions & 0 deletions test/components/views/dialogs/SpotlightDialog-test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
ConnectionError,
IProtocol,
IPublicRoomsChunkRoom,
JoinRule,
MatrixClient,
Room,
RoomMember,
Expand All @@ -38,6 +39,7 @@ import SettingsStore from "../../../../src/settings/SettingsStore";
import { SettingLevel } from "../../../../src/settings/SettingLevel";
import defaultDispatcher from "../../../../src/dispatcher/dispatcher";
import SdkConfig from "../../../../src/SdkConfig";
import { Action } from "../../../../src/dispatcher/actions";

jest.useFakeTimers();

Expand Down Expand Up @@ -574,4 +576,75 @@ describe("Spotlight Dialog", () => {

expect(screen.getByText("Failed to query public rooms")).toBeInTheDocument();
});

describe("knock rooms", () => {
const knockRoom: IPublicRoomsChunkRoom = {
guest_can_join: false,
join_rule: JoinRule.Knock,
num_joined_members: 0,
room_id: "some-room-id",
world_readable: false,
};

const viewRoomParams = {
action: Action.ViewRoom,
metricsTrigger: "WebUnifiedSearch",
metricsViaKeyboard: false,
room_alias: undefined,
room_id: knockRoom.room_id,
should_peek: false,
via_servers: ["example.tld"],
};

beforeEach(() => (mockedClient = mockClient({ rooms: [knockRoom] })));

describe("when disabling feature", () => {
beforeEach(async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) =>
setting === "feature_ask_to_join" ? false : [],
);

render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => {}} />);

// search is debounced
jest.advanceTimersByTime(200);
await flushPromisesWithFakeTimers();

fireEvent.click(screen.getByRole("button", { name: "View" }));
});

it("should not skip to auto join", async () => {
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ ...viewRoomParams, auto_join: true });
});

it("should not prompt ask to join", async () => {
expect(defaultDispatcher.dispatch).not.toHaveBeenCalledWith({ action: Action.PromptAskToJoin });
});
});

describe("when enabling feature", () => {
beforeEach(async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((setting) =>
setting === "feature_ask_to_join" ? true : [],
);
jest.spyOn(mockedClient, "getRoom").mockReturnValue(null);

render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => {}} />);

// search is debounced
jest.advanceTimersByTime(200);
await flushPromisesWithFakeTimers();

fireEvent.click(screen.getByRole("button", { name: "Ask to join" }));
});

it("should skip to auto join", async () => {
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ ...viewRoomParams, auto_join: false });
});

it("should prompt ask to join", async () => {
expect(defaultDispatcher.dispatch).toHaveBeenCalledWith({ action: Action.PromptAskToJoin });
});
});
});
});