/* 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 React from 'react'; import ReactTestUtils from 'react-dom/test-utils'; import ReactDOM from 'react-dom'; import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk/src/matrix'; import * as TestUtils from '../../../test-utils'; import { MatrixClientPeg } from '../../../../src/MatrixClientPeg'; import dis from '../../../../src/dispatcher/dispatcher'; import DMRoomMap from '../../../../src/utils/DMRoomMap'; import { DefaultTagID } from "../../../../src/stores/room-list/models"; import RoomListStore, { RoomListStoreClass } from "../../../../src/stores/room-list/RoomListStore"; import RoomListLayoutStore from "../../../../src/stores/room-list/RoomListLayoutStore"; import RoomList from "../../../../src/components/views/rooms/RoomList"; import RoomSublist from "../../../../src/components/views/rooms/RoomSublist"; import RoomTile from "../../../../src/components/views/rooms/RoomTile"; function generateRoomId() { return '!' + Math.random().toString().slice(2, 10) + ':domain'; } describe('RoomList', () => { function createRoom(opts) { const room = new Room(generateRoomId(), MatrixClientPeg.get(), client.getUserId(), { // The room list now uses getPendingEvents(), so we need a detached ordering. pendingEventOrdering: "detached", }); if (opts) { Object.assign(room, opts); } return room; } let parentDiv = null; let client = null; let root = null; const myUserId = '@me:domain'; const movingRoomId = '!someroomid'; let movingRoom; let otherRoom; let myMember; let myOtherMember; beforeEach(async function(done) { RoomListStoreClass.TEST_MODE = true; TestUtils.stubClient(); client = MatrixClientPeg.get(); client.credentials = { userId: myUserId }; //revert this to prototype method as the test-utils monkey-patches this to return a hardcoded value client.getUserId = MatrixClient.prototype.getUserId; DMRoomMap.makeShared(); parentDiv = document.createElement('div'); document.body.appendChild(parentDiv); const WrappedRoomList = TestUtils.wrapInMatrixClientContext(RoomList); root = ReactDOM.render( {}} />, parentDiv, ); ReactTestUtils.findRenderedComponentWithType(root, RoomList); movingRoom = createRoom({ name: 'Moving room' }); expect(movingRoom.roomId).not.toBe(null); // Mock joined member myMember = new RoomMember(movingRoomId, myUserId); myMember.membership = 'join'; movingRoom.updateMyMembership('join'); movingRoom.getMember = (userId) => ({ [client.credentials.userId]: myMember, }[userId]); otherRoom = createRoom({ name: 'Other room' }); myOtherMember = new RoomMember(otherRoom.roomId, myUserId); myOtherMember.membership = 'join'; otherRoom.updateMyMembership('join'); otherRoom.getMember = (userId) => ({ [client.credentials.userId]: myOtherMember, }[userId]); // Mock the matrix client client.getRooms = () => [ movingRoom, otherRoom, createRoom({ tags: { 'm.favourite': { order: 0.1 } }, name: 'Some other room' }), createRoom({ tags: { 'm.favourite': { order: 0.2 } }, name: 'Some other room 2' }), createRoom({ tags: { 'm.lowpriority': {} }, name: 'Some unimportant room' }), createRoom({ tags: { 'custom.tag': {} }, name: 'Some room customly tagged' }), ]; client.getVisibleRooms = client.getRooms; const roomMap = {}; client.getRooms().forEach((r) => { roomMap[r.roomId] = r; }); client.getRoom = (roomId) => roomMap[roomId]; // Now that everything has been set up, prepare and update the store await RoomListStore.instance.makeReady(client); done(); }); afterEach(async (done) => { if (parentDiv) { ReactDOM.unmountComponentAtNode(parentDiv); parentDiv.remove(); parentDiv = null; } await RoomListLayoutStore.instance.resetLayouts(); await RoomListStore.instance.resetStore(); done(); }); function expectRoomInSubList(room, subListTest) { const subLists = ReactTestUtils.scryRenderedComponentsWithType(root, RoomSublist); const containingSubList = subLists.find(subListTest); let expectedRoomTile; try { const roomTiles = ReactTestUtils.scryRenderedComponentsWithType(containingSubList, RoomTile); console.info({ roomTiles: roomTiles.length }); expectedRoomTile = roomTiles.find((tile) => tile.props.room === room); } catch (err) { // truncate the error message because it's spammy err.message = 'Error finding RoomTile for ' + room.roomId + ' in ' + subListTest + ': ' + err.message.split('componentType')[0] + '...'; throw err; } expect(expectedRoomTile).toBeTruthy(); expect(expectedRoomTile.props.room).toBe(room); } function expectCorrectMove(oldTagId, newTagId) { const getTagSubListTest = (tagId) => { return (s) => s.props.tagId === tagId; }; // Default to finding the destination sublist with newTag const destSubListTest = getTagSubListTest(newTagId); const srcSubListTest = getTagSubListTest(oldTagId); // Set up the room that will be moved such that it has the correct state for a room in // the section for oldTagId if (oldTagId === DefaultTagID.Favourite || oldTagId === DefaultTagID.LowPriority) { movingRoom.tags = { [oldTagId]: {} }; } else if (oldTagId === DefaultTagID.DM) { // Mock inverse m.direct DMRoomMap.shared().roomToUser = { [movingRoom.roomId]: '@someotheruser:domain', }; } dis.dispatch({ action: 'MatrixActions.sync', prevState: null, state: 'PREPARED', matrixClient: client }); expectRoomInSubList(movingRoom, srcSubListTest); dis.dispatch({ action: 'RoomListActions.tagRoom.pending', request: { oldTagId, newTagId, room: movingRoom, } }); expectRoomInSubList(movingRoom, destSubListTest); } function itDoesCorrectOptimisticUpdatesForDraggedRoomTiles() { // TODO: Re-enable dragging tests when we support dragging again. describe.skip('does correct optimistic update when dragging from', () => { it('rooms to people', () => { expectCorrectMove(undefined, DefaultTagID.DM); }); it('rooms to favourites', () => { expectCorrectMove(undefined, 'm.favourite'); }); it('rooms to low priority', () => { expectCorrectMove(undefined, 'm.lowpriority'); }); // XXX: Known to fail - the view does not update immediately to reflect the change. // Whe running the app live, it updates when some other event occurs (likely the // m.direct arriving) that these tests do not fire. xit('people to rooms', () => { expectCorrectMove(DefaultTagID.DM, undefined); }); it('people to favourites', () => { expectCorrectMove(DefaultTagID.DM, 'm.favourite'); }); it('people to lowpriority', () => { expectCorrectMove(DefaultTagID.DM, 'm.lowpriority'); }); it('low priority to rooms', () => { expectCorrectMove('m.lowpriority', undefined); }); it('low priority to people', () => { expectCorrectMove('m.lowpriority', DefaultTagID.DM); }); it('low priority to low priority', () => { expectCorrectMove('m.lowpriority', 'm.lowpriority'); }); it('favourites to rooms', () => { expectCorrectMove('m.favourite', undefined); }); it('favourites to people', () => { expectCorrectMove('m.favourite', DefaultTagID.DM); }); it('favourites to low priority', () => { expectCorrectMove('m.favourite', 'm.lowpriority'); }); }); } itDoesCorrectOptimisticUpdatesForDraggedRoomTiles(); });