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

Commit ccaf274

Browse files
committed
More thoroughly test HTMLExporter
This adds some more thorough coverage of the HTML exporter. Signed-off-by: Clark Fischer <clark.fischer@gmail.com>
1 parent e7b682a commit ccaf274

File tree

2 files changed

+236
-12
lines changed

2 files changed

+236
-12
lines changed

test/utils/exportUtils/HTMLExport-test.ts

Lines changed: 235 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,97 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
import { EventType, IRoomEvent, MatrixClient, Room } from "matrix-js-sdk/src/matrix";
17+
import { EventType, IRoomEvent, MatrixClient, MatrixEvent, MsgType, Room, RoomMember } from "matrix-js-sdk/src/matrix";
18+
import fetchMock from "fetch-mock-jest";
1819

19-
import { mkStubRoom, REPEATABLE_DATE, stubClient } from "../../test-utils";
20+
import { filterConsole, mkStubRoom, REPEATABLE_DATE, stubClient } from "../../test-utils";
2021
import { ExportType, IExportOptions } from "../../../src/utils/exportUtils/exportUtils";
2122
import SdkConfig from "../../../src/SdkConfig";
2223
import HTMLExporter from "../../../src/utils/exportUtils/HtmlExport";
2324
import DMRoomMap from "../../../src/utils/DMRoomMap";
25+
import { mediaFromMxc } from "../../../src/customisations/Media";
2426

2527
jest.mock("jszip");
2628

