diff --git a/src/components/structures/RoomView.tsx b/src/components/structures/RoomView.tsx index ed5f60d3572..648122af629 100644 --- a/src/components/structures/RoomView.tsx +++ b/src/components/structures/RoomView.tsx @@ -30,6 +30,7 @@ import { logger } from "matrix-js-sdk/src/logger"; import { EventTimeline } from 'matrix-js-sdk/src/models/event-timeline'; import { EventType } from 'matrix-js-sdk/src/@types/event'; import { RoomState, RoomStateEvent } from 'matrix-js-sdk/src/models/room-state'; +import { EventTimelineSet } from "matrix-js-sdk/src/models/event-timeline-set"; import { CallState, CallType, MatrixCall } from "matrix-js-sdk/src/webrtc/call"; import { throttle } from "lodash"; import { MatrixError } from 'matrix-js-sdk/src/http-api'; @@ -282,6 +283,7 @@ export class RoomView extends React.Component { this.dispatcherRef = dis.register(this.onAction); context.on(ClientEvent.Room, this.onRoom); context.on(RoomEvent.Timeline, this.onRoomTimeline); + context.on(RoomEvent.TimelineReset, this.onRoomTimelineReset); context.on(RoomEvent.Name, this.onRoomName); context.on(RoomStateEvent.Events, this.onRoomStateEvents); context.on(RoomStateEvent.Update, this.onRoomStateUpdate); @@ -1022,6 +1024,12 @@ export class RoomView extends React.Component { }); }; + private onRoomTimelineReset = (room: Room, timelineSet: EventTimelineSet) => { + if (!room || room.roomId !== this.state.room?.roomId) return; + logger.log(`Live timeline of ${room.roomId} was reset`); + this.setState({ liveTimeline: timelineSet.getLiveTimeline() }); + }; + private getRoomTombstone(room = this.state.room) { return room?.currentState.getStateEvents(EventType.RoomTombstone, ""); } diff --git a/test/components/structures/RoomView-test.tsx b/test/components/structures/RoomView-test.tsx index 98677891b9f..ec318bfca2a 100644 --- a/test/components/structures/RoomView-test.tsx +++ b/test/components/structures/RoomView-test.tsx @@ -15,64 +15,82 @@ limitations under the License. */ import React from "react"; -import TestRenderer from "react-test-renderer"; +import { mount, ReactWrapper } from "enzyme"; +import { mocked, MockedObject } from "jest-mock"; +import { MatrixClient } from "matrix-js-sdk/src/client"; import { Room, RoomEvent } from "matrix-js-sdk/src/models/room"; import { MatrixEvent } from "matrix-js-sdk/src/models/event"; +import { stubClient, wrapInMatrixClientContext } from "../../test-utils"; import { MatrixClientPeg } from "../../../src/MatrixClientPeg"; -import { stubClient } from "../../test-utils"; import { Action } from "../../../src/dispatcher/actions"; import dis from "../../../src/dispatcher/dispatcher"; import { ViewRoomPayload } from "../../../src/dispatcher/payloads/ViewRoomPayload"; -import MatrixClientContext from "../../../src/contexts/MatrixClientContext"; -import { RoomView } from "../../../src/components/structures/RoomView"; +import { RoomView as _RoomView } from "../../../src/components/structures/RoomView"; import ResizeNotifier from "../../../src/utils/ResizeNotifier"; import { RoomViewStore } from "../../../src/stores/RoomViewStore"; import DMRoomMap from "../../../src/utils/DMRoomMap"; +const RoomView = wrapInMatrixClientContext(_RoomView); + describe("RoomView", () => { - it("updates url preview visibility on encryption state change", async () => { + let cli: MockedObject; + let room: Room; + beforeEach(() => { stubClient(); - const cli = MatrixClientPeg.get(); - cli.hasLazyLoadMembersEnabled = () => false; - cli.isInitialSyncComplete = () => true; - cli.stopPeeking = () => undefined; + cli = mocked(MatrixClientPeg.get()); - const r1 = new Room("r1", cli, "@name:example.com"); - cli.getRoom = () => r1; - r1.getPendingEvents = () => []; + room = new Room("r1", cli, "@alice:example.com"); + room.getPendingEvents = () => []; + cli.getRoom.mockReturnValue(room); + // Re-emit certain events on the mocked client + room.on(RoomEvent.Timeline, (...args) => cli.emit(RoomEvent.Timeline, ...args)); + room.on(RoomEvent.TimelineReset, (...args) => cli.emit(RoomEvent.TimelineReset, ...args)); DMRoomMap.makeShared(); + }); - const switchRoomPromise = new Promise(resolve => { - const subscription = RoomViewStore.instance.addListener(() => { - if (RoomViewStore.instance.getRoomId()) { - subscription.remove(); - resolve(); - } + const mountRoomView = async (): Promise => { + if (RoomViewStore.instance.getRoomId() !== room.roomId) { + const switchRoomPromise = new Promise(resolve => { + const subscription = RoomViewStore.instance.addListener(() => { + if (RoomViewStore.instance.getRoomId()) { + subscription.remove(); + resolve(); + } + }); }); - }); - dis.dispatch({ - action: Action.ViewRoom, - room_id: r1.roomId, - metricsTrigger: null, - }); + dis.dispatch({ + action: Action.ViewRoom, + room_id: room.roomId, + metricsTrigger: null, + }); - await switchRoomPromise; + await switchRoomPromise; + } - const renderer = TestRenderer.create( - - ); + />, + ); + }; + const getRoomViewInstance = async (): Promise<_RoomView> => + (await mountRoomView()).find(_RoomView).instance() as _RoomView; + + it("updates url preview visibility on encryption state change", async () => { + // we should be starting unencrypted + expect(cli.isCryptoEnabled()).toEqual(false); + expect(cli.isRoomEncrypted(room.roomId)).toEqual(false); - const roomViewInstance = renderer.root.findByType(RoomView).instance; + const roomViewInstance = await getRoomViewInstance(); // in a default (non-encrypted room, it should start out with url previews enabled) // This is a white-box test in that we're asserting things about the state, which @@ -84,32 +102,28 @@ describe("RoomView", () => { // 2) SettingsStore is a static class and so very hard to mock out. expect(roomViewInstance.state.showUrlPreview).toBe(true); - // now enable encryption (by mocking out the tests for whether a room is encrypted) - cli.isCryptoEnabled = () => true; - cli.isRoomEncrypted = () => true; + // now enable encryption + cli.isCryptoEnabled.mockReturnValue(true); + cli.isRoomEncrypted.mockReturnValue(true); // and fake an encryption event into the room to prompt it to re-check - // wait until the event has been added - const eventAddedPromise = new Promise(resolve => { - r1.once(RoomEvent.Timeline, (...args) => { - // we're also using mock client that doesn't re-emit, so - // we emit the event to client manually - cli.emit(RoomEvent.Timeline, ...args); - resolve(); - }); - }); - - r1.addLiveEvents([new MatrixEvent({ + room.addLiveEvents([new MatrixEvent({ type: "m.room.encryption", sender: cli.getUserId(), content: {}, event_id: "someid", - room_id: r1.roomId, + room_id: room.roomId, })]); - await eventAddedPromise; - // URL previews should now be disabled expect(roomViewInstance.state.showUrlPreview).toBe(false); }); + + it("updates live timeline when a timeline reset happens", async () => { + const roomViewInstance = await getRoomViewInstance(); + const oldTimeline = roomViewInstance.state.liveTimeline; + + room.getUnfilteredTimelineSet().resetLiveTimeline(); + expect(roomViewInstance.state.liveTimeline).not.toEqual(oldTimeline); + }); }); diff --git a/test/test-utils/test-utils.ts b/test/test-utils/test-utils.ts index 14fdb65046d..63d24e46883 100644 --- a/test/test-utils/test-utils.ts +++ b/test/test-utils/test-utils.ts @@ -93,6 +93,7 @@ export function createTestClient(): MatrixClient { emit: eventEmitter.emit.bind(eventEmitter), isRoomEncrypted: jest.fn().mockReturnValue(false), peekInRoom: jest.fn().mockResolvedValue(mkStubRoom(undefined, undefined, undefined)), + stopPeeking: jest.fn(), paginateEventTimeline: jest.fn().mockResolvedValue(undefined), sendReadReceipt: jest.fn().mockResolvedValue(undefined), @@ -155,6 +156,8 @@ export function createTestClient(): MatrixClient { setPushRuleActions: jest.fn().mockResolvedValue(undefined), relations: jest.fn().mockRejectedValue(undefined), isCryptoEnabled: jest.fn().mockReturnValue(false), + hasLazyLoadMembersEnabled: jest.fn().mockReturnValue(false), + isInitialSyncComplete: jest.fn().mockReturnValue(true), downloadKeys: jest.fn(), fetchRoomEvent: jest.fn(), } as unknown as MatrixClient; @@ -358,7 +361,7 @@ export function mkStubRoom(roomId: string = null, name: string, client: MatrixCl getJoinedMemberCount: jest.fn().mockReturnValue(1), getMembers: jest.fn().mockReturnValue([]), getPendingEvents: () => [], - getLiveTimeline: () => stubTimeline, + getLiveTimeline: jest.fn().mockReturnValue(stubTimeline), getUnfilteredTimelineSet: () => null, findEventById: () => null, getAccountData: () => null,