diff --git a/test/utils/exportUtils/HTMLExport-test.ts b/test/utils/exportUtils/HTMLExport-test.ts
index 89a0ddedf50..607dfbdbdc7 100644
--- a/test/utils/exportUtils/HTMLExport-test.ts
+++ b/test/utils/exportUtils/HTMLExport-test.ts
@@ -14,18 +14,57 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
-import { EventType, IRoomEvent, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
+import { EventType, IRoomEvent, MatrixClient, MatrixEvent, MsgType, Room, RoomMember } from "matrix-js-sdk/src/matrix";
+import fetchMock from "fetch-mock-jest";
-import { mkStubRoom, REPEATABLE_DATE, stubClient } from "../../test-utils";
+import { filterConsole, mkStubRoom, REPEATABLE_DATE, stubClient } from "../../test-utils";
import { ExportType, IExportOptions } from "../../../src/utils/exportUtils/exportUtils";
import SdkConfig from "../../../src/SdkConfig";
import HTMLExporter from "../../../src/utils/exportUtils/HtmlExport";
import DMRoomMap from "../../../src/utils/DMRoomMap";
+import { mediaFromMxc } from "../../../src/customisations/Media";
jest.mock("jszip");
+const EVENT_MESSAGE: IRoomEvent = {
+ event_id: "$1",
+ type: EventType.RoomMessage,
+ sender: "@bob:example.com",
+ origin_server_ts: 0,
+ content: {
+ msgtype: "m.text",
+ body: "Message",
+ avatar_url: "mxc://example.org/avatar.bmp",
+ },
+};
+
+const EVENT_ATTACHMENT: IRoomEvent = {
+ event_id: "$2",
+ type: EventType.RoomMessage,
+ sender: "@alice:example.com",
+ origin_server_ts: 1,
+ content: {
+ msgtype: MsgType.File,
+ body: "hello.txt",
+ filename: "hello.txt",
+ url: "mxc://example.org/test-id",
+ },
+};
+
describe("HTMLExport", () => {
let client: jest.Mocked;
+ let room: Room;
+
+ filterConsole(
+ "Starting export",
+ "events in", // Fetched # events in # seconds
+ "events so far",
+ "Export successful!",
+ "does not have an m.room.create event",
+ "Creating HTML",
+ "Generating a ZIP",
+ "Cleaning up",
+ );
beforeEach(() => {
jest.useFakeTimers();
@@ -33,13 +72,39 @@ describe("HTMLExport", () => {
client = stubClient() as jest.Mocked;
DMRoomMap.makeShared();
+
+ room = new Room("!myroom:example.org", client, "@me:example.org");
+ client.getRoom.mockReturnValue(room);
});
- function getMessageFile(exporter: HTMLExporter): Blob {
+ function mockMessages(...events: IRoomEvent[]): void {
+ client.createMessagesRequest.mockImplementation((_roomId, fromStr, limit = 30) => {
+ const from = fromStr === null ? 0 : parseInt(fromStr);
+ const chunk = events.slice(from, limit);
+ return Promise.resolve({
+ chunk,
+ from: from.toString(),
+ to: (from + limit).toString(),
+ });
+ });
+ }
+
+ /** Retrieve a map of files within the zip. */
+ function getFiles(exporter: HTMLExporter): { [filename: string]: Blob } {
//@ts-ignore private access
const files = exporter.files;
- const file = files.find((f) => f.name == "messages.html")!;
- return file.blob;
+ return files.reduce((d, f) => ({ ...d, [f.name]: f.blob }), {});
+ }
+
+ function getMessageFile(exporter: HTMLExporter): Blob {
+ const files = getFiles(exporter);
+ return files["messages.html"]!;
+ }
+
+ /** set a mock fetch response for an MXC */
+ function mockMxc(mxc: string, body: string) {
+ const media = mediaFromMxc(mxc, client);
+ fetchMock.get(media.srcHttp, body);
}
it("should have an SDK-branded destination file name", () => {
@@ -59,10 +124,8 @@ describe("HTMLExport", () => {
});
it("should export", async () => {
- const room = new Room("!myroom:example.org", client, "@me:example.org");
-
const events = [...Array(50)].map((_, i) => ({
- event_id: "$1",
+ event_id: `${i}`,
type: EventType.RoomMessage,
sender: `@user${i}:example.com`,
origin_server_ts: 5_000 + i * 1000,
@@ -71,9 +134,7 @@ describe("HTMLExport", () => {
body: `Message #${i}`,
},
}));
-
- client.getRoom.mockReturnValue(room);
- client.createMessagesRequest.mockResolvedValue({ chunk: events });
+ mockMessages(...events);
const exporter = new HTMLExporter(
room,
@@ -91,4 +152,167 @@ describe("HTMLExport", () => {
const file = getMessageFile(exporter);
expect(await file.text()).toMatchSnapshot();
});
+
+ it("should include the room's avatar", async () => {
+ mockMessages(EVENT_MESSAGE);
+
+ const mxc = "mxc://www.example.com/avatars/nice-room.jpeg";
+ const avatar = "011011000110111101101100";
+ jest.spyOn(room, "getMxcAvatarUrl").mockReturnValue(mxc);
+ mockMxc(mxc, avatar);
+
+ const exporter = new HTMLExporter(
+ room,
+ ExportType.Timeline,
+ {
+ attachmentsIncluded: false,
+ maxSize: 1_024 * 1_024,
+ },
+ () => {},
+ );
+
+ await exporter.export();
+
+ const files = getFiles(exporter);
+ expect(await files["room.png"]!.text()).toBe(avatar);
+ });
+
+ it("should include the creation event", async () => {
+ const creator = "@bob:example.com";
+ mockMessages(EVENT_MESSAGE);
+ room.currentState.setStateEvents([
+ new MatrixEvent({
+ type: EventType.RoomCreate,
+ event_id: "$00001",
+ room_id: room.roomId,
+ sender: creator,
+ origin_server_ts: 0,
+ content: {},
+ state_key: "",
+ }),
+ ]);
+
+ const exporter = new HTMLExporter(
+ room,
+ ExportType.Timeline,
+ {
+ attachmentsIncluded: false,
+ maxSize: 1_024 * 1_024,
+ },
+ () => {},
+ );
+
+ await exporter.export();
+
+ expect(await getMessageFile(exporter).text()).toContain(`${creator} created this room.`);
+ });
+
+ it("should include the topic", async () => {
+ const topic = ":^-) (-^:";
+ mockMessages(EVENT_MESSAGE);
+ room.currentState.setStateEvents([
+ new MatrixEvent({
+ type: EventType.RoomTopic,
+ event_id: "$00001",
+ room_id: room.roomId,
+ sender: "@alice:example.com",
+ origin_server_ts: 0,
+ content: { topic },
+ state_key: "",
+ }),
+ ]);
+
+ const exporter = new HTMLExporter(
+ room,
+ ExportType.Timeline,
+ {
+ attachmentsIncluded: false,
+ maxSize: 1_024 * 1_024,
+ },
+ () => {},
+ );
+
+ await exporter.export();
+
+ expect(await getMessageFile(exporter).text()).toContain(`Topic: ${topic}`);
+ });
+
+ it("should include avatars", async () => {
+ mockMessages(EVENT_MESSAGE);
+
+ jest.spyOn(RoomMember.prototype, "getMxcAvatarUrl").mockReturnValue("mxc://example.org/avatar.bmp");
+
+ const avatarContent = "this is a bitmap all the pixels are red :^-)";
+ mockMxc("mxc://example.org/avatar.bmp", avatarContent);
+
+ const exporter = new HTMLExporter(
+ room,
+ ExportType.Timeline,
+ {
+ attachmentsIncluded: false,
+ maxSize: 1_024 * 1_024,
+ },
+ () => {},
+ );
+
+ await exporter.export();
+
+ // Ensure that the avatar is present
+ const files = getFiles(exporter);
+ const file = files["users/@bob-example.com.png"];
+ expect(file).not.toBeUndefined();
+
+ // Ensure it has the expected content
+ expect(await file.text()).toBe(avatarContent);
+ });
+
+ it("should include attachments", async () => {
+ mockMessages(EVENT_MESSAGE, EVENT_ATTACHMENT);
+ const attachmentBody = "Lorem ipsum dolor sit amet";
+
+ mockMxc("mxc://example.org/test-id", attachmentBody);
+
+ const exporter = new HTMLExporter(
+ room,
+ ExportType.Timeline,
+ {
+ attachmentsIncluded: true,
+ maxSize: 1_024 * 1_024,
+ },
+ () => {},
+ );
+
+ await exporter.export();
+
+ // Ensure that the attachment is present
+ const files = getFiles(exporter);
+ const file = files["files/hello-1-1-1970 at 12-00-00 AM.txt"];
+ expect(file).not.toBeUndefined();
+
+ // Ensure that the attachment has the expected content
+ const text = await file.text();
+ expect(text).toBe(attachmentBody);
+ });
+
+ it("should omit attachments", async () => {
+ mockMessages(EVENT_MESSAGE, EVENT_ATTACHMENT);
+
+ const exporter = new HTMLExporter(
+ room,
+ ExportType.Timeline,
+ {
+ attachmentsIncluded: false,
+ maxSize: 1_024 * 1_024,
+ },
+ () => {},
+ );
+
+ await exporter.export();
+
+ // Ensure that the attachment is present
+ const files = getFiles(exporter);
+ for (const fileName of Object.keys(files)) {
+ expect(fileName).not.toMatch(/^files\/hello/);
+ }
+ });
});
diff --git a/test/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap b/test/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap
index af50c8d604d..c47170d3eda 100644
--- a/test/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap
+++ b/test/utils/exportUtils/__snapshots__/HTMLExport-test.ts.snap
@@ -65,7 +65,7 @@ exports[`HTMLExport should export 1`] = `
-
Thu, Jan 1 1970
@user49:example.com
U @user48:example.com
U @user47:example.com
U @user46:example.com
U @user45:example.com
U @user44:example.com
U @user43:example.com
U @user42:example.com
U @user41:example.com
U @user40:example.com
U @user39:example.com
U @user38:example.com
U @user37:example.com
U @user36:example.com
U @user35:example.com
U @user34:example.com
U @user33:example.com
U @user32:example.com
U @user31:example.com
U @user30:example.com
U @user29:example.com
U @user28:example.com
U @user27:example.com
U @user26:example.com
U @user25:example.com
U @user24:example.com
U @user23:example.com
U @user22:example.com
U @user21:example.com
U @user20:example.com
U @user19:example.com
U @user18:example.com
U @user17:example.com
U @user16:example.com
U @user15:example.com
U @user14:example.com
U @user13:example.com
U @user12:example.com
U @user11:example.com
U @user10:example.com
U @user9:example.com
U @user8:example.com
U @user7:example.com
U @user6:example.com
U @user5:example.com
U @user4:example.com
U @user3:example.com
U @user2:example.com
U @user1:example.com
U @user0:example.com
U
+
Thu, Jan 1 1970
@user49:example.com
U @user48:example.com
U @user47:example.com
U @user46:example.com
U @user45:example.com
U @user44:example.com
U @user43:example.com
U @user42:example.com
U @user41:example.com
U @user40:example.com
U @user39:example.com
U @user38:example.com
U @user37:example.com
U @user36:example.com
U @user35:example.com
U @user34:example.com
U @user33:example.com
U @user32:example.com
U @user31:example.com
U @user30:example.com
U @user29:example.com
U @user28:example.com
U @user27:example.com
U @user26:example.com
U @user25:example.com
U @user24:example.com
U @user23:example.com
U @user22:example.com
U @user21:example.com
U @user20:example.com
U @user19:example.com
U @user18:example.com
U @user17:example.com
U @user16:example.com
U @user15:example.com
U @user14:example.com
U @user13:example.com
U @user12:example.com
U @user11:example.com
U @user10:example.com
U @user9:example.com
U @user8:example.com
U @user7:example.com
U @user6:example.com
U @user5:example.com
U @user4:example.com
U @user3:example.com
U @user2:example.com
U @user1:example.com
U @user0:example.com
U