Merge pull request #4919 from matrix-org/travis/room-list/enable

Enable the new room list by default
pull/21833/head
Travis Ralston 2020-07-13 06:56:25 -06:00 committed by GitHub
commit a70e575b96
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 146 additions and 97 deletions

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
$tagPanelWidth: 70px; // only applies in this file, used for calculations

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
.mx_RoomBreadcrumbs2 {
width: 100%;

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
.mx_RoomSublist2 {
// The sublist is a column of rows, essentially

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
// Note: the room tile expects to be in a flexbox column container
.mx_RoomTile2 {

View File

@ -35,8 +35,8 @@ import RoomListStore, { LISTS_UPDATE_EVENT } from "../../stores/room-list/RoomLi
import {Key} from "../../Keyboard";
import IndicatorScrollbar from "../structures/IndicatorScrollbar";
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
/*******************************************************************
* CAUTION *

View File

@ -668,8 +668,7 @@ class LoggedInView extends React.Component<IProps, IState> {
disabled={this.props.leftDisabled}
/>
);
if (SettingsStore.isFeatureEnabled("feature_new_room_list")) {
// TODO: Supply props like collapsed and disabled to LeftPanel2
if (SettingsStore.getValue("feature_new_room_list")) {
leftPanel = (
<LeftPanel2
isMinimized={this.props.collapseLhs || false}

View File

@ -25,7 +25,7 @@ import { Key } from "../../Keyboard";
import AccessibleButton from "../views/elements/AccessibleButton";
import { Action } from "../../dispatcher/actions";
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367
/*******************************************************************
* CAUTION *

View File

@ -27,8 +27,8 @@ import RoomListStore from "../../../stores/room-list/RoomListStore2";
import { DefaultTagID } from "../../../stores/room-list/models";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
/*******************************************************************
* CAUTION *

View File

@ -41,8 +41,8 @@ import { Action } from "../../../dispatcher/actions";
import { ViewRoomDeltaPayload } from "../../../dispatcher/payloads/ViewRoomDeltaPayload";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
/*******************************************************************
* CAUTION *

View File

@ -48,8 +48,8 @@ import { polyfillTouchEvent } from "../../../@types/polyfill";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import RoomListLayoutStore from "../../../stores/room-list/RoomListLayoutStore";
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
/*******************************************************************
* CAUTION *

View File

@ -55,8 +55,8 @@ import {ActionPayload} from "../../../dispatcher/payloads";
import { RoomNotificationStateStore } from "../../../stores/notifications/RoomNotificationStateStore";
import { NotificationState } from "../../../stores/notifications/NotificationState";
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14231
// TODO: Remove banner on launch: https://github.com/vector-im/riot-web/issues/14367
// TODO: Rename on launch: https://github.com/vector-im/riot-web/issues/14367
/*******************************************************************
* CAUTION *

View File

@ -32,12 +32,12 @@ export default class PreferencesUserSettingsTab extends React.Component {
'breadcrumbs',
];
// TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14231
// TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14367
static ROOM_LIST_2_SETTINGS = [
'breadcrumbs',
];
// TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14231
// TODO: Remove temp structures: https://github.com/vector-im/riot-web/issues/14367
static eligibleRoomListSettings = () => {
if (RoomListStoreTempProxy.isUsingNewStore()) {
return PreferencesUserSettingsTab.ROOM_LIST_2_SETTINGS;

View File

@ -147,7 +147,8 @@ export const SETTINGS = {
default: false,
},
"feature_new_room_list": {
isFeature: true,
// TODO: Remove setting: https://github.com/vector-im/riot-web/issues/14367
// XXX: We shouldn't have non-features appear like features.
displayName: _td("Use the improved room list (will refresh to apply changes)"),
supportedLevels: LEVELS_FEATURE,
default: true,

View File

@ -57,7 +57,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
protected async onAction(payload: ActionPayload) {
if (!this.matrixClient) return;
// TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231
// TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14367
if (!RoomListStoreTempProxy.isUsingNewStore()) return;
if (payload.action === 'setting_updated') {
@ -80,7 +80,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
}
protected async onReady() {
// TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231
// TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14367
if (!RoomListStoreTempProxy.isUsingNewStore()) return;
await this.updateRooms();
@ -91,7 +91,7 @@ export class BreadcrumbsStore extends AsyncStoreWithClient<IState> {
}
protected async onNotReady() {
// TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231
// TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14367
if (!RoomListStoreTempProxy.isUsingNewStore()) return;
this.matrixClient.removeListener("Room.myMembership", this.onMyMembership);

View File

@ -99,7 +99,7 @@ class RoomListStore extends Store {
}
_checkDisabled() {
this.disabled = SettingsStore.isFeatureEnabled("feature_new_room_list");
this.disabled = SettingsStore.getValue("feature_new_room_list");
if (this.disabled) {
console.warn("👋 legacy room list store has been disabled");
}

View File

@ -192,7 +192,7 @@ export class MessagePreviewStore extends AsyncStoreWithClient<IState> {
protected async onAction(payload: ActionPayload) {
if (!this.matrixClient) return;
// TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14231
// TODO: Remove when new room list is made the default: https://github.com/vector-im/riot-web/issues/14367
if (!RoomListStoreTempProxy.isUsingNewStore()) return;
if (payload.action === 'MatrixActions.Room.timeline' || payload.action === 'MatrixActions.Event.decrypted') {

View File

@ -46,6 +46,12 @@ interface IState {
export const LISTS_UPDATE_EVENT = "lists_update";
export class RoomListStore2 extends AsyncStore<ActionPayload> {
/**
* Set to true if you're running tests on the store. Should not be touched in
* any other environment.
*/
public static TEST_MODE = false;
private _matrixClient: MatrixClient;
private initialListsGenerated = false;
private enabled = false;
@ -77,9 +83,43 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
return this._matrixClient;
}
// TODO: Remove enabled flag with the old RoomListStore: https://github.com/vector-im/riot-web/issues/14231
// Intended for test usage
public async resetStore() {
await this.reset();
this.tagWatcher = new TagWatcher(this);
this.filterConditions = [];
this.initialListsGenerated = false;
this._matrixClient = null;
this.algorithm.off(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated);
this.algorithm.off(FILTER_CHANGED, this.onAlgorithmListUpdated);
this.algorithm = new Algorithm();
this.algorithm.on(LIST_UPDATED_EVENT, this.onAlgorithmListUpdated);
this.algorithm.on(FILTER_CHANGED, this.onAlgorithmListUpdated);
}
// Public for test usage. Do not call this.
public async makeReady(client: MatrixClient) {
// TODO: Remove with https://github.com/vector-im/riot-web/issues/14367
this.checkEnabled();
if (!this.enabled) return;
this._matrixClient = client;
// Update any settings here, as some may have happened before we were logically ready.
// Update any settings here, as some may have happened before we were logically ready.
console.log("Regenerating room lists: Startup");
await this.readAndCacheSettingsFromStore();
await this.regenerateAllLists({trigger: false});
await this.handleRVSUpdate({trigger: false}); // fake an RVS update to adjust sticky room, if needed
this.updateFn.mark(); // we almost certainly want to trigger an update.
this.updateFn.trigger();
}
// TODO: Remove enabled flag with the old RoomListStore: https://github.com/vector-im/riot-web/issues/14367
private checkEnabled() {
this.enabled = SettingsStore.isFeatureEnabled("feature_new_room_list");
this.enabled = SettingsStore.getValue("feature_new_room_list");
if (this.enabled) {
console.log("⚡ new room list store engaged");
}
@ -99,7 +139,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
* be used if the calling code will manually trigger the update.
*/
private async handleRVSUpdate({trigger = true}) {
if (!this.enabled) return; // TODO: Remove with https://github.com/vector-im/riot-web/issues/14231
if (!this.enabled) return; // TODO: Remove with https://github.com/vector-im/riot-web/issues/14367
if (!this.matrixClient) return; // We assume there won't be RVS updates without a client
const activeRoomId = RoomViewStore.getRoomId();
@ -122,7 +162,14 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
if (trigger) this.updateFn.trigger();
}
protected onDispatch(payload: ActionPayload) {
protected async onDispatch(payload: ActionPayload) {
// When we're running tests we can't reliably use setImmediate out of timing concerns.
// As such, we use a more synchronous model.
if (RoomListStore2.TEST_MODE) {
await this.onDispatchAsync(payload);
return;
}
// We do this to intentionally break out of the current event loop task, allowing
// us to instead wait for a more convenient time to run our updates.
setImmediate(() => this.onDispatchAsync(payload));
@ -135,19 +182,7 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
return;
}
// TODO: Remove with https://github.com/vector-im/riot-web/issues/14231
this.checkEnabled();
if (!this.enabled) return;
this._matrixClient = payload.matrixClient;
// Update any settings here, as some may have happened before we were logically ready.
console.log("Regenerating room lists: Startup");
await this.readAndCacheSettingsFromStore();
await this.regenerateAllLists({trigger: false});
await this.handleRVSUpdate({trigger: false}); // fake an RVS update to adjust sticky room, if needed
this.updateFn.trigger();
await this.makeReady(payload.matrixClient);
return; // no point in running the next conditions - they won't match
}
@ -496,10 +531,13 @@ export class RoomListStore2 extends AsyncStore<ActionPayload> {
/**
* Regenerates the room whole room list, discarding any previous results.
*
* Note: This is only exposed externally for the tests. Do not call this from within
* the app.
* @param trigger Set to false to prevent a list update from being sent. Should only
* be used if the calling code will manually trigger the update.
*/
private async regenerateAllLists({trigger = true}) {
public async regenerateAllLists({trigger = true}) {
console.warn("Regenerating all room lists");
const sorts: ITagSortingMap = {};

View File

@ -24,11 +24,11 @@ import { ITagMap } from "./algorithms/models";
* Temporary RoomListStore proxy. Should be replaced with RoomListStore2 when
* it is available to everyone.
*
* TODO: Delete this: https://github.com/vector-im/riot-web/issues/14231
* TODO: Delete this: https://github.com/vector-im/riot-web/issues/14367
*/
export class RoomListStoreTempProxy {
public static isUsingNewStore(): boolean {
return SettingsStore.isFeatureEnabled("feature_new_room_list");
return SettingsStore.getValue("feature_new_room_list");
}
public static addListener(handler: () => void): RoomListStoreTempToken {

View File

@ -1,7 +1,6 @@
import React from 'react';
import ReactTestUtils from 'react-dom/test-utils';
import ReactDOM from 'react-dom';
import lolex from 'lolex';
import * as TestUtils from '../../../test-utils';
@ -15,11 +14,18 @@ import GroupStore from '../../../../src/stores/GroupStore.js';
import { MatrixClient, Room, RoomMember } from 'matrix-js-sdk';
import {DefaultTagID} from "../../../../src/stores/room-list/models";
import RoomListStore, {LISTS_UPDATE_EVENT, RoomListStore2} from "../../../../src/stores/room-list/RoomListStore2";
import RoomListLayoutStore from "../../../../src/stores/room-list/RoomListLayoutStore";
function generateRoomId() {
return '!' + Math.random().toString().slice(2, 10) + ':domain';
}
function waitForRoomListStoreUpdate() {
return new Promise((resolve) => {
RoomListStore.instance.once(LISTS_UPDATE_EVENT, () => resolve());
});
}
describe('RoomList', () => {
function createRoom(opts) {
@ -34,7 +40,6 @@ describe('RoomList', () => {
let client = null;
let root = null;
const myUserId = '@me:domain';
let clock = null;
const movingRoomId = '!someroomid';
let movingRoom;
@ -43,25 +48,25 @@ describe('RoomList', () => {
let myMember;
let myOtherMember;
beforeEach(function() {
beforeEach(async function(done) {
RoomListStore2.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;
clock = lolex.install();
DMRoomMap.makeShared();
parentDiv = document.createElement('div');
document.body.appendChild(parentDiv);
const RoomList = sdk.getComponent('views.rooms.RoomList');
const RoomList = sdk.getComponent('views.rooms.RoomList2');
const WrappedRoomList = TestUtils.wrapInMatrixClientContext(RoomList);
root = ReactDOM.render(
<DragDropContext>
<WrappedRoomList searchFilter="" />
<WrappedRoomList searchFilter="" onResize={() => {}} />
</DragDropContext>
, parentDiv);
ReactTestUtils.findRenderedComponentWithType(root, RoomList);
@ -102,23 +107,29 @@ describe('RoomList', () => {
});
client.getRoom = (roomId) => roomMap[roomId];
// Now that everything has been set up, prepare and update the store
await RoomListStore.instance.makeReady(client);
done();
});
afterEach((done) => {
afterEach(async (done) => {
if (parentDiv) {
ReactDOM.unmountComponentAtNode(parentDiv);
parentDiv.remove();
parentDiv = null;
}
clock.uninstall();
await RoomListLayoutStore.instance.resetLayouts();
await RoomListStore.instance.resetStore();
done();
});
function expectRoomInSubList(room, subListTest) {
const RoomSubList = sdk.getComponent('structures.RoomSubList');
const RoomTile = sdk.getComponent('views.rooms.RoomTile');
const RoomSubList = sdk.getComponent('views.rooms.RoomSublist2');
const RoomTile = sdk.getComponent('views.rooms.RoomTile2');
const subLists = ReactTestUtils.scryRenderedComponentsWithType(root, RoomSubList);
const containingSubList = subLists.find(subListTest);
@ -140,20 +151,20 @@ describe('RoomList', () => {
expect(expectedRoomTile.props.room).toBe(room);
}
function expectCorrectMove(oldTag, newTag) {
const getTagSubListTest = (tag) => {
if (tag === undefined) return (s) => s.props.label.endsWith('Rooms');
return (s) => s.props.tagName === tag;
function expectCorrectMove(oldTagId, newTagId) {
const getTagSubListTest = (tagId) => {
return (s) => s.props.tagId === tagId;
};
// Default to finding the destination sublist with newTag
const destSubListTest = getTagSubListTest(newTag);
const srcSubListTest = getTagSubListTest(oldTag);
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 oldTag
if (['m.favourite', 'm.lowpriority'].includes(oldTag)) movingRoom.tags = {[oldTag]: {}};
if (oldTag === DefaultTagID.DM) {
// 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',
@ -162,17 +173,12 @@ describe('RoomList', () => {
dis.dispatch({action: 'MatrixActions.sync', prevState: null, state: 'PREPARED', matrixClient: client});
clock.runAll();
expectRoomInSubList(movingRoom, srcSubListTest);
dis.dispatch({action: 'RoomListActions.tagRoom.pending', request: {
oldTag, newTag, room: movingRoom,
oldTagId, newTagId, room: movingRoom,
}});
// Run all setTimeouts for dispatches and room list rate limiting
clock.runAll();
expectRoomInSubList(movingRoom, destSubListTest);
}
@ -269,6 +275,12 @@ describe('RoomList', () => {
};
GroupStore._notifyListeners();
// We also have to mock the client's getGroup function for the room list to filter it.
// It's not smart enough to tell the difference between a real group and a template though.
client.getGroup = (groupId) => {
return {groupId};
};
// Select tag
dis.dispatch({action: 'select_tag', tag: '+group:domain'}, true);
}
@ -277,17 +289,14 @@ describe('RoomList', () => {
setupSelectedTag();
});
it('displays the correct rooms when the groups rooms are changed', () => {
it('displays the correct rooms when the groups rooms are changed', async () => {
GroupStore.getGroupRooms = (groupId) => {
return [movingRoom, otherRoom];
};
GroupStore._notifyListeners();
// Run through RoomList debouncing
clock.runAll();
// By default, the test will
expectRoomInSubList(otherRoom, (s) => s.props.label.endsWith('Rooms'));
await waitForRoomListStoreUpdate();
expectRoomInSubList(otherRoom, (s) => s.props.tagId === DefaultTagID.Untagged);
});
itDoesCorrectOptimisticUpdatesForDraggedRoomTiles();

View File

@ -15,10 +15,12 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
const {findSublist} = require("./create-room");
module.exports = async function acceptInvite(session, name) {
session.log.step(`accepts "${name}" invite`);
//TODO: brittle selector
const invitesHandles = await session.queryAll('.mx_RoomTile_name.mx_RoomTile_invite');
const inviteSublist = await findSublist(session, "invites");
const invitesHandles = await inviteSublist.$$(".mx_RoomTile2_name");
const invitesWithText = await Promise.all(invitesHandles.map(async (inviteHandle) => {
const text = await session.innerText(inviteHandle);
return {inviteHandle, text};

View File

@ -16,21 +16,27 @@ limitations under the License.
*/
async function openRoomDirectory(session) {
const roomDirectoryButton = await session.query('.mx_LeftPanel_explore .mx_AccessibleButton');
const roomDirectoryButton = await session.query('.mx_LeftPanel2_exploreButton');
await roomDirectoryButton.click();
}
async function findSublist(session, name) {
const sublists = await session.queryAll('.mx_RoomSublist2');
for (const sublist of sublists) {
const header = await sublist.$('.mx_RoomSublist2_headerText');
const headerText = await session.innerText(header);
if (headerText.toLowerCase().includes(name.toLowerCase())) {
return sublist;
}
}
throw new Error(`could not find room list section that contains '${name}' in header`);
}
async function createRoom(session, roomName, encrypted=false) {
session.log.step(`creates room "${roomName}"`);
const roomListHeaders = await session.queryAll('.mx_RoomSubList_labelContainer');
const roomListHeaderLabels = await Promise.all(roomListHeaders.map(h => session.innerText(h)));
const roomsIndex = roomListHeaderLabels.findIndex(l => l.toLowerCase().includes("rooms"));
if (roomsIndex === -1) {
throw new Error("could not find room list section that contains 'rooms' in header");
}
const roomsHeader = roomListHeaders[roomsIndex];
const addRoomButton = await roomsHeader.$(".mx_RoomSubList_addRoom");
const roomsSublist = await findSublist(session, "rooms");
const addRoomButton = await roomsSublist.$(".mx_RoomSublist2_auxButton");
await addRoomButton.click();
const roomNameInput = await session.query('.mx_CreateRoomDialog_name input');
@ -51,14 +57,8 @@ async function createRoom(session, roomName, encrypted=false) {
async function createDm(session, invitees) {
session.log.step(`creates DM with ${JSON.stringify(invitees)}`);
const roomListHeaders = await session.queryAll('.mx_RoomSubList_labelContainer');
const roomListHeaderLabels = await Promise.all(roomListHeaders.map(h => session.innerText(h)));
const dmsIndex = roomListHeaderLabels.findIndex(l => l.toLowerCase().includes('direct messages'));
if (dmsIndex === -1) {
throw new Error("could not find room list section that contains 'direct messages' in header");
}
const dmsHeader = roomListHeaders[dmsIndex];
const startChatButton = await dmsHeader.$(".mx_RoomSubList_addRoom");
const dmsSublist = await findSublist(session, "people");
const startChatButton = await dmsSublist.$(".mx_RoomSublist2_auxButton");
await startChatButton.click();
const inviteesEditor = await session.query('.mx_InviteDialog_editor textarea');
@ -83,4 +83,4 @@ async function createDm(session, invitees) {
session.log.done();
}
module.exports = {openRoomDirectory, createRoom, createDm};
module.exports = {openRoomDirectory, findSublist, createRoom, createDm};