mirror of https://github.com/vector-im/riot-web
Fix "[object Promise]" appearing in HTML exports (#9975)
Fixes https://github.com/vector-im/element-web/issues/24272pull/28788/head^2
parent
3e2bf5640e
commit
4c1e4f5127
|
@ -175,7 +175,7 @@ function withinCurrentYear(prevDate: Date, nextDate: Date): boolean {
|
||||||
return prevDate.getFullYear() === nextDate.getFullYear();
|
return prevDate.getFullYear() === nextDate.getFullYear();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wantsDateSeparator(prevEventDate: Date, nextEventDate: Date): boolean {
|
export function wantsDateSeparator(prevEventDate: Date | undefined, nextEventDate: Date | undefined): boolean {
|
||||||
if (!nextEventDate || !prevEventDate) {
|
if (!nextEventDate || !prevEventDate) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ const groupedStateEvents = [
|
||||||
// check if there is a previous event and it has the same sender as this event
|
// check if there is a previous event and it has the same sender as this event
|
||||||
// and the types are the same/is in continuedTypes and the time between them is <= CONTINUATION_MAX_INTERVAL
|
// and the types are the same/is in continuedTypes and the time between them is <= CONTINUATION_MAX_INTERVAL
|
||||||
export function shouldFormContinuation(
|
export function shouldFormContinuation(
|
||||||
prevEvent: MatrixEvent,
|
prevEvent: MatrixEvent | null,
|
||||||
mxEvent: MatrixEvent,
|
mxEvent: MatrixEvent,
|
||||||
showHiddenEvents: boolean,
|
showHiddenEvents: boolean,
|
||||||
threadsEnabled: boolean,
|
threadsEnabled: boolean,
|
||||||
|
@ -821,7 +821,7 @@ export default class MessagePanel extends React.Component<IProps, IState> {
|
||||||
// here.
|
// here.
|
||||||
return !this.props.canBackPaginate;
|
return !this.props.canBackPaginate;
|
||||||
}
|
}
|
||||||
return wantsDateSeparator(prevEvent.getDate(), nextEventDate);
|
return wantsDateSeparator(prevEvent.getDate() || undefined, nextEventDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a list of read receipts that should be shown next to this event
|
// Get a list of read receipts that should be shown next to this event
|
||||||
|
|
|
@ -130,7 +130,7 @@ export default class MessageEditHistoryDialog extends React.PureComponent<IProps
|
||||||
}
|
}
|
||||||
const baseEventId = this.props.mxEvent.getId();
|
const baseEventId = this.props.mxEvent.getId();
|
||||||
allEvents.forEach((e, i) => {
|
allEvents.forEach((e, i) => {
|
||||||
if (!lastEvent || wantsDateSeparator(lastEvent.getDate(), e.getDate())) {
|
if (!lastEvent || wantsDateSeparator(lastEvent.getDate() || undefined, e.getDate() || undefined)) {
|
||||||
nodes.push(
|
nodes.push(
|
||||||
<li key={e.getTs() + "~"}>
|
<li key={e.getTs() + "~"}>
|
||||||
<DateSeparator roomId={e.getRoomId()} ts={e.getTs()} />
|
<DateSeparator roomId={e.getRoomId()} ts={e.getTs()} />
|
||||||
|
|
|
@ -84,7 +84,7 @@ export default class SearchResultTile extends React.Component<IProps> {
|
||||||
// is this a continuation of the previous message?
|
// is this a continuation of the previous message?
|
||||||
const continuation =
|
const continuation =
|
||||||
prevEv &&
|
prevEv &&
|
||||||
!wantsDateSeparator(prevEv.getDate(), mxEv.getDate()) &&
|
!wantsDateSeparator(prevEv.getDate() || undefined, mxEv.getDate() || undefined) &&
|
||||||
shouldFormContinuation(
|
shouldFormContinuation(
|
||||||
prevEv,
|
prevEv,
|
||||||
mxEv,
|
mxEv,
|
||||||
|
@ -96,7 +96,10 @@ export default class SearchResultTile extends React.Component<IProps> {
|
||||||
let lastInSection = true;
|
let lastInSection = true;
|
||||||
const nextEv = timeline[j + 1];
|
const nextEv = timeline[j + 1];
|
||||||
if (nextEv) {
|
if (nextEv) {
|
||||||
const willWantDateSeparator = wantsDateSeparator(mxEv.getDate(), nextEv.getDate());
|
const willWantDateSeparator = wantsDateSeparator(
|
||||||
|
mxEv.getDate() || undefined,
|
||||||
|
nextEv.getDate() || undefined,
|
||||||
|
);
|
||||||
lastInSection =
|
lastInSection =
|
||||||
willWantDateSeparator ||
|
willWantDateSeparator ||
|
||||||
mxEv.getSender() !== nextEv.getSender() ||
|
mxEv.getSender() !== nextEv.getSender() ||
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
Copyright 2021, 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -66,7 +66,7 @@ export default class HTMLExporter extends Exporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getRoomAvatar(): Promise<ReactNode> {
|
protected async getRoomAvatar(): Promise<ReactNode> {
|
||||||
let blob: Blob;
|
let blob: Blob | undefined = undefined;
|
||||||
const avatarUrl = Avatar.avatarUrlForRoom(this.room, 32, 32, "crop");
|
const avatarUrl = Avatar.avatarUrlForRoom(this.room, 32, 32, "crop");
|
||||||
const avatarPath = "room.png";
|
const avatarPath = "room.png";
|
||||||
if (avatarUrl) {
|
if (avatarUrl) {
|
||||||
|
@ -85,7 +85,7 @@ export default class HTMLExporter extends Exporter {
|
||||||
height={32}
|
height={32}
|
||||||
name={this.room.name}
|
name={this.room.name}
|
||||||
title={this.room.name}
|
title={this.room.name}
|
||||||
url={blob ? avatarPath : null}
|
url={blob ? avatarPath : ""}
|
||||||
resizeMethod="crop"
|
resizeMethod="crop"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
@ -96,9 +96,9 @@ export default class HTMLExporter extends Exporter {
|
||||||
const roomAvatar = await this.getRoomAvatar();
|
const roomAvatar = await this.getRoomAvatar();
|
||||||
const exportDate = formatFullDateNoDayNoTime(new Date());
|
const exportDate = formatFullDateNoDayNoTime(new Date());
|
||||||
const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
|
const creator = this.room.currentState.getStateEvents(EventType.RoomCreate, "")?.getSender();
|
||||||
const creatorName = this.room?.getMember(creator)?.rawDisplayName || creator;
|
const creatorName = (creator ? this.room.getMember(creator)?.rawDisplayName : creator) || creator;
|
||||||
const exporter = this.client.getUserId();
|
const exporter = this.client.getUserId()!;
|
||||||
const exporterName = this.room?.getMember(exporter)?.rawDisplayName;
|
const exporterName = this.room.getMember(exporter)?.rawDisplayName;
|
||||||
const topic = this.room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || "";
|
const topic = this.room.currentState.getStateEvents(EventType.RoomTopic, "")?.getContent()?.topic || "";
|
||||||
const createdText = _t("%(creatorName)s created this room.", {
|
const createdText = _t("%(creatorName)s created this room.", {
|
||||||
creatorName,
|
creatorName,
|
||||||
|
@ -217,20 +217,19 @@ export default class HTMLExporter extends Exporter {
|
||||||
</html>`;
|
</html>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getAvatarURL(event: MatrixEvent): string {
|
protected getAvatarURL(event: MatrixEvent): string | undefined {
|
||||||
const member = event.sender;
|
const member = event.sender;
|
||||||
return (
|
const avatarUrl = member?.getMxcAvatarUrl();
|
||||||
member.getMxcAvatarUrl() && mediaFromMxc(member.getMxcAvatarUrl()).getThumbnailOfSourceHttp(30, 30, "crop")
|
return avatarUrl ? mediaFromMxc(avatarUrl).getThumbnailOfSourceHttp(30, 30, "crop") : undefined;
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async saveAvatarIfNeeded(event: MatrixEvent): Promise<void> {
|
protected async saveAvatarIfNeeded(event: MatrixEvent): Promise<void> {
|
||||||
const member = event.sender;
|
const member = event.sender!;
|
||||||
if (!this.avatars.has(member.userId)) {
|
if (!this.avatars.has(member.userId)) {
|
||||||
try {
|
try {
|
||||||
const avatarUrl = this.getAvatarURL(event);
|
const avatarUrl = this.getAvatarURL(event);
|
||||||
this.avatars.set(member.userId, true);
|
this.avatars.set(member.userId, true);
|
||||||
const image = await fetch(avatarUrl);
|
const image = await fetch(avatarUrl!);
|
||||||
const blob = await image.blob();
|
const blob = await image.blob();
|
||||||
this.addFile(`users/${member.userId.replace(/:/g, "-")}.png`, blob);
|
this.addFile(`users/${member.userId.replace(/:/g, "-")}.png`, blob);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -239,19 +238,19 @@ export default class HTMLExporter extends Exporter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getDateSeparator(event: MatrixEvent): Promise<string> {
|
protected getDateSeparator(event: MatrixEvent): string {
|
||||||
const ts = event.getTs();
|
const ts = event.getTs();
|
||||||
const dateSeparator = (
|
const dateSeparator = (
|
||||||
<li key={ts}>
|
<li key={ts}>
|
||||||
<DateSeparator forExport={true} key={ts} roomId={event.getRoomId()} ts={ts} />
|
<DateSeparator forExport={true} key={ts} roomId={event.getRoomId()!} ts={ts} />
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
return renderToStaticMarkup(dateSeparator);
|
return renderToStaticMarkup(dateSeparator);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async needsDateSeparator(event: MatrixEvent, prevEvent: MatrixEvent): Promise<boolean> {
|
protected needsDateSeparator(event: MatrixEvent, prevEvent: MatrixEvent | null): boolean {
|
||||||
if (prevEvent == null) return true;
|
if (!prevEvent) return true;
|
||||||
return wantsDateSeparator(prevEvent.getDate(), event.getDate());
|
return wantsDateSeparator(prevEvent.getDate() || undefined, event.getDate() || undefined);
|
||||||
}
|
}
|
||||||
|
|
||||||
public getEventTile(mxEv: MatrixEvent, continuation: boolean): JSX.Element {
|
public getEventTile(mxEv: MatrixEvent, continuation: boolean): JSX.Element {
|
||||||
|
@ -264,9 +263,7 @@ export default class HTMLExporter extends Exporter {
|
||||||
isRedacted={mxEv.isRedacted()}
|
isRedacted={mxEv.isRedacted()}
|
||||||
replacingEventId={mxEv.replacingEventId()}
|
replacingEventId={mxEv.replacingEventId()}
|
||||||
forExport={true}
|
forExport={true}
|
||||||
readReceipts={null}
|
|
||||||
alwaysShowTimestamps={true}
|
alwaysShowTimestamps={true}
|
||||||
readReceiptMap={null}
|
|
||||||
showUrlPreview={false}
|
showUrlPreview={false}
|
||||||
checkUnmounting={() => false}
|
checkUnmounting={() => false}
|
||||||
isTwelveHour={false}
|
isTwelveHour={false}
|
||||||
|
@ -275,7 +272,6 @@ export default class HTMLExporter extends Exporter {
|
||||||
permalinkCreator={this.permalinkCreator}
|
permalinkCreator={this.permalinkCreator}
|
||||||
lastSuccessful={false}
|
lastSuccessful={false}
|
||||||
isSelectedEvent={false}
|
isSelectedEvent={false}
|
||||||
getRelationsForEvent={null}
|
|
||||||
showReactions={false}
|
showReactions={false}
|
||||||
layout={Layout.Group}
|
layout={Layout.Group}
|
||||||
showReadReceipts={false}
|
showReadReceipts={false}
|
||||||
|
@ -286,7 +282,8 @@ export default class HTMLExporter extends Exporter {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async getEventTileMarkup(mxEv: MatrixEvent, continuation: boolean, filePath?: string): Promise<string> {
|
protected async getEventTileMarkup(mxEv: MatrixEvent, continuation: boolean, filePath?: string): Promise<string> {
|
||||||
const hasAvatar = !!this.getAvatarURL(mxEv);
|
const avatarUrl = this.getAvatarURL(mxEv);
|
||||||
|
const hasAvatar = !!avatarUrl;
|
||||||
if (hasAvatar) await this.saveAvatarIfNeeded(mxEv);
|
if (hasAvatar) await this.saveAvatarIfNeeded(mxEv);
|
||||||
const EventTile = this.getEventTile(mxEv, continuation);
|
const EventTile = this.getEventTile(mxEv, continuation);
|
||||||
let eventTileMarkup: string;
|
let eventTileMarkup: string;
|
||||||
|
@ -312,8 +309,8 @@ export default class HTMLExporter extends Exporter {
|
||||||
eventTileMarkup = eventTileMarkup.replace(/<span class="mx_MFileBody_info_icon".*?>.*?<\/span>/, "");
|
eventTileMarkup = eventTileMarkup.replace(/<span class="mx_MFileBody_info_icon".*?>.*?<\/span>/, "");
|
||||||
if (hasAvatar) {
|
if (hasAvatar) {
|
||||||
eventTileMarkup = eventTileMarkup.replace(
|
eventTileMarkup = eventTileMarkup.replace(
|
||||||
encodeURI(this.getAvatarURL(mxEv)).replace(/&/g, "&"),
|
encodeURI(avatarUrl).replace(/&/g, "&"),
|
||||||
`users/${mxEv.sender.userId.replace(/:/g, "-")}.png`,
|
`users/${mxEv.sender!.userId.replace(/:/g, "-")}.png`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return eventTileMarkup;
|
return eventTileMarkup;
|
||||||
|
|
|
@ -58,8 +58,8 @@ const getExportCSS = async (usedClasses: Set<string>): Promise<string> => {
|
||||||
|
|
||||||
// If the light theme isn't loaded we will have to fetch & parse it manually
|
// If the light theme isn't loaded we will have to fetch & parse it manually
|
||||||
if (!stylesheets.some(isLightTheme)) {
|
if (!stylesheets.some(isLightTheme)) {
|
||||||
const href = document.querySelector<HTMLLinkElement>('link[rel="stylesheet"][href$="theme-light.css"]').href;
|
const href = document.querySelector<HTMLLinkElement>('link[rel="stylesheet"][href$="theme-light.css"]')?.href;
|
||||||
stylesheets.push(await getRulesFromCssFile(href));
|
if (href) stylesheets.push(await getRulesFromCssFile(href));
|
||||||
}
|
}
|
||||||
|
|
||||||
let css = "";
|
let css = "";
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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 { render, RenderResult } from "@testing-library/react";
|
||||||
|
import { EventType, MatrixEvent } from "matrix-js-sdk/src/matrix";
|
||||||
|
|
||||||
|
import type { MatrixClient } from "matrix-js-sdk/src/matrix";
|
||||||
|
import { flushPromises, mkMessage, stubClient } from "../../../test-utils";
|
||||||
|
import MessageEditHistoryDialog from "../../../../src/components/views/dialogs/MessageEditHistoryDialog";
|
||||||
|
|
||||||
|
describe("<MessageEditHistory />", () => {
|
||||||
|
const roomId = "!aroom:example.com";
|
||||||
|
let client: jest.Mocked<MatrixClient>;
|
||||||
|
let event: MatrixEvent;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
client = stubClient() as jest.Mocked<MatrixClient>;
|
||||||
|
event = mkMessage({
|
||||||
|
event: true,
|
||||||
|
user: "@user:example.com",
|
||||||
|
room: "!room:example.com",
|
||||||
|
msg: "My Great Message",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
async function renderComponent(): Promise<RenderResult> {
|
||||||
|
const result = render(<MessageEditHistoryDialog mxEvent={event} onFinished={jest.fn()} />);
|
||||||
|
await flushPromises();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function mockEdits(...edits: { msg: string; ts: number | undefined }[]) {
|
||||||
|
client.relations.mockImplementation(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
events: edits.map(
|
||||||
|
(e) =>
|
||||||
|
new MatrixEvent({
|
||||||
|
type: EventType.RoomMessage,
|
||||||
|
room_id: roomId,
|
||||||
|
origin_server_ts: e.ts,
|
||||||
|
content: {
|
||||||
|
body: e.msg,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
it("should match the snapshot", async () => {
|
||||||
|
mockEdits({ msg: "My Great Massage", ts: 1234 });
|
||||||
|
|
||||||
|
const { container } = await renderComponent();
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should support events with ", async () => {
|
||||||
|
mockEdits(
|
||||||
|
{ msg: "My Great Massage", ts: undefined },
|
||||||
|
{ msg: "My Great Massage?", ts: undefined },
|
||||||
|
{ msg: "My Great Missage", ts: undefined },
|
||||||
|
);
|
||||||
|
|
||||||
|
const { container } = await renderComponent();
|
||||||
|
|
||||||
|
expect(container).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,322 @@
|
||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`<MessageEditHistory /> should match the snapshot 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-focus-guard="true"
|
||||||
|
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-labelledby="mx_BaseDialog_title"
|
||||||
|
class="mx_MessageEditHistoryDialog 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"
|
||||||
|
>
|
||||||
|
Message edits
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_AutoHideScrollbar mx_ScrollPanel mx_MessageEditHistoryDialog_scrollPanel"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_RoomView_messageListWrapper"
|
||||||
|
>
|
||||||
|
<ol
|
||||||
|
aria-live="polite"
|
||||||
|
class="mx_RoomView_MessageList"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="mx_MessageEditHistoryDialog_edits"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<div
|
||||||
|
aria-label="Thu, Jan 1 1970"
|
||||||
|
class="mx_DateSeparator"
|
||||||
|
role="separator"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<hr
|
||||||
|
role="none"
|
||||||
|
/>
|
||||||
|
<h2
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
Thu, Jan 1 1970
|
||||||
|
</h2>
|
||||||
|
<hr
|
||||||
|
role="none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div
|
||||||
|
class="mx_EventTile"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_EventTile_line"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_MessageTimestamp"
|
||||||
|
>
|
||||||
|
00:00
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="mx_EventTile_content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_EventTile_body"
|
||||||
|
dir="auto"
|
||||||
|
>
|
||||||
|
My Great Massage
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_MessageActionBar"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_AccessibleButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
data-focus-guard="true"
|
||||||
|
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<MessageEditHistory /> should support events with 1`] = `
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
data-focus-guard="true"
|
||||||
|
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
<div
|
||||||
|
aria-labelledby="mx_BaseDialog_title"
|
||||||
|
class="mx_MessageEditHistoryDialog 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"
|
||||||
|
>
|
||||||
|
Message edits
|
||||||
|
</h2>
|
||||||
|
<div
|
||||||
|
aria-label="Close dialog"
|
||||||
|
class="mx_AccessibleButton mx_Dialog_cancelButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_AutoHideScrollbar mx_ScrollPanel mx_MessageEditHistoryDialog_scrollPanel"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_RoomView_messageListWrapper"
|
||||||
|
>
|
||||||
|
<ol
|
||||||
|
aria-live="polite"
|
||||||
|
class="mx_RoomView_MessageList"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="mx_MessageEditHistoryDialog_edits"
|
||||||
|
>
|
||||||
|
<li>
|
||||||
|
<div
|
||||||
|
aria-label=", NaN NaN"
|
||||||
|
class="mx_DateSeparator"
|
||||||
|
role="separator"
|
||||||
|
tabindex="-1"
|
||||||
|
>
|
||||||
|
<hr
|
||||||
|
role="none"
|
||||||
|
/>
|
||||||
|
<h2
|
||||||
|
aria-hidden="true"
|
||||||
|
>
|
||||||
|
, NaN NaN
|
||||||
|
</h2>
|
||||||
|
<hr
|
||||||
|
role="none"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div
|
||||||
|
class="mx_EventTile"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_EventTile_line"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_MessageTimestamp"
|
||||||
|
>
|
||||||
|
NaN:NaN
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="mx_EventTile_content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_EventTile_body markdown-body"
|
||||||
|
dir="auto"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
My Great Massage
|
||||||
|
<span
|
||||||
|
class="mx_EditHistoryMessage_deletion"
|
||||||
|
>
|
||||||
|
?
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_MessageActionBar"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_AccessibleButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div
|
||||||
|
class="mx_EventTile"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_EventTile_line"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_MessageTimestamp"
|
||||||
|
>
|
||||||
|
NaN:NaN
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="mx_EventTile_content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_EventTile_body markdown-body"
|
||||||
|
dir="auto"
|
||||||
|
>
|
||||||
|
<span>
|
||||||
|
My Great M
|
||||||
|
<span
|
||||||
|
class="mx_EditHistoryMessage_deletion"
|
||||||
|
>
|
||||||
|
i
|
||||||
|
</span>
|
||||||
|
<span
|
||||||
|
class="mx_EditHistoryMessage_insertion"
|
||||||
|
>
|
||||||
|
a
|
||||||
|
</span>
|
||||||
|
ssage
|
||||||
|
<span
|
||||||
|
class="mx_EditHistoryMessage_insertion"
|
||||||
|
>
|
||||||
|
?
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_MessageActionBar"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_AccessibleButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<div
|
||||||
|
class="mx_EventTile"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_EventTile_line"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_MessageTimestamp"
|
||||||
|
>
|
||||||
|
NaN:NaN
|
||||||
|
</span>
|
||||||
|
<div
|
||||||
|
class="mx_EventTile_content"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="mx_EventTile_body"
|
||||||
|
dir="auto"
|
||||||
|
>
|
||||||
|
My Great Missage
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="mx_MessageActionBar"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="mx_AccessibleButton"
|
||||||
|
role="button"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
Remove
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
data-focus-guard="true"
|
||||||
|
style="width: 1px; height: 0px; padding: 0px; overflow: hidden; position: fixed; top: 1px; left: 1px;"
|
||||||
|
tabindex="0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
Copyright 2022 - 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -17,7 +17,7 @@ limitations under the License.
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
import { MatrixEvent } from "matrix-js-sdk/src/models/event";
|
||||||
import { EventType } from "matrix-js-sdk/src/@types/event";
|
import { EventType } from "matrix-js-sdk/src/@types/event";
|
||||||
import { render } from "@testing-library/react";
|
import { render, type RenderResult } from "@testing-library/react";
|
||||||
import { Room } from "matrix-js-sdk/src/models/room";
|
import { Room } from "matrix-js-sdk/src/models/room";
|
||||||
|
|
||||||
import { stubClient } from "../../../test-utils";
|
import { stubClient } from "../../../test-utils";
|
||||||
|
@ -26,6 +26,8 @@ import { MatrixClientPeg } from "../../../../src/MatrixClientPeg";
|
||||||
|
|
||||||
const ROOM_ID = "!qPewotXpIctQySfjSy:localhost";
|
const ROOM_ID = "!qPewotXpIctQySfjSy:localhost";
|
||||||
|
|
||||||
|
type Props = React.ComponentPropsWithoutRef<typeof SearchResultTile>;
|
||||||
|
|
||||||
describe("SearchResultTile", () => {
|
describe("SearchResultTile", () => {
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
stubClient();
|
stubClient();
|
||||||
|
@ -35,50 +37,72 @@ describe("SearchResultTile", () => {
|
||||||
jest.spyOn(cli, "getRoom").mockReturnValue(room);
|
jest.spyOn(cli, "getRoom").mockReturnValue(room);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function renderComponent(props: Partial<Props>): RenderResult {
|
||||||
|
return render(<SearchResultTile timeline={[]} ourEventsIndexes={[1]} {...props} />);
|
||||||
|
}
|
||||||
|
|
||||||
it("Sets up appropriate callEventGrouper for m.call. events", () => {
|
it("Sets up appropriate callEventGrouper for m.call. events", () => {
|
||||||
const { container } = render(
|
const { container } = renderComponent({
|
||||||
<SearchResultTile
|
timeline: [
|
||||||
timeline={[
|
new MatrixEvent({
|
||||||
new MatrixEvent({
|
type: EventType.CallInvite,
|
||||||
type: EventType.CallInvite,
|
sender: "@user1:server",
|
||||||
sender: "@user1:server",
|
room_id: ROOM_ID,
|
||||||
room_id: ROOM_ID,
|
origin_server_ts: 1432735824652,
|
||||||
origin_server_ts: 1432735824652,
|
content: { call_id: "call.1" },
|
||||||
content: { call_id: "call.1" },
|
event_id: "$1:server",
|
||||||
event_id: "$1:server",
|
}),
|
||||||
}),
|
new MatrixEvent({
|
||||||
new MatrixEvent({
|
content: {
|
||||||
content: {
|
body: "This is an example text message",
|
||||||
body: "This is an example text message",
|
format: "org.matrix.custom.html",
|
||||||
format: "org.matrix.custom.html",
|
formatted_body: "<b>This is an example text message</b>",
|
||||||
formatted_body: "<b>This is an example text message</b>",
|
msgtype: "m.text",
|
||||||
msgtype: "m.text",
|
},
|
||||||
},
|
event_id: "$144429830826TWwbB:localhost",
|
||||||
event_id: "$144429830826TWwbB:localhost",
|
origin_server_ts: 1432735824653,
|
||||||
origin_server_ts: 1432735824653,
|
room_id: ROOM_ID,
|
||||||
room_id: ROOM_ID,
|
sender: "@example:example.org",
|
||||||
sender: "@example:example.org",
|
type: "m.room.message",
|
||||||
type: "m.room.message",
|
unsigned: {
|
||||||
unsigned: {
|
age: 1234,
|
||||||
age: 1234,
|
},
|
||||||
},
|
}),
|
||||||
}),
|
new MatrixEvent({
|
||||||
new MatrixEvent({
|
type: EventType.CallAnswer,
|
||||||
type: EventType.CallAnswer,
|
sender: "@user2:server",
|
||||||
sender: "@user2:server",
|
room_id: ROOM_ID,
|
||||||
room_id: ROOM_ID,
|
origin_server_ts: 1432735824654,
|
||||||
origin_server_ts: 1432735824654,
|
content: { call_id: "call.1" },
|
||||||
content: { call_id: "call.1" },
|
event_id: "$2:server",
|
||||||
event_id: "$2:server",
|
}),
|
||||||
}),
|
],
|
||||||
]}
|
});
|
||||||
ourEventsIndexes={[1]}
|
|
||||||
/>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const tiles = container.querySelectorAll<HTMLElement>(".mx_EventTile");
|
const tiles = container.querySelectorAll<HTMLElement>(".mx_EventTile");
|
||||||
expect(tiles.length).toEqual(2);
|
expect(tiles.length).toEqual(2);
|
||||||
expect(tiles[0].dataset.eventId).toBe("$1:server");
|
expect(tiles[0]!.dataset.eventId).toBe("$1:server");
|
||||||
expect(tiles[1].dataset.eventId).toBe("$144429830826TWwbB:localhost");
|
expect(tiles[1]!.dataset.eventId).toBe("$144429830826TWwbB:localhost");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("supports events with missing timestamps", () => {
|
||||||
|
const { container } = renderComponent({
|
||||||
|
timeline: [...Array(20)].map(
|
||||||
|
(_, i) =>
|
||||||
|
new MatrixEvent({
|
||||||
|
type: EventType.RoomMessage,
|
||||||
|
sender: "@user1:server",
|
||||||
|
room_id: ROOM_ID,
|
||||||
|
content: { body: `Message #${i}` },
|
||||||
|
event_id: `$${i}:server`,
|
||||||
|
origin_server_ts: undefined,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
});
|
||||||
|
|
||||||
|
const separators = container.querySelectorAll(".mx_DateSeparator");
|
||||||
|
// One separator is always rendered at the top, we don't want any
|
||||||
|
// between messages.
|
||||||
|
expect(separators.length).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -208,6 +208,10 @@ export function createTestClient(): MatrixClient {
|
||||||
setPassword: jest.fn().mockRejectedValue({}),
|
setPassword: jest.fn().mockRejectedValue({}),
|
||||||
groupCallEventHandler: { groupCalls: new Map<string, GroupCall>() },
|
groupCallEventHandler: { groupCalls: new Map<string, GroupCall>() },
|
||||||
redactEvent: jest.fn(),
|
redactEvent: jest.fn(),
|
||||||
|
|
||||||
|
createMessagesRequest: jest.fn().mockResolvedValue({
|
||||||
|
chunk: [],
|
||||||
|
}),
|
||||||
} as unknown as MatrixClient;
|
} as unknown as MatrixClient;
|
||||||
|
|
||||||
client.reEmitter = new ReEmitter(client);
|
client.reEmitter = new ReEmitter(client);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright 2022 The Matrix.org Foundation C.I.C.
|
Copyright 2022 - 2023 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
@ -14,26 +14,101 @@ See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { mocked } from "jest-mock";
|
import { EventType, IRoomEvent, MatrixClient, MatrixEvent, MsgType, Room, RoomMember } from "matrix-js-sdk/src/matrix";
|
||||||
|
import fetchMock from "fetch-mock-jest";
|
||||||
|
|
||||||
import { createTestClient, mkStubRoom, REPEATABLE_DATE } from "../../test-utils";
|
import { filterConsole, mkStubRoom, REPEATABLE_DATE, stubClient } from "../../test-utils";
|
||||||
import { ExportType, IExportOptions } from "../../../src/utils/exportUtils/exportUtils";
|
import { ExportType, IExportOptions } from "../../../src/utils/exportUtils/exportUtils";
|
||||||
import SdkConfig from "../../../src/SdkConfig";
|
import SdkConfig from "../../../src/SdkConfig";
|
||||||
import HTMLExporter from "../../../src/utils/exportUtils/HtmlExport";
|
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", () => {
|
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(() => {
|
beforeEach(() => {
|
||||||
jest.useFakeTimers();
|
jest.useFakeTimers();
|
||||||
jest.setSystemTime(REPEATABLE_DATE);
|
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);
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
function mockMessages(...events: IRoomEvent[]): void {
|
||||||
mocked(SdkConfig.get).mockRestore();
|
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;
|
||||||
|
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", () => {
|
it("should have an SDK-branded destination file name", () => {
|
||||||
const roomName = "My / Test / Room: Welcome";
|
const roomName = "My / Test / Room: Welcome";
|
||||||
const client = createTestClient();
|
|
||||||
const stubOptions: IExportOptions = {
|
const stubOptions: IExportOptions = {
|
||||||
attachmentsIncluded: false,
|
attachmentsIncluded: false,
|
||||||
maxSize: 50000000,
|
maxSize: 50000000,
|
||||||
|
@ -43,10 +118,201 @@ describe("HTMLExport", () => {
|
||||||
|
|
||||||
expect(exporter.destinationFileName).toMatchSnapshot();
|
expect(exporter.destinationFileName).toMatchSnapshot();
|
||||||
|
|
||||||
jest.spyOn(SdkConfig, "get").mockImplementation(() => {
|
SdkConfig.put({ brand: "BrandedChat/WithSlashes/ForFun" });
|
||||||
return { brand: "BrandedChat/WithSlashes/ForFun" };
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(exporter.destinationFileName).toMatchSnapshot();
|
expect(exporter.destinationFileName).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should export", async () => {
|
||||||
|
const events = [...Array(50)].map<IRoomEvent>((_, i) => ({
|
||||||
|
event_id: `${i}`,
|
||||||
|
type: EventType.RoomMessage,
|
||||||
|
sender: `@user${i}:example.com`,
|
||||||
|
origin_server_ts: 5_000 + i * 1000,
|
||||||
|
content: {
|
||||||
|
msgtype: "m.text",
|
||||||
|
body: `Message #${i}`,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
mockMessages(...events);
|
||||||
|
|
||||||
|
const exporter = new HTMLExporter(
|
||||||
|
room,
|
||||||
|
ExportType.LastNMessages,
|
||||||
|
{
|
||||||
|
attachmentsIncluded: false,
|
||||||
|
maxSize: 1_024 * 1_024,
|
||||||
|
numberOfMessages: events.length,
|
||||||
|
},
|
||||||
|
() => {},
|
||||||
|
);
|
||||||
|
|
||||||
|
await exporter.export();
|
||||||
|
|
||||||
|
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/);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 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 getExportCSS from "../../../src/utils/exportUtils/exportCSS";
|
||||||
|
|
||||||
|
describe("exportCSS", () => {
|
||||||
|
describe("getExportCSS", () => {
|
||||||
|
it("supports documents missing stylesheets", async () => {
|
||||||
|
const css = await getExportCSS(new Set());
|
||||||
|
expect(css).not.toContain("color-scheme: light");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Reference in New Issue