350 lines
14 KiB
TypeScript
350 lines
14 KiB
TypeScript
/*
|
|
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 { mocked } from "jest-mock";
|
|
import { SlidingSync, SlidingSyncEvent } from "matrix-js-sdk/src/sliding-sync";
|
|
import { Room } from "matrix-js-sdk/src/matrix";
|
|
|
|
import {
|
|
LISTS_UPDATE_EVENT,
|
|
SlidingRoomListStoreClass,
|
|
SlidingSyncSortToFilter,
|
|
} from "../../../src/stores/room-list/SlidingRoomListStore";
|
|
import { SpaceStoreClass } from "../../../src/stores/spaces/SpaceStore";
|
|
import { MockEventEmitter, stubClient, untilEmission } from "../../test-utils";
|
|
import { TestSdkContext } from "../../TestSdkContext";
|
|
import { SlidingSyncManager } from "../../../src/SlidingSyncManager";
|
|
import { RoomViewStore } from "../../../src/stores/RoomViewStore";
|
|
import { MatrixDispatcher } from "../../../src/dispatcher/dispatcher";
|
|
import { SortAlgorithm } from "../../../src/stores/room-list/algorithms/models";
|
|
import { DefaultTagID, TagID } from "../../../src/stores/room-list/models";
|
|
import { MetaSpace, UPDATE_SELECTED_SPACE } from "../../../src/stores/spaces";
|
|
import { LISTS_LOADING_EVENT } from "../../../src/stores/room-list/RoomListStore";
|
|
import { UPDATE_EVENT } from "../../../src/stores/AsyncStore";
|
|
|
|
jest.mock("../../../src/SlidingSyncManager");
|
|
const MockSlidingSyncManager = <jest.Mock<SlidingSyncManager>>(<unknown>SlidingSyncManager);
|
|
|
|
describe("SlidingRoomListStore", () => {
|
|
let store: SlidingRoomListStoreClass;
|
|
let context: TestSdkContext;
|
|
let dis: MatrixDispatcher;
|
|
let activeSpace: string;
|
|
|
|
beforeEach(async () => {
|
|
context = new TestSdkContext();
|
|
context.client = stubClient();
|
|
context._SpaceStore = new MockEventEmitter<SpaceStoreClass>({
|
|
traverseSpace: jest.fn(),
|
|
get activeSpace() {
|
|
return activeSpace;
|
|
},
|
|
}) as SpaceStoreClass;
|
|
context._SlidingSyncManager = new MockSlidingSyncManager();
|
|
context._SlidingSyncManager.slidingSync = mocked(
|
|
new MockEventEmitter({
|
|
getListData: jest.fn(),
|
|
}) as unknown as SlidingSync,
|
|
);
|
|
context._RoomViewStore = mocked(
|
|
new MockEventEmitter({
|
|
getRoomId: jest.fn(),
|
|
}) as unknown as RoomViewStore,
|
|
);
|
|
mocked(context._SlidingSyncManager.ensureListRegistered).mockResolvedValue({
|
|
ranges: [[0, 10]],
|
|
});
|
|
|
|
dis = new MatrixDispatcher();
|
|
store = new SlidingRoomListStoreClass(dis, context);
|
|
});
|
|
|
|
describe("spaces", () => {
|
|
it("alters 'filters.spaces' on the DefaultTagID.Untagged list when the selected space changes", async () => {
|
|
await store.start(); // call onReady
|
|
const spaceRoomId = "!foo:bar";
|
|
|
|
const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => {
|
|
return listName === DefaultTagID.Untagged && !isLoading;
|
|
});
|
|
|
|
// change the active space
|
|
activeSpace = spaceRoomId;
|
|
context._SpaceStore!.emit(UPDATE_SELECTED_SPACE, spaceRoomId, false);
|
|
await p;
|
|
|
|
expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(DefaultTagID.Untagged, {
|
|
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 () => {
|
|
// change the active space before we are ready
|
|
const spaceRoomId = "!foo2:bar";
|
|
activeSpace = spaceRoomId;
|
|
const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => {
|
|
return listName === DefaultTagID.Untagged && !isLoading;
|
|
});
|
|
await store.start(); // call onReady
|
|
await p;
|
|
expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(
|
|
DefaultTagID.Untagged,
|
|
expect.objectContaining({
|
|
filters: expect.objectContaining({
|
|
spaces: [spaceRoomId],
|
|
}),
|
|
}),
|
|
);
|
|
});
|
|
|
|
it("includes subspaces in 'filters.spaces' when the selected space has subspaces", async () => {
|
|
await store.start(); // call onReady
|
|
const spaceRoomId = "!foo:bar";
|
|
const subSpace1 = "!ss1:bar";
|
|
const subSpace2 = "!ss2:bar";
|
|
|
|
const p = untilEmission(store, LISTS_LOADING_EVENT, (listName, isLoading) => {
|
|
return listName === DefaultTagID.Untagged && !isLoading;
|
|
});
|
|
|
|
mocked(context._SpaceStore!.traverseSpace).mockImplementation(
|
|
(spaceId: string, fn: (roomId: string) => void) => {
|
|
if (spaceId === spaceRoomId) {
|
|
fn(subSpace1);
|
|
fn(subSpace2);
|
|
}
|
|
},
|
|
);
|
|
|
|
// change the active space
|
|
activeSpace = spaceRoomId;
|
|
context._SpaceStore!.emit(UPDATE_SELECTED_SPACE, spaceRoomId, false);
|
|
await p;
|
|
|
|
expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(DefaultTagID.Untagged, {
|
|
filters: expect.objectContaining({
|
|
spaces: [spaceRoomId, subSpace1, subSpace2],
|
|
}),
|
|
});
|
|
});
|
|
});
|
|
|
|
it("setTagSorting alters the 'sort' option in the list", async () => {
|
|
const tagId: TagID = "foo";
|
|
await store.setTagSorting(tagId, SortAlgorithm.Alphabetic);
|
|
expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(tagId, {
|
|
sort: SlidingSyncSortToFilter[SortAlgorithm.Alphabetic],
|
|
});
|
|
expect(store.getTagSorting(tagId)).toEqual(SortAlgorithm.Alphabetic);
|
|
|
|
await store.setTagSorting(tagId, SortAlgorithm.Recent);
|
|
expect(context._SlidingSyncManager!.ensureListRegistered).toHaveBeenCalledWith(tagId, {
|
|
sort: SlidingSyncSortToFilter[SortAlgorithm.Recent],
|
|
});
|
|
expect(store.getTagSorting(tagId)).toEqual(SortAlgorithm.Recent);
|
|
});
|
|
|
|
it("getTagsForRoom gets the tags for the room", async () => {
|
|
await store.start();
|
|
const roomA = "!a:localhost";
|
|
const roomB = "!b:localhost";
|
|
const keyToListData: Record<string, { joinedCount: number; roomIndexToRoomId: Record<number, string> }> = {
|
|
[DefaultTagID.Untagged]: {
|
|
joinedCount: 10,
|
|
roomIndexToRoomId: {
|
|
0: roomA,
|
|
1: roomB,
|
|
},
|
|
},
|
|
[DefaultTagID.Favourite]: {
|
|
joinedCount: 2,
|
|
roomIndexToRoomId: {
|
|
0: roomB,
|
|
},
|
|
},
|
|
};
|
|
mocked(context._SlidingSyncManager!.slidingSync!.getListData).mockImplementation((key: string) => {
|
|
return keyToListData[key] || null;
|
|
});
|
|
|
|
expect(store.getTagsForRoom(new Room(roomA, context.client!, context.client!.getUserId()!))).toEqual([
|
|
DefaultTagID.Untagged,
|
|
]);
|
|
expect(store.getTagsForRoom(new Room(roomB, context.client!, context.client!.getUserId()!))).toEqual([
|
|
DefaultTagID.Favourite,
|
|
DefaultTagID.Untagged,
|
|
]);
|
|
});
|
|
|
|
it("emits LISTS_UPDATE_EVENT when slidingSync lists update", async () => {
|
|
await store.start();
|
|
const roomA = "!a:localhost";
|
|
const roomB = "!b:localhost";
|
|
const roomC = "!c:localhost";
|
|
const tagId = DefaultTagID.Favourite;
|
|
const joinCount = 10;
|
|
const roomIndexToRoomId = {
|
|
// mixed to ensure we sort
|
|
1: roomB,
|
|
2: roomC,
|
|
0: roomA,
|
|
};
|
|
const rooms = [
|
|
new Room(roomA, context.client!, context.client!.getUserId()!),
|
|
new Room(roomB, context.client!, context.client!.getUserId()!),
|
|
new Room(roomC, context.client!, context.client!.getUserId()!),
|
|
];
|
|
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
|
|
switch (roomId) {
|
|
case roomA:
|
|
return rooms[0];
|
|
case roomB:
|
|
return rooms[1];
|
|
case roomC:
|
|
return rooms[2];
|
|
}
|
|
return null;
|
|
});
|
|
const p = untilEmission(store, LISTS_UPDATE_EVENT);
|
|
context.slidingSyncManager.slidingSync!.emit(SlidingSyncEvent.List, tagId, joinCount, roomIndexToRoomId);
|
|
await p;
|
|
expect(store.getCount(tagId)).toEqual(joinCount);
|
|
expect(store.orderedLists[tagId]).toEqual(rooms);
|
|
});
|
|
|
|
it("sets the sticky room on the basis of the viewed room in RoomViewStore", async () => {
|
|
await store.start();
|
|
// seed the store with 3 rooms
|
|
const roomIdA = "!a:localhost";
|
|
const roomIdB = "!b:localhost";
|
|
const roomIdC = "!c:localhost";
|
|
const tagId = DefaultTagID.Favourite;
|
|
const joinCount = 10;
|
|
const roomIndexToRoomId = {
|
|
// mixed to ensure we sort
|
|
1: roomIdB,
|
|
2: roomIdC,
|
|
0: roomIdA,
|
|
};
|
|
const roomA = new Room(roomIdA, 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()!);
|
|
mocked(context.client!.getRoom).mockImplementation((roomId: string) => {
|
|
switch (roomId) {
|
|
case roomIdA:
|
|
return roomA;
|
|
case roomIdB:
|
|
return roomB;
|
|
case roomIdC:
|
|
return roomC;
|
|
}
|
|
return null;
|
|
});
|
|
mocked(context._SlidingSyncManager!.slidingSync!.getListData).mockImplementation((key: string) => {
|
|
if (key !== tagId) {
|
|
return null;
|
|
}
|
|
return {
|
|
roomIndexToRoomId: roomIndexToRoomId,
|
|
joinedCount: joinCount,
|
|
};
|
|
});
|
|
let p = untilEmission(store, LISTS_UPDATE_EVENT);
|
|
context.slidingSyncManager.slidingSync!.emit(SlidingSyncEvent.List, tagId, joinCount, roomIndexToRoomId);
|
|
await p;
|
|
expect(store.orderedLists[tagId]).toEqual([roomA, roomB, roomC]);
|
|
|
|
// make roomB sticky and inform the store
|
|
mocked(context.roomViewStore.getRoomId).mockReturnValue(roomIdB);
|
|
context.roomViewStore.emit(UPDATE_EVENT);
|
|
|
|
// bump room C to the top, room B should not move from i=1 despite the list update saying to
|
|
roomIndexToRoomId[0] = roomIdC;
|
|
roomIndexToRoomId[1] = roomIdA;
|
|
roomIndexToRoomId[2] = roomIdB;
|
|
p = untilEmission(store, LISTS_UPDATE_EVENT);
|
|
context.slidingSyncManager.slidingSync!.emit(SlidingSyncEvent.List, tagId, joinCount, roomIndexToRoomId);
|
|
await p;
|
|
|
|
// check that B didn't move and that A was put below B
|
|
expect(store.orderedLists[tagId]).toEqual([roomC, roomB, roomA]);
|
|
|
|
// make room C sticky: rooms should move as a result, without needing an additional list update
|
|
mocked(context.roomViewStore.getRoomId).mockReturnValue(roomIdC);
|
|
p = untilEmission(store, LISTS_UPDATE_EVENT);
|
|
context.roomViewStore.emit(UPDATE_EVENT);
|
|
await p;
|
|
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]);
|
|
});
|
|
});
|