mirror of
https://github.com/vector-im/element-web.git
synced 2024-11-16 05:04:57 +08:00
Merge pull request #9934 from matrix-org/kegan/lists-as-keys
refactor: sliding sync: convert to lists-as-keys rather than indexes
This commit is contained in:
commit
51b4555106
@ -30,8 +30,8 @@ export default defineConfig({
|
|||||||
specPattern: "cypress/e2e/**/*.{js,jsx,ts,tsx}",
|
specPattern: "cypress/e2e/**/*.{js,jsx,ts,tsx}",
|
||||||
},
|
},
|
||||||
env: {
|
env: {
|
||||||
// Docker tag to use for `ghcr.io/matrix-org/sliding-sync-proxy` image.
|
// Docker tag to use for `ghcr.io/matrix-org/sliding-sync` image.
|
||||||
SLIDING_SYNC_PROXY_TAG: "v0.6.0",
|
SLIDING_SYNC_PROXY_TAG: "v0.99.0-rc1",
|
||||||
HOMESERVER: "synapse",
|
HOMESERVER: "synapse",
|
||||||
},
|
},
|
||||||
retries: {
|
retries: {
|
||||||
|
@ -21,8 +21,6 @@ import { MatrixClient } from "matrix-js-sdk/src/matrix";
|
|||||||
import { Interception } from "cypress/types/net-stubbing";
|
import { Interception } from "cypress/types/net-stubbing";
|
||||||
|
|
||||||
import { HomeserverInstance } from "../../plugins/utils/homeserver";
|
import { HomeserverInstance } from "../../plugins/utils/homeserver";
|
||||||
import { SettingLevel } from "../../../src/settings/SettingLevel";
|
|
||||||
import { Layout } from "../../../src/settings/enums/Layout";
|
|
||||||
import { ProxyInstance } from "../../plugins/sliding-sync";
|
import { ProxyInstance } from "../../plugins/sliding-sync";
|
||||||
|
|
||||||
describe("Sliding Sync", () => {
|
describe("Sliding Sync", () => {
|
||||||
@ -102,21 +100,6 @@ describe("Sliding Sync", () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// sanity check everything works
|
|
||||||
it("should correctly render expected messages", () => {
|
|
||||||
cy.get<string>("@roomId").then((roomId) => cy.visit("/#/room/" + roomId));
|
|
||||||
cy.setSettingValue("layout", null, SettingLevel.DEVICE, Layout.IRC);
|
|
||||||
|
|
||||||
// Wait until configuration is finished
|
|
||||||
cy.contains(
|
|
||||||
".mx_RoomView_body .mx_GenericEventListSummary .mx_GenericEventListSummary_summary",
|
|
||||||
"created and configured the room.",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Click "expand" link button
|
|
||||||
cy.get(".mx_GenericEventListSummary_toggle[aria-expanded=false]").click();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should render the Rooms list in reverse chronological order by default and allowing sorting A-Z", () => {
|
it("should render the Rooms list in reverse chronological order by default and allowing sorting A-Z", () => {
|
||||||
// create rooms and check room names are correct
|
// create rooms and check room names are correct
|
||||||
cy.createRoom({ name: "Apple" }).then(() => cy.contains(".mx_RoomSublist", "Apple"));
|
cy.createRoom({ name: "Apple" }).then(() => cy.contains(".mx_RoomSublist", "Apple"));
|
||||||
|
@ -23,7 +23,7 @@ import { getFreePort } from "../utils/port";
|
|||||||
import { HomeserverInstance } from "../utils/homeserver";
|
import { HomeserverInstance } from "../utils/homeserver";
|
||||||
|
|
||||||
// A cypress plugin to add command to start & stop https://github.com/matrix-org/sliding-sync
|
// A cypress plugin to add command to start & stop https://github.com/matrix-org/sliding-sync
|
||||||
// SLIDING_SYNC_PROXY_TAG env used as the docker tag to use for `ghcr.io/matrix-org/sliding-sync-proxy` image.
|
// SLIDING_SYNC_PROXY_TAG env used as the docker tag to use for `ghcr.io/matrix-org/sliding-sync` image.
|
||||||
|
|
||||||
export interface ProxyInstance {
|
export interface ProxyInstance {
|
||||||
containerId: string;
|
containerId: string;
|
||||||
@ -72,7 +72,7 @@ async function proxyStart(dockerTag: string, homeserver: HomeserverInstance): Pr
|
|||||||
const port = await getFreePort();
|
const port = await getFreePort();
|
||||||
console.log(new Date(), "starting proxy container...", dockerTag);
|
console.log(new Date(), "starting proxy container...", dockerTag);
|
||||||
const containerId = await dockerRun({
|
const containerId = await dockerRun({
|
||||||
image: "ghcr.io/matrix-org/sliding-sync-proxy:" + dockerTag,
|
image: "ghcr.io/matrix-org/sliding-sync:" + dockerTag,
|
||||||
containerName: "react-sdk-cypress-sliding-sync-proxy",
|
containerName: "react-sdk-cypress-sliding-sync-proxy",
|
||||||
params: [
|
params: [
|
||||||
"--rm",
|
"--rm",
|
||||||
|
@ -119,12 +119,10 @@ export class SlidingSyncManager {
|
|||||||
|
|
||||||
public slidingSync: SlidingSync;
|
public slidingSync: SlidingSync;
|
||||||
private client: MatrixClient;
|
private client: MatrixClient;
|
||||||
private listIdToIndex: Record<string, number>;
|
|
||||||
|
|
||||||
private configureDefer: IDeferred<void>;
|
private configureDefer: IDeferred<void>;
|
||||||
|
|
||||||
public constructor() {
|
public constructor() {
|
||||||
this.listIdToIndex = {};
|
|
||||||
this.configureDefer = defer<void>();
|
this.configureDefer = defer<void>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,13 +132,18 @@ export class SlidingSyncManager {
|
|||||||
|
|
||||||
public configure(client: MatrixClient, proxyUrl: string): SlidingSync {
|
public configure(client: MatrixClient, proxyUrl: string): SlidingSync {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
this.listIdToIndex = {};
|
|
||||||
// by default use the encrypted subscription as that gets everything, which is a safer
|
// by default use the encrypted subscription as that gets everything, which is a safer
|
||||||
// default than potentially missing member events.
|
// default than potentially missing member events.
|
||||||
this.slidingSync = new SlidingSync(proxyUrl, [], ENCRYPTED_SUBSCRIPTION, client, SLIDING_SYNC_TIMEOUT_MS);
|
this.slidingSync = new SlidingSync(
|
||||||
|
proxyUrl,
|
||||||
|
new Map(),
|
||||||
|
ENCRYPTED_SUBSCRIPTION,
|
||||||
|
client,
|
||||||
|
SLIDING_SYNC_TIMEOUT_MS,
|
||||||
|
);
|
||||||
this.slidingSync.addCustomSubscription(UNENCRYPTED_SUBSCRIPTION_NAME, UNENCRYPTED_SUBSCRIPTION);
|
this.slidingSync.addCustomSubscription(UNENCRYPTED_SUBSCRIPTION_NAME, UNENCRYPTED_SUBSCRIPTION);
|
||||||
// set the space list
|
// set the space list
|
||||||
this.slidingSync.setList(this.getOrAllocateListIndex(SlidingSyncManager.ListSpaces), {
|
this.slidingSync.setList(SlidingSyncManager.ListSpaces, {
|
||||||
ranges: [[0, 20]],
|
ranges: [[0, 20]],
|
||||||
sort: ["by_name"],
|
sort: ["by_name"],
|
||||||
slow_get_all_rooms: true,
|
slow_get_all_rooms: true,
|
||||||
@ -173,47 +176,16 @@ export class SlidingSyncManager {
|
|||||||
return this.slidingSync;
|
return this.slidingSync;
|
||||||
}
|
}
|
||||||
|
|
||||||
public listIdForIndex(index: number): string | null {
|
|
||||||
for (const listId in this.listIdToIndex) {
|
|
||||||
if (this.listIdToIndex[listId] === index) {
|
|
||||||
return listId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allocate or retrieve the list index for an arbitrary list ID. For example SlidingSyncManager.ListSpaces
|
|
||||||
* @param listId A string which represents the list.
|
|
||||||
* @returns The index to use when registering lists or listening for callbacks.
|
|
||||||
*/
|
|
||||||
public getOrAllocateListIndex(listId: string): number {
|
|
||||||
let index = this.listIdToIndex[listId];
|
|
||||||
if (index === undefined) {
|
|
||||||
// assign next highest index
|
|
||||||
index = -1;
|
|
||||||
for (const id in this.listIdToIndex) {
|
|
||||||
const listIndex = this.listIdToIndex[id];
|
|
||||||
if (listIndex > index) {
|
|
||||||
index = listIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
this.listIdToIndex[listId] = index;
|
|
||||||
}
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ensure that this list is registered.
|
* Ensure that this list is registered.
|
||||||
* @param listIndex The list index to register
|
* @param listKey The list key to register
|
||||||
* @param updateArgs The fields to update on the list.
|
* @param updateArgs The fields to update on the list.
|
||||||
* @returns The complete list request params
|
* @returns The complete list request params
|
||||||
*/
|
*/
|
||||||
public async ensureListRegistered(listIndex: number, updateArgs: PartialSlidingSyncRequest): Promise<MSC3575List> {
|
public async ensureListRegistered(listKey: string, updateArgs: PartialSlidingSyncRequest): Promise<MSC3575List> {
|
||||||
logger.debug("ensureListRegistered:::", listIndex, updateArgs);
|
logger.debug("ensureListRegistered:::", listKey, updateArgs);
|
||||||
await this.configureDefer.promise;
|
await this.configureDefer.promise;
|
||||||
let list = this.slidingSync.getList(listIndex);
|
let list = this.slidingSync.getListParams(listKey);
|
||||||
if (!list) {
|
if (!list) {
|
||||||
list = {
|
list = {
|
||||||
ranges: [[0, 20]],
|
ranges: [[0, 20]],
|
||||||
@ -252,14 +224,14 @@ export class SlidingSyncManager {
|
|||||||
try {
|
try {
|
||||||
// if we only have range changes then call a different function so we don't nuke the list from before
|
// if we only have range changes then call a different function so we don't nuke the list from before
|
||||||
if (updateArgs.ranges && Object.keys(updateArgs).length === 1) {
|
if (updateArgs.ranges && Object.keys(updateArgs).length === 1) {
|
||||||
await this.slidingSync.setListRanges(listIndex, updateArgs.ranges);
|
await this.slidingSync.setListRanges(listKey, updateArgs.ranges);
|
||||||
} else {
|
} else {
|
||||||
await this.slidingSync.setList(listIndex, list);
|
await this.slidingSync.setList(listKey, list);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.debug("ensureListRegistered: update failed txn_id=", err);
|
logger.debug("ensureListRegistered: update failed txn_id=", err);
|
||||||
}
|
}
|
||||||
return this.slidingSync.getList(listIndex);
|
return this.slidingSync.getListParams(listKey)!;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setRoomVisible(roomId: string, visible: boolean): Promise<string> {
|
public async setRoomVisible(roomId: string, visible: boolean): Promise<string> {
|
||||||
@ -304,7 +276,6 @@ export class SlidingSyncManager {
|
|||||||
*/
|
*/
|
||||||
public async startSpidering(batchSize: number, gapBetweenRequestsMs: number): Promise<void> {
|
public async startSpidering(batchSize: number, gapBetweenRequestsMs: number): Promise<void> {
|
||||||
await sleep(gapBetweenRequestsMs); // wait a bit as this is called on first render so let's let things load
|
await sleep(gapBetweenRequestsMs); // wait a bit as this is called on first render so let's let things load
|
||||||
const listIndex = this.getOrAllocateListIndex(SlidingSyncManager.ListSearch);
|
|
||||||
let startIndex = batchSize;
|
let startIndex = batchSize;
|
||||||
let hasMore = true;
|
let hasMore = true;
|
||||||
let firstTime = true;
|
let firstTime = true;
|
||||||
@ -316,7 +287,7 @@ export class SlidingSyncManager {
|
|||||||
[startIndex, endIndex],
|
[startIndex, endIndex],
|
||||||
];
|
];
|
||||||
if (firstTime) {
|
if (firstTime) {
|
||||||
await this.slidingSync.setList(listIndex, {
|
await this.slidingSync.setList(SlidingSyncManager.ListSearch, {
|
||||||
// e.g [0,19] [20,39] then [0,19] [40,59]. We keep [0,20] constantly to ensure
|
// e.g [0,19] [20,39] then [0,19] [40,59]. We keep [0,20] constantly to ensure
|
||||||
// any changes to the list whilst spidering are caught.
|
// any changes to the list whilst spidering are caught.
|
||||||
ranges: ranges,
|
ranges: ranges,
|
||||||
@ -342,15 +313,17 @@ export class SlidingSyncManager {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.slidingSync.setListRanges(listIndex, ranges);
|
await this.slidingSync.setListRanges(SlidingSyncManager.ListSearch, ranges);
|
||||||
}
|
}
|
||||||
// gradually request more over time
|
|
||||||
await sleep(gapBetweenRequestsMs);
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// do nothing, as we reject only when we get interrupted but that's fine as the next
|
// do nothing, as we reject only when we get interrupted but that's fine as the next
|
||||||
// request will include our data
|
// request will include our data
|
||||||
|
} finally {
|
||||||
|
// gradually request more over time, even on errors.
|
||||||
|
await sleep(gapBetweenRequestsMs);
|
||||||
}
|
}
|
||||||
hasMore = endIndex + 1 < this.slidingSync.getListData(listIndex)?.joinedCount;
|
const listData = this.slidingSync.getListData(SlidingSyncManager.ListSearch)!;
|
||||||
|
hasMore = endIndex + 1 < listData.joinedCount;
|
||||||
startIndex += batchSize;
|
startIndex += batchSize;
|
||||||
firstTime = false;
|
firstTime = false;
|
||||||
}
|
}
|
||||||
|
@ -340,9 +340,8 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||||||
|
|
||||||
private onShowAllClick = async (): Promise<void> => {
|
private onShowAllClick = async (): Promise<void> => {
|
||||||
if (this.slidingSyncMode) {
|
if (this.slidingSyncMode) {
|
||||||
const slidingSyncIndex = SlidingSyncManager.instance.getOrAllocateListIndex(this.props.tagId);
|
|
||||||
const count = RoomListStore.instance.getCount(this.props.tagId);
|
const count = RoomListStore.instance.getCount(this.props.tagId);
|
||||||
await SlidingSyncManager.instance.ensureListRegistered(slidingSyncIndex, {
|
await SlidingSyncManager.instance.ensureListRegistered(this.props.tagId, {
|
||||||
ranges: [[0, count]],
|
ranges: [[0, count]],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -566,10 +565,9 @@ export default class RoomSublist extends React.Component<IProps, IState> {
|
|||||||
let isAlphabetical = RoomListStore.instance.getTagSorting(this.props.tagId) === SortAlgorithm.Alphabetic;
|
let isAlphabetical = RoomListStore.instance.getTagSorting(this.props.tagId) === SortAlgorithm.Alphabetic;
|
||||||
let isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance;
|
let isUnreadFirst = RoomListStore.instance.getListOrder(this.props.tagId) === ListAlgorithm.Importance;
|
||||||
if (this.slidingSyncMode) {
|
if (this.slidingSyncMode) {
|
||||||
const slidingSyncIndex = SlidingSyncManager.instance.getOrAllocateListIndex(this.props.tagId);
|
const slidingList = SlidingSyncManager.instance.slidingSync.getListParams(this.props.tagId);
|
||||||
const slidingList = SlidingSyncManager.instance.slidingSync.getList(slidingSyncIndex);
|
isAlphabetical = (slidingList?.sort || [])[0] === "by_name";
|
||||||
isAlphabetical = slidingList.sort[0] === "by_name";
|
isUnreadFirst = (slidingList?.sort || [])[0] === "by_notification_level";
|
||||||
isUnreadFirst = slidingList.sort[0] === "by_notification_level";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Invites don't get some nonsense options, so only add them if we have to.
|
// Invites don't get some nonsense options, so only add them if we have to.
|
||||||
|
@ -34,7 +34,6 @@ export const useSlidingSyncRoomSearch = (): {
|
|||||||
const [rooms, setRooms] = useState<Room[]>([]);
|
const [rooms, setRooms] = useState<Room[]>([]);
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const listIndex = SlidingSyncManager.instance.getOrAllocateListIndex(SlidingSyncManager.ListSearch);
|
|
||||||
|
|
||||||
const [updateQuery, updateResult] = useLatestResult<{ term: string; limit?: number }, Room[]>(setRooms);
|
const [updateQuery, updateResult] = useLatestResult<{ term: string; limit?: number }, Room[]>(setRooms);
|
||||||
|
|
||||||
@ -50,14 +49,16 @@ export const useSlidingSyncRoomSearch = (): {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
await SlidingSyncManager.instance.ensureListRegistered(listIndex, {
|
await SlidingSyncManager.instance.ensureListRegistered(SlidingSyncManager.ListSearch, {
|
||||||
ranges: [[0, limit]],
|
ranges: [[0, limit]],
|
||||||
filters: {
|
filters: {
|
||||||
room_name_like: term,
|
room_name_like: term,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const rooms = [];
|
const rooms = [];
|
||||||
const { roomIndexToRoomId } = SlidingSyncManager.instance.slidingSync.getListData(listIndex);
|
const { roomIndexToRoomId } = SlidingSyncManager.instance.slidingSync.getListData(
|
||||||
|
SlidingSyncManager.ListSearch,
|
||||||
|
)!;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
while (roomIndexToRoomId[i]) {
|
while (roomIndexToRoomId[i]) {
|
||||||
const roomId = roomIndexToRoomId[i];
|
const roomId = roomIndexToRoomId[i];
|
||||||
@ -78,7 +79,7 @@ export const useSlidingSyncRoomSearch = (): {
|
|||||||
// TODO: delete the list?
|
// TODO: delete the list?
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[updateQuery, updateResult, listIndex],
|
[updateQuery, updateResult],
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -84,20 +84,20 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
|
|||||||
public constructor(dis: MatrixDispatcher, private readonly context: SdkContextClass) {
|
public constructor(dis: MatrixDispatcher, private readonly context: SdkContextClass) {
|
||||||
super(dis);
|
super(dis);
|
||||||
this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares
|
this.setMaxListeners(20); // RoomList + LeftPanel + 8xRoomSubList + spares
|
||||||
|
this.stickyRoomId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async setTagSorting(tagId: TagID, sort: SortAlgorithm): Promise<void> {
|
public async setTagSorting(tagId: TagID, sort: SortAlgorithm): Promise<void> {
|
||||||
logger.info("SlidingRoomListStore.setTagSorting ", tagId, sort);
|
logger.info("SlidingRoomListStore.setTagSorting ", tagId, sort);
|
||||||
this.tagIdToSortAlgo[tagId] = sort;
|
this.tagIdToSortAlgo[tagId] = sort;
|
||||||
const slidingSyncIndex = this.context.slidingSyncManager.getOrAllocateListIndex(tagId);
|
|
||||||
switch (sort) {
|
switch (sort) {
|
||||||
case SortAlgorithm.Alphabetic:
|
case SortAlgorithm.Alphabetic:
|
||||||
await this.context.slidingSyncManager.ensureListRegistered(slidingSyncIndex, {
|
await this.context.slidingSyncManager.ensureListRegistered(tagId, {
|
||||||
sort: SlidingSyncSortToFilter[SortAlgorithm.Alphabetic],
|
sort: SlidingSyncSortToFilter[SortAlgorithm.Alphabetic],
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case SortAlgorithm.Recent:
|
case SortAlgorithm.Recent:
|
||||||
await this.context.slidingSyncManager.ensureListRegistered(slidingSyncIndex, {
|
await this.context.slidingSyncManager.ensureListRegistered(tagId, {
|
||||||
sort: SlidingSyncSortToFilter[SortAlgorithm.Recent],
|
sort: SlidingSyncSortToFilter[SortAlgorithm.Recent],
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
@ -164,8 +164,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
|
|||||||
// check all lists for each tag we know about and see if the room is there
|
// check all lists for each tag we know about and see if the room is there
|
||||||
const tags: TagID[] = [];
|
const tags: TagID[] = [];
|
||||||
for (const tagId in this.tagIdToSortAlgo) {
|
for (const tagId in this.tagIdToSortAlgo) {
|
||||||
const index = this.context.slidingSyncManager.getOrAllocateListIndex(tagId);
|
const listData = this.context.slidingSyncManager.slidingSync.getListData(tagId);
|
||||||
const listData = this.context.slidingSyncManager.slidingSync.getListData(index);
|
|
||||||
if (!listData) {
|
if (!listData) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -251,19 +250,19 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
// now set the rooms
|
// now set the rooms
|
||||||
const rooms = orderedRoomIds.map((roomId) => {
|
const rooms: Room[] = [];
|
||||||
return this.matrixClient.getRoom(roomId);
|
orderedRoomIds.forEach((roomId) => {
|
||||||
|
const room = this.matrixClient.getRoom(roomId);
|
||||||
|
if (!room) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
rooms.push(room);
|
||||||
});
|
});
|
||||||
tagMap[tagId] = rooms;
|
tagMap[tagId] = rooms;
|
||||||
this.tagMap = tagMap;
|
this.tagMap = tagMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
private onSlidingSyncListUpdate(
|
private onSlidingSyncListUpdate(tagId: string, joinCount: number, roomIndexToRoomId: Record<number, string>): void {
|
||||||
listIndex: number,
|
|
||||||
joinCount: number,
|
|
||||||
roomIndexToRoomId: Record<number, string>,
|
|
||||||
): void {
|
|
||||||
const tagId = this.context.slidingSyncManager.listIdForIndex(listIndex);
|
|
||||||
this.counts[tagId] = joinCount;
|
this.counts[tagId] = joinCount;
|
||||||
this.refreshOrderedLists(tagId, roomIndexToRoomId);
|
this.refreshOrderedLists(tagId, roomIndexToRoomId);
|
||||||
// let the UI update
|
// let the UI update
|
||||||
@ -295,8 +294,7 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
|
|||||||
if (room) {
|
if (room) {
|
||||||
// resort it based on the slidingSync view of the list. This may cause this old sticky
|
// resort it based on the slidingSync view of the list. This may cause this old sticky
|
||||||
// room to cease to exist.
|
// room to cease to exist.
|
||||||
const index = this.context.slidingSyncManager.getOrAllocateListIndex(tagId);
|
const listData = this.context.slidingSyncManager.slidingSync.getListData(tagId);
|
||||||
const listData = this.context.slidingSyncManager.slidingSync.getListData(index);
|
|
||||||
if (!listData) {
|
if (!listData) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -334,9 +332,8 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
|
|||||||
const sort = SortAlgorithm.Recent; // default to recency sort, TODO: read from config
|
const sort = SortAlgorithm.Recent; // default to recency sort, TODO: read from config
|
||||||
this.tagIdToSortAlgo[tagId] = sort;
|
this.tagIdToSortAlgo[tagId] = sort;
|
||||||
this.emit(LISTS_LOADING_EVENT, tagId, true);
|
this.emit(LISTS_LOADING_EVENT, tagId, true);
|
||||||
const index = this.context.slidingSyncManager.getOrAllocateListIndex(tagId);
|
|
||||||
this.context.slidingSyncManager
|
this.context.slidingSyncManager
|
||||||
.ensureListRegistered(index, {
|
.ensureListRegistered(tagId, {
|
||||||
filters: filter,
|
filters: filter,
|
||||||
sort: SlidingSyncSortToFilter[sort],
|
sort: SlidingSyncSortToFilter[sort],
|
||||||
})
|
})
|
||||||
@ -361,15 +358,17 @@ export class SlidingRoomListStoreClass extends AsyncStoreWithClient<IState> impl
|
|||||||
if (roomId === activeSpace) {
|
if (roomId === activeSpace) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!filters.spaces) {
|
||||||
|
filters.spaces = [];
|
||||||
|
}
|
||||||
filters.spaces.push(roomId); // add subspace
|
filters.spaces.push(roomId); // add subspace
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.emit(LISTS_LOADING_EVENT, tagId, true);
|
this.emit(LISTS_LOADING_EVENT, tagId, true);
|
||||||
const index = this.context.slidingSyncManager.getOrAllocateListIndex(tagId);
|
|
||||||
this.context.slidingSyncManager
|
this.context.slidingSyncManager
|
||||||
.ensureListRegistered(index, {
|
.ensureListRegistered(tagId, {
|
||||||
filters: filters,
|
filters: filters,
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -78,13 +78,76 @@ describe("SlidingSyncManager", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("ensureListRegistered", () => {
|
||||||
|
it("creates a new list based on the key", async () => {
|
||||||
|
const listKey = "key";
|
||||||
|
mocked(slidingSync.getListParams).mockReturnValue(null);
|
||||||
|
mocked(slidingSync.setList).mockResolvedValue("yep");
|
||||||
|
await manager.ensureListRegistered(listKey, {
|
||||||
|
sort: ["by_recency"],
|
||||||
|
});
|
||||||
|
expect(slidingSync.setList).toBeCalledWith(
|
||||||
|
listKey,
|
||||||
|
expect.objectContaining({
|
||||||
|
sort: ["by_recency"],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates an existing list based on the key", async () => {
|
||||||
|
const listKey = "key";
|
||||||
|
mocked(slidingSync.getListParams).mockReturnValue({
|
||||||
|
ranges: [[0, 42]],
|
||||||
|
});
|
||||||
|
mocked(slidingSync.setList).mockResolvedValue("yep");
|
||||||
|
await manager.ensureListRegistered(listKey, {
|
||||||
|
sort: ["by_recency"],
|
||||||
|
});
|
||||||
|
expect(slidingSync.setList).toBeCalledWith(
|
||||||
|
listKey,
|
||||||
|
expect.objectContaining({
|
||||||
|
sort: ["by_recency"],
|
||||||
|
ranges: [[0, 42]],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates ranges on an existing list based on the key if there's no other changes", async () => {
|
||||||
|
const listKey = "key";
|
||||||
|
mocked(slidingSync.getListParams).mockReturnValue({
|
||||||
|
ranges: [[0, 42]],
|
||||||
|
});
|
||||||
|
mocked(slidingSync.setList).mockResolvedValue("yep");
|
||||||
|
await manager.ensureListRegistered(listKey, {
|
||||||
|
ranges: [[0, 52]],
|
||||||
|
});
|
||||||
|
expect(slidingSync.setList).not.toBeCalled();
|
||||||
|
expect(slidingSync.setListRanges).toBeCalledWith(listKey, [[0, 52]]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("no-ops for idential changes", async () => {
|
||||||
|
const listKey = "key";
|
||||||
|
mocked(slidingSync.getListParams).mockReturnValue({
|
||||||
|
ranges: [[0, 42]],
|
||||||
|
sort: ["by_recency"],
|
||||||
|
});
|
||||||
|
mocked(slidingSync.setList).mockResolvedValue("yep");
|
||||||
|
await manager.ensureListRegistered(listKey, {
|
||||||
|
ranges: [[0, 42]],
|
||||||
|
sort: ["by_recency"],
|
||||||
|
});
|
||||||
|
expect(slidingSync.setList).not.toBeCalled();
|
||||||
|
expect(slidingSync.setListRanges).not.toBeCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("startSpidering", () => {
|
describe("startSpidering", () => {
|
||||||
it("requests in batchSizes", async () => {
|
it("requests in batchSizes", async () => {
|
||||||
const gapMs = 1;
|
const gapMs = 1;
|
||||||
const batchSize = 10;
|
const batchSize = 10;
|
||||||
mocked(slidingSync.setList).mockResolvedValue("yep");
|
mocked(slidingSync.setList).mockResolvedValue("yep");
|
||||||
mocked(slidingSync.setListRanges).mockResolvedValue("yep");
|
mocked(slidingSync.setListRanges).mockResolvedValue("yep");
|
||||||
mocked(slidingSync.getListData).mockImplementation((i) => {
|
mocked(slidingSync.getListData).mockImplementation((key) => {
|
||||||
return {
|
return {
|
||||||
joinedCount: 64,
|
joinedCount: 64,
|
||||||
roomIndexToRoomId: {},
|
roomIndexToRoomId: {},
|
||||||
@ -106,24 +169,24 @@ describe("SlidingSyncManager", () => {
|
|||||||
wantWindows.forEach((range, i) => {
|
wantWindows.forEach((range, i) => {
|
||||||
if (i === 0) {
|
if (i === 0) {
|
||||||
expect(slidingSync.setList).toBeCalledWith(
|
expect(slidingSync.setList).toBeCalledWith(
|
||||||
manager.getOrAllocateListIndex(SlidingSyncManager.ListSearch),
|
SlidingSyncManager.ListSearch,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ranges: [[0, batchSize - 1], range],
|
ranges: [[0, batchSize - 1], range],
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
expect(slidingSync.setListRanges).toBeCalledWith(
|
expect(slidingSync.setListRanges).toBeCalledWith(SlidingSyncManager.ListSearch, [
|
||||||
manager.getOrAllocateListIndex(SlidingSyncManager.ListSearch),
|
[0, batchSize - 1],
|
||||||
[[0, batchSize - 1], range],
|
range,
|
||||||
);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it("handles accounts with zero rooms", async () => {
|
it("handles accounts with zero rooms", async () => {
|
||||||
const gapMs = 1;
|
const gapMs = 1;
|
||||||
const batchSize = 10;
|
const batchSize = 10;
|
||||||
mocked(slidingSync.setList).mockResolvedValue("yep");
|
mocked(slidingSync.setList).mockResolvedValue("yep");
|
||||||
mocked(slidingSync.getListData).mockImplementation((i) => {
|
mocked(slidingSync.getListData).mockImplementation((key) => {
|
||||||
return {
|
return {
|
||||||
joinedCount: 0,
|
joinedCount: 0,
|
||||||
roomIndexToRoomId: {},
|
roomIndexToRoomId: {},
|
||||||
@ -133,7 +196,7 @@ describe("SlidingSyncManager", () => {
|
|||||||
expect(slidingSync.getListData).toBeCalledTimes(1);
|
expect(slidingSync.getListData).toBeCalledTimes(1);
|
||||||
expect(slidingSync.setList).toBeCalledTimes(1);
|
expect(slidingSync.setList).toBeCalledTimes(1);
|
||||||
expect(slidingSync.setList).toBeCalledWith(
|
expect(slidingSync.setList).toBeCalledWith(
|
||||||
manager.getOrAllocateListIndex(SlidingSyncManager.ListSearch),
|
SlidingSyncManager.ListSearch,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ranges: [
|
ranges: [
|
||||||
[0, batchSize - 1],
|
[0, batchSize - 1],
|
||||||
@ -146,7 +209,7 @@ describe("SlidingSyncManager", () => {
|
|||||||
const gapMs = 1;
|
const gapMs = 1;
|
||||||
const batchSize = 10;
|
const batchSize = 10;
|
||||||
mocked(slidingSync.setList).mockRejectedValue("narp");
|
mocked(slidingSync.setList).mockRejectedValue("narp");
|
||||||
mocked(slidingSync.getListData).mockImplementation((i) => {
|
mocked(slidingSync.getListData).mockImplementation((key) => {
|
||||||
return {
|
return {
|
||||||
joinedCount: 0,
|
joinedCount: 0,
|
||||||
roomIndexToRoomId: {},
|
roomIndexToRoomId: {},
|
||||||
@ -156,7 +219,7 @@ describe("SlidingSyncManager", () => {
|
|||||||
expect(slidingSync.getListData).toBeCalledTimes(1);
|
expect(slidingSync.getListData).toBeCalledTimes(1);
|
||||||
expect(slidingSync.setList).toBeCalledTimes(1);
|
expect(slidingSync.setList).toBeCalledTimes(1);
|
||||||
expect(slidingSync.setList).toBeCalledWith(
|
expect(slidingSync.setList).toBeCalledWith(
|
||||||
manager.getOrAllocateListIndex(SlidingSyncManager.ListSearch),
|
SlidingSyncManager.ListSearch,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
ranges: [
|
ranges: [
|
||||||
[0, batchSize - 1],
|
[0, batchSize - 1],
|
||||||
|
111
test/hooks/useSlidingSyncRoomSearch-test.tsx
Normal file
111
test/hooks/useSlidingSyncRoomSearch-test.tsx
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// eslint-disable-next-line deprecate/import
|
||||||
|
import { mount } from "enzyme";
|
||||||
|
import { sleep } from "matrix-js-sdk/src/utils";
|
||||||
|
import React from "react";
|
||||||
|
import { act } from "react-dom/test-utils";
|
||||||
|
import { mocked } from "jest-mock";
|
||||||
|
import { SlidingSync } from "matrix-js-sdk/src/sliding-sync";
|
||||||
|
import { Room } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import { SlidingSyncRoomSearchOpts, useSlidingSyncRoomSearch } from "../../src/hooks/useSlidingSyncRoomSearch";
|
||||||
|
import { MockEventEmitter, stubClient } from "../test-utils";
|
||||||
|
import { SlidingSyncManager } from "../../src/SlidingSyncManager";
|
||||||
|
|
||||||
|
type RoomSearchHook = {
|
||||||
|
loading: boolean;
|
||||||
|
rooms: Room[];
|
||||||
|
search(opts: SlidingSyncRoomSearchOpts): Promise<boolean>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// hooks must be inside a React component else you get:
|
||||||
|
// "Invalid hook call. Hooks can only be called inside of the body of a function component."
|
||||||
|
function RoomSearchComponent(props: { onClick: (h: RoomSearchHook) => void }) {
|
||||||
|
const roomSearch = useSlidingSyncRoomSearch();
|
||||||
|
return <div onClick={() => props.onClick(roomSearch)} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("useSlidingSyncRoomSearch", () => {
|
||||||
|
it("should display rooms when searching", async () => {
|
||||||
|
const client = stubClient();
|
||||||
|
const roomA = new Room("!a:localhost", client, client.getUserId()!);
|
||||||
|
const roomB = new Room("!b:localhost", client, client.getUserId()!);
|
||||||
|
const slidingSync = mocked(
|
||||||
|
new MockEventEmitter({
|
||||||
|
getListData: jest.fn(),
|
||||||
|
}) as unknown as SlidingSync,
|
||||||
|
);
|
||||||
|
jest.spyOn(SlidingSyncManager.instance, "ensureListRegistered").mockResolvedValue({
|
||||||
|
ranges: [[0, 9]],
|
||||||
|
});
|
||||||
|
SlidingSyncManager.instance.slidingSync = slidingSync;
|
||||||
|
mocked(slidingSync.getListData).mockReturnValue({
|
||||||
|
joinedCount: 2,
|
||||||
|
roomIndexToRoomId: {
|
||||||
|
0: roomA.roomId,
|
||||||
|
1: roomB.roomId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
mocked(client.getRoom).mockImplementation((roomId) => {
|
||||||
|
switch (roomId) {
|
||||||
|
case roomA.roomId:
|
||||||
|
return roomA;
|
||||||
|
case roomB.roomId:
|
||||||
|
return roomB;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// first check that everything is empty and then do the search
|
||||||
|
let executeHook = (roomSearch: RoomSearchHook) => {
|
||||||
|
expect(roomSearch.loading).toBe(false);
|
||||||
|
expect(roomSearch.rooms).toEqual([]);
|
||||||
|
roomSearch.search({
|
||||||
|
limit: 10,
|
||||||
|
query: "foo",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const wrapper = mount(
|
||||||
|
<RoomSearchComponent
|
||||||
|
onClick={(roomSearch: RoomSearchHook) => {
|
||||||
|
executeHook(roomSearch);
|
||||||
|
}}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// run the query
|
||||||
|
await act(async () => {
|
||||||
|
await sleep(1);
|
||||||
|
wrapper.simulate("click");
|
||||||
|
return act(() => sleep(1));
|
||||||
|
});
|
||||||
|
// now we expect there to be rooms
|
||||||
|
executeHook = (roomSearch) => {
|
||||||
|
expect(roomSearch.loading).toBe(false);
|
||||||
|
expect(roomSearch.rooms).toEqual([roomA, roomB]);
|
||||||
|
};
|
||||||
|
|
||||||
|
// run the query
|
||||||
|
await act(async () => {
|
||||||
|
await sleep(1);
|
||||||
|
wrapper.simulate("click");
|
||||||
|
return act(() => sleep(1));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -30,7 +30,7 @@ import { RoomViewStore } from "../../../src/stores/RoomViewStore";
|
|||||||
import { MatrixDispatcher } from "../../../src/dispatcher/dispatcher";
|
import { MatrixDispatcher } from "../../../src/dispatcher/dispatcher";
|
||||||
import { SortAlgorithm } from "../../../src/stores/room-list/algorithms/models";
|
import { SortAlgorithm } from "../../../src/stores/room-list/algorithms/models";
|
||||||
import { DefaultTagID, TagID } from "../../../src/stores/room-list/models";
|
import { DefaultTagID, TagID } from "../../../src/stores/room-list/models";
|
||||||
import { UPDATE_SELECTED_SPACE } from "../../../src/stores/spaces";
|
import { MetaSpace, UPDATE_SELECTED_SPACE } from "../../../src/stores/spaces";
|
||||||
import { LISTS_LOADING_EVENT } from "../../../src/stores/room-list/RoomListStore";
|
import { LISTS_LOADING_EVENT } from "../../../src/stores/room-list/RoomListStore";
|
||||||
import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
|
import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
|
||||||
|
|
||||||
@ -42,7 +42,6 @@ describe("SlidingRoomListStore", () => {
|
|||||||
let context: TestSdkContext;
|
let context: TestSdkContext;
|
||||||
let dis: MatrixDispatcher;
|
let dis: MatrixDispatcher;
|
||||||
let activeSpace: string;
|
let activeSpace: string;
|
||||||
let tagIdToIndex = {};
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
context = new TestSdkContext();
|
context = new TestSdkContext();
|
||||||
@ -64,27 +63,6 @@ describe("SlidingRoomListStore", () => {
|
|||||||
getRoomId: jest.fn(),
|
getRoomId: jest.fn(),
|
||||||
}) as unknown as RoomViewStore,
|
}) as unknown as RoomViewStore,
|
||||||
);
|
);
|
||||||
|
|
||||||
// mock implementations to allow the store to map tag IDs to sliding sync list indexes and vice versa
|
|
||||||
let index = 0;
|
|
||||||
tagIdToIndex = {};
|
|
||||||
mocked(context._SlidingSyncManager.getOrAllocateListIndex).mockImplementation((listId: string): number => {
|
|
||||||
if (tagIdToIndex[listId] != null) {
|
|
||||||
return tagIdToIndex[listId];
|
|
||||||
}
|
|
||||||
tagIdToIndex[listId] = index;
|
|
||||||
index++;
|
|
||||||
return index;
|
|
||||||
});
|
|
||||||
mocked(context.slidingSyncManager.listIdForIndex).mockImplementation((i) => {
|
|
||||||
for (const tagId in tagIdToIndex) {
|
|
||||||
const j = tagIdToIndex[tagId];
|
|
||||||
if (i === j) {
|
|
||||||
return tagId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
mocked(context._SlidingSyncManager.ensureListRegistered).mockResolvedValue({
|
mocked(context._SlidingSyncManager.ensureListRegistered).mockResolvedValue({
|
||||||
ranges: [[0, 10]],
|
ranges: [[0, 10]],
|
||||||
});
|
});
|
||||||
@ -104,17 +82,31 @@ describe("SlidingRoomListStore", () => {
|
|||||||
|
|
||||||
// change the active space
|
// change the active space
|
||||||
activeSpace = spaceRoomId;
|
activeSpace = spaceRoomId;
|
||||||
context._SpaceStore.emit(UPDATE_SELECTED_SPACE, spaceRoomId, false);
|
context._SpaceStore!.emit(UPDATE_SELECTED_SPACE, spaceRoomId, false);
|
||||||
await p;
|
await p;
|
||||||
|
|
||||||
expect(context._SlidingSyncManager.ensureListRegistered).toHaveBeenCalledWith(
|
expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(DefaultTagID.Untagged, {
|
||||||
tagIdToIndex[DefaultTagID.Untagged],
|
filters: expect.objectContaining({
|
||||||
{
|
spaces: [spaceRoomId],
|
||||||
filters: expect.objectContaining({
|
}),
|
||||||
spaces: [spaceRoomId],
|
});
|
||||||
}),
|
});
|
||||||
|
|
||||||
|
it("gracefully handles subspaces in the home metaspace", async () => {
|
||||||
|
const subspace = "!sub:space";
|
||||||
|
mocked(context._SpaceStore!.traverseSpace).mockImplementation(
|
||||||
|
(spaceId: string, fn: (roomId: string) => void) => {
|
||||||
|
fn(subspace);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
activeSpace = MetaSpace.Home;
|
||||||
|
await store.start(); // call onReady
|
||||||
|
|
||||||
|
expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(DefaultTagID.Untagged, {
|
||||||
|
filters: expect.objectContaining({
|
||||||
|
spaces: [subspace],
|
||||||
|
}),
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("alters 'filters.spaces' on the DefaultTagID.Untagged list if it loads with an active space", async () => {
|
it("alters 'filters.spaces' on the DefaultTagID.Untagged list if it loads with an active space", async () => {
|
||||||
@ -126,8 +118,8 @@ describe("SlidingRoomListStore", () => {
|
|||||||
});
|
});
|
||||||
await store.start(); // call onReady
|
await store.start(); // call onReady
|
||||||
await p;
|
await p;
|
||||||
expect(context._SlidingSyncManager.ensureListRegistered).toHaveBeenCalledWith(
|
expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(
|
||||||
tagIdToIndex[DefaultTagID.Untagged],
|
DefaultTagID.Untagged,
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
filters: expect.objectContaining({
|
filters: expect.objectContaining({
|
||||||
spaces: [spaceRoomId],
|
spaces: [spaceRoomId],
|
||||||
@ -146,7 +138,7 @@ describe("SlidingRoomListStore", () => {
|
|||||||
return listName === DefaultTagID.Untagged && !isLoading;
|
return listName === DefaultTagID.Untagged && !isLoading;
|
||||||
});
|
});
|
||||||
|
|
||||||
mocked(context._SpaceStore.traverseSpace).mockImplementation(
|
mocked(context._SpaceStore!.traverseSpace).mockImplementation(
|
||||||
(spaceId: string, fn: (roomId: string) => void) => {
|
(spaceId: string, fn: (roomId: string) => void) => {
|
||||||
if (spaceId === spaceRoomId) {
|
if (spaceId === spaceRoomId) {
|
||||||
fn(subSpace1);
|
fn(subSpace1);
|
||||||
@ -157,31 +149,27 @@ describe("SlidingRoomListStore", () => {
|
|||||||
|
|
||||||
// change the active space
|
// change the active space
|
||||||
activeSpace = spaceRoomId;
|
activeSpace = spaceRoomId;
|
||||||
context._SpaceStore.emit(UPDATE_SELECTED_SPACE, spaceRoomId, false);
|
context._SpaceStore!.emit(UPDATE_SELECTED_SPACE, spaceRoomId, false);
|
||||||
await p;
|
await p;
|
||||||
|
|
||||||
expect(context._SlidingSyncManager.ensureListRegistered).toHaveBeenCalledWith(
|
expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(DefaultTagID.Untagged, {
|
||||||
tagIdToIndex[DefaultTagID.Untagged],
|
filters: expect.objectContaining({
|
||||||
{
|
spaces: [spaceRoomId, subSpace1, subSpace2],
|
||||||
filters: expect.objectContaining({
|
}),
|
||||||
spaces: [spaceRoomId, subSpace1, subSpace2],
|
});
|
||||||
}),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("setTagSorting alters the 'sort' option in the list", async () => {
|
it("setTagSorting alters the 'sort' option in the list", async () => {
|
||||||
mocked(context._SlidingSyncManager.getOrAllocateListIndex).mockReturnValue(0);
|
|
||||||
const tagId: TagID = "foo";
|
const tagId: TagID = "foo";
|
||||||
await store.setTagSorting(tagId, SortAlgorithm.Alphabetic);
|
await store.setTagSorting(tagId, SortAlgorithm.Alphabetic);
|
||||||
expect(context._SlidingSyncManager.ensureListRegistered).toBeCalledWith(0, {
|
expect(context._SlidingSyncManager!.ensureListRegistered).toBeCalledWith(tagId, {
|
||||||
sort: SlidingSyncSortToFilter[SortAlgorithm.Alphabetic],
|
sort: SlidingSyncSortToFilter[SortAlgorithm.Alphabetic],
|
||||||
});
|
});
|
||||||
expect(store.getTagSorting(tagId)).toEqual(SortAlgorithm.Alphabetic);
|
expect(store.getTagSorting(tagId)).toEqual(SortAlgorithm.Alphabetic);
|
||||||
|
|
||||||
await store.setTagSorting(tagId, SortAlgorithm.Recent);
|
await store.setTagSorting(tagId, SortAlgorithm.Recent);
|
||||||
expect(context._SlidingSyncManager.ensureListRegistered).toBeCalledWith(0, {
|
expect(context._SlidingSyncManager!.ensureListRegistered).toBeCalledWith(tagId, {
|
||||||
sort: SlidingSyncSortToFilter[SortAlgorithm.Recent],
|
sort: SlidingSyncSortToFilter[SortAlgorithm.Recent],
|
||||||
});
|
});
|
||||||
expect(store.getTagSorting(tagId)).toEqual(SortAlgorithm.Recent);
|
expect(store.getTagSorting(tagId)).toEqual(SortAlgorithm.Recent);
|
||||||
@ -189,33 +177,31 @@ describe("SlidingRoomListStore", () => {
|
|||||||
|
|
||||||
it("getTagsForRoom gets the tags for the room", async () => {
|
it("getTagsForRoom gets the tags for the room", async () => {
|
||||||
await store.start();
|
await store.start();
|
||||||
const untaggedIndex = context._SlidingSyncManager.getOrAllocateListIndex(DefaultTagID.Untagged);
|
|
||||||
const favIndex = context._SlidingSyncManager.getOrAllocateListIndex(DefaultTagID.Favourite);
|
|
||||||
const roomA = "!a:localhost";
|
const roomA = "!a:localhost";
|
||||||
const roomB = "!b:localhost";
|
const roomB = "!b:localhost";
|
||||||
const indexToListData = {
|
const keyToListData: Record<string, { joinedCount: number; roomIndexToRoomId: Record<number, string> }> = {
|
||||||
[untaggedIndex]: {
|
[DefaultTagID.Untagged]: {
|
||||||
joinedCount: 10,
|
joinedCount: 10,
|
||||||
roomIndexToRoomId: {
|
roomIndexToRoomId: {
|
||||||
0: roomA,
|
0: roomA,
|
||||||
1: roomB,
|
1: roomB,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
[favIndex]: {
|
[DefaultTagID.Favourite]: {
|
||||||
joinedCount: 2,
|
joinedCount: 2,
|
||||||
roomIndexToRoomId: {
|
roomIndexToRoomId: {
|
||||||
0: roomB,
|
0: roomB,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
mocked(context._SlidingSyncManager.slidingSync.getListData).mockImplementation((i: number) => {
|
mocked(context._SlidingSyncManager!.slidingSync.getListData).mockImplementation((key: string) => {
|
||||||
return indexToListData[i] || null;
|
return keyToListData[key] || null;
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(store.getTagsForRoom(new Room(roomA, context.client, context.client.getUserId()))).toEqual([
|
expect(store.getTagsForRoom(new Room(roomA, context.client!, context.client!.getUserId()))).toEqual([
|
||||||
DefaultTagID.Untagged,
|
DefaultTagID.Untagged,
|
||||||
]);
|
]);
|
||||||
expect(store.getTagsForRoom(new Room(roomB, context.client, context.client.getUserId()))).toEqual([
|
expect(store.getTagsForRoom(new Room(roomB, context.client!, context.client!.getUserId()))).toEqual([
|
||||||
DefaultTagID.Favourite,
|
DefaultTagID.Favourite,
|
||||||
DefaultTagID.Untagged,
|
DefaultTagID.Untagged,
|
||||||
]);
|
]);
|
||||||
@ -227,7 +213,6 @@ describe("SlidingRoomListStore", () => {
|
|||||||
const roomB = "!b:localhost";
|
const roomB = "!b:localhost";
|
||||||
const roomC = "!c:localhost";
|
const roomC = "!c:localhost";
|
||||||
const tagId = DefaultTagID.Favourite;
|
const tagId = DefaultTagID.Favourite;
|
||||||
const listIndex = context.slidingSyncManager.getOrAllocateListIndex(tagId);
|
|
||||||
const joinCount = 10;
|
const joinCount = 10;
|
||||||
const roomIndexToRoomId = {
|
const roomIndexToRoomId = {
|
||||||
// mixed to ensure we sort
|
// mixed to ensure we sort
|
||||||
@ -236,11 +221,11 @@ describe("SlidingRoomListStore", () => {
|
|||||||
0: roomA,
|
0: roomA,
|
||||||
};
|
};
|
||||||
const rooms = [
|
const rooms = [
|
||||||
new Room(roomA, context.client, context.client.getUserId()),
|
new Room(roomA, context.client!, context.client!.getUserId()),
|
||||||
new Room(roomB, context.client, context.client.getUserId()),
|
new Room(roomB, context.client!, context.client!.getUserId()),
|
||||||
new Room(roomC, context.client, context.client.getUserId()),
|
new Room(roomC, context.client!, context.client!.getUserId()),
|
||||||
];
|
];
|
||||||
mocked(context.client.getRoom).mockImplementation((roomId: string) => {
|
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
|
||||||
switch (roomId) {
|
switch (roomId) {
|
||||||
case roomA:
|
case roomA:
|
||||||
return rooms[0];
|
return rooms[0];
|
||||||
@ -252,7 +237,7 @@ describe("SlidingRoomListStore", () => {
|
|||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
const p = untilEmission(store, LISTS_UPDATE_EVENT);
|
const p = untilEmission(store, LISTS_UPDATE_EVENT);
|
||||||
context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, listIndex, joinCount, roomIndexToRoomId);
|
context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, tagId, joinCount, roomIndexToRoomId);
|
||||||
await p;
|
await p;
|
||||||
expect(store.getCount(tagId)).toEqual(joinCount);
|
expect(store.getCount(tagId)).toEqual(joinCount);
|
||||||
expect(store.orderedLists[tagId]).toEqual(rooms);
|
expect(store.orderedLists[tagId]).toEqual(rooms);
|
||||||
@ -265,7 +250,6 @@ describe("SlidingRoomListStore", () => {
|
|||||||
const roomIdB = "!b:localhost";
|
const roomIdB = "!b:localhost";
|
||||||
const roomIdC = "!c:localhost";
|
const roomIdC = "!c:localhost";
|
||||||
const tagId = DefaultTagID.Favourite;
|
const tagId = DefaultTagID.Favourite;
|
||||||
const listIndex = context.slidingSyncManager.getOrAllocateListIndex(tagId);
|
|
||||||
const joinCount = 10;
|
const joinCount = 10;
|
||||||
const roomIndexToRoomId = {
|
const roomIndexToRoomId = {
|
||||||
// mixed to ensure we sort
|
// mixed to ensure we sort
|
||||||
@ -273,10 +257,10 @@ describe("SlidingRoomListStore", () => {
|
|||||||
2: roomIdC,
|
2: roomIdC,
|
||||||
0: roomIdA,
|
0: roomIdA,
|
||||||
};
|
};
|
||||||
const roomA = new Room(roomIdA, context.client, context.client.getUserId());
|
const roomA = new Room(roomIdA, context.client!, context.client!.getUserId());
|
||||||
const roomB = new Room(roomIdB, context.client, context.client.getUserId());
|
const roomB = new Room(roomIdB, context.client!, context.client!.getUserId());
|
||||||
const roomC = new Room(roomIdC, context.client, context.client.getUserId());
|
const roomC = new Room(roomIdC, context.client!, context.client!.getUserId());
|
||||||
mocked(context.client.getRoom).mockImplementation((roomId: string) => {
|
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
|
||||||
switch (roomId) {
|
switch (roomId) {
|
||||||
case roomIdA:
|
case roomIdA:
|
||||||
return roomA;
|
return roomA;
|
||||||
@ -287,8 +271,8 @@ describe("SlidingRoomListStore", () => {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
mocked(context._SlidingSyncManager.slidingSync.getListData).mockImplementation((i: number) => {
|
mocked(context._SlidingSyncManager!.slidingSync.getListData).mockImplementation((key: string) => {
|
||||||
if (i !== listIndex) {
|
if (key !== tagId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -297,7 +281,7 @@ describe("SlidingRoomListStore", () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
let p = untilEmission(store, LISTS_UPDATE_EVENT);
|
let p = untilEmission(store, LISTS_UPDATE_EVENT);
|
||||||
context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, listIndex, joinCount, roomIndexToRoomId);
|
context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, tagId, joinCount, roomIndexToRoomId);
|
||||||
await p;
|
await p;
|
||||||
expect(store.orderedLists[tagId]).toEqual([roomA, roomB, roomC]);
|
expect(store.orderedLists[tagId]).toEqual([roomA, roomB, roomC]);
|
||||||
|
|
||||||
@ -310,7 +294,7 @@ describe("SlidingRoomListStore", () => {
|
|||||||
roomIndexToRoomId[1] = roomIdA;
|
roomIndexToRoomId[1] = roomIdA;
|
||||||
roomIndexToRoomId[2] = roomIdB;
|
roomIndexToRoomId[2] = roomIdB;
|
||||||
p = untilEmission(store, LISTS_UPDATE_EVENT);
|
p = untilEmission(store, LISTS_UPDATE_EVENT);
|
||||||
context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, listIndex, joinCount, roomIndexToRoomId);
|
context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, tagId, joinCount, roomIndexToRoomId);
|
||||||
await p;
|
await p;
|
||||||
|
|
||||||
// check that B didn't move and that A was put below B
|
// check that B didn't move and that A was put below B
|
||||||
@ -323,4 +307,43 @@ describe("SlidingRoomListStore", () => {
|
|||||||
await p;
|
await p;
|
||||||
expect(store.orderedLists[tagId].map((r) => r.roomId)).toEqual([roomC, roomA, roomB].map((r) => r.roomId));
|
expect(store.orderedLists[tagId].map((r) => r.roomId)).toEqual([roomC, roomA, roomB].map((r) => r.roomId));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("gracefully handles unknown room IDs", async () => {
|
||||||
|
await store.start();
|
||||||
|
const roomIdA = "!a:localhost";
|
||||||
|
const roomIdB = "!b:localhost"; // does not exist
|
||||||
|
const roomIdC = "!c:localhost";
|
||||||
|
const roomIndexToRoomId = {
|
||||||
|
0: roomIdA,
|
||||||
|
1: roomIdB, // does not exist
|
||||||
|
2: roomIdC,
|
||||||
|
};
|
||||||
|
const tagId = DefaultTagID.Favourite;
|
||||||
|
const joinCount = 10;
|
||||||
|
// seed the store with 2 rooms
|
||||||
|
const roomA = new Room(roomIdA, context.client!, context.client!.getUserId());
|
||||||
|
const roomC = new Room(roomIdC, context.client!, context.client!.getUserId());
|
||||||
|
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
|
||||||
|
switch (roomId) {
|
||||||
|
case roomIdA:
|
||||||
|
return roomA;
|
||||||
|
case roomIdC:
|
||||||
|
return roomC;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
mocked(context._SlidingSyncManager!.slidingSync.getListData).mockImplementation((key: string) => {
|
||||||
|
if (key !== tagId) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
roomIndexToRoomId: roomIndexToRoomId,
|
||||||
|
joinedCount: joinCount,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
const p = untilEmission(store, LISTS_UPDATE_EVENT);
|
||||||
|
context.slidingSyncManager.slidingSync.emit(SlidingSyncEvent.List, tagId, joinCount, roomIndexToRoomId);
|
||||||
|
await p;
|
||||||
|
expect(store.orderedLists[tagId]).toEqual([roomA, roomC]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user