Experimental Module API Additions (#30863)
* Module API experiments * Move ResizerNotifier into SDKContext so we don't have to pass it into RoomView * Add the MultiRoomViewStore * Make RoomViewStore able to take a roomId prop * Different interface to add space panel items A bit less flexible but probably simpler and will help keep things actually consistent rather than just allowing modules to stick any JSX into the space panel (which means they also have to worry about styling if they *do* want it to be consistent). * Allow space panel items to be updated and manage which one is selected, allowing module "spaces" to be considered spaces * Remove fetchRoomFn from SpaceNotificationStore which didn't really seem to have any point as it was only called from one place * Switch to using module api via .instance * Fairly awful workaround to actually break the dependency nightmare * Add test for multiroomviewstore * add test * Make room names deterministic So the tests don't fail if you add other tests or run them individually * Add test for builtinsapi * Update module api * RVS is not needed as prop anymore Since it's passed through context * Add roomId to prop * Remove RoomViewStore from state This is now accessed through class field * Fix test * No need to pass RVS from LoggedInView * Add RoomContextType * Implement new builtins api * Add tests * Fix import * Fix circular dependency issue * Fix import * Add more tests * Improve comment * room-id is optional * Update license * Add implementation for AccountDataApi * Add implementation for Room * Add implementation for ClientApi * Create ClientApi in Api.ts * Write tests * Use nullish coalescing assignment * Implement openRoom in NavigationApi * Write tests * Add implementation for StoresApi * Write tests * Fix circular dependency * Add comments in lieu of type and fix else block * Change to class field --------- Co-authored-by: R Midhun Suresh <hi@midhun.dev>
This commit is contained in:
@@ -10,8 +10,8 @@ import { type MockedObject } from "jest-mock";
|
||||
import { type EventTimeline, EventType, type MatrixClient, type MatrixEvent, Room } from "matrix-js-sdk/src/matrix";
|
||||
import { KnownMembership } from "matrix-js-sdk/src/types";
|
||||
|
||||
import { type IRoomState, MainSplitContentType } from "../../src/components/structures/RoomView";
|
||||
import { TimelineRenderingType } from "../../src/contexts/RoomContext";
|
||||
import { MainSplitContentType } from "../../src/components/structures/RoomView";
|
||||
import { type RoomContextType, TimelineRenderingType } from "../../src/contexts/RoomContext";
|
||||
import { Layout } from "../../src/settings/enums/Layout";
|
||||
import { mkEvent } from "./test-utils";
|
||||
import { SdkContextClass } from "../../src/contexts/SDKContext";
|
||||
@@ -43,7 +43,7 @@ export const makeRoomWithStateEvents = (
|
||||
return room1;
|
||||
};
|
||||
|
||||
export function getRoomContext(room: Room, override: Partial<IRoomState>): IRoomState {
|
||||
export function getRoomContext(room: Room, override: Partial<RoomContextType>): RoomContextType {
|
||||
return {
|
||||
roomViewStore: SdkContextClass.instance.roomViewStore,
|
||||
room,
|
||||
|
||||
@@ -650,6 +650,7 @@ export function mkStubRoom(
|
||||
getJoinedMembers: jest.fn().mockReturnValue([]),
|
||||
getLiveTimeline: jest.fn().mockReturnValue(stubTimeline),
|
||||
getLastLiveEvent: jest.fn().mockReturnValue(undefined),
|
||||
getLastActiveTimestamp: jest.fn().mockReturnValue(1183140000),
|
||||
getMember: jest.fn().mockReturnValue({
|
||||
userId: "@member:domain.bla",
|
||||
name: "Member",
|
||||
|
||||
@@ -15,7 +15,7 @@ import { render } from "jest-matrix-react";
|
||||
|
||||
import MessagePanel, { shouldFormContinuation } from "../../../../src/components/structures/MessagePanel";
|
||||
import SettingsStore from "../../../../src/settings/SettingsStore";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../src/contexts/RoomContext";
|
||||
import RoomContext, { type RoomContextType, TimelineRenderingType } from "../../../../src/contexts/RoomContext";
|
||||
import DMRoomMap from "../../../../src/utils/DMRoomMap";
|
||||
import * as TestUtilsMatrix from "../../../test-utils";
|
||||
import {
|
||||
@@ -29,7 +29,6 @@ import {
|
||||
mockClientPushProcessor,
|
||||
} from "../../../test-utils";
|
||||
import type ResizeNotifier from "../../../../src/utils/ResizeNotifier";
|
||||
import { type IRoomState } from "../../../../src/components/structures/RoomView";
|
||||
import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||
import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
import { SdkContextClass } from "../../../../src/contexts/SDKContext.ts";
|
||||
@@ -92,9 +91,9 @@ describe("MessagePanel", function () {
|
||||
showAvatarChanges: false,
|
||||
showDisplaynameChanges: true,
|
||||
showHiddenEvents: false,
|
||||
} as unknown as IRoomState;
|
||||
} as unknown as RoomContextType;
|
||||
|
||||
const getComponent = (props = {}, roomContext: Partial<IRoomState> = {}) => (
|
||||
const getComponent = (props = {}, roomContext: Partial<RoomContextType> = {}) => (
|
||||
<ScopedRoomContextProvider {...defaultRoomContext} {...roomContext}>
|
||||
<MessagePanel {...defaultProps} {...props} />
|
||||
</ScopedRoomContextProvider>
|
||||
|
||||
@@ -89,7 +89,6 @@ describe("RoomView", () => {
|
||||
let cli: MockedObject<MatrixClient>;
|
||||
let room: Room;
|
||||
let rooms: Map<string, Room>;
|
||||
let roomCount = 0;
|
||||
let stores: SdkContextClass;
|
||||
let crypto: CryptoApi;
|
||||
|
||||
@@ -100,7 +99,9 @@ describe("RoomView", () => {
|
||||
mockPlatformPeg({ reload: () => {} });
|
||||
cli = mocked(stubClient());
|
||||
|
||||
room = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org");
|
||||
const roomName = (expect.getState().currentTestName ?? "").replace(/[^a-zA-Z0-9]/g, "").toLowerCase();
|
||||
|
||||
room = new Room(`!${roomName}:example.org`, cli, "@alice:example.org");
|
||||
jest.spyOn(room, "findPredecessor");
|
||||
room.getPendingEvents = () => [];
|
||||
rooms = new Map();
|
||||
@@ -158,7 +159,6 @@ describe("RoomView", () => {
|
||||
threepidInvite={undefined as any}
|
||||
forceTimeline={false}
|
||||
ref={ref}
|
||||
roomViewStore={stores.roomViewStore}
|
||||
/>
|
||||
</SDKContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
@@ -197,7 +197,6 @@ describe("RoomView", () => {
|
||||
threepidInvite={undefined}
|
||||
forceTimeline={false}
|
||||
onRegistered={jest.fn()}
|
||||
roomViewStore={stores.roomViewStore}
|
||||
/>
|
||||
</SDKContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
@@ -211,6 +210,26 @@ describe("RoomView", () => {
|
||||
return ref.current!;
|
||||
};
|
||||
|
||||
it("gets a room view store from MultiRoomViewStore when given a room ID", async () => {
|
||||
stores.multiRoomViewStore.getRoomViewStoreForRoom = jest.fn().mockReturnValue(stores.roomViewStore);
|
||||
|
||||
const ref = createRef<RoomView>();
|
||||
render(
|
||||
<MatrixClientContext.Provider value={cli}>
|
||||
<SDKContext.Provider value={stores}>
|
||||
<RoomView
|
||||
threepidInvite={undefined as any}
|
||||
forceTimeline={false}
|
||||
ref={ref}
|
||||
roomId="!room:example.dummy"
|
||||
/>
|
||||
</SDKContext.Provider>
|
||||
</MatrixClientContext.Provider>,
|
||||
);
|
||||
|
||||
expect(stores.multiRoomViewStore.getRoomViewStoreForRoom).toHaveBeenCalledWith("!room:example.dummy");
|
||||
});
|
||||
|
||||
it("should show member list right panel phase on Action.ViewUser without `payload.member`", async () => {
|
||||
const spy = jest.spyOn(stores.rightPanelStore, "showOrHidePhase");
|
||||
await renderRoomView(false);
|
||||
@@ -707,7 +726,7 @@ describe("RoomView", () => {
|
||||
});
|
||||
|
||||
it("should switch rooms when edit is clicked on a search result for a different room", async () => {
|
||||
const room2 = new Room(`!${roomCount++}:example.org`, cli, "@alice:example.org");
|
||||
const room2 = new Room(`!roomswitchtest:example.org`, cli, "@alice:example.org");
|
||||
rooms.set(room2.roomId, room2);
|
||||
|
||||
room.getMyMembership = jest.fn().mockReturnValue(KnownMembership.Join);
|
||||
|
||||
@@ -26,8 +26,8 @@ import { RoomPermalinkCreator } from "../../../../src/utils/permalinks/Permalink
|
||||
import ResizeNotifier from "../../../../src/utils/ResizeNotifier";
|
||||
import { createTestClient, getRoomContext, mkRoom, mockPlatformPeg, stubClient } from "../../../test-utils";
|
||||
import { mkThread } from "../../../test-utils/threads";
|
||||
import { type IRoomState } from "../../../../src/components/structures/RoomView";
|
||||
import { ScopedRoomContextProvider } from "../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
import type { RoomContextType } from "../../../../src/contexts/RoomContext.ts";
|
||||
|
||||
jest.mock("../../../../src/utils/Feedback");
|
||||
|
||||
@@ -79,7 +79,7 @@ describe("ThreadPanel", () => {
|
||||
mockRoom.getLastLiveEvent.mockReturnValue(mockEvent);
|
||||
const roomContextObject = {
|
||||
room: mockRoom,
|
||||
} as unknown as IRoomState;
|
||||
} as unknown as RoomContextType;
|
||||
const { container } = render(
|
||||
<ScopedRoomContextProvider {...roomContextObject}>
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
|
||||
@@ -731,12 +731,12 @@ exports[`RoomView invites renders an invite room 1`] = `
|
||||
class="mx_RoomPreviewBar_message"
|
||||
>
|
||||
<h3>
|
||||
Do you want to join !2:example.org?
|
||||
Do you want to join !roomviewinvitesrendersaninviteroom:example.org?
|
||||
</h3>
|
||||
<p>
|
||||
<span
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||
data-color="4"
|
||||
data-color="2"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="presentation"
|
||||
@@ -817,7 +817,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
|
||||
aria-label="Open room settings"
|
||||
aria-live="off"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||
data-color="5"
|
||||
data-color="1"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
@@ -844,7 +844,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
|
||||
<span
|
||||
class="mx_RoomHeader_truncated mx_lineClamp"
|
||||
>
|
||||
!12:example.org
|
||||
!roomviewshouldnotdisplaythetimelinewhentheroomencryptionisloading:example.org
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1029,7 +1029,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
|
||||
aria-label="Open room settings"
|
||||
aria-live="off"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||
data-color="5"
|
||||
data-color="1"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
@@ -1056,7 +1056,7 @@ exports[`RoomView should not display the timeline when the room encryption is lo
|
||||
<span
|
||||
class="mx_RoomHeader_truncated mx_lineClamp"
|
||||
>
|
||||
!12:example.org
|
||||
!roomviewshouldnotdisplaythetimelinewhentheroomencryptionisloading:example.org
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1419,7 +1419,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
|
||||
aria-label="Open room settings"
|
||||
aria-live="off"
|
||||
class="_avatar_1qbcf_8 mx_BaseAvatar _avatar-imageless_1qbcf_52"
|
||||
data-color="4"
|
||||
data-color="2"
|
||||
data-testid="avatar-img"
|
||||
data-type="round"
|
||||
role="button"
|
||||
@@ -1446,7 +1446,7 @@ exports[`RoomView video rooms should render joined video room view 1`] = `
|
||||
<span
|
||||
class="mx_RoomHeader_truncated mx_lineClamp"
|
||||
>
|
||||
!17:example.org
|
||||
!roomviewvideoroomsshouldrenderjoinedvideoroomview:example.org
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -15,10 +15,9 @@ import RecordingPlayback, {
|
||||
PlaybackLayout,
|
||||
} from "../../../../../src/components/views/audio_messages/RecordingPlayback";
|
||||
import { Playback } from "../../../../../src/audio/Playback";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { type RoomContextType, TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { createAudioContext } from "../../../../../src/audio/compat";
|
||||
import { flushPromises } from "../../../../test-utils";
|
||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
|
||||
jest.mock("../../../../../src/WorkerManager", () => ({
|
||||
@@ -54,7 +53,10 @@ describe("<RecordingPlayback />", () => {
|
||||
|
||||
const mockChannelData = new Float32Array();
|
||||
|
||||
const defaultRoom = { roomId: "!room:server.org", timelineRenderingType: TimelineRenderingType.File } as IRoomState;
|
||||
const defaultRoom = {
|
||||
roomId: "!room:server.org",
|
||||
timelineRenderingType: TimelineRenderingType.File,
|
||||
} as RoomContextType;
|
||||
const getComponent = (props: React.ComponentProps<typeof RecordingPlayback>, room = defaultRoom) =>
|
||||
render(
|
||||
<ScopedRoomContextProvider {...room}>
|
||||
|
||||
@@ -31,8 +31,7 @@ import { WidgetMessagingStore } from "../../../../../src/stores/widgets/WidgetMe
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { ConnectionState } from "../../../../../src/models/Call";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext";
|
||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import RoomContext, { type RoomContextType } from "../../../../../src/contexts/RoomContext";
|
||||
|
||||
describe("<RoomCallBanner />", () => {
|
||||
let client: Mocked<MatrixClient>;
|
||||
@@ -51,7 +50,7 @@ describe("<RoomCallBanner />", () => {
|
||||
emit: jest.fn(),
|
||||
};
|
||||
|
||||
let roomContext: IRoomState;
|
||||
let roomContext: RoomContextType;
|
||||
|
||||
beforeEach(() => {
|
||||
stubClient();
|
||||
@@ -79,7 +78,7 @@ describe("<RoomCallBanner />", () => {
|
||||
...RoomContext,
|
||||
roomId: room.roomId,
|
||||
roomViewStore: mockRoomViewStore,
|
||||
} as unknown as IRoomState;
|
||||
} as unknown as RoomContextType;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
||||
@@ -27,8 +27,7 @@ import { mocked } from "jest-mock";
|
||||
import userEvent from "@testing-library/user-event";
|
||||
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import { type RoomContextType, TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { canEditContent } from "../../../../../src/utils/EventUtils";
|
||||
import { copyPlaintext, getSelectedText } from "../../../../../src/utils/strings";
|
||||
import MessageContextMenu from "../../../../../src/components/views/context_menus/MessageContextMenu";
|
||||
@@ -711,18 +710,18 @@ describe("MessageContextMenu", () => {
|
||||
});
|
||||
});
|
||||
|
||||
function createRightClickMenuWithContent(eventContent: object, context?: Partial<IRoomState>): RenderResult {
|
||||
function createRightClickMenuWithContent(eventContent: object, context?: Partial<RoomContextType>): RenderResult {
|
||||
return createMenuWithContent(eventContent, { rightClick: true }, context);
|
||||
}
|
||||
|
||||
function createRightClickMenu(mxEvent: MatrixEvent, context?: Partial<IRoomState>): RenderResult {
|
||||
function createRightClickMenu(mxEvent: MatrixEvent, context?: Partial<RoomContextType>): RenderResult {
|
||||
return createMenu(mxEvent, { rightClick: true }, context);
|
||||
}
|
||||
|
||||
function createMenuWithContent(
|
||||
eventContent: object,
|
||||
props?: Partial<MessageContextMenu["props"]>,
|
||||
context?: Partial<IRoomState>,
|
||||
context?: Partial<RoomContextType>,
|
||||
): RenderResult {
|
||||
// XXX: We probably shouldn't be assuming all events are going to be message events, but considering this
|
||||
// test is for the Message context menu, it's a fairly safe assumption.
|
||||
@@ -739,7 +738,7 @@ function makeDefaultRoom(): Room {
|
||||
function createMenu(
|
||||
mxEvent: MatrixEvent,
|
||||
props?: Partial<MessageContextMenu["props"]>,
|
||||
context: Partial<IRoomState> = {},
|
||||
context: Partial<RoomContextType> = {},
|
||||
beacons: Map<BeaconIdentifier, Beacon> = new Map(),
|
||||
room: Room = makeDefaultRoom(),
|
||||
): RenderResult {
|
||||
@@ -755,7 +754,7 @@ function createMenu(
|
||||
client.getRoom = jest.fn().mockReturnValue(room);
|
||||
|
||||
return render(
|
||||
<ScopedRoomContextProvider {...(context as IRoomState)}>
|
||||
<ScopedRoomContextProvider {...(context as RoomContextType)}>
|
||||
<MessageContextMenu mxEvent={mxEvent} onFinished={jest.fn()} {...props} />
|
||||
</ScopedRoomContextProvider>,
|
||||
);
|
||||
|
||||
@@ -26,8 +26,7 @@ import {
|
||||
} from "../../../../test-utils";
|
||||
import DateSeparator from "../../../../../src/components/views/messages/DateSeparator";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext";
|
||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import RoomContext from "../../../../../src/contexts/RoomContext";
|
||||
import RoomContext, { type RoomContextType } from "../../../../../src/contexts/RoomContext";
|
||||
|
||||
jest.mock("../../../../../src/settings/SettingsStore");
|
||||
|
||||
@@ -50,7 +49,7 @@ describe("DateSeparator", () => {
|
||||
...RoomContext,
|
||||
roomId,
|
||||
roomViewStore: mockRoomViewStore,
|
||||
} as unknown as IRoomState;
|
||||
} as unknown as RoomContextType;
|
||||
|
||||
const mockClient = getMockClientWithEventEmitter({
|
||||
timestampToEvent: jest.fn(),
|
||||
|
||||
@@ -29,8 +29,7 @@ import {
|
||||
makeBeaconInfoEvent,
|
||||
} from "../../../../test-utils";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import RoomContext, { type RoomContextType, TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import dispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
@@ -115,8 +114,8 @@ describe("<MessageActionBar />", () => {
|
||||
canSendMessages: true,
|
||||
canReact: true,
|
||||
room,
|
||||
} as unknown as IRoomState;
|
||||
const getComponent = (props = {}, roomContext: Partial<IRoomState> = {}) =>
|
||||
} as unknown as RoomContextType;
|
||||
const getComponent = (props = {}, roomContext: Partial<RoomContextType> = {}) =>
|
||||
render(
|
||||
<ScopedRoomContextProvider {...defaultRoomContext} {...roomContext}>
|
||||
<MessageActionBar {...defaultProps} {...props} />
|
||||
|
||||
@@ -27,12 +27,12 @@ import {
|
||||
import DocumentOffset from "../../../../../src/editor/offset";
|
||||
import SettingsStore from "../../../../../src/settings/SettingsStore";
|
||||
import EditorStateTransfer from "../../../../../src/utils/EditorStateTransfer";
|
||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import Autocompleter, { type IProviderCompletions } from "../../../../../src/autocomplete/Autocompleter";
|
||||
import NotifProvider from "../../../../../src/autocomplete/NotifProvider";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
import type { RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
|
||||
|
||||
describe("<EditMessageComposer/>", () => {
|
||||
const userId = "@alice:server.org";
|
||||
@@ -75,7 +75,7 @@ describe("<EditMessageComposer/>", () => {
|
||||
|
||||
const defaultRoomContext = getRoomContext(room, {});
|
||||
|
||||
const getComponent = (editState: EditorStateTransfer, roomContext: IRoomState = defaultRoomContext) =>
|
||||
const getComponent = (editState: EditorStateTransfer, roomContext: RoomContextType = defaultRoomContext) =>
|
||||
render(<EditMessageComposerWithMatrixClient editState={editState} />, {
|
||||
wrapper: ({ children }) => (
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
|
||||
@@ -30,14 +30,13 @@ import { mkEncryptedMatrixEvent } from "matrix-js-sdk/src/testing";
|
||||
|
||||
import EventTile, { type EventTileProps } from "../../../../../src/components/views/rooms/EventTile";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { type RoomContextType, TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { filterConsole, flushPromises, getRoomContext, mkEvent, mkMessage, stubClient } from "../../../../test-utils";
|
||||
import { mkThread } from "../../../../test-utils/threads";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import dis from "../../../../../src/dispatcher/dispatcher";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import PinningUtils from "../../../../../src/utils/PinningUtils";
|
||||
import { Layout } from "../../../../../src/settings/enums/Layout";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
@@ -52,7 +51,7 @@ describe("EventTile", () => {
|
||||
|
||||
/** wrap the EventTile up in context providers, and with basic properties, as it would be by MessagePanel normally. */
|
||||
function WrappedEventTile(props: {
|
||||
roomContext: IRoomState;
|
||||
roomContext: RoomContextType;
|
||||
eventTilePropertyOverrides?: Partial<EventTileProps>;
|
||||
}) {
|
||||
return (
|
||||
@@ -71,7 +70,7 @@ describe("EventTile", () => {
|
||||
function getComponent(
|
||||
overrides: Partial<EventTileProps> = {},
|
||||
renderingType: TimelineRenderingType = TimelineRenderingType.Room,
|
||||
roomContext: Partial<IRoomState> = {},
|
||||
roomContext: Partial<RoomContextType> = {},
|
||||
) {
|
||||
const context = getRoomContext(room, {
|
||||
timelineRenderingType: renderingType,
|
||||
|
||||
@@ -24,7 +24,6 @@ import {
|
||||
import MessageComposer from "../../../../../src/components/views/rooms/MessageComposer";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import ResizeNotifier from "../../../../../src/utils/ResizeNotifier";
|
||||
import { RoomPermalinkCreator } from "../../../../../src/utils/permalinks/Permalinks";
|
||||
import { LocalRoom } from "../../../../../src/models/LocalRoom";
|
||||
@@ -36,6 +35,7 @@ import { addTextToComposerRTL } from "../../../../test-utils/composer";
|
||||
import UIStore, { UI_EVENTS } from "../../../../../src/stores/UIStore";
|
||||
import { Action } from "../../../../../src/dispatcher/actions";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
import type { RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
|
||||
|
||||
const openStickerPicker = async (): Promise<void> => {
|
||||
await userEvent.click(screen.getByLabelText("More options"));
|
||||
@@ -155,7 +155,7 @@ describe("MessageComposer", () => {
|
||||
});
|
||||
|
||||
describe("when receiving a »reply_to_event«", () => {
|
||||
let roomContext: IRoomState;
|
||||
let roomContext: RoomContextType;
|
||||
let resizeNotifier: ResizeNotifier;
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -458,7 +458,7 @@ function wrapAndRender(
|
||||
canSendMessages,
|
||||
tombstone,
|
||||
narrow,
|
||||
} as unknown as IRoomState;
|
||||
} as unknown as RoomContextType;
|
||||
|
||||
const defaultProps = {
|
||||
room,
|
||||
|
||||
@@ -11,10 +11,10 @@ import { render, screen, waitFor } from "jest-matrix-react";
|
||||
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { createTestClient, getRoomContext, mkStubRoom } from "../../../../test-utils";
|
||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import MessageComposerButtons from "../../../../../src/components/views/rooms/MessageComposerButtons";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
import type { RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
|
||||
|
||||
describe("MessageComposerButtons", () => {
|
||||
// @ts-ignore - we're deliberately not implementing the whole interface here, but
|
||||
@@ -50,7 +50,7 @@ describe("MessageComposerButtons", () => {
|
||||
|
||||
function wrapAndRender(component: React.ReactElement, narrow: boolean) {
|
||||
const mockRoom = mkStubRoom("myfakeroom", "myfakeroom", mockClient) as any;
|
||||
const defaultRoomContext: IRoomState = getRoomContext(mockRoom, { narrow });
|
||||
const defaultRoomContext: RoomContextType = getRoomContext(mockRoom, { narrow });
|
||||
|
||||
return render(
|
||||
<MatrixClientContext.Provider value={mockClient}>
|
||||
|
||||
@@ -22,16 +22,16 @@ import {
|
||||
} from "../../../../test-utils";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import NewRoomIntro from "../../../../../src/components/views/rooms/NewRoomIntro";
|
||||
import { type IRoomState } from "../../../../../src/components/structures/RoomView";
|
||||
import DMRoomMap from "../../../../../src/utils/DMRoomMap";
|
||||
import { DirectoryMember } from "../../../../../src/utils/direct-messages";
|
||||
import { ScopedRoomContextProvider } from "../../../../../src/contexts/ScopedRoomContext.tsx";
|
||||
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import type { RoomContextType } from "../../../../../src/contexts/RoomContext.ts";
|
||||
|
||||
const renderNewRoomIntro = (client: MatrixClient, room: Room | LocalRoom) => {
|
||||
render(
|
||||
<MatrixClientContext.Provider value={client}>
|
||||
<ScopedRoomContextProvider {...({ room, roomId: room.roomId } as unknown as IRoomState)}>
|
||||
<ScopedRoomContextProvider {...({ room, roomId: room.roomId } as unknown as RoomContextType)}>
|
||||
<NewRoomIntro />
|
||||
</ScopedRoomContextProvider>
|
||||
</MatrixClientContext.Provider>,
|
||||
|
||||
@@ -41,8 +41,7 @@ import RoomHeader from "../../../../../../src/components/views/rooms/RoomHeader/
|
||||
import DMRoomMap from "../../../../../../src/utils/DMRoomMap";
|
||||
import { MatrixClientPeg } from "../../../../../../src/MatrixClientPeg";
|
||||
import { ScopedRoomContextProvider } from "../../../../../../src/contexts/ScopedRoomContext";
|
||||
import { type IRoomState } from "../../../../../../src/components/structures/RoomView";
|
||||
import RoomContext from "../../../../../../src/contexts/RoomContext";
|
||||
import RoomContext, { type RoomContextType } from "../../../../../../src/contexts/RoomContext";
|
||||
import RightPanelStore from "../../../../../../src/stores/right-panel/RightPanelStore";
|
||||
import { RightPanelPhases } from "../../../../../../src/stores/right-panel/RightPanelStorePhases";
|
||||
import LegacyCallHandler from "../../../../../../src/LegacyCallHandler";
|
||||
@@ -85,7 +84,7 @@ describe("RoomHeader", () => {
|
||||
emit: jest.fn(),
|
||||
};
|
||||
|
||||
let roomContext: IRoomState;
|
||||
let roomContext: RoomContextType;
|
||||
|
||||
function getWrapper(): RenderOptions {
|
||||
return {
|
||||
@@ -121,7 +120,7 @@ describe("RoomHeader", () => {
|
||||
...RoomContext,
|
||||
roomId: ROOM_ID,
|
||||
roomViewStore: mockRoomViewStore,
|
||||
} as unknown as IRoomState;
|
||||
} as unknown as RoomContextType;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
||||
@@ -17,7 +17,7 @@ import SendMessageComposer, {
|
||||
isQuickReaction,
|
||||
} from "../../../../../src/components/views/rooms/SendMessageComposer";
|
||||
import MatrixClientContext from "../../../../../src/contexts/MatrixClientContext";
|
||||
import { TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import { type RoomContextType, TimelineRenderingType } from "../../../../../src/contexts/RoomContext";
|
||||
import EditorModel from "../../../../../src/editor/model";
|
||||
import { createPartCreator } from "../../../editor/mock";
|
||||
import { createTestClient, mkEvent, mkStubRoom, stubClient } from "../../../../test-utils";
|
||||
@@ -25,7 +25,7 @@ import { MatrixClientPeg } from "../../../../../src/MatrixClientPeg";
|
||||
import defaultDispatcher from "../../../../../src/dispatcher/dispatcher";
|
||||
import DocumentOffset from "../../../../../src/editor/offset";
|
||||
import { Layout } from "../../../../../src/settings/enums/Layout";
|
||||
import { type IRoomState, MainSplitContentType } from "../../../../../src/components/structures/RoomView";
|
||||
import { MainSplitContentType } from "../../../../../src/components/structures/RoomView";
|
||||
import { mockPlatformPeg } from "../../../../test-utils/platform";
|
||||
import { doMaybeLocalRoomAction } from "../../../../../src/utils/local-room";
|
||||
import { addTextToComposer } from "../../../../test-utils/composer";
|
||||
@@ -37,7 +37,7 @@ jest.mock("../../../../../src/utils/local-room", () => ({
|
||||
}));
|
||||
|
||||
describe("<SendMessageComposer/>", () => {
|
||||
const defaultRoomContext: IRoomState = {
|
||||
const defaultRoomContext: RoomContextType = {
|
||||
roomViewStore: SdkContextClass.instance.roomViewStore,
|
||||
roomLoading: true,
|
||||
peekLoading: false,
|
||||
|
||||
@@ -9,8 +9,8 @@ Please see LICENSE files in the repository root for full details.
|
||||
import { type EventTimeline, type MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { getRoomContext, mkEvent, mkStubRoom, stubClient } from "../../../../../test-utils";
|
||||
import { type IRoomState } from "../../../../../../src/components/structures/RoomView";
|
||||
import EditorStateTransfer from "../../../../../../src/utils/EditorStateTransfer";
|
||||
import type { RoomContextType } from "../../../../../../src/contexts/RoomContext";
|
||||
|
||||
export function createMocks(eventContent = "Replying <strong>to</strong> this new content") {
|
||||
const mockClient = stubClient();
|
||||
@@ -31,7 +31,7 @@ export function createMocks(eventContent = "Replying <strong>to</strong> this ne
|
||||
return eventId === mockEvent.getId() ? mockEvent : null;
|
||||
});
|
||||
|
||||
const defaultRoomContext: IRoomState = getRoomContext(mockRoom, {
|
||||
const defaultRoomContext: RoomContextType = getRoomContext(mockRoom, {
|
||||
liveTimeline: { getEvents: (): MatrixEvent[] => [] } as unknown as EventTimeline,
|
||||
});
|
||||
|
||||
|
||||
72
test/unit-tests/modules/AccountDataApi-test.ts
Normal file
72
test/unit-tests/modules/AccountDataApi-test.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright 2025 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { ClientEvent } from "matrix-js-sdk/src/matrix";
|
||||
|
||||
import { AccountDataApi } from "../../../src/modules/AccountDataApi";
|
||||
import { mkEvent, stubClient } from "../../test-utils/test-utils";
|
||||
|
||||
describe("AccountDataApi", () => {
|
||||
describe("AccountDataWatchable", () => {
|
||||
it("should return content of account data event on get()", () => {
|
||||
const cli = stubClient();
|
||||
const api = new AccountDataApi();
|
||||
// Mock cli to return a event
|
||||
const content = { foo: "bar" };
|
||||
const event = mkEvent({ content, type: "m.test", user: "@foobar:matrix.org", event: true });
|
||||
cli.getAccountData = () => event;
|
||||
expect(api.get("m.test").value).toStrictEqual(content);
|
||||
});
|
||||
|
||||
it("should update value on event", () => {
|
||||
const cli = stubClient();
|
||||
const api = new AccountDataApi();
|
||||
// Mock cli to return a event
|
||||
const content = { foo: "bar" };
|
||||
const event = mkEvent({ content, type: "m.test", user: "@foobar:matrix.org", event: true });
|
||||
cli.getAccountData = () => event;
|
||||
|
||||
const watchable = api.get("m.test");
|
||||
expect(watchable.value).toStrictEqual(content);
|
||||
|
||||
const fn = jest.fn();
|
||||
watchable.watch(fn);
|
||||
|
||||
// Let's say that the account data event changed
|
||||
const event2 = mkEvent({
|
||||
content: { foo: "abc" },
|
||||
type: "m.test",
|
||||
user: "@foobar:matrix.org",
|
||||
event: true,
|
||||
});
|
||||
cli.emit(ClientEvent.AccountData, event2);
|
||||
// Watchable value should have been updated
|
||||
expect(watchable.value).toStrictEqual({ foo: "abc" });
|
||||
// Watched callbacks should be called
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
|
||||
// Make sure unwatch removed the event listener
|
||||
cli.off = jest.fn();
|
||||
watchable.unwatch(fn);
|
||||
expect(cli.off).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
it("should set account data via js-sdk on set()", async () => {
|
||||
const cli = stubClient();
|
||||
const api = new AccountDataApi();
|
||||
await api.set("m.test", { foo: "bar" });
|
||||
expect(cli.setAccountData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("should delete account data via js-sdk on set()", async () => {
|
||||
const cli = stubClient();
|
||||
const api = new AccountDataApi();
|
||||
await api.delete("m.test");
|
||||
expect(cli.deleteAccountData).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
53
test/unit-tests/modules/BuiltinsApi-test.tsx
Normal file
53
test/unit-tests/modules/BuiltinsApi-test.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2025 Element Creations Ltd.
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import React from "react";
|
||||
import { render } from "jest-matrix-react";
|
||||
|
||||
import { ElementWebBuiltinsApi } from "../../../src/modules/BuiltinsApi.tsx";
|
||||
import { stubClient } from "../../test-utils/test-utils";
|
||||
|
||||
const Avatar: React.FC<{ room: { roomId: string }; size: string }> = ({ room, size }) => {
|
||||
return (
|
||||
<div>
|
||||
Avatar, {room.roomId}, {size}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
describe("ElementWebBuiltinsApi", () => {
|
||||
it("returns the RoomView component thats been set", () => {
|
||||
const builtinsApi = new ElementWebBuiltinsApi();
|
||||
const sentinel = {};
|
||||
builtinsApi.setComponents({ roomView: sentinel, roomAvatar: Avatar } as any);
|
||||
expect(builtinsApi.getRoomViewComponent()).toBe(sentinel);
|
||||
});
|
||||
|
||||
it("returns rendered RoomView component", () => {
|
||||
const builtinsApi = new ElementWebBuiltinsApi();
|
||||
const RoomView = () => <div>hello world</div>;
|
||||
builtinsApi.setComponents({ roomView: RoomView, roomAvatar: Avatar } as any);
|
||||
const { container } = render(<> {builtinsApi.renderRoomView("!foo:m.org")}</>);
|
||||
expect(container).toHaveTextContent("hello world");
|
||||
});
|
||||
|
||||
it("returns rendered RoomAvatar component", () => {
|
||||
stubClient();
|
||||
const builtinsApi = new ElementWebBuiltinsApi();
|
||||
builtinsApi.setComponents({ roomView: {}, roomAvatar: Avatar } as any);
|
||||
const { container } = render(<> {builtinsApi.renderRoomAvatar("!foo:m.org", "50")}</>);
|
||||
expect(container).toHaveTextContent("Avatar");
|
||||
expect(container).toHaveTextContent("!foo:m.org");
|
||||
expect(container).toHaveTextContent("50");
|
||||
});
|
||||
|
||||
it("should throw error if called before components are set", () => {
|
||||
stubClient();
|
||||
const builtinsApi = new ElementWebBuiltinsApi();
|
||||
expect(() => builtinsApi.renderRoomAvatar("!foo:m.org")).toThrow("No RoomAvatar component has been set");
|
||||
expect(() => builtinsApi.renderRoomView("!foo:m.org")).toThrow("No RoomView component has been set");
|
||||
});
|
||||
});
|
||||
20
test/unit-tests/modules/ClientApi-test.ts
Normal file
20
test/unit-tests/modules/ClientApi-test.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
Copyright 2025 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { ClientApi } from "../../../src/modules/ClientApi";
|
||||
import { Room } from "../../../src/modules/models/Room";
|
||||
import { stubClient } from "../../test-utils/test-utils";
|
||||
|
||||
describe("ClientApi", () => {
|
||||
it("should return module room from getRoom()", () => {
|
||||
stubClient();
|
||||
const client = new ClientApi();
|
||||
const moduleRoom = client.getRoom("!foo:matrix.org");
|
||||
expect(moduleRoom).toBeInstanceOf(Room);
|
||||
expect(moduleRoom?.id).toStrictEqual("!foo:matrix.org");
|
||||
});
|
||||
});
|
||||
@@ -37,5 +37,25 @@ describe("NavigationApi", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should dispatch correct action on openRoom", () => {
|
||||
const spy = jest.spyOn(defaultDispatcher, "dispatch");
|
||||
// Non alias
|
||||
api.openRoom("!foo:m.org");
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "view_room",
|
||||
room_id: "!foo:m.org",
|
||||
}),
|
||||
);
|
||||
// Alias
|
||||
api.openRoom("#bar:m.org");
|
||||
expect(spy).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
action: "view_room",
|
||||
room_alias: "#bar:m.org",
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
84
test/unit-tests/modules/StoresApi-test.ts
Normal file
84
test/unit-tests/modules/StoresApi-test.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
Copyright 2025 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { waitFor } from "jest-matrix-react";
|
||||
|
||||
import { type RoomListStoreApi, StoresApi } from "../../../src/modules/StoresApi";
|
||||
import RoomListStoreV3, {
|
||||
LISTS_LOADED_EVENT,
|
||||
LISTS_UPDATE_EVENT,
|
||||
} from "../../../src/stores/room-list-v3/RoomListStoreV3";
|
||||
import { mkRoom, stubClient } from "../../test-utils/test-utils";
|
||||
import { Room } from "../../../src/modules/models/Room";
|
||||
import {} from "../../../src/stores/room-list/algorithms/Algorithm";
|
||||
|
||||
describe("StoresApi", () => {
|
||||
describe("RoomListStoreApi", () => {
|
||||
it("should return promise that resolves when RLS is ready", async () => {
|
||||
jest.spyOn(RoomListStoreV3.instance, "isLoadingRooms", "get").mockReturnValue(true);
|
||||
const store = new StoresApi();
|
||||
let hasResolved = false;
|
||||
// The following async function will set hasResolved to false
|
||||
// only when waitForReady resolves.
|
||||
(async () => {
|
||||
await store.roomListStore.waitForReady();
|
||||
hasResolved = true;
|
||||
})();
|
||||
// Shouldn't have resolved yet.
|
||||
expect(hasResolved).toStrictEqual(false);
|
||||
|
||||
// Wait for the module to load so that we can test the listener.
|
||||
await (store.roomListStore as RoomListStoreApi).moduleLoadPromise;
|
||||
// Emit the loaded event.
|
||||
RoomListStoreV3.instance.emit(LISTS_LOADED_EVENT);
|
||||
// Should resolve now.
|
||||
await waitFor(() => {
|
||||
expect(hasResolved).toStrictEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getRooms()", () => {
|
||||
it("should return rooms from RLS", async () => {
|
||||
const cli = stubClient();
|
||||
const room1 = mkRoom(cli, "!foo1:m.org");
|
||||
const room2 = mkRoom(cli, "!foo2:m.org");
|
||||
const room3 = mkRoom(cli, "!foo3:m.org");
|
||||
jest.spyOn(RoomListStoreV3.instance, "getSortedRooms").mockReturnValue([room1, room2, room3]);
|
||||
jest.spyOn(RoomListStoreV3.instance, "isLoadingRooms", "get").mockReturnValue(false);
|
||||
|
||||
const store = new StoresApi();
|
||||
await store.roomListStore.waitForReady();
|
||||
const watchable = store.roomListStore.getRooms();
|
||||
expect(watchable.value).toHaveLength(3);
|
||||
expect(watchable.value[0]).toBeInstanceOf(Room);
|
||||
});
|
||||
|
||||
it("should update from RLS", async () => {
|
||||
const cli = stubClient();
|
||||
const room1 = mkRoom(cli, "!foo1:m.org");
|
||||
const room2 = mkRoom(cli, "!foo2:m.org");
|
||||
const rooms = [room1, room2];
|
||||
|
||||
jest.spyOn(RoomListStoreV3.instance, "getSortedRooms").mockReturnValue(rooms);
|
||||
jest.spyOn(RoomListStoreV3.instance, "isLoadingRooms", "get").mockReturnValue(false);
|
||||
|
||||
const store = new StoresApi();
|
||||
await store.roomListStore.waitForReady();
|
||||
const watchable = store.roomListStore.getRooms();
|
||||
const fn = jest.fn();
|
||||
watchable.watch(fn);
|
||||
expect(watchable.value).toHaveLength(2);
|
||||
|
||||
const room3 = mkRoom(cli, "!foo3:m.org");
|
||||
rooms.push(room3);
|
||||
RoomListStoreV3.instance.emit(LISTS_UPDATE_EVENT);
|
||||
expect(fn).toHaveBeenCalledTimes(1);
|
||||
expect(watchable.value).toHaveLength(3);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
50
test/unit-tests/modules/models/Room-test.ts
Normal file
50
test/unit-tests/modules/models/Room-test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
Copyright 2025 Element Creations Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { Room } from "../../../../src/modules/models/Room";
|
||||
import { mkRoom, stubClient } from "../../../test-utils";
|
||||
|
||||
describe("Room", () => {
|
||||
it("should return id from sdk room", () => {
|
||||
const cli = stubClient();
|
||||
const sdkRoom = mkRoom(cli, "!foo:m.org");
|
||||
const room = new Room(sdkRoom);
|
||||
expect(room.id).toStrictEqual("!foo:m.org");
|
||||
});
|
||||
|
||||
it("should return last timestamp from sdk room", () => {
|
||||
const cli = stubClient();
|
||||
const sdkRoom = mkRoom(cli, "!foo:m.org");
|
||||
const room = new Room(sdkRoom);
|
||||
expect(room.getLastActiveTimestamp()).toStrictEqual(sdkRoom.getLastActiveTimestamp());
|
||||
});
|
||||
|
||||
describe("watchableName", () => {
|
||||
it("should return name from sdkRoom", () => {
|
||||
const cli = stubClient();
|
||||
const sdkRoom = mkRoom(cli, "!foo:m.org");
|
||||
sdkRoom.name = "Foo Name";
|
||||
const room = new Room(sdkRoom);
|
||||
expect(room.name.value).toStrictEqual("Foo Name");
|
||||
});
|
||||
|
||||
it("should add/remove event listener on sdk room", () => {
|
||||
const cli = stubClient();
|
||||
const sdkRoom = mkRoom(cli, "!foo:m.org");
|
||||
sdkRoom.name = "Foo Name";
|
||||
|
||||
const room = new Room(sdkRoom);
|
||||
const fn = jest.fn();
|
||||
|
||||
room.name.watch(fn);
|
||||
expect(sdkRoom.on).toHaveBeenCalledTimes(1);
|
||||
|
||||
room.name.unwatch(fn);
|
||||
expect(sdkRoom.off).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
101
test/unit-tests/stores/MultiRoomViewStore-test.ts
Normal file
101
test/unit-tests/stores/MultiRoomViewStore-test.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
Copyright 2025 New Vector Ltd.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { MultiRoomViewStore } from "../../../src/stores/MultiRoomViewStore";
|
||||
import { RoomViewStore } from "../../../src/stores/RoomViewStore";
|
||||
import { Action } from "../../../src/dispatcher/actions";
|
||||
import type { MatrixDispatcher } from "../../../src/dispatcher/dispatcher";
|
||||
import { TestSdkContext } from "../TestSdkContext";
|
||||
|
||||
jest.mock("../../../src/stores/RoomViewStore");
|
||||
|
||||
describe("MultiRoomViewStore", () => {
|
||||
let multiRoomViewStore: MultiRoomViewStore;
|
||||
let mockDispatcher: MatrixDispatcher;
|
||||
let mockSdkContext: TestSdkContext;
|
||||
let mockRoomViewStore: jest.Mocked<RoomViewStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Create mock dispatcher
|
||||
mockDispatcher = {
|
||||
dispatch: jest.fn(),
|
||||
register: jest.fn(),
|
||||
unregister: jest.fn(),
|
||||
} as unknown as MatrixDispatcher;
|
||||
|
||||
// Create mock SDK context
|
||||
mockSdkContext = new TestSdkContext();
|
||||
|
||||
// Create mock RoomViewStore instance
|
||||
mockRoomViewStore = {
|
||||
viewRoom: jest.fn(),
|
||||
dispose: jest.fn(),
|
||||
} as any;
|
||||
|
||||
(RoomViewStore as jest.MockedClass<typeof RoomViewStore>).mockImplementation(() => mockRoomViewStore as any);
|
||||
|
||||
// Create the MultiRoomViewStore instance
|
||||
multiRoomViewStore = new MultiRoomViewStore(mockDispatcher, mockSdkContext);
|
||||
});
|
||||
|
||||
describe("getRoomViewStoreForRoom", () => {
|
||||
it("should create a new RoomViewStore for a room that doesn't exist in cache", () => {
|
||||
const roomId = "!room1:example.com";
|
||||
|
||||
const result = multiRoomViewStore.getRoomViewStoreForRoom(roomId);
|
||||
|
||||
expect(RoomViewStore).toHaveBeenCalledWith(mockDispatcher, mockSdkContext, roomId);
|
||||
expect(mockRoomViewStore.viewRoom).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
room_id: roomId,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
expect(result).toBe(mockRoomViewStore);
|
||||
});
|
||||
|
||||
it("should return existing RoomViewStore for a room that exists in cache", () => {
|
||||
const roomId = "!room1:example.com";
|
||||
|
||||
// First call creates the store
|
||||
const firstResult = multiRoomViewStore.getRoomViewStoreForRoom(roomId);
|
||||
|
||||
jest.clearAllMocks();
|
||||
|
||||
// Should return the same store
|
||||
const secondResult = multiRoomViewStore.getRoomViewStoreForRoom(roomId);
|
||||
|
||||
expect(RoomViewStore).not.toHaveBeenCalled();
|
||||
expect(mockRoomViewStore.viewRoom).toHaveBeenCalledWith({
|
||||
action: Action.ViewRoom,
|
||||
room_id: roomId,
|
||||
metricsTrigger: undefined,
|
||||
});
|
||||
expect(secondResult).toBe(firstResult);
|
||||
expect(secondResult).toBe(mockRoomViewStore);
|
||||
});
|
||||
});
|
||||
|
||||
describe("removeRoomViewStore", () => {
|
||||
it("should remove an existing RoomViewStore from cache", () => {
|
||||
const roomId = "!room1:example.com";
|
||||
|
||||
multiRoomViewStore.getRoomViewStoreForRoom(roomId);
|
||||
multiRoomViewStore.removeRoomViewStore(roomId);
|
||||
|
||||
// New store should be created now
|
||||
jest.clearAllMocks();
|
||||
(RoomViewStore as jest.MockedClass<typeof RoomViewStore>).mockImplementation(
|
||||
() => mockRoomViewStore as any,
|
||||
);
|
||||
|
||||
multiRoomViewStore.getRoomViewStoreForRoom(roomId);
|
||||
expect(RoomViewStore).toHaveBeenCalledWith(mockDispatcher, mockSdkContext, roomId);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user