1
0
mirror of https://github.com/matrix-org/matrix-react-sdk.git synced 2026-01-03 21:42:32 +03:00

Move from browser-request to fetch (#9345)

This commit is contained in:
Michael Telatynski
2022-10-12 18:59:07 +01:00
committed by GitHub
parent ae883bb94b
commit 8b54be6f48
50 changed files with 1474 additions and 607 deletions

View File

@@ -15,15 +15,31 @@ limitations under the License.
*/
import { mocked } from "jest-mock";
import { IImageInfo, ISendEventResponse, MatrixClient } from "matrix-js-sdk/src/matrix";
import { IImageInfo, ISendEventResponse, MatrixClient, RelationType, UploadResponse } from "matrix-js-sdk/src/matrix";
import { defer } from "matrix-js-sdk/src/utils";
import encrypt, { IEncryptedFile } from "matrix-encrypt-attachment";
import ContentMessages from "../src/ContentMessages";
import ContentMessages, { UploadCanceledError, uploadFile } from "../src/ContentMessages";
import { doMaybeLocalRoomAction } from "../src/utils/local-room";
import { createTestClient } from "./test-utils";
import { BlurhashEncoder } from "../src/BlurhashEncoder";
jest.mock("matrix-encrypt-attachment", () => ({ encryptAttachment: jest.fn().mockResolvedValue({}) }));
jest.mock("../src/BlurhashEncoder", () => ({
BlurhashEncoder: {
instance: {
getBlurhash: jest.fn(),
},
},
}));
jest.mock("../src/utils/local-room", () => ({
doMaybeLocalRoomAction: jest.fn(),
}));
const createElement = document.createElement.bind(document);
describe("ContentMessages", () => {
const stickerUrl = "https://example.com/sticker";
const roomId = "!room:example.com";
@@ -36,6 +52,9 @@ describe("ContentMessages", () => {
beforeEach(() => {
client = {
sendStickerMessage: jest.fn(),
sendMessage: jest.fn(),
isRoomEncrypted: jest.fn().mockReturnValue(false),
uploadContent: jest.fn().mockResolvedValue({ content_uri: "mxc://server/file" }),
} as unknown as MatrixClient;
contentMessages = new ContentMessages();
prom = Promise.resolve(null);
@@ -65,4 +84,226 @@ describe("ContentMessages", () => {
expect(client.sendStickerMessage).toHaveBeenCalledWith(roomId, null, stickerUrl, imageInfo, text);
});
});
describe("sendContentToRoom", () => {
const roomId = "!roomId:server";
beforeEach(() => {
Object.defineProperty(global.Image.prototype, 'src', {
// Define the property setter
set(src) {
setTimeout(() => this.onload());
},
});
Object.defineProperty(global.Image.prototype, 'height', {
get() { return 600; },
});
Object.defineProperty(global.Image.prototype, 'width', {
get() { return 800; },
});
mocked(doMaybeLocalRoomAction).mockImplementation((
roomId: string,
fn: (actualRoomId: string) => Promise<ISendEventResponse>,
) => fn(roomId));
mocked(BlurhashEncoder.instance.getBlurhash).mockResolvedValue(undefined);
});
it("should use m.image for image files", async () => {
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
const file = new File([], "fileName", { type: "image/jpeg" });
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.image",
}));
});
it("should fall back to m.file for invalid image files", async () => {
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
const file = new File([], "fileName", { type: "image/png" });
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.file",
}));
});
it("should use m.video for video files", async () => {
jest.spyOn(document, "createElement").mockImplementation(tagName => {
const element = createElement(tagName);
if (tagName === "video") {
element.load = jest.fn();
element.play = () => element.onloadeddata(new Event("loadeddata"));
element.pause = jest.fn();
Object.defineProperty(element, 'videoHeight', {
get() { return 600; },
});
Object.defineProperty(element, 'videoWidth', {
get() { return 800; },
});
}
return element;
});
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
const file = new File([], "fileName", { type: "video/mp4" });
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.video",
}));
});
it("should use m.audio for audio files", async () => {
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
const file = new File([], "fileName", { type: "audio/mp3" });
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.audio",
}));
});
it("should default to name 'Attachment' if file doesn't have a name", async () => {
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
const file = new File([], "", { type: "text/plain" });
await contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
expect(client.sendMessage).toHaveBeenCalledWith(roomId, null, expect.objectContaining({
url: "mxc://server/file",
msgtype: "m.file",
body: "Attachment",
}));
});
it("should keep RoomUpload's total and loaded values up to date", async () => {
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
const file = new File([], "", { type: "text/plain" });
const prom = contentMessages.sendContentToRoom(file, roomId, undefined, client, undefined);
const [upload] = contentMessages.getCurrentUploads();
expect(upload.loaded).toBe(0);
expect(upload.total).toBe(file.size);
const { progressHandler } = mocked(client.uploadContent).mock.calls[0][1];
progressHandler({ loaded: 123, total: 1234 });
expect(upload.loaded).toBe(123);
expect(upload.total).toBe(1234);
await prom;
});
});
describe("getCurrentUploads", () => {
const file1 = new File([], "file1");
const file2 = new File([], "file2");
const roomId = "!roomId:server";
beforeEach(() => {
mocked(doMaybeLocalRoomAction).mockImplementation((
roomId: string,
fn: (actualRoomId: string) => Promise<ISendEventResponse>,
) => fn(roomId));
});
it("should return only uploads for the given relation", async () => {
const relation = {
rel_type: RelationType.Thread,
event_id: "!threadId:server",
};
const p1 = contentMessages.sendContentToRoom(file1, roomId, relation, client, undefined);
const p2 = contentMessages.sendContentToRoom(file2, roomId, undefined, client, undefined);
const uploads = contentMessages.getCurrentUploads(relation);
expect(uploads).toHaveLength(1);
expect(uploads[0].relation).toEqual(relation);
expect(uploads[0].fileName).toEqual("file1");
await Promise.all([p1, p2]);
});
it("should return only uploads for no relation when not passed one", async () => {
const relation = {
rel_type: RelationType.Thread,
event_id: "!threadId:server",
};
const p1 = contentMessages.sendContentToRoom(file1, roomId, relation, client, undefined);
const p2 = contentMessages.sendContentToRoom(file2, roomId, undefined, client, undefined);
const uploads = contentMessages.getCurrentUploads();
expect(uploads).toHaveLength(1);
expect(uploads[0].relation).toEqual(undefined);
expect(uploads[0].fileName).toEqual("file2");
await Promise.all([p1, p2]);
});
});
describe("cancelUpload", () => {
it("should cancel in-flight upload", async () => {
const deferred = defer<UploadResponse>();
mocked(client.uploadContent).mockReturnValue(deferred.promise);
const file1 = new File([], "file1");
const prom = contentMessages.sendContentToRoom(file1, roomId, undefined, client, undefined);
const { abortController } = mocked(client.uploadContent).mock.calls[0][1];
expect(abortController.signal.aborted).toBeFalsy();
const [upload] = contentMessages.getCurrentUploads();
contentMessages.cancelUpload(upload);
expect(abortController.signal.aborted).toBeTruthy();
deferred.resolve({} as UploadResponse);
await prom;
});
});
});
describe("uploadFile", () => {
beforeEach(() => {
jest.clearAllMocks();
});
const client = createTestClient();
it("should not encrypt the file if the room isn't encrypted", async () => {
mocked(client.isRoomEncrypted).mockReturnValue(false);
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
const progressHandler = jest.fn();
const file = new Blob([]);
const res = await uploadFile(client, "!roomId:server", file, progressHandler);
expect(res.url).toBe("mxc://server/file");
expect(res.file).toBeFalsy();
expect(encrypt.encryptAttachment).not.toHaveBeenCalled();
expect(client.uploadContent).toHaveBeenCalledWith(file, expect.objectContaining({ progressHandler }));
});
it("should encrypt the file if the room is encrypted", async () => {
mocked(client.isRoomEncrypted).mockReturnValue(true);
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
mocked(encrypt.encryptAttachment).mockResolvedValue({
data: new ArrayBuffer(123),
info: {} as IEncryptedFile,
});
const progressHandler = jest.fn();
const file = new Blob(["123"]);
const res = await uploadFile(client, "!roomId:server", file, progressHandler);
expect(res.url).toBeFalsy();
expect(res.file).toEqual(expect.objectContaining({
url: "mxc://server/file",
}));
expect(encrypt.encryptAttachment).toHaveBeenCalled();
expect(client.uploadContent).toHaveBeenCalledWith(expect.any(Blob), expect.objectContaining({
progressHandler,
includeFilename: false,
}));
expect(mocked(client.uploadContent).mock.calls[0][0]).not.toBe(file);
});
it("should throw UploadCanceledError upon aborting the upload", async () => {
mocked(client.isRoomEncrypted).mockReturnValue(false);
const deferred = defer<UploadResponse>();
mocked(client.uploadContent).mockReturnValue(deferred.promise);
const file = new Blob([]);
const prom = uploadFile(client, "!roomId:server", file);
mocked(client.uploadContent).mock.calls[0][1].abortController.abort();
deferred.resolve({ content_uri: "mxc://foo/bar" });
await expect(prom).rejects.toThrowError(UploadCanceledError);
});
});

