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

Commit

Permalink
More thoroughly test HTMLExporter
Browse files Browse the repository at this point in the history
This adds some more thorough coverage of the HTML exporter.

Signed-off-by: Clark Fischer <clark.fischer@gmail.com>
  • Loading branch information
clarkf committed Jan 26, 2023
1 parent e7b682a commit ccaf274
Show file tree
Hide file tree
Showing 2 changed files with 236 additions and 12 deletions.
246 changes: 235 additions & 11 deletions test/utils/exportUtils/HTMLExport-test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,32 +14,97 @@ 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<MatrixClient>;
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();
jest.setSystemTime(REPEATABLE_DATE);

client = stubClient() as jest.Mocked<MatrixClient>;
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", () => {
Expand All @@ -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<IRoomEvent>((_, i) => ({
event_id: "$1",
event_id: `${i}`,
type: EventType.RoomMessage,
sender: `@user${i}:example.com`,
origin_server_ts: 5_000 + i * 1000,
Expand All @@ -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,
Expand All @@ -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/);
}
});
});
Loading

0 comments on commit ccaf274

Please sign in to comment.