From de201f54b0b84b661499a9adc1312ad0c393e28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Sun, 5 Jun 2022 17:31:32 +0200 Subject: [PATCH 01/12] Delabs `Show current avatar and name for users in message history` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- src/components/views/avatars/MemberAvatar.tsx | 4 ++-- src/components/views/messages/SenderProfile.tsx | 2 +- .../views/settings/tabs/user/PreferencesUserSettingsTab.tsx | 1 + src/settings/Settings.tsx | 6 ++---- 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/views/avatars/MemberAvatar.tsx b/src/components/views/avatars/MemberAvatar.tsx index 76dc9e69621..48664394731 100644 --- a/src/components/views/avatars/MemberAvatar.tsx +++ b/src/components/views/avatars/MemberAvatar.tsx @@ -42,7 +42,7 @@ interface IProps extends Omit, "name" | pushUserOnClick?: boolean; title?: string; style?: any; - forceHistorical?: boolean; // true to deny `feature_use_only_current_profiles` usage. Default false. + forceHistorical?: boolean; // true to deny `useOnlyCurrentProfiles` usage. Default false. hideTitle?: boolean; } @@ -72,7 +72,7 @@ export default class MemberAvatar extends React.PureComponent { private static getState(props: IProps): IState { let member = props.member; - if (member && !props.forceHistorical && SettingsStore.getValue("feature_use_only_current_profiles")) { + if (member && !props.forceHistorical && SettingsStore.getValue("useOnlyCurrentProfiles")) { const room = MatrixClientPeg.get().getRoom(member.roomId); if (room) { member = room.getMember(member.userId); diff --git a/src/components/views/messages/SenderProfile.tsx b/src/components/views/messages/SenderProfile.tsx index da7d1206d11..db44cfeb04d 100644 --- a/src/components/views/messages/SenderProfile.tsx +++ b/src/components/views/messages/SenderProfile.tsx @@ -38,7 +38,7 @@ export default class SenderProfile extends React.PureComponent { const msgtype = mxEvent.getContent().msgtype; let member = mxEvent.sender; - if (SettingsStore.getValue("feature_use_only_current_profiles")) { + if (SettingsStore.getValue("useOnlyCurrentProfiles")) { const room = MatrixClientPeg.get().getRoom(mxEvent.getRoomId()); if (room) { member = room.getMember(mxEvent.getSender()); diff --git a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx index 2b67d22c0df..759c8159b4e 100644 --- a/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx +++ b/src/components/views/settings/tabs/user/PreferencesUserSettingsTab.tsx @@ -89,6 +89,7 @@ export default class PreferencesUserSettingsTab extends React.Component Date: Mon, 27 Jun 2022 16:36:21 +0200 Subject: [PATCH 02/12] Improve cypress support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- cypress/global.d.ts | 2 ++ cypress/support/client.ts | 40 ++++++++++++++++++++++++++- cypress/support/settings.ts | 55 +++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+), 1 deletion(-) diff --git a/cypress/global.d.ts b/cypress/global.d.ts index 3d36daf951c..5c479424ba6 100644 --- a/cypress/global.d.ts +++ b/cypress/global.d.ts @@ -28,11 +28,13 @@ import type { } from "matrix-js-sdk/src/matrix"; import type { MatrixDispatcher } from "../src/dispatcher/dispatcher"; import type PerformanceMonitor from "../src/performance"; +import type SettingsStore from "../src/settings/SettingsStore"; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface ApplicationWindow { + mxSettingsStore: typeof SettingsStore; mxMatrixClientPeg: { matrixClient?: MatrixClient; }; diff --git a/cypress/support/client.ts b/cypress/support/client.ts index db27f4d2b1e..bc4b48fdd29 100644 --- a/cypress/support/client.ts +++ b/cypress/support/client.ts @@ -16,9 +16,10 @@ limitations under the License. /// -import type { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests"; +import type { ICreateRoomOpts, ISendEventResponse } from "matrix-js-sdk/src/@types/requests"; import type { MatrixClient } from "matrix-js-sdk/src/client"; import type { Room } from "matrix-js-sdk/src/models/room"; +import type { IContent } from "matrix-js-sdk/src/models/event"; import Chainable = Cypress.Chainable; declare global { @@ -53,6 +54,26 @@ declare global { * @param data The data to store. */ setAccountData(type: string, data: object): Chainable<{}>; + /** + * @param {string} roomId + * @param {string} threadId + * @param {string} eventType + * @param {Object} content + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ + sendEvent( + roomId: string, + threadId: string | null, + eventType: string, + content: IContent + ): Chainable; + /** + * @param {string} name + * @param {module:client.callback} callback Optional. + * @return {Promise} Resolves: {} an empty object. + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ + setDisplayName(name: string): Chainable<{}>; } } } @@ -103,3 +124,20 @@ Cypress.Commands.add("setAccountData", (type: string, data: object): Chainable<{ return cli.setAccountData(type, data); }); }); + +Cypress.Commands.add("sendEvent", ( + roomId: string, + threadId: string | null, + eventType: string, + content: IContent, +): Chainable => { + return cy.getClient().then(async (cli: MatrixClient) => { + return cli.sendEvent(roomId, threadId, eventType, content); + }); +}); + +Cypress.Commands.add("setDisplayName", (name: string): Chainable<{}> => { + return cy.getClient().then(async (cli: MatrixClient) => { + return cli.setDisplayName(name); + }); +}); diff --git a/cypress/support/settings.ts b/cypress/support/settings.ts index aed0631354b..cebbec6a761 100644 --- a/cypress/support/settings.ts +++ b/cypress/support/settings.ts @@ -17,11 +17,17 @@ limitations under the License. /// import Chainable = Cypress.Chainable; +import type { SettingLevel } from "../../src/settings/SettingLevel"; +import SettingsStore from "../../src/settings/SettingsStore"; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface Chainable { + /** + * Returns the SettingsStore + */ + getSettingsStore(): Chainable; /** * Open the top left user menu, returning a handle to the resulting context menu. */ @@ -63,10 +69,59 @@ declare global { * @param name the name of the beta to leave. */ leaveBeta(name: string): Chainable>; + + /** + * Sets the value for a setting. The room ID is optional if the + * setting is not being set for a particular room, otherwise it + * should be supplied. The value may be null to indicate that the + * level should no longer have an override. + * @param {string} settingName The name of the setting to change. + * @param {String} roomId The room ID to change the value in, may be + * null. + * @param {SettingLevel} level The level to change the value at. + * @param {*} value The new value of the setting, may be null. + * @return {Promise} Resolves when the setting has been changed. + */ + setSettingValue(name: string, roomId: string, level: SettingLevel, value: any): Chainable; + + /** + * Gets the value of a setting. The room ID is optional if the + * setting is not to be applied to any particular room, otherwise it + * should be supplied. + * @param {string} settingName The name of the setting to read the + * value of. + * @param {String} roomId The room ID to read the setting value in, + * may be null. + * @param {boolean} excludeDefault True to disable using the default + * value. + * @return {*} The value, or null if not found + */ + getSettingValue(name: string, roomId?: string): Chainable; } } } +Cypress.Commands.add("getSettingsStore", (): Chainable => { + return cy.window({ log: false }).then(win => win.mxSettingsStore); +}); + +Cypress.Commands.add("setSettingValue", ( + name: string, + roomId: string, + level: SettingLevel, + value: any, +): Chainable => { + return cy.getSettingsStore().then(async (store: typeof SettingsStore) => { + return store.setValue(name, roomId, level, value); + }); +}); + +Cypress.Commands.add("getSettingValue", (name: string, roomId?: string): Chainable => { + return cy.getSettingsStore().then((store: typeof SettingsStore) => { + return store.getValue(name, roomId); + }); +}); + Cypress.Commands.add("openUserMenu", (): Chainable> => { cy.get('[aria-label="User menu"]').click(); return cy.get(".mx_ContextualMenu"); From efdecf802d02a050d758133a086a8a9766e1574a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 27 Jun 2022 16:54:39 +0200 Subject: [PATCH 03/12] Add `timeline.spec.ts` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../integration/13-timeline/timeline.spec.ts | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 cypress/integration/13-timeline/timeline.spec.ts diff --git a/cypress/integration/13-timeline/timeline.spec.ts b/cypress/integration/13-timeline/timeline.spec.ts new file mode 100644 index 00000000000..618a5ffbe58 --- /dev/null +++ b/cypress/integration/13-timeline/timeline.spec.ts @@ -0,0 +1,91 @@ +/* +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 { EventType } from "matrix-js-sdk/src/@types/event"; +import { MessageEvent } from "matrix-events-sdk"; + +import { SynapseInstance } from "../../plugins/synapsedocker"; +import { SettingLevel } from "../../../src/settings/SettingLevel"; +import Chainable = Cypress.Chainable; + +const getEventTilesWithBodies = (): Chainable => { + return cy.get(".mx_EventTile").filter((_i, e) => e.getElementsByClassName("mx_EventTile_body").length > 0); +}; + +const expectDisplayName = (e: JQuery, displayName: string): void => { + expect(e.find(".mx_DisambiguatedProfile_displayName").text()).to.equal(displayName); +}; + +describe("Timeline", () => { + let synapse: SynapseInstance; + + const roomName = "Test room"; + let roomId: string; + + describe("useOnlyCurrentProfiles", () => { + beforeEach(() => { + cy.startSynapse("default").then(data => { + synapse = data; + cy.initTestUser(synapse, "Alan").then(() => + cy.window({ log: false }).then(() => { + cy.createRoom({ name: roomName }).then(_room1Id => { + roomId = _room1Id; + }); + }), + ); + }); + }); + + afterEach(() => { + cy.stopSynapse(synapse); + }); + + it("should show historical profiles if disabled", () => { + cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, false); + cy.sendEvent(roomId, null, EventType.RoomMessage, MessageEvent.from("Message 1").serialize().content); + cy.setDisplayName("Alan (away)"); + cy.wait(500); + cy.sendEvent(roomId, null, EventType.RoomMessage, MessageEvent.from("Message 2").serialize().content); + cy.viewRoomByName(roomName); + + const events = getEventTilesWithBodies(); + + events.should("have.length", 2); + events.each((e, i) => { + if (i === 0) expectDisplayName(e, "Alan"); + else if (i === 1) expectDisplayName(e, "Alan (away)"); + }); + }); + + it("should not show historical profiles if enabled", () => { + cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, true); + cy.sendEvent(roomId, null, EventType.RoomMessage, MessageEvent.from("Message 1").serialize().content); + cy.setDisplayName("Alan (away)"); + cy.wait(500); + cy.sendEvent(roomId, null, EventType.RoomMessage, MessageEvent.from("Message 2").serialize().content); + cy.viewRoomByName(roomName); + + const events = getEventTilesWithBodies(); + + events.should("have.length", 2); + events.each((e) => { + expectDisplayName(e, "Alan (away)"); + }); + }); + }); +}); From f4704f1f30c63d561eaf57e4db7d1da4529fdfc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 27 Jun 2022 17:35:54 +0200 Subject: [PATCH 04/12] "Fix" type issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- cypress/global.d.ts | 3 +-- cypress/support/settings.ts | 11 +++++------ 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/cypress/global.d.ts b/cypress/global.d.ts index 5c479424ba6..99943e13be3 100644 --- a/cypress/global.d.ts +++ b/cypress/global.d.ts @@ -28,13 +28,12 @@ import type { } from "matrix-js-sdk/src/matrix"; import type { MatrixDispatcher } from "../src/dispatcher/dispatcher"; import type PerformanceMonitor from "../src/performance"; -import type SettingsStore from "../src/settings/SettingsStore"; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface ApplicationWindow { - mxSettingsStore: typeof SettingsStore; + mxSettingsStore: any; // XXX: Importing SettingsStore causes a bunch of type lint errors mxMatrixClientPeg: { matrixClient?: MatrixClient; }; diff --git a/cypress/support/settings.ts b/cypress/support/settings.ts index cebbec6a761..4387a3c330d 100644 --- a/cypress/support/settings.ts +++ b/cypress/support/settings.ts @@ -18,7 +18,6 @@ limitations under the License. import Chainable = Cypress.Chainable; import type { SettingLevel } from "../../src/settings/SettingLevel"; -import SettingsStore from "../../src/settings/SettingsStore"; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -27,7 +26,7 @@ declare global { /** * Returns the SettingsStore */ - getSettingsStore(): Chainable; + getSettingsStore(): Chainable; // XXX: Importing SettingsStore causes a bunch of type lint errors /** * Open the top left user menu, returning a handle to the resulting context menu. */ @@ -101,7 +100,7 @@ declare global { } } -Cypress.Commands.add("getSettingsStore", (): Chainable => { +Cypress.Commands.add("getSettingsStore", (): Chainable => { return cy.window({ log: false }).then(win => win.mxSettingsStore); }); @@ -111,14 +110,14 @@ Cypress.Commands.add("setSettingValue", ( level: SettingLevel, value: any, ): Chainable => { - return cy.getSettingsStore().then(async (store: typeof SettingsStore) => { + return cy.getSettingsStore().then(async (store: any) => { return store.setValue(name, roomId, level, value); }); }); Cypress.Commands.add("getSettingValue", (name: string, roomId?: string): Chainable => { - return cy.getSettingsStore().then((store: typeof SettingsStore) => { - return store.getValue(name, roomId); + return cy.getSettingsStore().then((store: any) => { + return store.getValue(name, roomId); }); }); From 08556296497d58af93e42d9f3f56c76d14f30b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 27 Jun 2022 18:02:22 +0200 Subject: [PATCH 05/12] Try to fix tests failing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../integration/13-timeline/timeline.spec.ts | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/cypress/integration/13-timeline/timeline.spec.ts b/cypress/integration/13-timeline/timeline.spec.ts index 618a5ffbe58..d86dc4edd3b 100644 --- a/cypress/integration/13-timeline/timeline.spec.ts +++ b/cypress/integration/13-timeline/timeline.spec.ts @@ -16,9 +16,10 @@ limitations under the License. /// -import { EventType } from "matrix-js-sdk/src/@types/event"; import { MessageEvent } from "matrix-events-sdk"; +import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests"; +import type { EventType } from "matrix-js-sdk/src/@types/event"; import { SynapseInstance } from "../../plugins/synapsedocker"; import { SettingLevel } from "../../../src/settings/SettingLevel"; import Chainable = Cypress.Chainable; @@ -31,6 +32,15 @@ const expectDisplayName = (e: JQuery, displayName: string): void => expect(e.find(".mx_DisambiguatedProfile_displayName").text()).to.equal(displayName); }; +const sendEvent = (roomId: string): Chainable => { + return cy.sendEvent( + roomId, + null, + "m.room.message" as EventType, + MessageEvent.from("Message").serialize().content, + ); +}; + describe("Timeline", () => { let synapse: SynapseInstance; @@ -57,10 +67,10 @@ describe("Timeline", () => { it("should show historical profiles if disabled", () => { cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, false); - cy.sendEvent(roomId, null, EventType.RoomMessage, MessageEvent.from("Message 1").serialize().content); + sendEvent(roomId); cy.setDisplayName("Alan (away)"); cy.wait(500); - cy.sendEvent(roomId, null, EventType.RoomMessage, MessageEvent.from("Message 2").serialize().content); + sendEvent(roomId); cy.viewRoomByName(roomName); const events = getEventTilesWithBodies(); @@ -74,10 +84,10 @@ describe("Timeline", () => { it("should not show historical profiles if enabled", () => { cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, true); - cy.sendEvent(roomId, null, EventType.RoomMessage, MessageEvent.from("Message 1").serialize().content); + sendEvent(roomId); cy.setDisplayName("Alan (away)"); cy.wait(500); - cy.sendEvent(roomId, null, EventType.RoomMessage, MessageEvent.from("Message 2").serialize().content); + sendEvent(roomId); cy.viewRoomByName(roomName); const events = getEventTilesWithBodies(); From cd8c254ace44a6f1e645f2e3afdcde992461c9d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 28 Jun 2022 15:01:43 +0200 Subject: [PATCH 06/12] Add display name consts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- cypress/integration/13-timeline/timeline.spec.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cypress/integration/13-timeline/timeline.spec.ts b/cypress/integration/13-timeline/timeline.spec.ts index d86dc4edd3b..4f3f6e47788 100644 --- a/cypress/integration/13-timeline/timeline.spec.ts +++ b/cypress/integration/13-timeline/timeline.spec.ts @@ -44,6 +44,8 @@ const sendEvent = (roomId: string): Chainable => { describe("Timeline", () => { let synapse: SynapseInstance; + const oldName = "Alan"; + const newName = "Alan (away)"; const roomName = "Test room"; let roomId: string; @@ -51,7 +53,7 @@ describe("Timeline", () => { beforeEach(() => { cy.startSynapse("default").then(data => { synapse = data; - cy.initTestUser(synapse, "Alan").then(() => + cy.initTestUser(synapse, oldName).then(() => cy.window({ log: false }).then(() => { cy.createRoom({ name: roomName }).then(_room1Id => { roomId = _room1Id; @@ -77,15 +79,15 @@ describe("Timeline", () => { events.should("have.length", 2); events.each((e, i) => { - if (i === 0) expectDisplayName(e, "Alan"); - else if (i === 1) expectDisplayName(e, "Alan (away)"); + if (i === 0) expectDisplayName(e, oldName); + else if (i === 1) expectDisplayName(e, newName); }); }); it("should not show historical profiles if enabled", () => { cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, true); sendEvent(roomId); - cy.setDisplayName("Alan (away)"); + cy.setDisplayName(newName); cy.wait(500); sendEvent(roomId); cy.viewRoomByName(roomName); @@ -94,7 +96,7 @@ describe("Timeline", () => { events.should("have.length", 2); events.each((e) => { - expectDisplayName(e, "Alan (away)"); + expectDisplayName(e, newName); }); }); }); From e8f015c34225096d65c1eeeff9b703e49e6dfd31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 28 Jun 2022 15:49:56 +0200 Subject: [PATCH 07/12] Test avatars too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../integration/13-timeline/timeline.spec.ts | 44 +++++++++++++-- cypress/support/client.ts | 54 ++++++++++++++++++- 2 files changed, 94 insertions(+), 4 deletions(-) diff --git a/cypress/integration/13-timeline/timeline.spec.ts b/cypress/integration/13-timeline/timeline.spec.ts index 4f3f6e47788..a953cadce7b 100644 --- a/cypress/integration/13-timeline/timeline.spec.ts +++ b/cypress/integration/13-timeline/timeline.spec.ts @@ -20,10 +20,16 @@ import { MessageEvent } from "matrix-events-sdk"; import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests"; import type { EventType } from "matrix-js-sdk/src/@types/event"; +import type { MatrixClient } from "matrix-js-sdk/src/client"; import { SynapseInstance } from "../../plugins/synapsedocker"; import { SettingLevel } from "../../../src/settings/SettingLevel"; import Chainable = Cypress.Chainable; +// The avatar size used in the timeline +const AVATAR_SIZE = 30; +// The resize method used in the timeline +const AVATAR_RESIZE_METHOD = "crop"; + const getEventTilesWithBodies = (): Chainable => { return cy.get(".mx_EventTile").filter((_i, e) => e.getElementsByClassName("mx_EventTile_body").length > 0); }; @@ -32,6 +38,15 @@ const expectDisplayName = (e: JQuery, displayName: string): void => expect(e.find(".mx_DisambiguatedProfile_displayName").text()).to.equal(displayName); }; +const expectAvatar = (e: JQuery, avatarUrl: string): void => { + cy.getClient().then((cli: MatrixClient) => { + expect(e.find(".mx_BaseAvatar_image").attr("src")).to.equal( + // eslint-disable-next-line no-restricted-properties + cli.mxcUrlToHttp(avatarUrl, AVATAR_SIZE, AVATAR_SIZE, AVATAR_RESIZE_METHOD), + ); + }); +}; + const sendEvent = (roomId: string): Chainable => { return cy.sendEvent( roomId, @@ -44,8 +59,14 @@ const sendEvent = (roomId: string): Chainable => { describe("Timeline", () => { let synapse: SynapseInstance; + const oldAvatar = "avatar_image1"; + const newAvatar = "avatar_image2"; + let oldAvatarUrl: string; + let newAvatarUrl: string; + const oldName = "Alan"; const newName = "Alan (away)"; + const roomName = "Test room"; let roomId: string; @@ -59,7 +80,16 @@ describe("Timeline", () => { roomId = _room1Id; }); }), - ); + ).then(() => { + cy.uploadContent(oldAvatar).then((url) => { + oldAvatarUrl = url; + cy.setAvatarUrl(url); + }); + }).then(() => { + cy.uploadContent(newAvatar).then((url) => { + newAvatarUrl = url; + }); + }); }); }); @@ -71,6 +101,7 @@ describe("Timeline", () => { cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, false); sendEvent(roomId); cy.setDisplayName("Alan (away)"); + cy.setAvatarUrl(newAvatarUrl); cy.wait(500); sendEvent(roomId); cy.viewRoomByName(roomName); @@ -79,8 +110,13 @@ describe("Timeline", () => { events.should("have.length", 2); events.each((e, i) => { - if (i === 0) expectDisplayName(e, oldName); - else if (i === 1) expectDisplayName(e, newName); + if (i === 0) { + expectDisplayName(e, oldName); + expectAvatar(e, oldAvatarUrl); + } else if (i === 1) { + expectDisplayName(e, newName); + expectAvatar(e, newAvatarUrl); + } }); }); @@ -88,6 +124,7 @@ describe("Timeline", () => { cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, true); sendEvent(roomId); cy.setDisplayName(newName); + cy.setAvatarUrl(newAvatarUrl); cy.wait(500); sendEvent(roomId); cy.viewRoomByName(roomName); @@ -97,6 +134,7 @@ describe("Timeline", () => { events.should("have.length", 2); events.each((e) => { expectDisplayName(e, newName); + expectAvatar(e, newAvatarUrl); }); }); }); diff --git a/cypress/support/client.ts b/cypress/support/client.ts index bc4b48fdd29..d82d05068c8 100644 --- a/cypress/support/client.ts +++ b/cypress/support/client.ts @@ -16,7 +16,9 @@ limitations under the License. /// -import type { ICreateRoomOpts, ISendEventResponse } from "matrix-js-sdk/src/@types/requests"; +import type { FileType, UploadContentResponseType } from "matrix-js-sdk/src/http-api"; +import type { IAbortablePromise } from "matrix-js-sdk/src/@types/partials"; +import type { ICreateRoomOpts, ISendEventResponse, IUploadOpts } from "matrix-js-sdk/src/@types/requests"; import type { MatrixClient } from "matrix-js-sdk/src/client"; import type { Room } from "matrix-js-sdk/src/models/room"; import type { IContent } from "matrix-js-sdk/src/models/event"; @@ -74,6 +76,44 @@ declare global { * @return {module:http-api.MatrixError} Rejects: with an error response. */ setDisplayName(name: string): Chainable<{}>; + /** + * @param {string} url + * @param {module:client.callback} callback Optional. + * @return {Promise} Resolves: {} an empty object. + * @return {module:http-api.MatrixError} Rejects: with an error response. + */ + setAvatarUrl(url: string): Chainable<{}>; + /** + * Upload a file to the media repository on the homeserver. + * + * @param {object} file The object to upload. On a browser, something that + * can be sent to XMLHttpRequest.send (typically a File). Under node.js, + * a a Buffer, String or ReadStream. + */ + uploadContent( + file: FileType, + opts?: O, + ): IAbortablePromise>; + /** + * Turn an MXC URL into an HTTP one. This method is experimental and + * may change. + * @param {string} mxcUrl The MXC URL + * @param {Number} width The desired width of the thumbnail. + * @param {Number} height The desired height of the thumbnail. + * @param {string} resizeMethod The thumbnail resize method to use, either + * "crop" or "scale". + * @param {Boolean} allowDirectLinks If true, return any non-mxc URLs + * directly. Fetching such URLs will leak information about the user to + * anyone they share a room with. If false, will return null for such URLs. + * @return {?string} the avatar URL or null. + */ + mxcUrlToHttp( + mxcUrl: string, + width?: number, + height?: number, + resizeMethod?: string, + allowDirectLinks?: boolean, + ): string | null; } } } @@ -141,3 +181,15 @@ Cypress.Commands.add("setDisplayName", (name: string): Chainable<{}> => { return cli.setDisplayName(name); }); }); + +Cypress.Commands.add("uploadContent", (file: FileType): Chainable<{}> => { + return cy.getClient().then(async (cli: MatrixClient) => { + return cli.uploadContent(file); + }); +}); + +Cypress.Commands.add("setAvatarUrl", (url: string): Chainable<{}> => { + return cy.getClient().then(async (cli: MatrixClient) => { + return cli.setAvatarUrl(url); + }); +}); From e9ef0d7d3541fa4e3f5459093eb29c44ff5d348d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Tue, 28 Jun 2022 15:52:55 +0200 Subject: [PATCH 08/12] A bit of cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- .../integration/13-timeline/timeline.spec.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/cypress/integration/13-timeline/timeline.spec.ts b/cypress/integration/13-timeline/timeline.spec.ts index a953cadce7b..6ba8ee3fbe5 100644 --- a/cypress/integration/13-timeline/timeline.spec.ts +++ b/cypress/integration/13-timeline/timeline.spec.ts @@ -30,6 +30,12 @@ const AVATAR_SIZE = 30; // The resize method used in the timeline const AVATAR_RESIZE_METHOD = "crop"; +const ROOM_NAME = "Test room"; +const OLD_AVATAR = "avatar_image1"; +const NEW_AVATAR = "avatar_image2"; +const OLD_NAME = "Alan"; +const NEW_NAME = "Alan (away)"; + const getEventTilesWithBodies = (): Chainable => { return cy.get(".mx_EventTile").filter((_i, e) => e.getElementsByClassName("mx_EventTile_body").length > 0); }; @@ -59,34 +65,28 @@ const sendEvent = (roomId: string): Chainable => { describe("Timeline", () => { let synapse: SynapseInstance; - const oldAvatar = "avatar_image1"; - const newAvatar = "avatar_image2"; + let roomId: string; + let oldAvatarUrl: string; let newAvatarUrl: string; - const oldName = "Alan"; - const newName = "Alan (away)"; - - const roomName = "Test room"; - let roomId: string; - describe("useOnlyCurrentProfiles", () => { beforeEach(() => { cy.startSynapse("default").then(data => { synapse = data; - cy.initTestUser(synapse, oldName).then(() => + cy.initTestUser(synapse, OLD_NAME).then(() => cy.window({ log: false }).then(() => { - cy.createRoom({ name: roomName }).then(_room1Id => { + cy.createRoom({ name: ROOM_NAME }).then(_room1Id => { roomId = _room1Id; }); }), ).then(() => { - cy.uploadContent(oldAvatar).then((url) => { + cy.uploadContent(OLD_AVATAR).then((url) => { oldAvatarUrl = url; cy.setAvatarUrl(url); }); }).then(() => { - cy.uploadContent(newAvatar).then((url) => { + cy.uploadContent(NEW_AVATAR).then((url) => { newAvatarUrl = url; }); }); @@ -104,17 +104,17 @@ describe("Timeline", () => { cy.setAvatarUrl(newAvatarUrl); cy.wait(500); sendEvent(roomId); - cy.viewRoomByName(roomName); + cy.viewRoomByName(ROOM_NAME); const events = getEventTilesWithBodies(); events.should("have.length", 2); events.each((e, i) => { if (i === 0) { - expectDisplayName(e, oldName); + expectDisplayName(e, OLD_NAME); expectAvatar(e, oldAvatarUrl); } else if (i === 1) { - expectDisplayName(e, newName); + expectDisplayName(e, NEW_NAME); expectAvatar(e, newAvatarUrl); } }); @@ -123,17 +123,17 @@ describe("Timeline", () => { it("should not show historical profiles if enabled", () => { cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, true); sendEvent(roomId); - cy.setDisplayName(newName); + cy.setDisplayName(NEW_NAME); cy.setAvatarUrl(newAvatarUrl); cy.wait(500); sendEvent(roomId); - cy.viewRoomByName(roomName); + cy.viewRoomByName(ROOM_NAME); const events = getEventTilesWithBodies(); events.should("have.length", 2); events.each((e) => { - expectDisplayName(e, newName); + expectDisplayName(e, NEW_NAME); expectAvatar(e, newAvatarUrl); }); }); From 3f8b56bdc48219f0b6ca1478ac187f0167c143e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 30 Jun 2022 16:53:32 +0200 Subject: [PATCH 09/12] Update dir name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- cypress/integration/{13-timeline => 14-timeline}/timeline.spec.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename cypress/integration/{13-timeline => 14-timeline}/timeline.spec.ts (100%) diff --git a/cypress/integration/13-timeline/timeline.spec.ts b/cypress/integration/14-timeline/timeline.spec.ts similarity index 100% rename from cypress/integration/13-timeline/timeline.spec.ts rename to cypress/integration/14-timeline/timeline.spec.ts From 4b68c7e08da3a936505e8b2c60e46a444c5b3d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 30 Jun 2022 17:09:42 +0200 Subject: [PATCH 10/12] Add comment about waiting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- cypress/integration/14-timeline/timeline.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cypress/integration/14-timeline/timeline.spec.ts b/cypress/integration/14-timeline/timeline.spec.ts index 6ba8ee3fbe5..22861c8fd70 100644 --- a/cypress/integration/14-timeline/timeline.spec.ts +++ b/cypress/integration/14-timeline/timeline.spec.ts @@ -102,6 +102,8 @@ describe("Timeline", () => { sendEvent(roomId); cy.setDisplayName("Alan (away)"); cy.setAvatarUrl(newAvatarUrl); + // XXX: If we send the second event too quickly, there won't be + // enough time for the client to register the profile change cy.wait(500); sendEvent(roomId); cy.viewRoomByName(ROOM_NAME); @@ -125,6 +127,8 @@ describe("Timeline", () => { sendEvent(roomId); cy.setDisplayName(NEW_NAME); cy.setAvatarUrl(newAvatarUrl); + // XXX: If we send the second event too quickly, there won't be + // enough time for the client to register the profile change cy.wait(500); sendEvent(roomId); cy.viewRoomByName(ROOM_NAME); From 224f8502059e7dbd7cc8b55e69055ebd4a4448f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Thu, 30 Jun 2022 17:17:21 +0200 Subject: [PATCH 11/12] Add `SettingsStore` type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Šimon Brandner --- cypress/global.d.ts | 3 ++- cypress/support/settings.ts | 9 +++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cypress/global.d.ts b/cypress/global.d.ts index 99943e13be3..5c479424ba6 100644 --- a/cypress/global.d.ts +++ b/cypress/global.d.ts @@ -28,12 +28,13 @@ import type { } from "matrix-js-sdk/src/matrix"; import type { MatrixDispatcher } from "../src/dispatcher/dispatcher"; import type PerformanceMonitor from "../src/performance"; +import type SettingsStore from "../src/settings/SettingsStore"; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace namespace Cypress { interface ApplicationWindow { - mxSettingsStore: any; // XXX: Importing SettingsStore causes a bunch of type lint errors + mxSettingsStore: typeof SettingsStore; mxMatrixClientPeg: { matrixClient?: MatrixClient; }; diff --git a/cypress/support/settings.ts b/cypress/support/settings.ts index 4387a3c330d..a44f3f06d25 100644 --- a/cypress/support/settings.ts +++ b/cypress/support/settings.ts @@ -18,6 +18,7 @@ limitations under the License. import Chainable = Cypress.Chainable; import type { SettingLevel } from "../../src/settings/SettingLevel"; +import type SettingsStore from "../../src/settings/SettingsStore"; declare global { // eslint-disable-next-line @typescript-eslint/no-namespace @@ -26,7 +27,7 @@ declare global { /** * Returns the SettingsStore */ - getSettingsStore(): Chainable; // XXX: Importing SettingsStore causes a bunch of type lint errors + getSettingsStore(): Chainable; // XXX: Importing SettingsStore causes a bunch of type lint errors /** * Open the top left user menu, returning a handle to the resulting context menu. */ @@ -100,7 +101,7 @@ declare global { } } -Cypress.Commands.add("getSettingsStore", (): Chainable => { +Cypress.Commands.add("getSettingsStore", (): Chainable => { return cy.window({ log: false }).then(win => win.mxSettingsStore); }); @@ -110,13 +111,13 @@ Cypress.Commands.add("setSettingValue", ( level: SettingLevel, value: any, ): Chainable => { - return cy.getSettingsStore().then(async (store: any) => { + return cy.getSettingsStore().then(async (store: typeof SettingsStore) => { return store.setValue(name, roomId, level, value); }); }); Cypress.Commands.add("getSettingValue", (name: string, roomId?: string): Chainable => { - return cy.getSettingsStore().then((store: any) => { + return cy.getSettingsStore().then((store: typeof SettingsStore) => { return store.getValue(name, roomId); }); }); From fbe8c521ce8467f13b0f865d490abd9a9b6db93e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=A0imon=20Brandner?= Date: Mon, 4 Jul 2022 21:19:39 +0200 Subject: [PATCH 12/12] Use `LEVELS_ACCOUNT_SETTINGS` Co-authored-by: Travis Ralston --- src/settings/Settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx index 9e3f223db42..d783c19db32 100644 --- a/src/settings/Settings.tsx +++ b/src/settings/Settings.tsx @@ -348,7 +348,7 @@ export const SETTINGS: {[setting: string]: ISetting} = { default: false, }, "useOnlyCurrentProfiles": { - supportedLevels: [SettingLevel.ACCOUNT], + supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td("Show current avatar and name for users in message history"), default: false, },