Apply PR Suggestions

This commit is contained in:
Jaiwanth 2021-08-13 08:30:50 +05:30
parent d32f945e24
commit edfc8af6cf
8 changed files with 143 additions and 144 deletions

View file

@ -24,8 +24,8 @@ import Field from "../elements/Field";
import StyledRadioGroup from "../elements/StyledRadioGroup"; import StyledRadioGroup from "../elements/StyledRadioGroup";
import StyledCheckbox from "../elements/StyledCheckbox"; import StyledCheckbox from "../elements/StyledCheckbox";
import { import {
ExportFormats, ExportFormat,
ExportTypes, ExportType,
textForFormat, textForFormat,
textForType, textForType,
} from "../../../utils/exportUtils/exportUtils"; } from "../../../utils/exportUtils/exportUtils";
@ -42,8 +42,8 @@ interface IProps extends IDialogProps {
} }
const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => { const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
const [exportFormat, setExportFormat] = useState(ExportFormats.HTML); const [exportFormat, setExportFormat] = useState(ExportFormat.Html);
const [exportType, setExportType] = useState(ExportTypes.TIMELINE); const [exportType, setExportType] = useState(ExportType.Timeline);
const [includeAttachments, setAttachments] = useState(false); const [includeAttachments, setAttachments] = useState(false);
const [isExporting, setExporting] = useState(false); const [isExporting, setExporting] = useState(false);
const [numberOfMessages, setNumberOfMessages] = useState<number>(100); const [numberOfMessages, setNumberOfMessages] = useState<number>(100);
@ -70,31 +70,31 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
maxSize: sizeLimit * 1024 * 1024, maxSize: sizeLimit * 1024 * 1024,
}; };
switch (exportFormat) { switch (exportFormat) {
case ExportFormats.HTML: case ExportFormat.Html:
setExporter( setExporter(
new HTMLExporter( new HTMLExporter(
room, room,
ExportTypes[exportType], ExportType[exportType],
exportOptions, exportOptions,
exportProgressRef, exportProgressRef,
), ),
); );
break; break;
case ExportFormats.JSON: case ExportFormat.Json:
setExporter( setExporter(
new JSONExporter( new JSONExporter(
room, room,
ExportTypes[exportType], ExportType[exportType],
exportOptions, exportOptions,
exportProgressRef, exportProgressRef,
), ),
); );
break; break;
case ExportFormats.PLAIN_TEXT: case ExportFormat.PlainText:
setExporter( setExporter(
new PlainTextExporter( new PlainTextExporter(
room, room,
ExportTypes[exportType], ExportType[exportType],
exportOptions, exportOptions,
exportProgressRef, exportProgressRef,
), ),
@ -114,7 +114,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
sizeLimitRef.current.validate({ focused: true }); sizeLimitRef.current.validate({ focused: true });
return; return;
} }
if (exportType === ExportTypes.LAST_N_MESSAGES) { if (exportType === ExportType.LastNMessages) {
const isValidNumberOfMessages = const isValidNumberOfMessages =
await messageCountRef.current.validate({ focused: false }); await messageCountRef.current.validate({ focused: false });
if (!isValidNumberOfMessages) { if (!isValidNumberOfMessages) {
@ -202,12 +202,12 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
}); });
}; };
const exportFormatOptions = Object.keys(ExportFormats).map((format) => ({ const exportFormatOptions = Object.keys(ExportFormat).map((format) => ({
value: format, value: format,
label: textForFormat(format), label: textForFormat(format),
})); }));
const exportTypeOptions = Object.keys(ExportTypes).map((type) => { const exportTypeOptions = Object.keys(ExportType).map((type) => {
return ( return (
<option key={type} value={type}> <option key={type} value={type}>
{ textForType(type) } { textForType(type) }
@ -216,7 +216,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
}); });
let messageCount = null; let messageCount = null;
if (exportType === ExportTypes.LAST_N_MESSAGES) { if (exportType === ExportType.LastNMessages) {
messageCount = ( messageCount = (
<Field <Field
element="input" element="input"
@ -322,7 +322,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
<StyledRadioGroup <StyledRadioGroup
name="exportFormat" name="exportFormat"
value={exportFormat} value={exportFormat}
onChange={(key) => setExportFormat(ExportFormats[key])} onChange={(key) => setExportFormat(ExportFormat[key])}
definitions={exportFormatOptions} definitions={exportFormatOptions}
/> />
@ -334,7 +334,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
element="select" element="select"
value={exportType} value={exportType}
onChange={(e) => { onChange={(e) => {
setExportType(ExportTypes[e.target.value]); setExportType(ExportType[e.target.value]);
}} }}
> >
{ exportTypeOptions } { exportTypeOptions }

View file

@ -222,125 +222,125 @@ export default class MFileBody extends React.Component<IProps, IState> {
{ placeholder } { placeholder }
</a> </a>
</span>; </span>;
} else { }
const showDownloadLink = this.props.tileShape || !this.props.showGenericPlaceholder;
if (isEncrypted) { const showDownloadLink = this.props.tileShape || !this.props.showGenericPlaceholder;
if (!this.state.decryptedBlob) {
if (isEncrypted) {
if (!this.state.decryptedBlob) {
// Need to decrypt the attachment // Need to decrypt the attachment
// Wait for the user to click on the link before downloading // Wait for the user to click on the link before downloading
// and decrypting the attachment. // and decrypting the attachment.
// This button should actually Download because usercontent/ will try to click itself // This button should actually Download because usercontent/ will try to click itself
// but it is not guaranteed between various browsers' settings. // but it is not guaranteed between various browsers' settings.
return (
<span className="mx_MFileBody">
{ placeholder }
{ showDownloadLink && <div className="mx_MFileBody_download">
<AccessibleButton onClick={this.decryptFile}>
{ _t("Decrypt %(text)s", { text: this.linkText }) }
</AccessibleButton>
</div> }
</span>
);
}
const url = "usercontent/"; // XXX: this path should probably be passed from the skin
// If the attachment is encrypted then put the link inside an iframe.
return ( return (
<span className="mx_MFileBody"> <span className="mx_MFileBody">
{ placeholder } { placeholder }
{ showDownloadLink && <div className="mx_MFileBody_download"> { showDownloadLink && <div className="mx_MFileBody_download">
<div style={{ display: "none" }}> <AccessibleButton onClick={this.decryptFile}>
{ /* { _t("Decrypt %(text)s", { text: this.linkText }) }
</AccessibleButton>
</div> }
</span>
);
}
const url = "usercontent/"; // XXX: this path should probably be passed from the skin
// If the attachment is encrypted then put the link inside an iframe.
return (
<span className="mx_MFileBody">
{ placeholder }
{ showDownloadLink && <div className="mx_MFileBody_download">
<div style={{ display: "none" }}>
{ /*
* Add dummy copy of the "a" tag * Add dummy copy of the "a" tag
* We'll use it to learn how the download link * We'll use it to learn how the download link
* would have been styled if it was rendered inline. * would have been styled if it was rendered inline.
*/ } */ }
<a ref={this.dummyLink} /> <a ref={this.dummyLink} />
</div> </div>
{ /* { /*
TODO: Move iframe (and dummy link) into FileDownloader. TODO: Move iframe (and dummy link) into FileDownloader.
We currently have it set up this way because of styles applied to the iframe We currently have it set up this way because of styles applied to the iframe
itself which cannot be easily handled/overridden by the FileDownloader. In itself which cannot be easily handled/overridden by the FileDownloader. In
future, the download link may disappear entirely at which point it could also future, the download link may disappear entirely at which point it could also
be suitable to just remove this bit of code. be suitable to just remove this bit of code.
*/ } */ }
<iframe <iframe
src={url} src={url}
onLoad={() => this.downloadFile(this.fileName, this.linkText)} onLoad={() => this.downloadFile(this.fileName, this.linkText)}
ref={this.iframe} ref={this.iframe}
sandbox="allow-scripts allow-downloads allow-downloads-without-user-activation" /> sandbox="allow-scripts allow-downloads allow-downloads-without-user-activation" />
</div> } </div> }
</span> </span>
); );
} else if (contentUrl) { } else if (contentUrl) {
const downloadProps = { const downloadProps = {
target: "_blank", target: "_blank",
rel: "noreferrer noopener", rel: "noreferrer noopener",
// We set the href regardless of whether or not we intercept the download // We set the href regardless of whether or not we intercept the download
// because we don't really want to convert the file to a blob eagerly, and // because we don't really want to convert the file to a blob eagerly, and
// still want "open in new tab" and "save link as" to work. // still want "open in new tab" and "save link as" to work.
href: contentUrl, href: contentUrl,
}; };
// Blobs can only have up to 500mb, so if the file reports as being too large then // Blobs can only have up to 500mb, so if the file reports as being too large then
// we won't try and convert it. Likewise, if the file size is unknown then we'll assume // we won't try and convert it. Likewise, if the file size is unknown then we'll assume
// it is too big. There is the risk of the reported file size and the actual file size // it is too big. There is the risk of the reported file size and the actual file size
// being different, however the user shouldn't normally run into this problem. // being different, however the user shouldn't normally run into this problem.
const fileTooBig = typeof(fileSize) === 'number' ? fileSize > 524288000 : true; const fileTooBig = typeof(fileSize) === 'number' ? fileSize > 524288000 : true;
if (["application/pdf"].includes(fileType) && !fileTooBig) { if (["application/pdf"].includes(fileType) && !fileTooBig) {
// We want to force a download on this type, so use an onClick handler. // We want to force a download on this type, so use an onClick handler.
downloadProps["onClick"] = (e) => { downloadProps["onClick"] = (e) => {
console.log(`Downloading ${fileType} as blob (unencrypted)`); console.log(`Downloading ${fileType} as blob (unencrypted)`);
// Avoid letting the <a> do its thing // Avoid letting the <a> do its thing
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
// Start a fetch for the download // Start a fetch for the download
// Based upon https://stackoverflow.com/a/49500465 // Based upon https://stackoverflow.com/a/49500465
this.props.mediaEventHelper.sourceBlob.value.then((blob) => { this.props.mediaEventHelper.sourceBlob.value.then((blob) => {
const blobUrl = URL.createObjectURL(blob); const blobUrl = URL.createObjectURL(blob);
// We have to create an anchor to download the file // We have to create an anchor to download the file
const tempAnchor = document.createElement('a'); const tempAnchor = document.createElement('a');
tempAnchor.download = this.fileName; tempAnchor.download = this.fileName;
tempAnchor.href = blobUrl; tempAnchor.href = blobUrl;
document.body.appendChild(tempAnchor); // for firefox: https://stackoverflow.com/a/32226068 document.body.appendChild(tempAnchor); // for firefox: https://stackoverflow.com/a/32226068
tempAnchor.click(); tempAnchor.click();
tempAnchor.remove(); tempAnchor.remove();
}); });
}; };
} else {
// Else we are hoping the browser will do the right thing
downloadProps["download"] = this.fileName;
}
return (
<span className="mx_MFileBody">
{ placeholder }
{ showDownloadLink && <div className="mx_MFileBody_download">
<a {...downloadProps}>
<span className="mx_MFileBody_download_icon" />
{ _t("Download %(text)s", { text: this.linkText }) }
</a>
{ this.props.tileShape === TileShape.FileGrid && <div className="mx_MImageBody_size">
{ this.content.info && this.content.info.size ? filesize(this.content.info.size) : "" }
</div> }
</div> }
</span>
);
} else { } else {
const extra = this.linkText ? (': ' + this.linkText) : ''; // Else we are hoping the browser will do the right thing
return <span className="mx_MFileBody"> downloadProps["download"] = this.fileName;
{ placeholder }
{ _t("Invalid file%(extra)s", { extra: extra }) }
</span>;
} }
return (
<span className="mx_MFileBody">
{ placeholder }
{ showDownloadLink && <div className="mx_MFileBody_download">
<a {...downloadProps}>
<span className="mx_MFileBody_download_icon" />
{ _t("Download %(text)s", { text: this.linkText }) }
</a>
{ this.props.tileShape === TileShape.FileGrid && <div className="mx_MImageBody_size">
{ this.content.info && this.content.info.size ? filesize(this.content.info.size) : "" }
</div> }
</div> }
</span>
);
} else {
const extra = this.linkText ? (': ' + this.linkText) : '';
return <span className="mx_MFileBody">
{ placeholder }
{ _t("Invalid file%(extra)s", { extra: extra }) }
</span>;
} }
} }
} }