29+
const EVENT_MESSAGE: IRoomEvent = {
30+
event_id: "$1",
31+
type: EventType.RoomMessage,
32+
sender: "@bob:example.com",
33+
origin_server_ts: 0,
34+
content: {
35+
msgtype: "m.text",
36+
body: "Message",
37+
avatar_url: "mxc://example.org/avatar.bmp",
38+
},
39+
};
40+
41+
const EVENT_ATTACHMENT: IRoomEvent = {
42+
event_id: "$2",
43+
type: EventType.RoomMessage,
44+
sender: "@alice:example.com",
45+
origin_server_ts: 1,
46+
content: {
47+
msgtype: MsgType.File,
48+
body: "hello.txt",
49+
filename: "hello.txt",
50+
url: "mxc://example.org/test-id",
51+
},
52+
};
53+
2754
describe("HTMLExport", () => {
2855
let client: jest.Mocked<MatrixClient>;
56+
let room: Room;
57+
58+
filterConsole(
59+
"Starting export",
60+
"events in", // Fetched # events in # seconds
61+
"events so far",
62+
"Export successful!",
63+
"does not have an m.room.create event",
64+
"Creating HTML",
65+
"Generating a ZIP",
66+
"Cleaning up",
67+
);
2968

3069
beforeEach(() => {
3170
jest.useFakeTimers();
3271
jest.setSystemTime(REPEATABLE_DATE);
3372

3473
client = stubClient() as jest.Mocked<MatrixClient>;
3574
DMRoomMap.makeShared();
75+
76+
room = new Room("!myroom:example.org", client, "@me:example.org");
77+
client.getRoom.mockReturnValue(room);
3678
});
3779

38-
function getMessageFile(exporter: HTMLExporter): Blob {
80+
function mockMessages(...events: IRoomEvent[]): void {
81+
client.createMessagesRequest.mockImplementation((_roomId, fromStr, limit = 30) => {
82+
const from = fromStr === null ? 0 : parseInt(fromStr);
83+
const chunk = events.slice(from, limit);
84+
return Promise.resolve({
85+
chunk,
86+
from: from.toString(),
87+
to: (from + limit).toString(),
88+
});
89+
});
90+
}
91+
92+
/** Retrieve a map of files within the zip. */
93+
function getFiles(exporter: HTMLExporter): { [filename: string]: Blob } {
3994
//@ts-ignore private access
4095
const files = exporter.files;
41-
const file = files.find((f) => f.name == "messages.html")!;
42-
return file.blob;
96+
return files.reduce((d, f) => ({ ...d, [f.name]: f.blob }), {});
97+
}
98+
99+
function getMessageFile(exporter: HTMLExporter): Blob {
100+
const files = getFiles(exporter);
101+
return files["messages.html"]!;
102+
}
103+
104+
/** set a mock fetch response for an MXC */
105+
function mockMxc(mxc: string, body: string) {
106+
const media = mediaFromMxc(mxc, client);
107+
fetchMock.get(media.srcHttp, body);
43108
}
44109

45110
it("should have an SDK-branded destination file name", () => {
@@ -59,10 +124,8 @@ describe("HTMLExport", () => {
59124
});
60125

61126
it("should export", async () => {
62-
const room = new Room("!myroom:example.org", client, "@me:example.org");
63-
64127
const events = [...Array(50)].map<IRoomEvent>((_, i) => ({
65-
event_id: "$1",
128+
event_id: `${i}`,
66129
type: EventType.RoomMessage,
67130
sender: `@user${i}:example.com`,
68131
origin_server_ts: 5_000 + i * 1000,
@@ -71,9 +134,7 @@ describe("HTMLExport", () => {
71134
body: `Message #${i}`,
72135
},
73136
}));
74-
75-
client.getRoom.mockReturnValue(room);
76-
client.createMessagesRequest.mockResolvedValue({ chunk: events });
137+
mockMessages(...events);
77138

78139
const exporter = new HTMLExporter(
79140
room,
@@ -91,4 +152,167 @@ describe("HTMLExport", () => {
91152
const file = getMessageFile(exporter);
92153
expect(await file.text()).toMatchSnapshot();
93154
});
155+
156+
it("should include the room's avatar", async () => {
157+
mockMessages(EVENT_MESSAGE);
158+
159+
const mxc = "mxc://www.example.com/avatars/nice-room.jpeg";
160+
const avatar = "011011000110111101101100";
161+
jest.spyOn(room, "getMxcAvatarUrl").mockReturnValue(mxc);
162+
mockMxc(mxc, avatar);
163+
164+
const exporter = new HTMLExporter(
165+
room,
166+
ExportType.Timeline,
167+
{
168+
attachmentsIncluded: false,
169+
maxSize: 1_024 * 1_024,
170+
},
171+
() => {},
172+
);
173+
174+
await exporter.export();
175+
176+
const files = getFiles(exporter);
177+
expect(await files["room.png"]!.text()).toBe(avatar);
178+
});
179+
180+
it("should include the creation event", async () => {
181+
const creator = "@bob:example.com";
182+
mockMessages(EVENT_MESSAGE);
183+
room.currentState.setStateEvents([
184+
new MatrixEvent({
185+
type: EventType.RoomCreate,
186+
event_id: "$00001",
187+
room_id: room.roomId,
188+
sender: creator,
189+
origin_server_ts: 0,
190+
content: {},
191+
state_key: "",
192+
}),
193+
]);
194+
195+
const exporter = new HTMLExporter(
196+
room,
197+
ExportType.Timeline,
198+
{
199+
attachmentsIncluded: false,
200+
maxSize: 1_024 * 1_024,
201+
},
202+
() => {},
203+
);
204+
205+
await exporter.export();
206+
207+
expect(await getMessageFile(exporter).text()).toContain(`${creator} created this room.`);
208+
});
209+
210+
it("should include the topic", async () => {
211+
const topic = ":^-) (-^:";
212+
mockMessages(EVENT_MESSAGE);
213+
room.currentState.setStateEvents([
214+
new MatrixEvent({
215+
type: EventType.RoomTopic,
216+
event_id: "$00001",
217+
room_id: room.roomId,
218+
sender: "@alice:example.com",
219+
origin_server_ts: 0,
220+
content: { topic },
221+
state_key: "",
222+
}),
223+
]);
224+
225+
const exporter = new HTMLExporter(
226+
room,
227+
ExportType.Timeline,
228+
{
229+
attachmentsIncluded: false,
230+
maxSize: 1_024 * 1_024,
231+
},
232+
() => {},
233+
);
234+
235+
await exporter.export();
236+
237+
expect(await getMessageFile(exporter).text()).toContain(`Topic: ${topic}`);
238+
});
239+
240+
it("should include avatars", async () => {
241+
mockMessages(EVENT_MESSAGE);
242+
243+
jest.spyOn(RoomMember.prototype, "getMxcAvatarUrl").mockReturnValue("mxc://example.org/avatar.bmp");
244+
245+
const avatarContent = "this is a bitmap all the pixels are red :^-)";
246+
mockMxc("mxc://example.org/avatar.bmp", avatarContent);
247+
248+
const exporter = new HTMLExporter(
249+
room,
250+
ExportType.Timeline,
251+
{
252+
attachmentsIncluded: false,
253+
maxSize: 1_024 * 1_024,
254+
},
255+
() => {},
256+
);
257+
258+
await exporter.export();
259+
260+
// Ensure that the avatar is present
261+
const files = getFiles(exporter);
262+
const file = files["users/@bob-example.com.png"];
263+
expect(file).not.toBeUndefined();
264+
265+
// Ensure it has the expected content
266+
expect(await file.text()).toBe(avatarContent);
267+
});
268+
269+
it("should include attachments", async () => {
270+
mockMessages(EVENT_MESSAGE, EVENT_ATTACHMENT);
271+
const attachmentBody = "Lorem ipsum dolor sit amet";
272+
273+
mockMxc("mxc://example.org/test-id", attachmentBody);
274+
275+
const exporter = new HTMLExporter(
276+
room,
277+
ExportType.Timeline,
278+
{
279+
attachmentsIncluded: true,
280+
maxSize: 1_024 * 1_024,
281+
},
282+
() => {},
283+
);
284+
285+
await exporter.export();
286+
287+
// Ensure that the attachment is present
288+
const files = getFiles(exporter);
289+
const file = files["files/hello-1-1-1970 at 12-00-00 AM.txt"];
290+
expect(file).not.toBeUndefined();
291+
292+
// Ensure that the attachment has the expected content
293+
const text = await file.text();
294+
expect(text).toBe(attachmentBody);
295+
});
296+
297+
it("should omit attachments", async () => {
298+
mockMessages(EVENT_MESSAGE, EVENT_ATTACHMENT);
299+
300+
const exporter = new HTMLExporter(
301+
room,
302+
ExportType.Timeline,
303+
{
304+
attachmentsIncluded: false,
305+
maxSize: 1_024 * 1_024,
306+
},
307+
() => {},
308+
);
309+
310+
await exporter.export();
311+
312+
// Ensure that the attachment is present
313+
const files = getFiles(exporter);
314+
for (const fileName of Object.keys(files)) {
315+
expect(fileName).not.toMatch(/^files\/hello/);
316+
}
317+
});
94318
});

0 commit comments

Comments
 (0)