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

Commit

Permalink
Merge branch 'develop' into justjanne/feat/use-case-selection
Browse files Browse the repository at this point in the history
  • Loading branch information
justjanne authored Jul 6, 2022
2 parents 85dd30a + 2706f14 commit 4713d0a
Show file tree
Hide file tree
Showing 106 changed files with 2,733 additions and 1,050 deletions.
105 changes: 105 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,6 @@ Now the yarn commands should work as normal.
### End-to-End tests

Make sure you've got your Element development server running (by doing `yarn
start` in element-web), and then in this project, run `yarn run e2etests`. See
[`test/end-to-end-tests/README.md`](https://github.com/matrix-org/matrix-react-sdk/blob/develop/test/end-to-end-tests/README.md)
start` in element-web), and then in this project, run `yarn run test:cypress`. See
[`docs/cypress.md`](https://github.com/matrix-org/matrix-react-sdk/blob/develop/docs/cypress.md)
for more information.
1 change: 1 addition & 0 deletions cypress.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
"runMode": 2,
"openMode": 0
},
"defaultCommandTimeout": 10000,
"chromeWebSecurity": false
}
5 changes: 5 additions & 0 deletions cypress/global.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

import "../src/@types/global";
import "../src/@types/svg";
import "../src/@types/raw-loader";
import "matrix-js-sdk/src/@types/global";
import type {
MatrixClient,
Expand All @@ -28,11 +31,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;
};
Expand Down
145 changes: 145 additions & 0 deletions cypress/integration/14-timeline/timeline.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
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.
*/

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

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 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<JQuery> => {
return cy.get(".mx_EventTile").filter((_i, e) => e.getElementsByClassName("mx_EventTile_body").length > 0);
};

const expectDisplayName = (e: JQuery<HTMLElement>, displayName: string): void => {
expect(e.find(".mx_DisambiguatedProfile_displayName").text()).to.equal(displayName);
};

const expectAvatar = (e: JQuery<HTMLElement>, 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<ISendEventResponse> => {
return cy.sendEvent(
roomId,
null,
"m.room.message" as EventType,
MessageEvent.from("Message").serialize().content,
);
};

describe("Timeline", () => {
let synapse: SynapseInstance;

let roomId: string;

let oldAvatarUrl: string;
let newAvatarUrl: string;

describe("useOnlyCurrentProfiles", () => {
beforeEach(() => {
cy.startSynapse("default").then(data => {
synapse = data;
cy.initTestUser(synapse, OLD_NAME).then(() =>
cy.window({ log: false }).then(() => {
cy.createRoom({ name: ROOM_NAME }).then(_room1Id => {
roomId = _room1Id;
});
}),
).then(() => {
cy.uploadContent(OLD_AVATAR).then((url) => {
oldAvatarUrl = url;
cy.setAvatarUrl(url);
});
}).then(() => {
cy.uploadContent(NEW_AVATAR).then((url) => {
newAvatarUrl = url;
});
});
});
});

afterEach(() => {
cy.stopSynapse(synapse);
});

it("should show historical profiles if disabled", () => {
cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, false);
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);

const events = getEventTilesWithBodies();

events.should("have.length", 2);
events.each((e, i) => {
if (i === 0) {
expectDisplayName(e, OLD_NAME);
expectAvatar(e, oldAvatarUrl);
} else if (i === 1) {
expectDisplayName(e, NEW_NAME);
expectAvatar(e, newAvatarUrl);
}
});
});

it("should not show historical profiles if enabled", () => {
cy.setSettingValue("useOnlyCurrentProfiles", null, SettingLevel.ACCOUNT, true);
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);

const events = getEventTilesWithBodies();

events.should("have.length", 2);
events.each((e) => {
expectDisplayName(e, NEW_NAME);
expectAvatar(e, newAvatarUrl);
});
});
});
});
92 changes: 91 additions & 1 deletion cypress/support/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ limitations under the License.

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

import type { ICreateRoomOpts } 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";
import Chainable = Cypress.Chainable;

declare global {
Expand Down Expand Up @@ -53,6 +56,64 @@ 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<ISendEventResponse>;
/**
* @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<{}>;
/**
* @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<O extends IUploadOpts>(
file: FileType,
opts?: O,
): IAbortablePromise<UploadContentResponseType<O>>;
/**
* Turn an MXC URL into an HTTP one. <strong>This method is experimental and
* may change.</strong>
* @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;
/**
* Gets the list of DMs with a given user
* @param userId The ID of the user
Expand Down Expand Up @@ -120,6 +181,35 @@ Cypress.Commands.add("setAccountData", (type: string, data: object): Chainable<{
});
});

Cypress.Commands.add("sendEvent", (
roomId: string,
threadId: string | null,
eventType: string,
content: IContent,
): Chainable<ISendEventResponse> => {
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);
});
});

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);
});
});

Cypress.Commands.add("bootstrapCrossSigning", () => {
cy.window({ log: false }).then(win => {
win.mxMatrixClientPeg.matrixClient.bootstrapCrossSigning({
Expand Down
3 changes: 3 additions & 0 deletions cypress/support/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ Cypress.Commands.add("initTestUser", (synapse: SynapseInstance, displayName: str
win.localStorage.setItem("mx_is_guest", "false");
win.localStorage.setItem("mx_has_pickle_key", "false");
win.localStorage.setItem("mx_has_access_token", "true");

// Ensure the language is set to a consistent value
win.localStorage.setItem("mx_local_settings", '{"language":"en"}');
});

return cy.visit("/").then(() => {
Expand Down
55 changes: 55 additions & 0 deletions cypress/support/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@ limitations under the License.
/// <reference types="cypress" />

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
namespace Cypress {
interface Chainable {
/**
* Returns the SettingsStore
*/
getSettingsStore(): Chainable<typeof SettingsStore | undefined>; // XXX: Importing SettingsStore causes a bunch of type lint errors
/**
* Open the top left user menu, returning a handle to the resulting context menu.
*/
Expand Down Expand Up @@ -63,10 +69,59 @@ declare global {
* @param name the name of the beta to leave.
*/
leaveBeta(name: string): Chainable<JQuery<HTMLElement>>;

/**
* 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<void>;

/**
* 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<T>(name: string, roomId?: string): Chainable<T>;
}
}
}

Cypress.Commands.add("getSettingsStore", (): Chainable<typeof SettingsStore> => {
return cy.window({ log: false }).then(win => win.mxSettingsStore);
});

Cypress.Commands.add("setSettingValue", (
name: string,
roomId: string,
level: SettingLevel,
value: any,
): Chainable<void> => {
return cy.getSettingsStore().then(async (store: typeof SettingsStore) => {
return store.setValue(name, roomId, level, value);
});
});

Cypress.Commands.add("getSettingValue", <T = any>(name: string, roomId?: string): Chainable<T> => {
return cy.getSettingsStore().then((store: typeof SettingsStore) => {
return store.getValue(name, roomId);
});
});

Cypress.Commands.add("openUserMenu", (): Chainable<JQuery<HTMLElement>> => {
cy.get('[aria-label="User menu"]').click();
return cy.get(".mx_ContextualMenu");
Expand Down
Loading

0 comments on commit 4713d0a

Please sign in to comment.