Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit 4713d0a

Browse files
authored
Merge branch 'develop' into justjanne/feat/use-case-selection
2 parents 85dd30a + 2706f14 commit 4713d0a

File tree

106 files changed

+2733
-1050
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+2733
-1050
lines changed

CHANGELOG.md

Lines changed: 105 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,6 @@ Now the yarn commands should work as normal.
211211
### End-to-End tests
212212

213213
Make sure you've got your Element development server running (by doing `yarn
214-
start` in element-web), and then in this project, run `yarn run e2etests`. See
215-
[`test/end-to-end-tests/README.md`](https://github.com/matrix-org/matrix-react-sdk/blob/develop/test/end-to-end-tests/README.md)
214+
start` in element-web), and then in this project, run `yarn run test:cypress`. See
215+
[`docs/cypress.md`](https://github.com/matrix-org/matrix-react-sdk/blob/develop/docs/cypress.md)
216216
for more information.

cypress.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
"runMode": 2,
99
"openMode": 0
1010
},
11+
"defaultCommandTimeout": 10000,
1112
"chromeWebSecurity": false
1213
}

cypress/global.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17+
import "../src/@types/global";
18+
import "../src/@types/svg";
19+
import "../src/@types/raw-loader";
1720
import "matrix-js-sdk/src/@types/global";
1821
import type {
1922
MatrixClient,
@@ -28,11 +31,13 @@ import type {
2831
} from "matrix-js-sdk/src/matrix";
2932
import type { MatrixDispatcher } from "../src/dispatcher/dispatcher";
3033
import type PerformanceMonitor from "../src/performance";
34+
import type SettingsStore from "../src/settings/SettingsStore";
3135

3236
declare global {
3337
// eslint-disable-next-line @typescript-eslint/no-namespace
3438
namespace Cypress {
3539
interface ApplicationWindow {
40+
mxSettingsStore: typeof SettingsStore;
3641
mxMatrixClientPeg: {
3742
matrixClient?: MatrixClient;
3843
};
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
/// <reference types="cypress" />
18+
19+
import { MessageEvent } from "matrix-events-sdk";
20+
21+
import type { ISendEventResponse } from "matrix-js-sdk/src/@types/requests";
22+
import type { EventType } from "matrix-js-sdk/src/@types/event";
23+
import type { MatrixClient } from "matrix-js-sdk/src/client";
24+
import { SynapseInstance } from "../../plugins/synapsedocker";
25+
import { SettingLevel } from "../../../src/settings/SettingLevel";
26+
import Chainable = Cypress.Chainable;
27+
28+
// The avatar size used in the timeline
29+
const AVATAR_SIZE = 30;
30+
// The resize method used in the timeline
31+
const AVATAR_RESIZE_METHOD = "crop";
32+
33+
const ROOM_NAME = "Test room";
34+
const OLD_AVATAR = "avatar_image1";
35+
const NEW_AVATAR = "avatar_image2";
36+
const OLD_NAME = "Alan";
37+
const NEW_NAME = "Alan (away)";
38+
39+
const getEventTilesWithBodies = (): Chainable<JQuery> => {
40+
return cy.get(".mx_EventTile").filter((_i, e) => e.getElementsByClassName("mx_EventTile_body").length > 0);
41+
};
42+
43+
const expectDisplayName = (e: JQuery<HTMLElement>, displayName: string): void => {
44+
expect(e.find(".mx_DisambiguatedProfile_displayName").text()).to.equal(displayName);
45+
};
46+
47+
const expectAvatar = (e: JQuery<HTMLElement>, avatarUrl: string): void => {
48+
cy.getClient().then((cli: MatrixClient) => {
49+
expect(e.find(".mx_BaseAvatar_image").attr("src")).to.equal(
50+
// eslint-disable-next-line no-restricted-properties
51+
cli.mxcUrlToHttp(avatarUrl, AVATAR_SIZE, AVATAR_SIZE, AVATAR_RESIZE_METHOD),
52+
);
53+
});
54+
};
55+
56+
const sendEvent = (roomId: string): Chainable<ISendEventResponse> => {
57+
return cy.sendEvent(
58+
roomId,
59+
null,
60+
"m.room.message" as EventType,
61+
MessageEvent.from("Message").serialize().content,
62+
);
63+
};
64+
65+
describe("Timeline", () => {
66+
let synapse: SynapseInstance;
67+
68+
let roomId: string;
69+
70+
let oldAvatarUrl: string;
71+
let newAvatarUrl: string;
72+
73+
describe("useOnlyCurrentProfiles", () => {
74+
beforeEach(() => {
75+
cy.startSynapse("default").then(data => {
76+
synapse = data;
77+
cy.initTestUser(synapse, OLD_NAME).then(() =>
78+
cy.window({ log: false }).then(() => {
79+
cy.createRoom({ name: ROOM_NAME }).then(_room1Id => {
80+
roomId = _room1Id;
81+
});
82+
}),
83+
).then(() => {
84+
cy.uploadContent(OLD_AVATAR).then((url) => {
85+
oldAvatarUrl = url;
86+
cy.setAvatarUrl(url);
87+
});
88+
}).then(() => {
89+
cy.uploadContent(NEW_AVATAR).then((url) => {
90+
newAvatarUrl = url;
91+
});
92+
});
93+
});
94+
});
95+
96+
afterEach(() => {
97+
cy.stopSynapse(synapse);
98+
});
99+
100+
it("should show historical profiles if disabled", () => {
101+
cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, false);
102+
sendEvent(roomId);
103+
cy.setDisplayName("Alan (away)");
104+
cy.setAvatarUrl(newAvatarUrl);
105+
// XXX: If we send the second event too quickly, there won't be
106+
// enough time for the client to register the profile change
107+
cy.wait(500);
108+
sendEvent(roomId);
109+
cy.viewRoomByName(ROOM_NAME);
110+
111+
const events = getEventTilesWithBodies();
112+
113+
events.should("have.length", 2);
114+
events.each((e, i) => {
115+
if (i === 0) {
116+
expectDisplayName(e, OLD_NAME);
117+
expectAvatar(e, oldAvatarUrl);
118+
} else if (i === 1) {
119+
expectDisplayName(e, NEW_NAME);
120+
expectAvatar(e, newAvatarUrl);
121+
}
122+
});
123+
});
124+
125+
it("should not show historical profiles if enabled", () => {
126+
cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, true);
127+
sendEvent(roomId);
128+
cy.setDisplayName(NEW_NAME);
129+
cy.setAvatarUrl(newAvatarUrl);
130+
// XXX: If we send the second event too quickly, there won't be
131+
// enough time for the client to register the profile change
132+
cy.wait(500);
133+
sendEvent(roomId);
134+
cy.viewRoomByName(ROOM_NAME);
135+
136+
const events = getEventTilesWithBodies();
137+
138+
events.should("have.length", 2);
139+
events.each((e) => {
140+
expectDisplayName(e, NEW_NAME);
141+
expectAvatar(e, newAvatarUrl);
142+
});
143+
});
144+
});
145+
});

cypress/support/client.ts

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,12 @@ limitations under the License.
1616

1717
/// <reference types="cypress" />
1818

19-
import type { ICreateRoomOpts } from "matrix-js-sdk/src/@types/requests";
19+
import type { FileType, UploadContentResponseType } from "matrix-js-sdk/src/http-api";
20+
import type { IAbortablePromise } from "matrix-js-sdk/src/@types/partials";
21+
import type { ICreateRoomOpts, ISendEventResponse, IUploadOpts } from "matrix-js-sdk/src/@types/requests";
2022
import type { MatrixClient } from "matrix-js-sdk/src/client";
2123
import type { Room } from "matrix-js-sdk/src/models/room";
24+
import type { IContent } from "matrix-js-sdk/src/models/event";
2225
import Chainable = Cypress.Chainable;
2326

2427
declare global {
@@ -53,6 +56,64 @@ declare global {
5356
* @param data The data to store.
5457
*/
5558
setAccountData(type: string, data: object): Chainable<{}>;
59+
/**
60+
* @param {string} roomId
61+
* @param {string} threadId
62+
* @param {string} eventType
63+
* @param {Object} content
64+
* @return {module:http-api.MatrixError} Rejects: with an error response.
65+
*/
66+
sendEvent(
67+
roomId: string,
68+
threadId: string | null,
69+
eventType: string,
70+
content: IContent
71+
): Chainable<ISendEventResponse>;
72+
/**
73+
* @param {string} name
74+
* @param {module:client.callback} callback Optional.
75+
* @return {Promise} Resolves: {} an empty object.
76+
* @return {module:http-api.MatrixError} Rejects: with an error response.
77+
*/
78+
setDisplayName(name: string): Chainable<{}>;
79+
/**
80+
* @param {string} url
81+
* @param {module:client.callback} callback Optional.
82+
* @return {Promise} Resolves: {} an empty object.
83+
* @return {module:http-api.MatrixError} Rejects: with an error response.
84+
*/
85+
setAvatarUrl(url: string): Chainable<{}>;
86+
/**
87+
* Upload a file to the media repository on the homeserver.
88+
*
89+
* @param {object} file The object to upload. On a browser, something that
90+
* can be sent to XMLHttpRequest.send (typically a File). Under node.js,
91+
* a a Buffer, String or ReadStream.
92+
*/
93+
uploadContent<O extends IUploadOpts>(
94+
file: FileType,
95+
opts?: O,
96+
): IAbortablePromise<UploadContentResponseType<O>>;
97+
/**
98+
* Turn an MXC URL into an HTTP one. <strong>This method is experimental and
99+
* may change.</strong>
100+
* @param {string} mxcUrl The MXC URL
101+
* @param {Number} width The desired width of the thumbnail.
102+
* @param {Number} height The desired height of the thumbnail.
103+
* @param {string} resizeMethod The thumbnail resize method to use, either
104+
* "crop" or "scale".
105+
* @param {Boolean} allowDirectLinks If true, return any non-mxc URLs
106+
* directly. Fetching such URLs will leak information about the user to
107+
* anyone they share a room with. If false, will return null for such URLs.
108+
* @return {?string} the avatar URL or null.
109+
*/
110+
mxcUrlToHttp(
111+
mxcUrl: string,
112+
width?: number,
113+
height?: number,
114+
resizeMethod?: string,
115+
allowDirectLinks?: boolean,
116+
): string | null;
56117
/**
57118
* Gets the list of DMs with a given user
58119
* @param userId The ID of the user
@@ -120,6 +181,35 @@ Cypress.Commands.add("setAccountData", (type: string, data: object): Chainable<{
120181
});
121182
});
122183

184+
Cypress.Commands.add("sendEvent", (
185+
roomId: string,
186+
threadId: string | null,
187+
eventType: string,
188+
content: IContent,
189+
): Chainable<ISendEventResponse> => {
190+
return cy.getClient().then(async (cli: MatrixClient) => {
191+
return cli.sendEvent(roomId, threadId, eventType, content);
192+
});
193+
});
194+
195+
Cypress.Commands.add("setDisplayName", (name: string): Chainable<{}> => {
196+
return cy.getClient().then(async (cli: MatrixClient) => {
197+
return cli.setDisplayName(name);
198+
});
199+
});
200+
201+
Cypress.Commands.add("uploadContent", (file: FileType): Chainable<{}> => {
202+
return cy.getClient().then(async (cli: MatrixClient) => {
203+
return cli.uploadContent(file);
204+
});
205+
});
206+
207+
Cypress.Commands.add("setAvatarUrl", (url: string): Chainable<{}> => {
208+
return cy.getClient().then(async (cli: MatrixClient) => {
209+
return cli.setAvatarUrl(url);
210+
});
211+
});
212+
123213
Cypress.Commands.add("bootstrapCrossSigning", () => {
124214
cy.window({ log: false }).then(win => {
125215
win.mxMatrixClientPeg.matrixClient.bootstrapCrossSigning({

cypress/support/login.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: str
8282
win.localStorage.setItem("mx_is_guest", "false");
8383
win.localStorage.setItem("mx_has_pickle_key", "false");
8484
win.localStorage.setItem("mx_has_access_token", "true");
85+
86+
// Ensure the language is set to a consistent value
87+
win.localStorage.setItem("mx_local_settings", '{"language":"en"}');
8588
});
8689

8790
return cy.visit("/").then(() => {

cypress/support/settings.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,17 @@ limitations under the License.
1717
/// <reference types="cypress" />
1818

1919
import Chainable = Cypress.Chainable;
20+
import type { SettingLevel } from "../../src/settings/SettingLevel";
21+
import type SettingsStore from "../../src/settings/SettingsStore";
2022

2123
declare global {
2224
// eslint-disable-next-line @typescript-eslint/no-namespace
2325
namespace Cypress {
2426
interface Chainable {
27+
/**
28+
* Returns the SettingsStore
29+
*/
30+
getSettingsStore(): Chainable<typeof SettingsStore | undefined>; // XXX: Importing SettingsStore causes a bunch of type lint errors
2531
/**
2632
* Open the top left user menu, returning a handle to the resulting context menu.
2733
*/
@@ -63,10 +69,59 @@ declare global {
6369
* @param name the name of the beta to leave.
6470
*/
6571
leaveBeta(name: string): Chainable<JQuery<HTMLElement>>;
72+
73+
/**
74+
* Sets the value for a setting. The room ID is optional if the
75+
* setting is not being set for a particular room, otherwise it
76+
* should be supplied. The value may be null to indicate that the
77+
* level should no longer have an override.
78+
* @param {string} settingName The name of the setting to change.
79+
* @param {String} roomId The room ID to change the value in, may be
80+
* null.
81+
* @param {SettingLevel} level The level to change the value at.
82+
* @param {*} value The new value of the setting, may be null.
83+
* @return {Promise} Resolves when the setting has been changed.
84+
*/
85+
setSettingValue(name: string, roomId: string, level: SettingLevel, value: any): Chainable<void>;
86+
87+
/**
88+
* Gets the value of a setting. The room ID is optional if the
89+
* setting is not to be applied to any particular room, otherwise it
90+
* should be supplied.
91+
* @param {string} settingName The name of the setting to read the
92+
* value of.
93+
* @param {String} roomId The room ID to read the setting value in,
94+
* may be null.
95+
* @param {boolean} excludeDefault True to disable using the default
96+
* value.
97+
* @return {*} The value, or null if not found
98+
*/
99+
getSettingValue<T>(name: string, roomId?: string): Chainable<T>;
66100
}
67101
}
68102
}
69103

104+
Cypress.Commands.add("getSettingsStore", (): Chainable<typeof SettingsStore> => {
105+
return cy.window({ log: false }).then(win => win.mxSettingsStore);
106+
});
107+
108+
Cypress.Commands.add("setSettingValue", (
109+
name: string,
110+
roomId: string,
111+
level: SettingLevel,
112+
value: any,
113+
): Chainable<void> => {
114+
return cy.getSettingsStore().then(async (store: typeof SettingsStore) => {
115+
return store.setValue(name, roomId, level, value);
116+
});
117+
});
118+
119+
Cypress.Commands.add("getSettingValue", <T = any>(name: string, roomId?: string): Chainable<T> => {
120+
return cy.getSettingsStore().then((store: typeof SettingsStore) => {
121+
return store.getValue(name, roomId);
122+
});
123+
});
124+
70125
Cypress.Commands.add("openUserMenu", (): Chainable<JQuery<HTMLElement>> => {
71126
cy.get('[aria-label="User menu"]').click();
72127
return cy.get(".mx_ContextualMenu");

0 commit comments

Comments
 (0)