Prepare for repo merge
Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
0f670b8dc0
commit
b084ff2313
807 changed files with 0 additions and 0 deletions
418
test/unit-tests/ContentMessages-test.ts
Normal file
418
test/unit-tests/ContentMessages-test.ts
Normal file
|
@ -0,0 +1,418 @@
|
|||
/*
|
||||
Copyright 2024 New Vector Ltd.
|
||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only
|
||||
Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
import { mocked } from "jest-mock";
|
||||
import { ISendEventResponse, MatrixClient, RelationType, UploadResponse } from "matrix-js-sdk/src/matrix";
|
||||
import { ImageInfo } from "matrix-js-sdk/src/types";
|
||||
import { defer } from "matrix-js-sdk/src/utils";
|
||||
import encrypt, { IEncryptedFile } from "matrix-encrypt-attachment";
|
||||
|
||||
import ContentMessages, { UploadCanceledError, uploadFile } from "../src/ContentMessages";
|
||||
import { doMaybeLocalRoomAction } from "../src/utils/local-room";
|
||||
import { createTestClient, mkEvent } 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";
|
||||
const imageInfo = {} as unknown as ImageInfo;
|
||||
const text = "test sticker";
|
||||
let client: MatrixClient;
|
||||
let contentMessages: ContentMessages;
|
||||
let prom: Promise<ISendEventResponse>;
|
||||
|
||||
beforeEach(() => {
|
||||
client = {
|
||||
getSafeUserId: jest.fn().mockReturnValue("@alice:test"),
|
||||
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<ISendEventResponse>({ event_id: "$event_id" });
|
||||
});
|
||||
|
||||
describe("sendStickerContentToRoom", () => {
|
||||
beforeEach(() => {
|
||||
mocked(client.sendStickerMessage).mockReturnValue(prom);
|
||||
mocked(doMaybeLocalRoomAction).mockImplementation(
|
||||
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>, client?: MatrixClient) => {
|
||||
return fn(roomId);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it("should forward the call to doMaybeLocalRoomAction", async () => {
|
||||
await contentMessages.sendStickerContentToRoom(stickerUrl, roomId, null, imageInfo, text, client);
|
||||
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) {
|
||||
window.setTimeout(() => this.onload());
|
||||
},
|
||||
});
|
||||
Object.defineProperty(global.Image.prototype, "height", {
|
||||
get() {
|
||||
return 600;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(global.Image.prototype, "width", {
|
||||
get() {
|
||||
return 800;
|
||||
},
|
||||
});
|
||||
mocked(doMaybeLocalRoomAction).mockImplementation(
|
||||
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>) => fn(roomId),
|
||||
);
|
||||
mocked(BlurhashEncoder.instance.getBlurhash).mockResolvedValue("blurhashstring");
|
||||
});
|
||||
|
||||
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 use m.image for PNG files which cannot be parsed but successfully thumbnail", 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.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/jpeg" });
|
||||
mocked(BlurhashEncoder.instance.getBlurhash).mockRejectedValue("NOT_AN_IMAGE");
|
||||
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") {
|
||||
(<HTMLVideoElement>element).load = jest.fn();
|
||||
(<HTMLVideoElement>element).play = () => element.onloadeddata!(new Event("loadeddata"));
|
||||
(<HTMLVideoElement>element).pause = jest.fn();
|
||||
Object.defineProperty(element, "videoHeight", {
|
||||
get() {
|
||||
return 600;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(element, "videoWidth", {
|
||||
get() {
|
||||
return 800;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(element, "duration", {
|
||||
get() {
|
||||
return 123;
|
||||
},
|
||||
});
|
||||
}
|
||||
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",
|
||||
info: expect.objectContaining({
|
||||
duration: 123000,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should use m.audio for audio files", async () => {
|
||||
jest.spyOn(document, "createElement").mockImplementation((tagName) => {
|
||||
const element = createElement(tagName);
|
||||
if (tagName === "audio") {
|
||||
Object.defineProperty(element, "duration", {
|
||||
get() {
|
||||
return 621;
|
||||
},
|
||||
});
|
||||
Object.defineProperty(element, "src", {
|
||||
set() {
|
||||
element.onloadedmetadata!(new Event("loadedmetadata"));
|
||||
},
|
||||
});
|
||||
}
|
||||
return element;
|
||||
});
|
||||
|
||||
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",
|
||||
info: expect.objectContaining({
|
||||
duration: 621000,
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("should fall back to m.file for invalid audio files", async () => {
|
||||
jest.spyOn(document, "createElement").mockImplementation((tagName) => {
|
||||
const element = createElement(tagName);
|
||||
if (tagName === "audio") {
|
||||
Object.defineProperty(element, "src", {
|
||||
set() {
|
||||
element.onerror!("fail");
|
||||
},
|
||||
});
|
||||
}
|
||||
return element;
|
||||
});
|
||||
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.file",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
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;
|
||||
});
|
||||
|
||||
it("properly handles replies", async () => {
|
||||
mocked(client.uploadContent).mockResolvedValue({ content_uri: "mxc://server/file" });
|
||||
const file = new File([], "fileName", { type: "image/jpeg" });
|
||||
const replyToEvent = mkEvent({
|
||||
type: "m.room.message",
|
||||
user: "@bob:test",
|
||||
room: roomId,
|
||||
content: {},
|
||||
event: true,
|
||||
});
|
||||
await contentMessages.sendContentToRoom(file, roomId, undefined, client, replyToEvent);
|
||||
expect(client.sendMessage).toHaveBeenCalledWith(
|
||||
roomId,
|
||||
null,
|
||||
expect.objectContaining({
|
||||
"url": "mxc://server/file",
|
||||
"msgtype": "m.image",
|
||||
"m.mentions": {
|
||||
user_ids: ["@bob:test"],
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getCurrentUploads", () => {
|
||||
const file1 = new File([], "file1");
|
||||
const file2 = new File([], "file2");
|
||||
const roomId = "!roomId:server";
|
||||
|
||||
beforeEach(() => {
|
||||
mocked(doMaybeLocalRoomAction).mockImplementation(
|
||||
<T>(roomId: string, fn: (actualRoomId: string) => Promise<T>) => 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,
|
||||
type: "application/octet-stream",
|
||||
}),
|
||||
);
|
||||
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.toThrow(UploadCanceledError);
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue