Integrate searching public rooms and people into the new search experience (#8707)
* Implement searching for public rooms and users in new search experience * Implement loading indicator for spotlight results * Moved spotlight dialog into own subfolder * Extract search result avatar into separate component * Build generic new dropdown menu component * Build new network menu based on new network dropdown component * Switch roomdirectory to use new network dropdown * Replace old networkdropdown with new networkdropdown * Added component for public room result details * Extract hooks and subcomponents from SpotlightDialog * Create new hook to get profile info based for an mxid * Add hook to automatically re-request search results * Add hook to prevent out-of-order search results * Extract member sort algorithm from InviteDialog * Keep sorting for non-room results stable * Sort people suggestions using sort algorithm from InviteDialog * Add copy/copied tooltip for invite link option in spotlight * Clamp length of topic for public room results * Add unit test for useDebouncedSearch * Add unit test for useProfileInfo * Create cypress test cases for spotlight dialog * Add test for useLatestResult to prevent out-of-order results
This commit is contained in:
parent
37298d7b1b
commit
5096e7b992
38 changed files with 3520 additions and 1397 deletions
292
test/components/views/dialogs/SpotlightDialog-test.tsx
Normal file
292
test/components/views/dialogs/SpotlightDialog-test.tsx
Normal file
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
Copyright 2022 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 { mount } from "enzyme";
|
||||
import { IProtocol, IPublicRoomsChunkRoom, MatrixClient, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||
import { sleep } from "matrix-js-sdk/src/utils";
|
||||
import React from "react";
|
||||
import { act } from "react-dom/test-utils";
|
||||
import sanitizeHtml from "sanitize-html";
|
||||
|
||||
import SpotlightDialog, { Filter } from "../../../../src/components/views/dialogs/spotlight/SpotlightDialog";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { stubClient } from "../../../test-utils";
|
||||
|
||||
interface IUserChunkMember {
|
||||
user_id: string;
|
||||
display_name?: string;
|
||||
avatar_url?: string;
|
||||
}
|
||||
|
||||
interface MockClientOptions {
|
||||
userId?: string;
|
||||
homeserver?: string;
|
||||
thirdPartyProtocols?: Record<string, IProtocol>;
|
||||
rooms?: IPublicRoomsChunkRoom[];
|
||||
members?: RoomMember[];
|
||||
users?: IUserChunkMember[];
|
||||
}
|
||||
|
||||
function mockClient(
|
||||
{
|
||||
userId = "testuser",
|
||||
homeserver = "example.tld",
|
||||
thirdPartyProtocols = {},
|
||||
rooms = [],
|
||||
members = [],
|
||||
users = [],
|
||||
}: MockClientOptions = {},
|
||||
): MatrixClient {
|
||||
stubClient();
|
||||
const cli = MatrixClientPeg.get();
|
||||
MatrixClientPeg.getHomeserverName = jest.fn(() => homeserver);
|
||||
cli.getUserId = jest.fn(() => userId);
|
||||
cli.getHomeserverUrl = jest.fn(() => homeserver);
|
||||
cli.getThirdpartyProtocols = jest.fn(() => Promise.resolve(thirdPartyProtocols));
|
||||
cli.publicRooms = jest.fn((options) => {
|
||||
const searchTerm = options?.filter?.generic_search_term?.toLowerCase();
|
||||
const chunk = rooms.filter(it =>
|
||||
!searchTerm ||
|
||||
it.room_id.toLowerCase().includes(searchTerm) ||
|
||||
it.name?.toLowerCase().includes(searchTerm) ||
|
||||
sanitizeHtml(it?.topic, { allowedTags: [] }).toLowerCase().includes(searchTerm) ||
|
||||
it.canonical_alias?.toLowerCase().includes(searchTerm) ||
|
||||
it.aliases?.find(alias => alias.toLowerCase().includes(searchTerm)));
|
||||
return Promise.resolve({
|
||||
chunk,
|
||||
total_room_count_estimate: chunk.length,
|
||||
});
|
||||
});
|
||||
cli.searchUserDirectory = jest.fn(({ term, limit }) => {
|
||||
const searchTerm = term?.toLowerCase();
|
||||
const results = users.filter(it => !searchTerm ||
|
||||
it.user_id.toLowerCase().includes(searchTerm) ||
|
||||
it.display_name.toLowerCase().includes(searchTerm));
|
||||
return Promise.resolve({
|
||||
results: results.slice(0, limit ?? +Infinity),
|
||||
limited: limit && limit < results.length,
|
||||
});
|
||||
});
|
||||
cli.getProfileInfo = jest.fn(async (userId) => {
|
||||
const member = members.find(it => it.userId === userId);
|
||||
if (member) {
|
||||
return Promise.resolve({
|
||||
displayname: member.rawDisplayName,
|
||||
avatar_url: member.getMxcAvatarUrl(),
|
||||
});
|
||||
} else {
|
||||
return Promise.reject();
|
||||
}
|
||||
});
|
||||
return cli;
|
||||
}
|
||||
|
||||
describe("Spotlight Dialog", () => {
|
||||
const testPerson: IUserChunkMember = {
|
||||
user_id: "@janedoe:matrix.org",
|
||||
display_name: "Jane Doe",
|
||||
avatar_url: undefined,
|
||||
};
|
||||
|
||||
const testPublicRoom: IPublicRoomsChunkRoom = {
|
||||
room_id: "@room247:matrix.org",
|
||||
name: "Room #247",
|
||||
topic: "We hope you'll have a <b>shining</b> experience!",
|
||||
world_readable: false,
|
||||
num_joined_members: 1,
|
||||
guest_can_join: false,
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
mockClient({ rooms: [testPublicRoom], users: [testPerson] });
|
||||
});
|
||||
|
||||
describe("should apply filters supplied via props", () => {
|
||||
it("without filter", async () => {
|
||||
const wrapper = mount(
|
||||
<SpotlightDialog
|
||||
initialFilter={null}
|
||||
onFinished={() => null} />,
|
||||
);
|
||||
await act(async () => {
|
||||
await sleep(200);
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
const filterChip = wrapper.find("div.mx_SpotlightDialog_filter");
|
||||
expect(filterChip.exists()).toBeFalsy();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
it("with public room filter", async () => {
|
||||
const wrapper = mount(
|
||||
<SpotlightDialog
|
||||
initialFilter={Filter.PublicRooms}
|
||||
onFinished={() => null} />,
|
||||
);
|
||||
await act(async () => {
|
||||
await sleep(200);
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
const filterChip = wrapper.find("div.mx_SpotlightDialog_filter");
|
||||
expect(filterChip.exists()).toBeTruthy();
|
||||
expect(filterChip.text()).toEqual("Public rooms");
|
||||
|
||||
const content = wrapper.find("#mx_SpotlightDialog_content");
|
||||
const options = content.find("div.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBe(1);
|
||||
expect(options.first().text()).toContain(testPublicRoom.name);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
it("with people filter", async () => {
|
||||
const wrapper = mount(
|
||||
<SpotlightDialog
|
||||
initialFilter={Filter.People}
|
||||
initialText={testPerson.display_name}
|
||||
onFinished={() => null} />,
|
||||
);
|
||||
await act(async () => {
|
||||
await sleep(200);
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
const filterChip = wrapper.find("div.mx_SpotlightDialog_filter");
|
||||
expect(filterChip.exists()).toBeTruthy();
|
||||
expect(filterChip.text()).toEqual("People");
|
||||
|
||||
const content = wrapper.find("#mx_SpotlightDialog_content");
|
||||
const options = content.find("div.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBeGreaterThanOrEqual(1);
|
||||
expect(options.first().text()).toContain(testPerson.display_name);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe("should apply manually selected filter", () => {
|
||||
it("with public rooms", async () => {
|
||||
const wrapper = mount(
|
||||
<SpotlightDialog
|
||||
onFinished={() => null} />,
|
||||
);
|
||||
await act(async () => {
|
||||
await sleep(1);
|
||||
});
|
||||
wrapper.update();
|
||||
wrapper.find("#mx_SpotlightDialog_button_explorePublicRooms").first().simulate("click");
|
||||
await act(async () => {
|
||||
await sleep(200);
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
const filterChip = wrapper.find("div.mx_SpotlightDialog_filter");
|
||||
expect(filterChip.exists()).toBeTruthy();
|
||||
expect(filterChip.text()).toEqual("Public rooms");
|
||||
|
||||
const content = wrapper.find("#mx_SpotlightDialog_content");
|
||||
const options = content.find("div.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBe(1);
|
||||
expect(options.first().text()).toContain(testPublicRoom.name);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
it("with people", async () => {
|
||||
const wrapper = mount(
|
||||
<SpotlightDialog
|
||||
initialText={testPerson.display_name}
|
||||
onFinished={() => null} />,
|
||||
);
|
||||
await act(async () => {
|
||||
await sleep(1);
|
||||
});
|
||||
wrapper.update();
|
||||
wrapper.find("#mx_SpotlightDialog_button_startChat").first().simulate("click");
|
||||
await act(async () => {
|
||||
await sleep(200);
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
const filterChip = wrapper.find("div.mx_SpotlightDialog_filter");
|
||||
expect(filterChip.exists()).toBeTruthy();
|
||||
expect(filterChip.text()).toEqual("People");
|
||||
|
||||
const content = wrapper.find("#mx_SpotlightDialog_content");
|
||||
const options = content.find("div.mx_SpotlightDialog_option");
|
||||
expect(options.length).toBeGreaterThanOrEqual(1);
|
||||
expect(options.first().text()).toContain(testPerson.display_name);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
describe("should allow clearing filter manually", () => {
|
||||
it("with public room filter", async () => {
|
||||
const wrapper = mount(
|
||||
<SpotlightDialog
|
||||
initialFilter={Filter.PublicRooms}
|
||||
onFinished={() => null} />,
|
||||
);
|
||||
await act(async () => {
|
||||
await sleep(200);
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
let filterChip = wrapper.find("div.mx_SpotlightDialog_filter");
|
||||
expect(filterChip.exists()).toBeTruthy();
|
||||
expect(filterChip.text()).toEqual("Public rooms");
|
||||
|
||||
filterChip.find("div.mx_SpotlightDialog_filter--close").simulate("click");
|
||||
await act(async () => {
|
||||
await sleep(1);
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
filterChip = wrapper.find("div.mx_SpotlightDialog_filter");
|
||||
expect(filterChip.exists()).toBeFalsy();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
it("with people filter", async () => {
|
||||
const wrapper = mount(
|
||||
<SpotlightDialog
|
||||
initialFilter={Filter.People}
|
||||
initialText={testPerson.display_name}
|
||||
onFinished={() => null} />,
|
||||
);
|
||||
await act(async () => {
|
||||
await sleep(200);
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
let filterChip = wrapper.find("div.mx_SpotlightDialog_filter");
|
||||
expect(filterChip.exists()).toBeTruthy();
|
||||
expect(filterChip.text()).toEqual("People");
|
||||
|
||||
filterChip.find("div.mx_SpotlightDialog_filter--close").simulate("click");
|
||||
await act(async () => {
|
||||
await sleep(1);
|
||||
});
|
||||
wrapper.update();
|
||||
|
||||
filterChip = wrapper.find("div.mx_SpotlightDialog_filter");
|
||||
expect(filterChip.exists()).toBeFalsy();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue