Polish & delabs Exploring public spaces feature (#11423)

* Iterate search public spaces UX

* Tweak iconography in spotlight

* Delabs `Exploring public spaces`

* Tweak msc3827 v1.4 support discovery

* i18n

* Delete stale test

* Fix tests

* Iterate

* Iterate PR based on review

* Improve types

* Add shortcut to search for public spaces to create space menu

* Update import

* Add org.matrix.msc3827.stable filtering

* Fix tests

* silence some errors
This commit is contained in:
Michael Telatynski 2023-08-21 10:39:20 +01:00 committed by GitHub
parent d81f71f993
commit dd6097c568
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 180 additions and 107 deletions

View file

@ -218,20 +218,20 @@ describe("Spotlight", () => {
cy.openSpotlightDialog().within(() => {
cy.wait(1000); // wait for the dialog to settle, otherwise our keypresses might race with an update
// initially, publicrooms should be highlighted (because there are no other suggestions)
cy.get("#mx_SpotlightDialog_button_explorePublicRooms").should("have.attr", "aria-selected", "true");
// initially, public spaces should be highlighted (because there are no other suggestions)
cy.get("#mx_SpotlightDialog_button_explorePublicSpaces").should("have.attr", "aria-selected", "true");
// hitting enter should enable the publicrooms filter
// hitting enter should enable the public rooms filter
cy.spotlightSearch().type("{enter}");
cy.get(".mx_SpotlightDialog_filter").should("contain", "Public rooms");
cy.get(".mx_SpotlightDialog_filter").should("contain", "Public spaces");
cy.spotlightSearch().type("{backspace}");
cy.get(".mx_SpotlightDialog_filter").should("not.exist");
cy.spotlightSearch().type("{downArrow}");
cy.spotlightSearch().type("{downArrow}");
cy.get("#mx_SpotlightDialog_button_startChat").should("have.attr", "aria-selected", "true");
cy.get("#mx_SpotlightDialog_button_explorePublicRooms").should("have.attr", "aria-selected", "true");
cy.spotlightSearch().type("{enter}");
cy.get(".mx_SpotlightDialog_filter").should("contain", "People");
cy.get(".mx_SpotlightDialog_filter").should("contain", "Public rooms");
cy.spotlightSearch().type("{backspace}");
cy.get(".mx_SpotlightDialog_filter").should("not.exist");
});

View file

@ -97,7 +97,11 @@ limitations under the License.
}
&.mx_SpotlightDialog_filterPublicRooms::before {
mask-image: url("$(res)/img/element-icons/roomlist/explore.svg");
mask-image: url("$(res)/img/element-icons/roomlist/hash-circle.svg");
}
&.mx_SpotlightDialog_filterPublicSpaces::before {
mask-image: url("$(res)/img/element-icons/spaces.svg");
}
.mx_SpotlightDialog_filter--close {
@ -408,6 +412,7 @@ limitations under the License.
.mx_SpotlightDialog_startChat,
.mx_SpotlightDialog_joinRoomAlias,
.mx_SpotlightDialog_explorePublicRooms,
.mx_SpotlightDialog_explorePublicSpaces,
.mx_SpotlightDialog_startGroupChat {
padding-left: $spacing-32;
position: relative;
@ -436,7 +441,11 @@ limitations under the License.
}
.mx_SpotlightDialog_explorePublicRooms::before {
mask-image: url("$(res)/img/element-icons/roomlist/explore.svg");
mask-image: url("$(res)/img/element-icons/roomlist/hash-circle.svg");
}
.mx_SpotlightDialog_explorePublicSpaces::before {
mask-image: url("$(res)/img/element-icons/spaces.svg");
}
.mx_SpotlightDialog_startGroupChat::before {

View file

@ -0,0 +1,3 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M2 12.1638C2 6.64097 6.47715 2.16382 12 2.16382C17.5228 2.16382 22 6.64097 22 12.1638C22 17.6867 17.5228 22.1638 12 22.1638C6.47715 22.1638 2 17.6867 2 12.1638ZM10.2814 7.58052L7.41682 7.58052L7.41682 10.4451L10.2814 10.4451L10.2814 7.58052ZM7.41682 6.43469C6.784 6.43469 6.271 6.94769 6.271 7.58052V10.4451C6.271 11.0779 6.784 11.5909 7.41682 11.5909H10.2814C10.9142 11.5909 11.4272 11.0779 11.4272 10.4451V7.58052C11.4272 6.9477 10.9142 6.43469 10.2814 6.43469H7.41682ZM7.41682 13.9462L10.2814 13.9462L10.2814 16.8108L7.41682 16.8108L7.41682 13.9462ZM6.271 13.9462C6.271 13.3134 6.784 12.8004 7.41682 12.8004H10.2814C10.9142 12.8004 11.4272 13.3134 11.4272 13.9462V16.8108C11.4272 17.4436 10.9142 17.9566 10.2814 17.9566H7.41682C6.784 17.9566 6.271 17.4436 6.271 16.8108V13.9462ZM16.6471 7.58052L13.7825 7.58052L13.7825 10.4451L16.6471 10.4451L16.6471 7.58052ZM13.7825 6.43469C13.1497 6.43469 12.6367 6.94769 12.6367 7.58052V10.4451C12.6367 11.0779 13.1497 11.5909 13.7825 11.5909H16.6471C17.2799 11.5909 17.7929 11.0779 17.7929 10.4451V7.58052C17.7929 6.9477 17.2799 6.43469 16.6471 6.43469H13.7825ZM15.2148 16.8108C14.4238 16.8108 13.7825 16.1695 13.7825 15.3785C13.7825 14.5875 14.4238 13.9462 15.2148 13.9462C16.0058 13.9462 16.6471 14.5875 16.6471 15.3785C16.6471 16.1695 16.0058 16.8108 15.2148 16.8108ZM12.6367 15.3785C12.6367 13.9546 13.7909 12.8004 15.2148 12.8004C16.6386 12.8004 17.7929 13.9546 17.7929 15.3785C17.7929 16.8023 16.6386 17.9566 15.2148 17.9566C13.7909 17.9566 12.6367 16.8023 12.6367 15.3785Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

@ -140,12 +140,13 @@ import { SdkContextClass, SDKContext } from "../../contexts/SDKContext";
import { viewUserDeviceSettings } from "../../actions/handlers/viewUserDeviceSettings";
import { cleanUpBroadcasts, VoiceBroadcastResumer } from "../../voice-broadcast";
import GenericToast from "../views/toasts/GenericToast";
import RovingSpotlightDialog, { Filter } from "../views/dialogs/spotlight/SpotlightDialog";
import RovingSpotlightDialog from "../views/dialogs/spotlight/SpotlightDialog";
import { findDMForUser } from "../../utils/dm/findDMForUser";
import { Linkify } from "../../HtmlUtils";
import { NotificationColor } from "../../stores/notifications/NotificationColor";
import { UserTab } from "../views/dialogs/UserTab";
import { shouldSkipSetupEncryption } from "../../utils/crypto/shouldSkipSetupEncryption";
import { Filter } from "../views/dialogs/spotlight/Filter";
// legacy export
export { default as Views } from "../../Views";
@ -898,6 +899,18 @@ export default class MatrixChat extends React.PureComponent<IProps, IState> {
break;
}
case Action.OpenSpotlight:
Modal.createDialog(
RovingSpotlightDialog,
{
initialText: payload.initialText,
initialFilter: payload.initialFilter,
},
"mx_SpotlightDialog_wrapper",
false,
true,
);
break;
}
};

View file

@ -22,9 +22,8 @@ import defaultDispatcher from "../../dispatcher/dispatcher";
import { ActionPayload } from "../../dispatcher/payloads";
import { IS_MAC, Key } from "../../Keyboard";
import { _t } from "../../languageHandler";
import Modal from "../../Modal";
import SpotlightDialog from "../views/dialogs/spotlight/SpotlightDialog";
import AccessibleButton from "../views/elements/AccessibleButton";
import { Action } from "../../dispatcher/actions";
interface IProps {
isMinimized: boolean;
@ -44,7 +43,7 @@ export default class RoomSearch extends React.PureComponent<IProps> {
}
private openSpotlight(): void {
Modal.createDialog(SpotlightDialog, {}, "mx_SpotlightDialog_wrapper", false, true);
defaultDispatcher.fire(Action.OpenSpotlight);
}
private onAction = (payload: ActionPayload): void => {

View file

@ -0,0 +1,21 @@
/*
Copyright 2021 - 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
export enum Filter {
People,
PublicRooms,
PublicSpaces,
}

View file

@ -71,7 +71,6 @@ import DecoratedRoomAvatar from "../../avatars/DecoratedRoomAvatar";
import { SearchResultAvatar } from "../../avatars/SearchResultAvatar";
import { NetworkDropdown } from "../../directory/NetworkDropdown";
import AccessibleButton, { ButtonEvent } from "../../elements/AccessibleButton";
import LabelledCheckbox from "../../elements/LabelledCheckbox";
import Spinner from "../../elements/Spinner";
import NotificationBadge from "../../rooms/NotificationBadge";
import BaseDialog from "../BaseDialog";
@ -85,6 +84,7 @@ import RoomAvatar from "../../avatars/RoomAvatar";
import { useFeatureEnabled } from "../../../../hooks/useSettings";
import { filterBoolean } from "../../../../utils/arrays";
import { transformSearchTerm } from "../../../../utils/SearchInput";
import { Filter } from "./Filter";
const MAX_RECENT_SEARCHES = 10;
const SECTION_LIMIT = 50; // only show 50 results per section for performance reasons
@ -100,11 +100,11 @@ function refIsForRecentlyViewed(ref?: RefObject<HTMLElement>): boolean {
return ref?.current?.id?.startsWith("mx_SpotlightDialog_button_recentlyViewed_") === true;
}
function getRoomTypes(showRooms: boolean, showSpaces: boolean): Set<RoomType | null> {
function getRoomTypes(filter: Filter | null): Set<RoomType | null> {
const roomTypes = new Set<RoomType | null>();
if (showRooms) roomTypes.add(null);
if (showSpaces) roomTypes.add(RoomType.Space);
if (filter === Filter.PublicRooms) roomTypes.add(null);
if (filter === Filter.PublicSpaces) roomTypes.add(RoomType.Space);
return roomTypes;
}
@ -114,12 +114,7 @@ enum Section {
Rooms,
Spaces,
Suggestions,
PublicRooms,
}
export enum Filter {
People,
PublicRooms,
PublicRoomsAndSpaces,
}
function filterToLabel(filter: Filter): string {
@ -128,6 +123,8 @@ function filterToLabel(filter: Filter): string {
return _t("People");
case Filter.PublicRooms:
return _t("Public rooms");
case Filter.PublicSpaces:
return _t("Public spaces");
}
}
@ -168,8 +165,8 @@ const isMemberResult = (result: any): result is IMemberResult => !!result?.membe
const toPublicRoomResult = (publicRoom: IPublicRoomsChunkRoom): IPublicRoomResult => ({
publicRoom,
section: Section.PublicRooms,
filter: [Filter.PublicRooms],
section: Section.PublicRoomsAndSpaces,
filter: [Filter.PublicRooms, Filter.PublicSpaces],
query: filterBoolean([
publicRoom.room_id.toLowerCase(),
publicRoom.canonical_alias?.toLowerCase(),
@ -308,7 +305,16 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
const [inviteLinkCopied, setInviteLinkCopied] = useState<boolean>(false);
const trimmedQuery = useMemo(() => query.trim(), [query]);
const exploringPublicSpacesEnabled = useFeatureEnabled("feature_exploring_public_spaces");
const [supportsSpaceFiltering, setSupportsSpaceFiltering] = useState(true); // assume it does until we find out it doesn't
useEffect(() => {
cli.isVersionSupported("v1.4")
.then((supported) => {
return supported || cli.doesServerSupportUnstableFeature("org.matrix.msc3827.stable");
})
.then((supported) => {
setSupportsSpaceFiltering(supported);
});
}, [cli]);
const {
loading: publicRoomsLoading,
@ -319,21 +325,23 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
search: searchPublicRooms,
error: publicRoomsError,
} = usePublicRoomDirectory();
const [showRooms, setShowRooms] = useState(true);
const [showSpaces, setShowSpaces] = useState(false);
const { loading: peopleLoading, users: userDirectorySearchResults, search: searchPeople } = useUserDirectory();
const { loading: profileLoading, profile, search: searchProfileInfo } = useProfileInfo();
const searchParams: [IDirectoryOpts] = useMemo(
() => [
{
query: trimmedQuery,
roomTypes: getRoomTypes(showRooms, showSpaces),
roomTypes: getRoomTypes(filter),
limit: SECTION_LIMIT,
},
],
[trimmedQuery, showRooms, showSpaces],
[trimmedQuery, filter],
);
useDebouncedCallback(
filter === Filter.PublicRooms || filter === Filter.PublicSpaces,
searchPublicRooms,
searchParams,
);
useDebouncedCallback(filter === Filter.PublicRooms, searchPublicRooms, searchParams);
useDebouncedCallback(filter === Filter.People, searchPeople, searchParams);
useDebouncedCallback(filter === Filter.People, searchProfileInfo, searchParams);
@ -403,7 +411,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
[Section.Rooms]: [],
[Section.Spaces]: [],
[Section.Suggestions]: [],
[Section.PublicRooms]: [],
[Section.PublicRoomsAndSpaces]: [],
};
// Group results in their respective sections
@ -436,7 +444,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
results[entry.section].push(entry);
});
} else if (filter === Filter.PublicRooms) {
} else if (filter === Filter.PublicRooms || filter === Filter.PublicSpaces) {
// return all results for public rooms if no query is given
possibleResults.forEach((entry) => {
if (isPublicRoomResult(entry)) {
@ -549,6 +557,15 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
{trimmedQuery ? _t('Use "%(query)s" to search', { query }) : _t("Search for")}
</h4>
<div>
{filter !== Filter.PublicSpaces && supportsSpaceFiltering && (
<Option
id="mx_SpotlightDialog_button_explorePublicSpaces"
className="mx_SpotlightDialog_explorePublicSpaces"
onClick={() => setFilter(Filter.PublicSpaces)}
>
{filterToLabel(Filter.PublicSpaces)}
</Option>
)}
{filter !== Filter.PublicRooms && (
<Option
id="mx_SpotlightDialog_button_explorePublicRooms"
@ -769,22 +786,18 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
}
let publicRoomsSection: JSX.Element | undefined;
if (filter === Filter.PublicRooms) {
if (filter === Filter.PublicRooms || filter === Filter.PublicSpaces) {
let content: JSX.Element | JSX.Element[];
if (!showRooms && !showSpaces) {
if (publicRoomsError) {
content = (
<div className="mx_SpotlightDialog_otherSearches_messageSearchText">
{_t("You cannot search for rooms that are neither a room nor a space")}
</div>
);
} else if (publicRoomsError) {
content = (
<div className="mx_SpotlightDialog_otherSearches_messageSearchText">
{_t("Failed to query public rooms")}
{filter === Filter.PublicRooms
? _t("Failed to query public rooms")
: _t("Failed to query public spaces")}
</div>
);
} else {
content = results[Section.PublicRooms].slice(0, SECTION_LIMIT).map(resultMapper);
content = results[Section.PublicRoomsAndSpaces].slice(0, SECTION_LIMIT).map(resultMapper);
}
publicRoomsSection = (
@ -796,20 +809,6 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
<div className="mx_SpotlightDialog_sectionHeader">
<h4 id="mx_SpotlightDialog_section_publicRooms">{_t("Suggestions")}</h4>
<div className="mx_SpotlightDialog_options">
{exploringPublicSpacesEnabled && (
<>
<LabelledCheckbox
label={_t("Show rooms")}
value={showRooms}
onChange={setShowRooms}
/>
<LabelledCheckbox
label={_t("Show spaces")}
value={showSpaces}
onChange={setShowSpaces}
/>
</>
)}
<NetworkDropdown protocols={protocols} config={config ?? null} setConfig={setConfig} />
</div>
</div>
@ -919,7 +918,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
</TooltipOption>
</div>
);
} else if (trimmedQuery && filter === Filter.PublicRooms) {
} else if (trimmedQuery && (filter === Filter.PublicRooms || filter === Filter.PublicSpaces)) {
hiddenResultsSection = (
<div className="mx_SpotlightDialog_section mx_SpotlightDialog_hiddenResults" role="group">
<h4>{_t("Some results may be hidden")}</h4>
@ -1218,6 +1217,7 @@ const SpotlightDialog: React.FC<IProps> = ({ initialText = "", initialFilter = n
className={classNames("mx_SpotlightDialog_filter", {
mx_SpotlightDialog_filterPeople: filter === Filter.People,
mx_SpotlightDialog_filterPublicRooms: filter === Filter.PublicRooms,
mx_SpotlightDialog_filterPublicSpaces: filter === Filter.PublicSpaces,
})}
>
<span>{filterToLabel(filter)}</span>

View file

@ -24,6 +24,7 @@ import React, {
useState,
ChangeEvent,
ReactNode,
useEffect,
} from "react";
import classNames from "classnames";
import {
@ -48,6 +49,9 @@ import withValidation from "../elements/Validation";
import RoomAliasField from "../elements/RoomAliasField";
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
import defaultDispatcher from "../../../dispatcher/dispatcher";
import { Action } from "../../../dispatcher/actions";
import { Filter } from "../dialogs/spotlight/Filter";
export const createSpace = async (
client: MatrixClient,
@ -225,6 +229,17 @@ const SpaceCreateMenu: React.FC<{
const [avatar, setAvatar] = useState<File | undefined>(undefined);
const [topic, setTopic] = useState<string>("");
const [supportsSpaceFiltering, setSupportsSpaceFiltering] = useState(true); // assume it does until we find out it doesn't
useEffect(() => {
cli.isVersionSupported("v1.4")
.then((supported) => {
return supported || cli.doesServerSupportUnstableFeature("org.matrix.msc3827.stable");
})
.then((supported) => {
setSupportsSpaceFiltering(supported);
});
}, [cli]);
const onSpaceCreateClick = async (e: ButtonEvent): Promise<void> => {
e.preventDefault();
if (busy) return;
@ -258,6 +273,13 @@ const SpaceCreateMenu: React.FC<{
}
};
const onSearchClick = (): void => {
defaultDispatcher.dispatch({
action: Action.OpenSpotlight,
initialFilter: Filter.PublicSpaces,
});
};
let body;
if (visibility === null) {
body = (
@ -283,7 +305,11 @@ const SpaceCreateMenu: React.FC<{
onClick={() => setVisibility(Visibility.Private)}
/>
<p>{_t("To join a space you'll need an invite.")}</p>
{supportsSpaceFiltering && (
<AccessibleButton kind="primary_outline" onClick={onSearchClick}>
{_t("Search for public spaces")}
</AccessibleButton>
)}
</React.Fragment>
);
} else {

View file

@ -40,7 +40,9 @@ const SpaceSettingsVisibilityTab: React.FC<IProps> = ({ matrixClient: cli, space
const [error, setError] = useState("");
const serverSupportsExploringSpaces = useAsyncMemo<boolean>(
async (): Promise<boolean> => {
return cli.doesServerSupportUnstableFeature("org.matrix.msc3827.stable");
return cli.isVersionSupported("v1.4").then((supported) => {
return supported || cli.doesServerSupportUnstableFeature("org.matrix.msc3827.stable");
});
},
[cli],
false,

View file

@ -366,4 +366,9 @@ export enum Action {
* Fired when requesting to cancel an ask to join a room. Use with a CancelAskToJoinPayload.
*/
CancelAskToJoin = "cancel_ask_to_join",
/**
* Fired when we want to open spotlight search. Use with a OpenSpotlightPayload.
*/
OpenSpotlight = "open_spotlight",
}

View file

@ -0,0 +1,26 @@
/*
Copyright 2023 The Matrix.org Foundation C.I.C.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import { Action } from "../actions";
import { ActionPayload } from "../payloads";
import { Filter } from "../../components/views/dialogs/spotlight/Filter";
export interface OpenSpotlightPayload extends ActionPayload {
action: Action.OpenSpotlight;
initialFilter?: Filter;
initialText?: string;
}

View file

@ -20,7 +20,7 @@ const DEBOUNCE_TIMEOUT = 100;
export function useDebouncedCallback<T extends any[]>(
enabled: boolean,
callback: (...params: T) => void,
callback: (...params: T) => unknown,
params: T,
): void {
useEffect(() => {

View file

@ -992,8 +992,6 @@
"New Notification Settings": "New Notification Settings",
"Notification Settings": "Notification Settings",
"Introducing a simpler way to change your notification settings. Customize your %(brand)s, just the way you like.": "Introducing a simpler way to change your notification settings. Customize your %(brand)s, just the way you like.",
"Explore public spaces in the new search dialog": "Explore public spaces in the new search dialog",
"Requires your server to support the stable version of MSC3827": "Requires your server to support the stable version of MSC3827",
"Let moderators hide messages pending moderation.": "Let moderators hide messages pending moderation.",
"Report to moderators": "Report to moderators",
"In rooms that support moderation, the “Report” button will let you report abuse to room moderators.": "In rooms that support moderation, the “Report” button will let you report abuse to room moderators.",
@ -1269,7 +1267,7 @@
"Open space for anyone, best for communities": "Open space for anyone, best for communities",
"Private": "Private",
"Invite only, best for yourself or teams": "Invite only, best for yourself or teams",
"To join a space you'll need an invite.": "To join a space you'll need an invite.",
"Search for public spaces": "Search for public spaces",
"Go back": "Go back",
"Your public space": "Your public space",
"Your private space": "Your private space",
@ -3254,14 +3252,13 @@
"one": "%(count)s Member"
},
"Public rooms": "Public rooms",
"Public spaces": "Public spaces",
"Use \"%(query)s\" to search": "Use \"%(query)s\" to search",
"Search for": "Search for",
"View": "View",
"Spaces you're in": "Spaces you're in",
"You cannot search for rooms that are neither a room nor a space": "You cannot search for rooms that are neither a room nor a space",
"Failed to query public rooms": "Failed to query public rooms",
"Show rooms": "Show rooms",
"Show spaces": "Show spaces",
"Failed to query public spaces": "Failed to query public spaces",
"Other rooms in %(spaceName)s": "Other rooms in %(spaceName)s",
"Join %(roomAddress)s": "Join %(roomAddress)s",
"Some results may be hidden for privacy": "Some results may be hidden for privacy",

View file

@ -252,20 +252,6 @@ export const SETTINGS: { [setting: string]: ISetting } = {
),
},
},
"feature_exploring_public_spaces": {
isFeature: true,
labsGroup: LabGroup.Spaces,
displayName: _td("Explore public spaces in the new search dialog"),
supportedLevels: LEVELS_FEATURE,
default: false,
controller: new ServerSupportUnstableFeatureController(
"feature_exploring_public_spaces",
defaultWatchManager,
[["org.matrix.msc3827.stable"]],
"v1.4",
_td("Requires your server to support the stable version of MSC3827"),
),
},
"feature_msc3531_hide_messages_pending_moderation": {
isFeature: true,
labsGroup: LabGroup.Moderation,

View file

@ -27,7 +27,8 @@ import {
import sanitizeHtml from "sanitize-html";
import { fireEvent, render, screen } from "@testing-library/react";
import SpotlightDialog, { Filter } from "../../../../src/components/views/dialogs/spotlight/SpotlightDialog";
import SpotlightDialog from "../../../../src/components/views/dialogs/spotlight/SpotlightDialog";
import { Filter } from "../../../../src/components/views/dialogs/spotlight/Filter";
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { LocalRoom, LOCAL_ROOM_ID_PREFIX } from "../../../../src/models/LocalRoom";
import { DirectoryMember, startDmOnFirstMessage } from "../../../../src/utils/direct-messages";
@ -352,12 +353,12 @@ describe("Spotlight Dialog", () => {
});
it("should find Rooms", () => {
expect(options.length).toBe(3);
expect(options).toHaveLength(4);
expect(options[0]!.innerHTML).toContain(testRoom.name);
});
it("should not find LocalRooms", () => {
expect(options.length).toBe(3);
expect(options).toHaveLength(4);
expect(options[0]!.innerHTML).not.toContain(testLocalRoom.name);
});
});
@ -573,22 +574,4 @@ describe("Spotlight Dialog", () => {
expect(screen.getByText("Failed to query public rooms")).toBeInTheDocument();
});
it("should show error both 'Show rooms' and 'Show spaces' are unchecked", async () => {
jest.spyOn(SettingsStore, "getValue").mockImplementation((settingName, roomId, excludeDefault) => {
if (settingName === "feature_exploring_public_spaces") {
return true;
} else {
return []; // SpotlightSearch.recentSearches
}
});
render(<SpotlightDialog initialFilter={Filter.PublicRooms} onFinished={() => null} />);
jest.advanceTimersByTime(200);
await flushPromisesWithFakeTimers();
fireEvent.click(screen.getByText("Show rooms"));
expect(screen.getByText("You cannot search for rooms that are neither a room nor a space")).toBeInTheDocument();
});
});

View file

@ -71,7 +71,7 @@ describe("<LabsUserSettingsTab />", () => {
// non-beta labs section
expect(screen.getByText("Early previews")).toBeInTheDocument();
const labsSections = container.getElementsByClassName("mx_SettingsSubsection");
expect(labsSections).toHaveLength(10);
expect(labsSections).toHaveLength(9);
});
it("allow setting a labs flag which requires unstable support once support is confirmed", async () => {
@ -80,21 +80,21 @@ describe("<LabsUserSettingsTab />", () => {
const deferred = defer<boolean>();
cli.doesServerSupportUnstableFeature.mockImplementation(async (featureName) => {
return featureName === "org.matrix.msc3827.stable" ? deferred.promise : false;
return featureName === "org.matrix.msc3952_intentional_mentions" ? deferred.promise : false;
});
MatrixClientBackedController.matrixClient = cli;
const { queryByText } = render(getComponent());
expect(
queryByText("Explore public spaces in the new search dialog")!
queryByText("Enable intentional mentions")!
.closest(".mx_SettingsFlag")!
.querySelector(".mx_AccessibleButton"),
).toHaveAttribute("aria-disabled", "true");
deferred.resolve(true);
await waitFor(() => {
expect(
queryByText("Explore public spaces in the new search dialog")!
queryByText("Enable intentional mentions")!
.closest(".mx_SettingsFlag")!
.querySelector(".mx_AccessibleButton"),
).toHaveAttribute("aria-disabled", "false");

View file

@ -24,7 +24,7 @@ import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
import { MetaSpace, SpaceKey } from "../../../../src/stores/spaces";
import { shouldShowComponent } from "../../../../src/customisations/helpers/UIComponents";
import { UIComponent } from "../../../../src/settings/UIFeature";
import { mkStubRoom, wrapInSdkContext } from "../../../test-utils";
import { mkStubRoom, wrapInMatrixClientContext, wrapInSdkContext } from "../../../test-utils";
import { SdkContextClass } from "../../../../src/contexts/SDKContext";
import SpaceStore from "../../../../src/stores/spaces/SpaceStore";
import DMRoomMap from "../../../../src/utils/DMRoomMap";
@ -122,9 +122,12 @@ describe("<SpacePanel />", () => {
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),
} as unknown as MatrixClient;
const SpacePanel = wrapInSdkContext(UnwrappedSpacePanel, SdkContextClass.instance);
const SpacePanel = wrapInSdkContext(wrapInMatrixClientContext(UnwrappedSpacePanel), SdkContextClass.instance);
beforeAll(() => {
jest.spyOn(MatrixClientPeg, "get").mockReturnValue(mockClient);