View file

@ -17,7 +17,7 @@ limitations under the License.
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { Room } from "matrix-js-sdk/src/models/room"; import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixClientPeg } from "../../MatrixClientPeg"; import { MatrixClientPeg } from "../../MatrixClientPeg";
import { IExportOptions, ExportTypes } from "./exportUtils"; import { IExportOptions, ExportType } from "./exportUtils";
import { decryptFile } from "../DecryptFile"; import { decryptFile } from "../DecryptFile";
import { mediaFromContent } from "../../customisations/Media"; import { mediaFromContent } from "../../customisations/Media";
import { formatFullDateNoDay } from "../../DateUtils"; import { formatFullDateNoDay } from "../../DateUtils";
@ -38,13 +38,14 @@ export default abstract class Exporter {
protected constructor( protected constructor(
protected room: Room, protected room: Room,
protected exportType: ExportTypes, protected exportType: ExportType,
protected exportOptions: IExportOptions, protected exportOptions: IExportOptions,
protected exportProgressRef: MutableRefObject<HTMLParagraphElement>, protected exportProgressRef: MutableRefObject<HTMLParagraphElement>,
) { ) {
if (exportOptions.maxSize < 1 * 1024 * 1024|| if (exportOptions.maxSize < 1 * 1024 * 1024|| // Less than 1 MB
exportOptions.maxSize > 2000 * 1024 * 1024|| exportOptions.maxSize > 2000 * 1024 * 1024|| // More than ~ 2 GB
exportOptions.numberOfMessages > 10**8) { exportOptions.numberOfMessages > 10**8
) {
throw new Error("Invalid export options"); throw new Error("Invalid export options");
} }
this.cancelled = false; this.cancelled = false;
@ -118,10 +119,10 @@ export default abstract class Exporter {
public getLimit(): number { public getLimit(): number {
let limit: number; let limit: number;
switch (this.exportType) { switch (this.exportType) {
case ExportTypes.LAST_N_MESSAGES: case ExportType.LastNMessages:
limit = this.exportOptions.numberOfMessages; limit = this.exportOptions.numberOfMessages;
break; break;
case ExportTypes.TIMELINE: case ExportType.Timeline:
limit = 40; limit = 40;
break; break;
default: default:
@ -166,7 +167,7 @@ export default abstract class Exporter {
events.push(mxEv); events.push(mxEv);
} }
this.updateProgress( this.updateProgress(
("Fetched " + events.length + " events ") + (this.exportType === ExportTypes.LAST_N_MESSAGES ("Fetched " + events.length + " events ") + (this.exportType === ExportType.LastNMessages
? `out of ${this.exportOptions.numberOfMessages}` ? `out of ${this.exportOptions.numberOfMessages}`
: "so far"), : "so far"),
); );

View file

@ -32,7 +32,7 @@ import DateSeparator from "../../components/views/messages/DateSeparator";
import BaseAvatar from "../../components/views/avatars/BaseAvatar"; import BaseAvatar from "../../components/views/avatars/BaseAvatar";
import exportJS from "!!raw-loader!./exportJS"; import exportJS from "!!raw-loader!./exportJS";
import exportIcons from "./exportIcons"; import exportIcons from "./exportIcons";
import { ExportTypes } from "./exportUtils"; import { ExportType } from "./exportUtils";
import { IExportOptions } from "./exportUtils"; import { IExportOptions } from "./exportUtils";
import MatrixClientContext from "../../contexts/MatrixClientContext"; import MatrixClientContext from "../../contexts/MatrixClientContext";
import getExportCSS from "./exportCSS"; import getExportCSS from "./exportCSS";
@ -46,7 +46,7 @@ export default class HTMLExporter extends Exporter {
constructor( constructor(
room: Room, room: Room,
exportType: ExportTypes, exportType: ExportType,
exportOptions: IExportOptions, exportOptions: IExportOptions,
exportProgressRef: MutableRefObject<HTMLParagraphElement>, exportProgressRef: MutableRefObject<HTMLParagraphElement>,
) { ) {

View file

@ -19,24 +19,22 @@ import { Room } from "matrix-js-sdk/src/models/room";
import { MatrixEvent } from "matrix-js-sdk/src/models/event"; import { MatrixEvent } from "matrix-js-sdk/src/models/event";
import { formatFullDateNoDay, formatFullDateNoDayNoTime } from "../../DateUtils"; import { formatFullDateNoDay, formatFullDateNoDayNoTime } from "../../DateUtils";
import { haveTileForEvent } from "../../components/views/rooms/EventTile"; import { haveTileForEvent } from "../../components/views/rooms/EventTile";
import { ExportTypes } from "./exportUtils"; import { ExportType } from "./exportUtils";
import { IExportOptions } from "./exportUtils"; import { IExportOptions } from "./exportUtils";
import { EventType } from "matrix-js-sdk/src/@types/event"; import { EventType } from "matrix-js-sdk/src/@types/event";
import { MutableRefObject } from "react"; import { MutableRefObject } from "react";
export default class JSONExporter extends Exporter { export default class JSONExporter extends Exporter {
protected totalSize: number; protected totalSize = 0;
protected messages: any[]; protected messages: any[] = [];
constructor( constructor(
room: Room, room: Room,
exportType: ExportTypes, exportType: ExportType,
exportOptions: IExportOptions, exportOptions: IExportOptions,
exportProgressRef: MutableRefObject<HTMLParagraphElement>, exportProgressRef: MutableRefObject<HTMLParagraphElement>,
) { ) {
super(room, exportType, exportOptions, exportProgressRef); super(room, exportType, exportOptions, exportProgressRef);
this.totalSize = 0;
this.messages = [];
} }
protected createJSONString(): string { protected createJSONString(): string {

View file

@ -20,7 +20,7 @@ import { IContent, MatrixEvent } from "matrix-js-sdk/src/models/event";
import { formatFullDateNoDay } from "../../DateUtils"; import { formatFullDateNoDay } from "../../DateUtils";
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
import { haveTileForEvent } from "../../components/views/rooms/EventTile"; import { haveTileForEvent } from "../../components/views/rooms/EventTile";
import { ExportTypes } from "./exportUtils"; import { ExportType } from "./exportUtils";
import { IExportOptions } from "./exportUtils"; import { IExportOptions } from "./exportUtils";
import { textForEvent } from "../../TextForEvent"; import { textForEvent } from "../../TextForEvent";
import { MutableRefObject } from "react"; import { MutableRefObject } from "react";
@ -31,7 +31,7 @@ export default class PlainTextExporter extends Exporter {
constructor( constructor(
room: Room, room: Room,
exportType: ExportTypes, exportType: ExportType,
exportOptions: IExportOptions, exportOptions: IExportOptions,
exportProgressRef: MutableRefObject<HTMLParagraphElement>, exportProgressRef: MutableRefObject<HTMLParagraphElement>,
) { ) {

View file

@ -16,26 +16,26 @@ limitations under the License.
import { _t } from "../../languageHandler"; import { _t } from "../../languageHandler";
export enum ExportFormats { export enum ExportFormat {
HTML = "HTML", Html = "HTML",
PLAIN_TEXT = "PLAIN_TEXT", PlainText = "PLAIN_TEXT",
JSON = "JSON", Json = "JSON",
} }
export enum ExportTypes { export enum ExportType {
TIMELINE = "TIMELINE", Timeline = "TIMELINE",
BEGINNING = "BEGINNING", Beginning = "BEGINNING",
LAST_N_MESSAGES = "LAST_N_MESSAGES", LastNMessages = "LAST_N_MESSAGES",
// START_DATE = "START_DATE", // START_DATE = "START_DATE",
} }
export const textForFormat = (format: string): string => { export const textForFormat = (format: string): string => {
switch (format) { switch (format) {
case ExportFormats.HTML: case ExportFormat.Html:
return _t("HTML"); return _t("HTML");
case ExportFormats.JSON: case ExportFormat.Json:
return _t("JSON"); return _t("JSON");
case ExportFormats.PLAIN_TEXT: case ExportFormat.PlainText:
return _t("Plain Text"); return _t("Plain Text");
default: default:
throw new Error("Unknown format"); throw new Error("Unknown format");
@ -44,11 +44,11 @@ export const textForFormat = (format: string): string => {
export const textForType = (type: string): string => { export const textForType = (type: string): string => {
switch (type) { switch (type) {
case ExportTypes.BEGINNING: case ExportType.Beginning:
return _t("From the beginning"); return _t("From the beginning");
case ExportTypes.LAST_N_MESSAGES: case ExportType.LastNMessages:
return _t("Specify a number of messages"); return _t("Specify a number of messages");
case ExportTypes.TIMELINE: case ExportType.Timeline:
return _t("Current Timeline"); return _t("Current Timeline");
default: default:
throw new Error("Unknown type: " + type); throw new Error("Unknown type: " + type);

View file

@ -16,7 +16,7 @@ limitations under the License.
import { IContent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk"; import { IContent, MatrixClient, MatrixEvent, Room } from "matrix-js-sdk";
import { MatrixClientPeg } from "../../src/MatrixClientPeg"; import { MatrixClientPeg } from "../../src/MatrixClientPeg";
import { textForFormat, IExportOptions, ExportTypes } from "../../src/utils/exportUtils/exportUtils"; import { textForFormat, IExportOptions, ExportType } from "../../src/utils/exportUtils/exportUtils";
import '../skinned-sdk'; import '../skinned-sdk';
import PlainTextExporter from "../../src/utils/exportUtils/PlainTextExport"; import PlainTextExporter from "../../src/utils/exportUtils/PlainTextExport";
import HTMLExporter from "../../src/utils/exportUtils/HtmlExport"; import HTMLExporter from "../../src/utils/exportUtils/HtmlExport";
@ -179,7 +179,7 @@ describe('export', function() {
it('checks if the export options are valid', function() { it('checks if the export options are valid', function() {
for (const exportOption of invalidExportOptions) { for (const exportOption of invalidExportOptions) {
try { try {
new PlainTextExporter(mockRoom, ExportTypes.BEGINNING, exportOption, null); new PlainTextExporter(mockRoom, ExportType.Beginning, exportOption, null);
throw new Error("Expected to throw an error"); throw new Error("Expected to throw an error");
} catch (e) { } catch (e) {
expect(e.message).toBe("Invalid export options"); expect(e.message).toBe("Invalid export options");
@ -188,7 +188,7 @@ describe('export', function() {
}); });
it('tests the file extension splitter', function() { it('tests the file extension splitter', function() {
const exporter = new PlainTextExporter(mockRoom, ExportTypes.BEGINNING, mockExportOptions, null); const exporter = new PlainTextExporter(mockRoom, ExportType.Beginning, mockExportOptions, null);
const fileNameWithExtensions = { const fileNameWithExtensions = {
"": ["", ""], "": ["", ""],
"name": ["name", ""], "name": ["name", ""],
@ -225,14 +225,14 @@ describe('export', function() {
"expectedText": "<@me:here \"This\"> Reply", "expectedText": "<@me:here \"This\"> Reply",
}, },
]; ];
const exporter = new PlainTextExporter(mockRoom, ExportTypes.BEGINNING, mockExportOptions, null); const exporter = new PlainTextExporter(mockRoom, ExportType.Beginning, mockExportOptions, null);
for (const content of eventContents) { for (const content of eventContents) {
expect(exporter.textForReplyEvent(content)).toBe(content.expectedText); expect(exporter.textForReplyEvent(content)).toBe(content.expectedText);
} }
}); });
it("checks if the render to string doesn't throw any error for different types of events", function() { it("checks if the render to string doesn't throw any error for different types of events", function() {
const exporter = new HTMLExporter(mockRoom, ExportTypes.BEGINNING, mockExportOptions, null); const exporter = new HTMLExporter(mockRoom, ExportType.Beginning, mockExportOptions, null);
for (const event of events) { for (const event of events) {
expect(renderToString(exporter.getEventTile(event, false))).toBeTruthy(); expect(renderToString(exporter.getEventTile(event, false))).toBeTruthy();
} }