View File

@@ -14,47 +14,199 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { mocked } from "jest-mock";
import fetchMock from "fetch-mock-jest";
import ScalarAuthClient from '../src/ScalarAuthClient';
import { MatrixClientPeg } from '../src/MatrixClientPeg';
import { stubClient } from './test-utils';
import SdkConfig from "../src/SdkConfig";
import { WidgetType } from "../src/widgets/WidgetType";
describe('ScalarAuthClient', function() {
const apiUrl = 'test.com/api';
const uiUrl = 'test.com/app';
const apiUrl = 'https://test.com/api';
const uiUrl = 'https:/test.com/app';
const tokenObject = {
access_token: "token",
token_type: "Bearer",
matrix_server_name: "localhost",
expires_in: 999,
};
let client;
beforeEach(function() {
window.localStorage.getItem = jest.fn((arg) => {
if (arg === "mx_scalar_token") return "brokentoken";
});
stubClient();
jest.clearAllMocks();
client = stubClient();
});
it('should request a new token if the old one fails', async function() {
const sac = new ScalarAuthClient(apiUrl, uiUrl);
const sac = new ScalarAuthClient(apiUrl + 0, uiUrl);
// @ts-ignore unhappy with Promise calls
jest.spyOn(sac, 'getAccountName').mockImplementation((arg: string) => {
switch (arg) {
case "brokentoken":
return Promise.reject({
message: "Invalid token",
});
case "wokentoken":
default:
return Promise.resolve(MatrixClientPeg.get().getUserId());
}
fetchMock.get("https://test.com/api0/account?scalar_token=brokentoken&v=1.1", {
body: { message: "Invalid token" },
});
MatrixClientPeg.get().getOpenIdToken = jest.fn().mockResolvedValue('this is your openid token');
fetchMock.get("https://test.com/api0/account?scalar_token=wokentoken&v=1.1", {
body: { user_id: client.getUserId() },
});
client.getOpenIdToken = jest.fn().mockResolvedValue(tokenObject);
sac.exchangeForScalarToken = jest.fn((arg) => {
if (arg === "this is your openid token") return Promise.resolve("wokentoken");
if (arg === tokenObject) return Promise.resolve("wokentoken");
});
await sac.connect();
expect(sac.exchangeForScalarToken).toBeCalledWith('this is your openid token');
expect(sac.exchangeForScalarToken).toBeCalledWith(tokenObject);
expect(sac.hasCredentials).toBeTruthy();
// @ts-ignore private property
expect(sac.scalarToken).toEqual('wokentoken');
});
describe("exchangeForScalarToken", () => {
it("should return `scalar_token` from API /register", async () => {
const sac = new ScalarAuthClient(apiUrl + 1, uiUrl);
fetchMock.postOnce("https://test.com/api1/register?v=1.1", {
body: { scalar_token: "stoken" },
});
await expect(sac.exchangeForScalarToken(tokenObject)).resolves.toBe("stoken");
});
it("should throw upon non-20x code", async () => {
const sac = new ScalarAuthClient(apiUrl + 2, uiUrl);
fetchMock.postOnce("https://test.com/api2/register?v=1.1", {
status: 500,
});
await expect(sac.exchangeForScalarToken(tokenObject)).rejects.toThrow("Scalar request failed: 500");
});
it("should throw if scalar_token is missing in response", async () => {
const sac = new ScalarAuthClient(apiUrl + 3, uiUrl);
fetchMock.postOnce("https://test.com/api3/register?v=1.1", {
body: {},
});
await expect(sac.exchangeForScalarToken(tokenObject)).rejects.toThrow("Missing scalar_token in response");
});
});
describe("registerForToken", () => {
it("should call `termsInteractionCallback` upon M_TERMS_NOT_SIGNED error", async () => {
const sac = new ScalarAuthClient(apiUrl + 4, uiUrl);
const termsInteractionCallback = jest.fn();
sac.setTermsInteractionCallback(termsInteractionCallback);
fetchMock.get("https://test.com/api4/account?scalar_token=testtoken1&v=1.1", {
body: { errcode: "M_TERMS_NOT_SIGNED" },
});
sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken1"));
mocked(client.getTerms).mockResolvedValue({ policies: [] });
await expect(sac.registerForToken()).resolves.toBe("testtoken1");
});
it("should throw upon non-20x code", async () => {
const sac = new ScalarAuthClient(apiUrl + 5, uiUrl);
fetchMock.get("https://test.com/api5/account?scalar_token=testtoken2&v=1.1", {
body: { errcode: "SERVER_IS_SAD" },
status: 500,
});
sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken2"));
await expect(sac.registerForToken()).rejects.toBeTruthy();
});
it("should throw if user_id is missing from response", async () => {
const sac = new ScalarAuthClient(apiUrl + 6, uiUrl);
fetchMock.get("https://test.com/api6/account?scalar_token=testtoken3&v=1.1", {
body: {},
});
sac.exchangeForScalarToken = jest.fn(() => Promise.resolve("testtoken3"));
await expect(sac.registerForToken()).rejects.toThrow("Missing user_id in response");
});
});
describe("getScalarPageTitle", () => {
let sac: ScalarAuthClient;
beforeEach(async () => {
SdkConfig.put({
integrations_rest_url: apiUrl + 7,
integrations_ui_url: uiUrl,
});
window.localStorage.setItem("mx_scalar_token_at_https://test.com/api7", "wokentoken1");
fetchMock.get("https://test.com/api7/account?scalar_token=wokentoken1&v=1.1", {
body: { user_id: client.getUserId() },
});
sac = new ScalarAuthClient(apiUrl + 7, uiUrl);
await sac.connect();
});
it("should return `cached_title` from API /widgets/title_lookup", async () => {
const url = "google.com";
fetchMock.get("https://test.com/api7/widgets/title_lookup?scalar_token=wokentoken1&curl=" + url, {
body: {
page_title_cache_item: {
cached_title: "Google",
},
},
});
await expect(sac.getScalarPageTitle(url)).resolves.toBe("Google");
});
it("should throw upon non-20x code", async () => {
const url = "yahoo.com";
fetchMock.get("https://test.com/api7/widgets/title_lookup?scalar_token=wokentoken1&curl=" + url, {
status: 500,
});
await expect(sac.getScalarPageTitle(url)).rejects.toThrow("Scalar request failed: 500");
});
});
describe("disableWidgetAssets", () => {
let sac: ScalarAuthClient;
beforeEach(async () => {
SdkConfig.put({
integrations_rest_url: apiUrl + 8,
integrations_ui_url: uiUrl,
});
window.localStorage.setItem("mx_scalar_token_at_https://test.com/api8", "wokentoken1");
fetchMock.get("https://test.com/api8/account?scalar_token=wokentoken1&v=1.1", {
body: { user_id: client.getUserId() },
});
sac = new ScalarAuthClient(apiUrl + 8, uiUrl);
await sac.connect();
});
it("should send state=disable to API /widgets/set_assets_state", async () => {
fetchMock.get("https://test.com/api8/widgets/set_assets_state?scalar_token=wokentoken1" +
"&widget_type=m.custom&widget_id=id1&state=disable", {
body: "OK",
});
await expect(sac.disableWidgetAssets(WidgetType.CUSTOM, "id1")).resolves.toBeUndefined();
});
it("should throw upon non-20x code", async () => {
fetchMock.get("https://test.com/api8/widgets/set_assets_state?scalar_token=wokentoken1" +
"&widget_type=m.custom&widget_id=id2&state=disable", {
status: 500,
});
await expect(sac.disableWidgetAssets(WidgetType.CUSTOM, "id2"))
.rejects.toThrow("Scalar request failed: 500");
});
});
});

View File

@@ -15,7 +15,7 @@ limitations under the License.
*/
import { mocked } from "jest-mock";
import { IAbortablePromise, IEncryptedFile, IUploadOpts, MatrixClient } from "matrix-js-sdk/src/matrix";
import { IEncryptedFile, UploadOpts, MatrixClient } from "matrix-js-sdk/src/matrix";
import { createVoiceMessageRecording, VoiceMessageRecording } from "../../src/audio/VoiceMessageRecording";
import { RecordingState, VoiceRecording } from "../../src/audio/VoiceRecording";
@@ -161,8 +161,8 @@ describe("VoiceMessageRecording", () => {
matrixClient: MatrixClient,
roomId: string,
file: File | Blob,
_progressHandler?: IUploadOpts["progressHandler"],
): IAbortablePromise<{ url?: string, file?: IEncryptedFile }> => {
_progressHandler?: UploadOpts["progressHandler"],
): Promise<{ url?: string, file?: IEncryptedFile }> => {
uploadFileClient = matrixClient;
uploadFileRoomId = roomId;
uploadBlob = file;

View File

@@ -0,0 +1,58 @@
/*
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 fetchMock from "fetch-mock-jest";
import { render, screen } from "@testing-library/react";
import { mocked } from "jest-mock";
import { _t } from "../../../../src/languageHandler";
import EmbeddedPage from "../../../../src/components/structures/EmbeddedPage";
jest.mock("../../../../src/languageHandler", () => ({
_t: jest.fn(),
}));
describe("<EmbeddedPage />", () => {
it("should translate _t strings", async () => {
mocked(_t).mockReturnValue("Przeglądaj pokoje");
fetchMock.get("https://home.page", {
body: '<h1>_t("Explore rooms")</h1>',
});
const { asFragment } = render(<EmbeddedPage url="https://home.page" />);
await screen.findByText("Przeglądaj pokoje");
expect(_t).toHaveBeenCalledWith("Explore rooms");
expect(asFragment()).toMatchSnapshot();
});
it("should show error if unable to load", async () => {
mocked(_t).mockReturnValue("Couldn't load page");
fetchMock.get("https://other.page", {
status: 404,
});
const { asFragment } = render(<EmbeddedPage url="https://other.page" />);
await screen.findByText("Couldn't load page");
expect(_t).toHaveBeenCalledWith("Couldn't load page");
expect(asFragment()).toMatchSnapshot();
});
it("should render nothing if no url given", () => {
const { asFragment } = render(<EmbeddedPage />);
expect(asFragment()).toMatchSnapshot();
});
});

View File

@@ -0,0 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<EmbeddedPage /> should render nothing if no url given 1`] = `
<DocumentFragment>
<div
class="undefined undefined_guest"
>
<div
class="undefined_body"
/>
</div>
</DocumentFragment>
`;
exports[`<EmbeddedPage /> should show error if unable to load 1`] = `
<DocumentFragment>
<div
class="undefined undefined_guest"
>
<div
class="undefined_body"
>
Couldn't load page
</div>
</div>
</DocumentFragment>
`;
exports[`<EmbeddedPage /> should translate _t strings 1`] = `
<DocumentFragment>
<div
class="undefined undefined_guest"
>
<div
class="undefined_body"
>
<h1>
Przeglądaj pokoje
</h1>
</div>
</div>
</DocumentFragment>
`;

View File

@@ -0,0 +1,104 @@
/*
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 fetchMock from "fetch-mock-jest";
import { render, screen, waitForElementToBeRemoved } from "@testing-library/react";
import ChangelogDialog from "../../../../src/components/views/dialogs/ChangelogDialog";
describe("<ChangelogDialog />", () => {
it("should fetch github proxy url for each repo with old and new version strings", async () => {
const webUrl = "https://riot.im/github/repos/vector-im/element-web/compare/oldsha1...newsha1";
fetchMock.get(webUrl, {
url: "https://api.github.com/repos/vector-im/element-web/compare/master...develop",
html_url: "https://github.com/vector-im/element-web/compare/master...develop",
permalink_url: "https://github.com/vector-im/element-web/compare/vector-im:72ca95e...vector-im:8891698",
diff_url: "https://github.com/vector-im/element-web/compare/master...develop.diff",
patch_url: "https://github.com/vector-im/element-web/compare/master...develop.patch",
base_commit: {},
merge_base_commit: {},
status: "ahead",
ahead_by: 24,
behind_by: 0,
total_commits: 24,
commits: [{
sha: "commit-sha",
html_url: "https://api.github.com/repos/vector-im/element-web/commit/commit-sha",
commit: { message: "This is the first commit message" },
}],
files: [],
});
const reactUrl = "https://riot.im/github/repos/matrix-org/matrix-react-sdk/compare/oldsha2...newsha2";
fetchMock.get(reactUrl, {
url: "https://api.github.com/repos/matrix-org/matrix-react-sdk/compare/master...develop",
html_url: "https://github.com/matrix-org/matrix-react-sdk/compare/master...develop",
permalink_url: "https://github.com/matrix-org/matrix-react-sdk/compare/matrix-org:cdb00...matrix-org:4a926",
diff_url: "https://github.com/matrix-org/matrix-react-sdk/compare/master...develop.diff",
patch_url: "https://github.com/matrix-org/matrix-react-sdk/compare/master...develop.patch",
base_commit: {},
merge_base_commit: {},
status: "ahead",
ahead_by: 83,
behind_by: 0,
total_commits: 83,
commits: [{
sha: "commit-sha0",
html_url: "https://api.github.com/repos/matrix-org/matrix-react-sdk/commit/commit-sha",
commit: { message: "This is a commit message" },
}],
files: [],
});
const jsUrl = "https://riot.im/github/repos/matrix-org/matrix-js-sdk/compare/oldsha3...newsha3";
fetchMock.get(jsUrl, {
url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/compare/master...develop",
html_url: "https://github.com/matrix-org/matrix-js-sdk/compare/master...develop",
permalink_url: "https://github.com/matrix-org/matrix-js-sdk/compare/matrix-org:6166a8f...matrix-org:fec350",
diff_url: "https://github.com/matrix-org/matrix-js-sdk/compare/master...develop.diff",
patch_url: "https://github.com/matrix-org/matrix-js-sdk/compare/master...develop.patch",
base_commit: {},
merge_base_commit: {},
status: "ahead",
ahead_by: 48,
behind_by: 0,
total_commits: 48,
commits: [{
sha: "commit-sha1",
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha1",
commit: { message: "This is a commit message" },
}, {
sha: "commit-sha2",
html_url: "https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha2",
commit: { message: "This is another commit message" },
}],
files: [],
});
const newVersion = "newsha1-react-newsha2-js-newsha3";
const oldVersion = "oldsha1-react-oldsha2-js-oldsha3";
const { asFragment } = render((
<ChangelogDialog newVersion={newVersion} version={oldVersion} onFinished={jest.fn()} />
));
// Wait for spinners to go away
await waitForElementToBeRemoved(screen.getAllByRole("progressbar"));
expect(fetchMock).toHaveFetched(webUrl);
expect(fetchMock).toHaveFetched(reactUrl);
expect(fetchMock).toHaveFetched(jsUrl);
expect(asFragment()).toMatchSnapshot();
});
});

View File

@@ -26,6 +26,11 @@ import SdkConfig from "../../../../src/SdkConfig";
import { ValidatedServerConfig } from "../../../../src/utils/ValidatedServerConfig";
import { IConfigOptions } from "../../../../src/IConfigOptions";
const mockGetAccessToken = jest.fn().mockResolvedValue("getAccessToken");
jest.mock("../../../../src/IdentityAuthClient", () => jest.fn().mockImplementation(() => ({
getAccessToken: mockGetAccessToken,
})));
describe("InviteDialog", () => {
const roomId = "!111111111111111111:example.org";
const aliceId = "@alice:example.org";
@@ -42,6 +47,14 @@ describe("InviteDialog", () => {
getProfileInfo: jest.fn().mockRejectedValue({ errcode: "" }),
getIdentityServerUrl: jest.fn(),
searchUserDirectory: jest.fn().mockResolvedValue({}),
lookupThreePid: jest.fn(),
registerWithIdentityServer: jest.fn().mockResolvedValue({
access_token: "access_token",
token: "token",
}),
getOpenIdToken: jest.fn().mockResolvedValue({}),
getIdentityAccount: jest.fn().mockResolvedValue({}),
getTerms: jest.fn().mockResolvedValue({ policies: [] }),
});
beforeEach(() => {
@@ -85,7 +98,7 @@ describe("InviteDialog", () => {
expect(screen.queryByText("Invite to Room")).toBeTruthy();
});
it("should suggest valid MXIDs even if unknown", () => {
it("should suggest valid MXIDs even if unknown", async () => {
render((
<InviteDialog
kind={KIND_INVITE}
@@ -95,7 +108,7 @@ describe("InviteDialog", () => {
/>
));
expect(screen.queryByText("@localpart:server.tld")).toBeFalsy();
await screen.findAllByText("@localpart:server.tld"); // Using findAllByText as the MXID is used for name too
});
it("should not suggest invalid MXIDs", () => {
@@ -110,4 +123,48 @@ describe("InviteDialog", () => {
expect(screen.queryByText("@localpart:server:tld")).toBeFalsy();
});
it("should lookup inputs which look like email addresses", async () => {
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
mockClient.lookupThreePid.mockResolvedValue({
address: "foobar@email.com",
medium: "email",
mxid: "@foobar:server",
});
mockClient.getProfileInfo.mockResolvedValue({
displayname: "Mr. Foo",
avatar_url: "mxc://foo/bar",
});
render((
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
initialText="foobar@email.com"
/>
));
await screen.findByText("Mr. Foo");
await screen.findByText("@foobar:server");
expect(mockClient.lookupThreePid).toHaveBeenCalledWith("email", "foobar@email.com", expect.anything());
expect(mockClient.getProfileInfo).toHaveBeenCalledWith("@foobar:server");
});
it("should suggest e-mail even if lookup fails", async () => {
mockClient.getIdentityServerUrl.mockReturnValue("https://identity-server");
mockClient.lookupThreePid.mockResolvedValue({});
render((
<InviteDialog
kind={KIND_INVITE}
roomId={roomId}
onFinished={jest.fn()}
initialText="foobar@email.com"
/>
));
await screen.findByText("foobar@email.com");
await screen.findByText("Invite by email");
});
});

View File

@@ -0,0 +1,135 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`<ChangelogDialog /> should fetch github proxy url for each repo with old and new version strings 1`] = `
<DocumentFragment>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
<div
aria-describedby="mx_Dialog_content"
aria-labelledby="mx_BaseDialog_title"
class="mx_QuestionDialog mx_Dialog_fixedWidth"
data-focus-lock-disabled="false"
role="dialog"
>
<div
class="mx_Dialog_header mx_Dialog_headerWithCancel"
>
<h2
class="mx_Heading_h2 mx_Dialog_title"
id="mx_BaseDialog_title"
>
Changelog
</h2>
<div
aria-label="Close dialog"
class="mx_AccessibleButton mx_Dialog_cancelButton"
role="button"
tabindex="0"
/>
</div>
<div
class="mx_Dialog_content"
id="mx_Dialog_content"
>
<div
class="mx_ChangelogDialog_content"
>
<div>
<h2>
vector-im/element-web
</h2>
<ul>
<li
class="mx_ChangelogDialog_li"
>
<a
href="https://api.github.com/repos/vector-im/element-web/commit/commit-sha"
rel="noreferrer noopener"
target="_blank"
>
This is the first commit message
</a>
</li>
</ul>
</div>
<div>
<h2>
matrix-org/matrix-react-sdk
</h2>
<ul>
<li
class="mx_ChangelogDialog_li"
>
<a
href="https://api.github.com/repos/matrix-org/matrix-react-sdk/commit/commit-sha"
rel="noreferrer noopener"
target="_blank"
>
This is a commit message
</a>
</li>
</ul>
</div>
<div>
<h2>
matrix-org/matrix-js-sdk
</h2>
<ul>
<li
class="mx_ChangelogDialog_li"
>
<a
href="https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha1"
rel="noreferrer noopener"
target="_blank"
>
This is a commit message
</a>
</li>
<li
class="mx_ChangelogDialog_li"
>
<a
href="https://api.github.com/repos/matrix-org/matrix-js-sdk/commit/commit-sha2"
rel="noreferrer noopener"
target="_blank"
>
This is another commit message
</a>
</li>
</ul>
</div>
</div>
</div>
<div
class="mx_Dialog_buttons"
>
<span
class="mx_Dialog_buttons_row"
>
<button
data-test-id="dialog-cancel-button"
type="button"
>
Cancel
</button>
<button
class="mx_Dialog_primary"
data-test-id="dialog-primary-button"
type="button"
>
Update
</button>
</span>
</div>
</div>
<div
data-focus-guard="true"
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
tabindex="0"
/>
</DocumentFragment>
`;

View File

@@ -364,7 +364,7 @@ describe('<RoomPreviewBar />', () => {
expect(getMessage(component)).toMatchSnapshot();
expect(MatrixClientPeg.get().lookupThreePid).toHaveBeenCalledWith(
'email', invitedEmail, undefined, 'mock-token',
'email', invitedEmail, 'mock-token',
);
await testJoinButton({ inviterName, invitedEmail })();
});

View File

@@ -130,6 +130,20 @@ describe("createRoom", () => {
expect(callPower).toBe(100);
expect(callMemberPower).toBe(100);
});
it("should upload avatar if one is passed", async () => {
client.uploadContent.mockResolvedValue({ content_uri: "mxc://foobar" });
const avatar = new File([], "avatar.png");
await createRoom({ avatar });
expect(client.createRoom).toHaveBeenCalledWith(expect.objectContaining({
initial_state: expect.arrayContaining([{
content: {
url: "mxc://foobar",
},
type: "m.room.avatar",
}]),
}));
});
});
describe("canEncryptToAllUsers", () => {

View File

@@ -27,10 +27,7 @@ import {
import { stubClient } from '../test-utils';
describe('languageHandler', function() {
/*
See /__mocks__/browser-request.js/ for how we are stubbing out translations
to provide fixture data for these tests
*/
// See setupLanguage.ts for how we are stubbing out translations to provide fixture data for these tests
const basicString = 'Rooms';
const selfClosingTagSub = 'Accept <policyLink /> to continue:';
const textInTagSub = '<a>Upgrade</a> to your own domain';

View File

@@ -14,7 +14,70 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import fetchMock from "fetch-mock-jest";
import * as languageHandler from "../../src/languageHandler";
import en from "../../src/i18n/strings/en_EN.json";
import de from "../../src/i18n/strings/de_DE.json";
fetchMock.config.overwriteRoutes = false;
fetchMock.catch("");
window.fetch = fetchMock.sandbox();
const lv = {
"Save": "Saglabāt",
"Uploading %(filename)s and %(count)s others|one": "Качване на %(filename)s и %(count)s друг",
};
// Fake languages.json containing references to en_EN, de_DE and lv
// en_EN.json
// de_DE.json
// lv.json - mock version with few translations, used to test fallback translation
function weblateToCounterpart(inTrs: object): object {
const outTrs = {};
for (const key of Object.keys(inTrs)) {
const keyParts = key.split('|', 2);
if (keyParts.length === 2) {
let obj = outTrs[keyParts[0]];
if (obj === undefined) {
obj = outTrs[keyParts[0]] = {};
} else if (typeof obj === "string") {
// This is a transitional edge case if a string went from singular to pluralised and both still remain
// in the translation json file. Use the singular translation as `other` and merge pluralisation atop.
obj = outTrs[keyParts[0]] = {
"other": inTrs[key],
};
console.warn("Found entry in i18n file in both singular and pluralised form", keyParts[0]);
}
obj[keyParts[1]] = inTrs[key];
} else {
outTrs[key] = inTrs[key];
}
}
return outTrs;
}
fetchMock
.get("/i18n/languages.json", {
"en": {
"fileName": "en_EN.json",
"label": "English",
},
"de": {
"fileName": "de_DE.json",
"label": "German",
},
"lv": {
"fileName": "lv.json",
"label": "Latvian",
},
})
.get("end:en_EN.json", weblateToCounterpart(en))
.get("end:de_DE.json", weblateToCounterpart(de))
.get("end:lv.json", weblateToCounterpart(lv));
languageHandler.setLanguage('en');
languageHandler.setMissingEntryGenerator(key => key.split("|", 2)[1]);

View File

@@ -45,6 +45,7 @@ global.matchMedia = mockMatchMedia;
// maplibre requires a createObjectURL mock
global.URL.createObjectURL = jest.fn();
global.URL.revokeObjectURL = jest.fn();
// polyfilling TextEncoder as it is not available on JSDOM
// view https://github.com/facebook/jest/issues/9983

View File

@@ -20,8 +20,7 @@ import Adapter from "@wojtekmaj/enzyme-adapter-react-17";
import { configure } from "enzyme";
import "blob-polyfill"; // https://github.com/jsdom/jsdom/issues/2555
// Enable the jest & enzyme mocks
require('jest-fetch-mock').enableMocks();
// Enable the enzyme mocks
configure({ adapter: new Adapter() });
// Very carefully enable the mocks for everything else in

View File

@@ -158,7 +158,7 @@ export function createTestClient(): MatrixClient {
getOpenIdToken: jest.fn().mockResolvedValue(undefined),
registerWithIdentityServer: jest.fn().mockResolvedValue({}),
getIdentityAccount: jest.fn().mockResolvedValue({}),
getTerms: jest.fn().mockResolvedValueOnce(undefined),
getTerms: jest.fn().mockResolvedValue({ policies: [] }),
doesServerSupportUnstableFeature: jest.fn().mockResolvedValue(undefined),
isVersionSupported: jest.fn().mockResolvedValue(undefined),
getPushRules: jest.fn().mockResolvedValue(undefined),
@@ -182,6 +182,7 @@ export function createTestClient(): MatrixClient {
setVideoInput: jest.fn(),
setAudioInput: jest.fn(),
} as unknown as MediaHandler),
uploadContent: jest.fn(),
} as unknown as MatrixClient;
client.reEmitter = new ReEmitter(client);

View File

@@ -98,9 +98,9 @@ describe('MultiInviter', () => {
const result = await inviter.invite([MXID1, MXID2, MXID3]);
expect(client.invite).toHaveBeenCalledTimes(3);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined, undefined);
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined, undefined);
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined, undefined);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined);
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined);
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined);
expectAllInvitedResult(result);
});
@@ -116,9 +116,9 @@ describe('MultiInviter', () => {
const result = await inviter.invite([MXID1, MXID2, MXID3]);
expect(client.invite).toHaveBeenCalledTimes(3);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined, undefined);
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined, undefined);
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined, undefined);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined);
expect(client.invite).toHaveBeenNthCalledWith(2, ROOMID, MXID2, undefined);
expect(client.invite).toHaveBeenNthCalledWith(3, ROOMID, MXID3, undefined);
expectAllInvitedResult(result);
});
@@ -131,7 +131,7 @@ describe('MultiInviter', () => {
const result = await inviter.invite([MXID1, MXID2, MXID3]);
expect(client.invite).toHaveBeenCalledTimes(1);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined, undefined);
expect(client.invite).toHaveBeenNthCalledWith(1, ROOMID, MXID1, undefined);
// The resolved state is 'invited' for all users.
// With the above client expectations, the test ensures that only the first user is invited.