Select from the options below to export chats from your timeline
-
- Format
-
+
+ Format
+
Date: Wed, 26 Jan 2022 18:17:17 +0100
Subject: [PATCH 02/11] fix exporter tests
Signed-off-by: Kerry Archibald
---
test/utils/export-test.tsx | 47 ++++++++++++++++++--------------------
1 file changed, 22 insertions(+), 25 deletions(-)
diff --git a/test/utils/export-test.tsx b/test/utils/export-test.tsx
index 1ce463fdbd2..74ae8a51c3a 100644
--- a/test/utils/export-test.tsx
+++ b/test/utils/export-test.tsx
@@ -50,24 +50,6 @@ describe('export', function() {
attachmentsIncluded: false,
};
- const invalidExportOptions: IExportOptions[] = [
- {
- numberOfMessages: 10**9,
- maxSize: 1024 * 1024 * 1024,
- attachmentsIncluded: false,
- },
- {
- numberOfMessages: -1,
- maxSize: 4096 * 1024 * 1024,
- attachmentsIncluded: false,
- },
- {
- numberOfMessages: 0,
- maxSize: 0,
- attachmentsIncluded: false,
- },
- ];
-
function createRoom() {
const room = new Room(generateRoomId(), null, client.getUserId());
return room;
@@ -212,13 +194,28 @@ describe('export', function() {
).toBeTruthy();
});
- it('checks if the export options are valid', function() {
- for (const exportOption of invalidExportOptions) {
- expect(
- () =>
- new PlainTextExporter(mockRoom, ExportType.Beginning, exportOption, null),
- ).toThrowError("Invalid export options");
- }
+ const testCases: [string, IExportOptions][] = [
+ ['numberOfMessages exceeds max', {
+ numberOfMessages: 10 ** 9,
+ maxSize: 1024 * 1024 * 1024,
+ attachmentsIncluded: false,
+ }],
+ ['maxSize exceeds 1000GB', {
+ numberOfMessages: -1,
+ maxSize: 1024001 * 1024 * 1024,
+ attachmentsIncluded: false,
+ }],
+ ['maxSize is less than 1mb', {
+ numberOfMessages: 0,
+ maxSize: 0,
+ attachmentsIncluded: false,
+ }],
+ ];
+ it.each(testCases)('%s', (_d, options) => {
+ expect(
+ () =>
+ new PlainTextExporter(mockRoom, ExportType.Beginning, options, null),
+ ).toThrowError("Invalid export options");
});
it('tests the file extension splitter', function() {
From 3146317a2011e1c9ebb1652b4a56148a5af5e3d7 Mon Sep 17 00:00:00 2001
From: Kerry Archibald
Date: Wed, 26 Jan 2022 18:18:19 +0100
Subject: [PATCH 03/11] test ExportDialog with settings
Signed-off-by: Kerry Archibald
---
package.json | 1 +
src/components/views/dialogs/ExportDialog.tsx | 8 +-
.../views/dialogs/ExportDialog-test.tsx | 153 ++++++++++++++++--
test/utils/export-test.tsx | 4 +-
yarn.lock | 104 +++++++++---
5 files changed, 233 insertions(+), 37 deletions(-)
diff --git a/package.json b/package.json
index 1ac0c7b28d1..f1e81966d17 100644
--- a/package.json
+++ b/package.json
@@ -191,6 +191,7 @@
"stylelint": "^13.9.0",
"stylelint-config-standard": "^20.0.0",
"stylelint-scss": "^3.18.0",
+ "ts-jest": "^27.1.3",
"typescript": "4.5.3",
"walk": "^2.3.14"
},
diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx
index a2815758ae6..dde138a7048 100644
--- a/src/components/views/dialogs/ExportDialog.tsx
+++ b/src/components/views/dialogs/ExportDialog.tsx
@@ -57,10 +57,11 @@ const validateNumberInRange = (min: number, max: number) => (value?: string | nu
};
// Sanitize setting values, exclude invalid or missing values
-export const getSafeForceRoomExportSettings = (): {
+export type ForceRoomExportSettings = {
format?: ExportFormat; range?: ExportType; numberOfMessages?: number; includeAttachments?: boolean; sizeMb?: number;
-} => {
- const config = SettingsStore.getValue(UIFeature.ForceRoomExportSettings);
+};
+export const getSafeForceRoomExportSettings = (): ForceRoomExportSettings => {
+ const config = SettingsStore.getValue(UIFeature.ForceRoomExportSettings);
if (!config || typeof config !== "object") return {};
const { format, range, numberOfMessages, includeAttachments, sizeMb } = config;
@@ -87,6 +88,7 @@ interface ExportConfig {
setNumberOfMessages?: Dispatch>;
setSizeLimit?: Dispatch>;
}
+
/**
* Set up form state using UIFeature.ForceRoomExportSettings or defaults
* Form fields configured in ForceRoomExportSettings are not allowed to be edited
diff --git a/test/components/views/dialogs/ExportDialog-test.tsx b/test/components/views/dialogs/ExportDialog-test.tsx
index 8d6d36ad08c..c8561d4799b 100644
--- a/test/components/views/dialogs/ExportDialog-test.tsx
+++ b/test/components/views/dialogs/ExportDialog-test.tsx
@@ -16,22 +16,40 @@ limitations under the License.
import React from 'react';
import { mount } from 'enzyme';
+import { mocked } from 'ts-jest/utils';
import '../../../skinned-sdk';
import { act } from "react-dom/test-utils";
import { Room } from 'matrix-js-sdk';
-import ExportDialog from '../../../../src/components/views/dialogs/ExportDialog';
+import ExportDialog,
+{ getSafeForceRoomExportSettings, ForceRoomExportSettings }
+ from '../../../../src/components/views/dialogs/ExportDialog';
import { ExportType, ExportFormat } from '../../../../src/utils/exportUtils/exportUtils';
import { createTestClient, mkStubRoom } from '../../../test-utils';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import HTMLExporter from "../../../../src/utils/exportUtils/HtmlExport";
+import SettingsStore from '../../../../src/settings/SettingsStore';
+import PlainTextExporter from '../../../../src/utils/exportUtils/PlainTextExport';
jest.useFakeTimers();
-const mockHtmlExporter = ({
+const htmlExporterInstance = ({
+ export: jest.fn().mockResolvedValue({}),
+});
+const plainTextExporterInstance = ({
export: jest.fn().mockResolvedValue({}),
});
jest.mock("../../../../src/utils/exportUtils/HtmlExport", () => jest.fn());
+jest.mock("../../../../src/utils/exportUtils/PlainTextExport", () => jest.fn());
+
+jest.mock('../../../../src/settings/SettingsStore', () => ({
+ monitorSetting: jest.fn(),
+ getValue: jest.fn(),
+}));
+
+const SettingsStoreMock = mocked(SettingsStore);
+const HTMLExporterMock = mocked(HTMLExporter);
+const PlainTextExporterMock = mocked(PlainTextExporter);
describe('', () => {
const mockClient = createTestClient();
@@ -81,8 +99,13 @@ describe('', () => {
});
beforeEach(() => {
- (HTMLExporter as jest.Mock).mockImplementation(jest.fn().mockReturnValue(mockHtmlExporter));
- mockHtmlExporter.export.mockClear();
+ HTMLExporterMock.mockClear().mockImplementation(jest.fn().mockReturnValue(htmlExporterInstance));
+ PlainTextExporterMock.mockClear().mockImplementation(jest.fn().mockReturnValue(plainTextExporterInstance));
+ htmlExporterInstance.export.mockClear();
+ plainTextExporterInstance.export.mockClear();
+
+ // default setting value
+ SettingsStoreMock.getValue.mockClear().mockReturnValue({});
});
it('renders export dialog', () => {
@@ -104,7 +127,7 @@ describe('', () => {
await submitForm(component);
// 4th arg is an component function
- const exportConstructorProps = (HTMLExporter as jest.Mock).mock.calls[0].slice(0, 3);
+ const exportConstructorProps = HTMLExporterMock.mock.calls[0].slice(0, 3);
expect(exportConstructorProps).toEqual([
defaultProps.room,
ExportType.Timeline,
@@ -114,7 +137,32 @@ describe('', () => {
numberOfMessages: 100,
},
]);
- expect(mockHtmlExporter.export).toHaveBeenCalled();
+ expect(htmlExporterInstance.export).toHaveBeenCalled();
+ });
+
+ it('exports room using values set from ForceRoomExportSettings', async () => {
+ SettingsStoreMock.getValue.mockReturnValue({
+ format: ExportFormat.PlainText,
+ range: ExportType.Beginning,
+ sizeMb: 15000,
+ numberOfMessages: 30,
+ attachmentsIncluded: true,
+ });
+ const component = getComponent();
+ await submitForm(component);
+
+ // 4th arg is an component function
+ const exportConstructorProps = PlainTextExporterMock.mock.calls[0].slice(0, 3);
+ expect(exportConstructorProps).toEqual([
+ defaultProps.room,
+ ExportType.Beginning,
+ {
+ attachmentsIncluded: false,
+ maxSize: 15000 * 1024 * 1024,
+ numberOfMessages: 30,
+ },
+ ]);
+ expect(plainTextExporterInstance.export).toHaveBeenCalled();
});
it('renders success screen when export is finished', async () => {
@@ -139,6 +187,19 @@ describe('', () => {
expect(getExportFormatInput(component, ExportFormat.PlainText).props().checked).toBeTruthy();
expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeFalsy();
});
+
+ it('hides export format input when format is valid in ForceRoomExportSettings', () => {
+ const component = getComponent();
+ expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeTruthy();
+ });
+
+ it('does not render export format when set in ForceRoomExportSettings', () => {
+ SettingsStoreMock.getValue.mockReturnValue({
+ format: ExportFormat.PlainText,
+ });
+ const component = getComponent();
+ expect(getExportFormatInput(component, ExportFormat.Html).length).toBeFalsy();
+ });
});
describe('export type', () => {
@@ -153,6 +214,14 @@ describe('', () => {
expect(getExportTypeInput(component).props().value).toEqual(ExportType.Beginning);
});
+ it('does not render export type when set in ForceRoomExportSettings', () => {
+ SettingsStoreMock.getValue.mockReturnValue({
+ range: ExportType.Beginning,
+ });
+ const component = getComponent();
+ expect(getExportTypeInput(component).length).toBeFalsy();
+ });
+
it('does not render message count input', async () => {
const component = getComponent();
expect(getMessageCountInput(component).length).toBeFalsy();
@@ -177,7 +246,7 @@ describe('', () => {
await setMessageCount(component, 0);
await submitForm(component);
- expect(mockHtmlExporter.export).not.toHaveBeenCalled();
+ expect(htmlExporterInstance.export).not.toHaveBeenCalled();
});
it('does not export when export type is lastNMessages and message count is more than max', async () => {
@@ -186,7 +255,7 @@ describe('', () => {
await setMessageCount(component, 99999999999);
await submitForm(component);
- expect(mockHtmlExporter.export).not.toHaveBeenCalled();
+ expect(htmlExporterInstance.export).not.toHaveBeenCalled();
});
it('exports when export type is NOT lastNMessages and message count is falsy', async () => {
@@ -196,7 +265,7 @@ describe('', () => {
await selectExportType(component, ExportType.Timeline);
await submitForm(component);
- expect(mockHtmlExporter.export).toHaveBeenCalled();
+ expect(htmlExporterInstance.export).toHaveBeenCalled();
});
});
@@ -217,7 +286,7 @@ describe('', () => {
await setSizeLimit(component, 0);
await submitForm(component);
- expect(mockHtmlExporter.export).not.toHaveBeenCalled();
+ expect(htmlExporterInstance.export).not.toHaveBeenCalled();
});
it('does not export when size limit is larger than max', async () => {
@@ -225,7 +294,7 @@ describe('', () => {
await setSizeLimit(component, 2001);
await submitForm(component);
- expect(mockHtmlExporter.export).not.toHaveBeenCalled();
+ expect(htmlExporterInstance.export).not.toHaveBeenCalled();
});
it('exports when size limit is max', async () => {
@@ -233,11 +302,32 @@ describe('', () => {
await setSizeLimit(component, 2000);
await submitForm(component);
- expect(mockHtmlExporter.export).toHaveBeenCalled();
+ expect(htmlExporterInstance.export).toHaveBeenCalled();
+ });
+
+ it('does not render size limit input when set in ForceRoomExportSettings', () => {
+ SettingsStoreMock.getValue.mockReturnValue({
+ sizeMb: 10000,
+ });
+ const component = getComponent();
+ expect(getSizeInput(component).length).toBeFalsy();
+ });
+
+ /**
+ * 2000mb size limit does not apply when higher limit is configured in config
+ */
+ it('exports when size limit set in ForceRoomExportSettings is larger than 2000', async () => {
+ SettingsStoreMock.getValue.mockReturnValue({
+ sizeMb: 10000,
+ });
+ const component = getComponent();
+ await submitForm(component);
+
+ expect(htmlExporterInstance.export).toHaveBeenCalled();
});
});
- describe('include attachements', () => {
+ describe('include attachments', () => {
it('renders input with default value of false', () => {
const component = getComponent();
expect(getAttachmentsCheckbox(component).props().checked).toEqual(false);
@@ -248,6 +338,43 @@ describe('', () => {
await setIncludeAttachments(component, true);
expect(getAttachmentsCheckbox(component).props().checked).toEqual(true);
});
+
+ it('does not render input when set in ForceRoomExportSettings', () => {
+ SettingsStoreMock.getValue.mockReturnValue({
+ includeAttachments: false,
+ });
+ const component = getComponent();
+ expect(getAttachmentsCheckbox(component).length).toBeFalsy();
+ });
+ });
+
+ describe('getSafeForceRoomExportSettings()', () => {
+ const testCases: [string, ForceRoomExportSettings, ForceRoomExportSettings][] = [
+ ['setting is falsy', undefined, {}],
+ ['setting is configured to string', 'test' as unknown, {}],
+ ['setting is empty', {}, {}],
+ ['format is not a valid ExportFormat', { format: 'mp3' }, {}],
+ ['format is a valid ExportFormat', { format: ExportFormat.Html }, { format: ExportFormat.Html }],
+ ['range is not a valid ExportType', { range: 'yesterday' }, {}],
+ ['range is a valid ExportType', { range: ExportType.LastNMessages }, { range: ExportType.LastNMessages }],
+ ['numberOfMessages is not a number', { numberOfMessages: 'test' }, {}],
+ ['numberOfMessages is less than 1', { numberOfMessages: -1 }, {}],
+ ['numberOfMessages is more than 100000000', { numberOfMessages: 9999999999 }, {}],
+ ['numberOfMessages is valid', { numberOfMessages: 2000 }, { numberOfMessages: 2000 }],
+ ['sizeMb is not a number', { sizeMb: 'test' }, {}],
+ ['sizeMb is less than 1', { sizeMb: -1 }, {}],
+ ['sizeMb is more than 1024000', { sizeMb: Number.MAX_SAFE_INTEGER }, {}],
+ ['sizeMb is valid', { sizeMb: 50000 }, { sizeMb: 50000 }],
+ ['includeAttachments is not a boolean', { includeAttachments: 'yes' }, {}],
+ ['includeAttachments is true', { includeAttachments: true }, { includeAttachments: true }],
+ ['includeAttachments is false', { includeAttachments: false }, { includeAttachments: false }],
+ ];
+
+ it.each(testCases)('sanitizes correctly when %s', (_d, setting, expected) => {
+ SettingsStoreMock.getValue.mockReturnValue(setting);
+
+ expect(getSafeForceRoomExportSettings()).toEqual(expected);
+ });
});
});
diff --git a/test/utils/export-test.tsx b/test/utils/export-test.tsx
index 74ae8a51c3a..749f062b46e 100644
--- a/test/utils/export-test.tsx
+++ b/test/utils/export-test.tsx
@@ -194,7 +194,7 @@ describe('export', function() {
).toBeTruthy();
});
- const testCases: [string, IExportOptions][] = [
+ const invalidExportOptions: [string, IExportOptions][] = [
['numberOfMessages exceeds max', {
numberOfMessages: 10 ** 9,
maxSize: 1024 * 1024 * 1024,
@@ -211,7 +211,7 @@ describe('export', function() {
attachmentsIncluded: false,
}],
];
- it.each(testCases)('%s', (_d, options) => {
+ it.each(invalidExportOptions)('%s', (_d, options) => {
expect(
() =>
new PlainTextExporter(mockRoom, ExportType.Beginning, options, null),
diff --git a/yarn.lock b/yarn.lock
index 1e91b21ada5..5d3b5f2ab56 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1334,6 +1334,17 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
+"@jest/types@^27.4.2":
+ version "27.4.2"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.4.2.tgz#96536ebd34da6392c2b7c7737d693885b5dd44a5"
+ integrity sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==
+ dependencies:
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^3.0.0"
+ "@types/node" "*"
+ "@types/yargs" "^16.0.0"
+ chalk "^4.0.0"
+
"@mapbox/geojson-rewind@^0.5.0":
version "0.5.1"
resolved "https://registry.yarnpkg.com/@mapbox/geojson-rewind/-/geojson-rewind-0.5.1.tgz#adbe16dc683eb40e90934c51a5e28c7bbf44f4e1"
@@ -1983,6 +1994,13 @@
dependencies:
"@types/yargs-parser" "*"
+"@types/yargs@^16.0.0":
+ version "16.0.4"
+ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977"
+ integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==
+ dependencies:
+ "@types/yargs-parser" "*"
+
"@types/zxcvbn@^4.4.0":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@types/zxcvbn/-/zxcvbn-4.4.1.tgz#46e42cbdcee681b22181478feaf4af2bc4c1abd2"
@@ -2634,6 +2652,13 @@ browserslist@^4.12.0, browserslist@^4.17.5, browserslist@^4.19.1:
node-releases "^2.0.1"
picocolors "^1.0.0"
+bs-logger@0.x:
+ version "0.2.6"
+ resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
+ integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==
+ dependencies:
+ fast-json-stable-stringify "2.x"
+
bs58@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a"
@@ -2832,6 +2857,11 @@ ci-info@^2.0.0:
resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46"
integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==
+ci-info@^3.2.0:
+ version "3.3.0"
+ resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.3.0.tgz#b4ed1fb6818dea4803a55c623041f9165d2066b2"
+ integrity sha512-riT/3vI5YpVH6/qomlDnJow6TBee2PBKSEpx3O32EGPYbWGIRsIlGRms3Sm74wYE1JMo8RnO04Hb12+v1J5ICw==
+
cjs-module-lexer@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f"
@@ -4079,7 +4109,7 @@ fast-glob@^3.2.5, fast-glob@^3.2.9:
merge2 "^1.3.0"
micromatch "^4.0.4"
-fast-json-stable-stringify@^2.0.0:
+fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
@@ -5697,6 +5727,18 @@ jest-util@^26.6.2:
is-ci "^2.0.0"
micromatch "^4.0.2"
+jest-util@^27.0.0:
+ version "27.4.2"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.4.2.tgz#ed95b05b1adfd761e2cda47e0144c6a58e05a621"
+ integrity sha512-YuxxpXU6nlMan9qyLuxHaMMOzXAl5aGZWCSzben5DhLHemYQxCc4YK+4L3ZrCutT8GPQ+ui9k5D8rUJoDioMnA==
+ dependencies:
+ "@jest/types" "^27.4.2"
+ "@types/node" "*"
+ chalk "^4.0.0"
+ ci-info "^3.2.0"
+ graceful-fs "^4.2.4"
+ picomatch "^2.2.3"
+
jest-validate@^26.6.2:
version "26.6.2"
resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec"
@@ -5843,6 +5885,13 @@ json-stringify-safe@~5.0.1:
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
+json5@2.x, json5@^2.1.2:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
+ integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
+ dependencies:
+ minimist "^1.2.5"
+
json5@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe"
@@ -5850,13 +5899,6 @@ json5@^1.0.1:
dependencies:
minimist "^1.2.0"
-json5@^2.1.2:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
- integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
- dependencies:
- minimist "^1.2.5"
-
jsprim@^1.2.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.2.tgz#712c65533a15c878ba59e9ed5f0e26d5b77c5feb"
@@ -6028,6 +6070,11 @@ lodash.isequal@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha1-QVxEePK8wwEgwizhDtMib30+GOA=
+lodash.memoize@4.x:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+ integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
+
lodash.truncate@^4.4.2:
version "4.4.2"
resolved "https://registry.yarnpkg.com/lodash.truncate/-/lodash.truncate-4.4.2.tgz#5a350da0b1113b837ecfffd5812cbe58d6eae193"
@@ -6099,6 +6146,11 @@ make-dir@^3.0.0:
dependencies:
semver "^6.0.0"
+make-error@1.x:
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
+ integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
+
makeerror@1.0.12:
version "1.0.12"
resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
@@ -7787,18 +7839,18 @@ semver@7.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
-semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
- version "6.3.0"
- resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
- integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
-
-semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
+semver@7.x, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5:
version "7.3.5"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
dependencies:
lru-cache "^6.0.0"
+semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+ integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -8484,6 +8536,20 @@ trough@^1.0.0:
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.5.tgz#b8b639cefad7d0bb2abd37d433ff8293efa5f406"
integrity sha512-rvuRbTarPXmMb79SmzEp8aqXNKcK+y0XaB298IXueQ8I2PsrATcPBCSPyK/dDNa2iWOhKlfNnOjdAOTBU/nkFA==
+ts-jest@^27.1.3:
+ version "27.1.3"
+ resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.3.tgz#1f723e7e74027c4da92c0ffbd73287e8af2b2957"
+ integrity sha512-6Nlura7s6uM9BVUAoqLH7JHyMXjz8gluryjpPXxr3IxZdAXnU6FhjvVLHFtfd1vsE1p8zD1OJfskkc0jhTSnkA==
+ dependencies:
+ bs-logger "0.x"
+ fast-json-stable-stringify "2.x"
+ jest-util "^27.0.0"
+ json5 "2.x"
+ lodash.memoize "4.x"
+ make-error "1.x"
+ semver "7.x"
+ yargs-parser "20.x"
+
tsconfig-paths@^3.12.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.12.0.tgz#19769aca6ee8f6a1a341e38c8fa45dd9fb18899b"
@@ -9057,6 +9123,11 @@ yaml@^1.10.0:
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
+yargs-parser@20.x, yargs-parser@^20.2.3:
+ version "20.2.9"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
+ integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
+
yargs-parser@^13.1.2:
version "13.1.2"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38"
@@ -9073,11 +9144,6 @@ yargs-parser@^18.1.2:
camelcase "^5.0.0"
decamelize "^1.2.0"
-yargs-parser@^20.2.3:
- version "20.2.9"
- resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
- integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
-
yargs-parser@^21.0.0:
version "21.0.0"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.0.0.tgz#a485d3966be4317426dd56bdb6a30131b281dc55"
From 63f3559640dbc4b111acadc4a00c3df37631ba3c Mon Sep 17 00:00:00 2001
From: Kerry Archibald
Date: Thu, 27 Jan 2022 10:02:40 +0100
Subject: [PATCH 04/11] tidy debugs, rename setting to Parameters
Signed-off-by: Kerry Archibald
---
src/components/views/dialogs/ExportDialog.tsx | 12 +++++-----
src/settings/Settings.tsx | 2 +-
src/settings/UIFeature.ts | 2 +-
.../views/dialogs/ExportDialog-test.tsx | 22 +++++++++----------
4 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx
index dde138a7048..44cc9b79154 100644
--- a/src/components/views/dialogs/ExportDialog.tsx
+++ b/src/components/views/dialogs/ExportDialog.tsx
@@ -57,11 +57,11 @@ const validateNumberInRange = (min: number, max: number) => (value?: string | nu
};
// Sanitize setting values, exclude invalid or missing values
-export type ForceRoomExportSettings = {
+export type ForceRoomExportParameters = {
format?: ExportFormat; range?: ExportType; numberOfMessages?: number; includeAttachments?: boolean; sizeMb?: number;
};
-export const getSafeForceRoomExportSettings = (): ForceRoomExportSettings => {
- const config = SettingsStore.getValue(UIFeature.ForceRoomExportSettings);
+export const getSafeForceRoomExportParameters = (): ForceRoomExportParameters => {
+ const config = SettingsStore.getValue(UIFeature.ForceRoomExportParameters);
if (!config || typeof config !== "object") return {};
const { format, range, numberOfMessages, includeAttachments, sizeMb } = config;
@@ -90,12 +90,12 @@ interface ExportConfig {
}
/**
- * Set up form state using UIFeature.ForceRoomExportSettings or defaults
- * Form fields configured in ForceRoomExportSettings are not allowed to be edited
+ * Set up form state using UIFeature.ForceRoomExportParameters or defaults
+ * Form fields configured in ForceRoomExportParameters are not allowed to be edited
* Only return change handlers for editable values
*/
const useExportFormState = (): ExportConfig => {
- const config = getSafeForceRoomExportSettings();
+ const config = getSafeForceRoomExportParameters();
const [exportFormat, setExportFormat] = useState(config.format || ExportFormat.Html);
const [exportType, setExportType] = useState(config.range || ExportType.Timeline);
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 432e3aa4747..5edad814cf3 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -956,7 +956,7 @@ export const SETTINGS: {[setting: string]: ISetting} = {
supportedLevels: LEVELS_UI_FEATURE,
default: true,
},
- [UIFeature.ForceRoomExportSettings]: {
+ [UIFeature.ForceRoomExportParameters]: {
supportedLevels: LEVELS_UI_FEATURE,
default: {},
},
diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts
index 41581d517b9..4e32c0413c4 100644
--- a/src/settings/UIFeature.ts
+++ b/src/settings/UIFeature.ts
@@ -33,7 +33,7 @@ export enum UIFeature {
AdvancedSettings = "UIFeature.advancedSettings",
RoomHistorySettings = "UIFeature.roomHistorySettings",
TimelineEnableRelativeDates = "UIFeature.timelineEnableRelativeDates",
- ForceRoomExportSettings = "UIFeature.forceRoomExportSettings"
+ ForceRoomExportParameters = "UIFeature.ForceRoomExportParameters"
}
export enum UIComponent {
diff --git a/test/components/views/dialogs/ExportDialog-test.tsx b/test/components/views/dialogs/ExportDialog-test.tsx
index c8561d4799b..3db56a7eebc 100644
--- a/test/components/views/dialogs/ExportDialog-test.tsx
+++ b/test/components/views/dialogs/ExportDialog-test.tsx
@@ -22,7 +22,7 @@ import { act } from "react-dom/test-utils";
import { Room } from 'matrix-js-sdk';
import ExportDialog,
-{ getSafeForceRoomExportSettings, ForceRoomExportSettings }
+{ getSafeForceRoomExportParameters, ForceRoomExportParameters }
from '../../../../src/components/views/dialogs/ExportDialog';
import { ExportType, ExportFormat } from '../../../../src/utils/exportUtils/exportUtils';
import { createTestClient, mkStubRoom } from '../../../test-utils';
@@ -140,7 +140,7 @@ describe('', () => {
expect(htmlExporterInstance.export).toHaveBeenCalled();
});
- it('exports room using values set from ForceRoomExportSettings', async () => {
+ it('exports room using values set from ForceRoomExportParameters', async () => {
SettingsStoreMock.getValue.mockReturnValue({
format: ExportFormat.PlainText,
range: ExportType.Beginning,
@@ -188,12 +188,12 @@ describe('', () => {
expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeFalsy();
});
- it('hides export format input when format is valid in ForceRoomExportSettings', () => {
+ it('hides export format input when format is valid in ForceRoomExportParameters', () => {
const component = getComponent();
expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeTruthy();
});
- it('does not render export format when set in ForceRoomExportSettings', () => {
+ it('does not render export format when set in ForceRoomExportParameters', () => {
SettingsStoreMock.getValue.mockReturnValue({
format: ExportFormat.PlainText,
});
@@ -214,7 +214,7 @@ describe('', () => {
expect(getExportTypeInput(component).props().value).toEqual(ExportType.Beginning);
});
- it('does not render export type when set in ForceRoomExportSettings', () => {
+ it('does not render export type when set in ForceRoomExportParameters', () => {
SettingsStoreMock.getValue.mockReturnValue({
range: ExportType.Beginning,
});
@@ -305,7 +305,7 @@ describe('', () => {
expect(htmlExporterInstance.export).toHaveBeenCalled();
});
- it('does not render size limit input when set in ForceRoomExportSettings', () => {
+ it('does not render size limit input when set in ForceRoomExportParameters', () => {
SettingsStoreMock.getValue.mockReturnValue({
sizeMb: 10000,
});
@@ -316,7 +316,7 @@ describe('', () => {
/**
* 2000mb size limit does not apply when higher limit is configured in config
*/
- it('exports when size limit set in ForceRoomExportSettings is larger than 2000', async () => {
+ it('exports when size limit set in ForceRoomExportParameters is larger than 2000', async () => {
SettingsStoreMock.getValue.mockReturnValue({
sizeMb: 10000,
});
@@ -339,7 +339,7 @@ describe('', () => {
expect(getAttachmentsCheckbox(component).props().checked).toEqual(true);
});
- it('does not render input when set in ForceRoomExportSettings', () => {
+ it('does not render input when set in ForceRoomExportParameters', () => {
SettingsStoreMock.getValue.mockReturnValue({
includeAttachments: false,
});
@@ -348,8 +348,8 @@ describe('', () => {
});
});
- describe('getSafeForceRoomExportSettings()', () => {
- const testCases: [string, ForceRoomExportSettings, ForceRoomExportSettings][] = [
+ describe('getSafeForceRoomExportParameters()', () => {
+ const testCases: [string, ForceRoomExportParameters, ForceRoomExportParameters][] = [
['setting is falsy', undefined, {}],
['setting is configured to string', 'test' as unknown, {}],
['setting is empty', {}, {}],
@@ -373,7 +373,7 @@ describe('', () => {
it.each(testCases)('sanitizes correctly when %s', (_d, setting, expected) => {
SettingsStoreMock.getValue.mockReturnValue(setting);
- expect(getSafeForceRoomExportSettings()).toEqual(expected);
+ expect(getSafeForceRoomExportParameters()).toEqual(expected);
});
});
});
From 74ccf3d47ae8936bd45b3774ad6b1cf48f7d63fb Mon Sep 17 00:00:00 2001
From: Kerry Archibald
Date: Thu, 27 Jan 2022 10:09:57 +0100
Subject: [PATCH 05/11] use reasonable 100gb limit
Signed-off-by: Kerry Archibald
---
src/components/views/dialogs/ExportDialog.tsx | 4 ++--
src/utils/exportUtils/Exporter.ts | 2 +-
test/components/views/dialogs/ExportDialog-test.tsx | 2 +-
test/utils/export-test.tsx | 4 ++--
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx
index 44cc9b79154..a287df486d3 100644
--- a/src/components/views/dialogs/ExportDialog.tsx
+++ b/src/components/views/dialogs/ExportDialog.tsx
@@ -70,8 +70,8 @@ export const getSafeForceRoomExportParameters = (): ForceRoomExportParameters =>
...(isExportFormat(format) && { format }),
...(isExportType(range) && { range }),
...(validateNumberInRange(1, 10 ** 8)(numberOfMessages) && { numberOfMessages }),
- // 100GB limit
- ...(validateNumberInRange(1, 1024000)(sizeMb) && { sizeMb }),
+ // ~100GB limit
+ ...(validateNumberInRange(1, 100000)(sizeMb) && { sizeMb }),
...(typeof includeAttachments === 'boolean' && { includeAttachments }),
};
};
diff --git a/src/utils/exportUtils/Exporter.ts b/src/utils/exportUtils/Exporter.ts
index 70826cbfcef..b55cb6d1bad 100644
--- a/src/utils/exportUtils/Exporter.ts
+++ b/src/utils/exportUtils/Exporter.ts
@@ -48,7 +48,7 @@ export default abstract class Exporter {
protected setProgressText: React.Dispatch>,
) {
if (exportOptions.maxSize < 1 * 1024 * 1024|| // Less than 1 MB
- exportOptions.maxSize > 1024000 * 1024 * 1024 || // More than 1000 GB
+ exportOptions.maxSize > 100000 * 1024 * 1024 || // More than 100 GB
exportOptions.numberOfMessages > 10**8
) {
throw new Error("Invalid export options");
diff --git a/test/components/views/dialogs/ExportDialog-test.tsx b/test/components/views/dialogs/ExportDialog-test.tsx
index 3db56a7eebc..a69190f5047 100644
--- a/test/components/views/dialogs/ExportDialog-test.tsx
+++ b/test/components/views/dialogs/ExportDialog-test.tsx
@@ -363,7 +363,7 @@ describe('', () => {
['numberOfMessages is valid', { numberOfMessages: 2000 }, { numberOfMessages: 2000 }],
['sizeMb is not a number', { sizeMb: 'test' }, {}],
['sizeMb is less than 1', { sizeMb: -1 }, {}],
- ['sizeMb is more than 1024000', { sizeMb: Number.MAX_SAFE_INTEGER }, {}],
+ ['sizeMb is more than 100000', { sizeMb: Number.MAX_SAFE_INTEGER }, {}],
['sizeMb is valid', { sizeMb: 50000 }, { sizeMb: 50000 }],
['includeAttachments is not a boolean', { includeAttachments: 'yes' }, {}],
['includeAttachments is true', { includeAttachments: true }, { includeAttachments: true }],
diff --git a/test/utils/export-test.tsx b/test/utils/export-test.tsx
index 749f062b46e..e0232c775d4 100644
--- a/test/utils/export-test.tsx
+++ b/test/utils/export-test.tsx
@@ -200,9 +200,9 @@ describe('export', function() {
maxSize: 1024 * 1024 * 1024,
attachmentsIncluded: false,
}],
- ['maxSize exceeds 1000GB', {
+ ['maxSize exceeds 100GB', {
numberOfMessages: -1,
- maxSize: 1024001 * 1024 * 1024,
+ maxSize: 100001 * 1024 * 1024,
attachmentsIncluded: false,
}],
['maxSize is less than 1mb', {
From 699830211c45ba16779c28a5a88f689096d629d7 Mon Sep 17 00:00:00 2001
From: Kerry Archibald
Date: Thu, 27 Jan 2022 14:35:33 +0100
Subject: [PATCH 06/11] use normal setting instead of UIFeature
Signed-off-by: Kerry Archibald
---
src/components/views/dialogs/ExportDialog.tsx | 5 ++---
src/settings/Settings.tsx | 8 ++++----
src/settings/UIFeature.ts | 1 -
3 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx
index a287df486d3..3ce314fb07b 100644
--- a/src/components/views/dialogs/ExportDialog.tsx
+++ b/src/components/views/dialogs/ExportDialog.tsx
@@ -40,7 +40,6 @@ import Exporter from "../../../utils/exportUtils/Exporter";
import Spinner from "../elements/Spinner";
import InfoDialog from "./InfoDialog";
import SettingsStore from "../../../settings/SettingsStore";
-import { UIFeature } from "../../../settings/UIFeature";
interface IProps extends IDialogProps {
room: Room;
@@ -61,7 +60,7 @@ export type ForceRoomExportParameters = {
format?: ExportFormat; range?: ExportType; numberOfMessages?: number; includeAttachments?: boolean; sizeMb?: number;
};
export const getSafeForceRoomExportParameters = (): ForceRoomExportParameters => {
- const config = SettingsStore.getValue(UIFeature.ForceRoomExportParameters);
+ const config = SettingsStore.getValue("forceRoomExportParameters");
if (!config || typeof config !== "object") return {};
const { format, range, numberOfMessages, includeAttachments, sizeMb } = config;
@@ -90,7 +89,7 @@ interface ExportConfig {
}
/**
- * Set up form state using UIFeature.ForceRoomExportParameters or defaults
+ * Set up form state using "forceRoomExportParameters" or defaults
* Form fields configured in ForceRoomExportParameters are not allowed to be edited
* Only return change handlers for editable values
*/
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 5edad814cf3..56499568df0 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -883,6 +883,10 @@ export const SETTINGS: {[setting: string]: ISetting} = {
default: false,
controller: new ReloadOnChangeController(),
},
+ "forceRoomExportParameters": {
+ supportedLevels: LEVELS_UI_FEATURE,
+ default: {},
+ },
[UIFeature.RoomHistorySettings]: {
supportedLevels: LEVELS_UI_FEATURE,
default: true,
@@ -956,8 +960,4 @@ export const SETTINGS: {[setting: string]: ISetting} = {
supportedLevels: LEVELS_UI_FEATURE,
default: true,
},
- [UIFeature.ForceRoomExportParameters]: {
- supportedLevels: LEVELS_UI_FEATURE,
- default: {},
- },
};
diff --git a/src/settings/UIFeature.ts b/src/settings/UIFeature.ts
index 4e32c0413c4..f0339942143 100644
--- a/src/settings/UIFeature.ts
+++ b/src/settings/UIFeature.ts
@@ -33,7 +33,6 @@ export enum UIFeature {
AdvancedSettings = "UIFeature.advancedSettings",
RoomHistorySettings = "UIFeature.roomHistorySettings",
TimelineEnableRelativeDates = "UIFeature.timelineEnableRelativeDates",
- ForceRoomExportParameters = "UIFeature.ForceRoomExportParameters"
}
export enum UIComponent {
From 63d97853f6f4b9ec9fb1c4cec593ccd8d664297d Mon Sep 17 00:00:00 2001
From: Kerry Archibald
Date: Thu, 27 Jan 2022 17:52:12 +0100
Subject: [PATCH 07/11] use a customisation
Signed-off-by: Kerry Archibald
---
src/components/views/dialogs/ExportDialog.tsx | 29 +--------
src/customisations/ChatExport.ts | 49 +++++++++++++++
src/settings/Settings.tsx | 4 --
.../views/dialogs/ExportDialog-test.tsx | 60 +++++--------------
4 files changed, 65 insertions(+), 77 deletions(-)
create mode 100644 src/customisations/ChatExport.ts
diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx
index 3ce314fb07b..83a871e8dad 100644
--- a/src/components/views/dialogs/ExportDialog.tsx
+++ b/src/components/views/dialogs/ExportDialog.tsx
@@ -39,42 +39,17 @@ import { useStateCallback } from "../../../hooks/useStateCallback";
import Exporter from "../../../utils/exportUtils/Exporter";
import Spinner from "../elements/Spinner";
import InfoDialog from "./InfoDialog";
-import SettingsStore from "../../../settings/SettingsStore";
+import ChatExport from "../../../customisations/ChatExport";
interface IProps extends IDialogProps {
room: Room;
}
-const isExportFormat = (config?: string): config is ExportFormat =>
- config && Object.values(ExportFormat).includes(config as ExportFormat);
-
-const isExportType = (config?: string): config is ExportType =>
- config && Object.values(ExportType).includes(config as ExportType);
const validateNumberInRange = (min: number, max: number) => (value?: string | number) => {
const parsedSize = parseInt(value as string, 10);
return !(isNaN(parsedSize) || min > parsedSize || parsedSize > max);
};
-// Sanitize setting values, exclude invalid or missing values
-export type ForceRoomExportParameters = {
- format?: ExportFormat; range?: ExportType; numberOfMessages?: number; includeAttachments?: boolean; sizeMb?: number;
-};
-export const getSafeForceRoomExportParameters = (): ForceRoomExportParameters => {
- const config = SettingsStore.getValue("forceRoomExportParameters");
- if (!config || typeof config !== "object") return {};
-
- const { format, range, numberOfMessages, includeAttachments, sizeMb } = config;
-
- return {
- ...(isExportFormat(format) && { format }),
- ...(isExportType(range) && { range }),
- ...(validateNumberInRange(1, 10 ** 8)(numberOfMessages) && { numberOfMessages }),
- // ~100GB limit
- ...(validateNumberInRange(1, 100000)(sizeMb) && { sizeMb }),
- ...(typeof includeAttachments === 'boolean' && { includeAttachments }),
- };
-};
-
interface ExportConfig {
exportFormat: ExportFormat;
exportType: ExportType;
@@ -94,7 +69,7 @@ interface ExportConfig {
* Only return change handlers for editable values
*/
const useExportFormState = (): ExportConfig => {
- const config = getSafeForceRoomExportParameters();
+ const config = ChatExport.getForceChatExportParameters();
const [exportFormat, setExportFormat] = useState(config.format || ExportFormat.Html);
const [exportType, setExportType] = useState(config.range || ExportType.Timeline);
diff --git a/src/customisations/ChatExport.ts b/src/customisations/ChatExport.ts
new file mode 100644
index 00000000000..6c69e55154f
--- /dev/null
+++ b/src/customisations/ChatExport.ts
@@ -0,0 +1,49 @@
+/*
+Copyright 2022 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 { ExportFormat, ExportType } from "../utils/exportUtils/exportUtils";
+
+export type ForceChatExportParameters = {
+ format?: ExportFormat;
+ range?: ExportType;
+ // must be < 10**8
+ numberOfMessages?: number;
+ includeAttachments?: boolean;
+ // must be > 0 and < 100000
+ sizeMb?: number;
+};
+
+/**
+ * Force parameters in room chat export
+ * fields returned here are forced
+ * and not allowed to be edited in the chat export form
+ */
+const getForceChatExportParameters = (): ForceChatExportParameters => {
+ return {};
+};
+
+// This interface summarises all available customisation points and also marks
+// them all as optional. This allows customisers to only define and export the
+// customisations they need while still maintaining type safety.
+export interface IChatExportCustomisations {
+ getForceChatExportParameters?: typeof getForceChatExportParameters;
+}
+
+// A real customisation module will define and export one or more of the
+// customisation points that make up `IChatExportCustomisations`.
+export default {
+ getForceChatExportParameters,
+} as IChatExportCustomisations;
diff --git a/src/settings/Settings.tsx b/src/settings/Settings.tsx
index 56499568df0..cf4e7d2b43f 100644
--- a/src/settings/Settings.tsx
+++ b/src/settings/Settings.tsx
@@ -883,10 +883,6 @@ export const SETTINGS: {[setting: string]: ISetting} = {
default: false,
controller: new ReloadOnChangeController(),
},
- "forceRoomExportParameters": {
- supportedLevels: LEVELS_UI_FEATURE,
- default: {},
- },
[UIFeature.RoomHistorySettings]: {
supportedLevels: LEVELS_UI_FEATURE,
default: true,
diff --git a/test/components/views/dialogs/ExportDialog-test.tsx b/test/components/views/dialogs/ExportDialog-test.tsx
index a69190f5047..db87a1c1c67 100644
--- a/test/components/views/dialogs/ExportDialog-test.tsx
+++ b/test/components/views/dialogs/ExportDialog-test.tsx
@@ -21,14 +21,12 @@ import '../../../skinned-sdk';
import { act } from "react-dom/test-utils";
import { Room } from 'matrix-js-sdk';
-import ExportDialog,
-{ getSafeForceRoomExportParameters, ForceRoomExportParameters }
- from '../../../../src/components/views/dialogs/ExportDialog';
+import ExportDialog from '../../../../src/components/views/dialogs/ExportDialog';
import { ExportType, ExportFormat } from '../../../../src/utils/exportUtils/exportUtils';
import { createTestClient, mkStubRoom } from '../../../test-utils';
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
import HTMLExporter from "../../../../src/utils/exportUtils/HtmlExport";
-import SettingsStore from '../../../../src/settings/SettingsStore';
+import ChatExport from '../../../../src/customisations/ChatExport';
import PlainTextExporter from '../../../../src/utils/exportUtils/PlainTextExport';
jest.useFakeTimers();
@@ -42,12 +40,11 @@ const plainTextExporterInstance = ({
jest.mock("../../../../src/utils/exportUtils/HtmlExport", () => jest.fn());
jest.mock("../../../../src/utils/exportUtils/PlainTextExport", () => jest.fn());
-jest.mock('../../../../src/settings/SettingsStore', () => ({
- monitorSetting: jest.fn(),
- getValue: jest.fn(),
+jest.mock('../../../../src/customisations/ChatExport', () => ({
+ getForceChatExportParameters: jest.fn().mockReturnValue({}),
}));
-const SettingsStoreMock = mocked(SettingsStore);
+const ChatExportMock = mocked(ChatExport);
const HTMLExporterMock = mocked(HTMLExporter);
const PlainTextExporterMock = mocked(PlainTextExporter);
@@ -105,7 +102,7 @@ describe('', () => {
plainTextExporterInstance.export.mockClear();
// default setting value
- SettingsStoreMock.getValue.mockClear().mockReturnValue({});
+ ChatExportMock.getForceChatExportParameters.mockClear().mockReturnValue({});
});
it('renders export dialog', () => {
@@ -141,12 +138,12 @@ describe('', () => {
});
it('exports room using values set from ForceRoomExportParameters', async () => {
- SettingsStoreMock.getValue.mockReturnValue({
+ ChatExportMock.getForceChatExportParameters.mockReturnValue({
format: ExportFormat.PlainText,
range: ExportType.Beginning,
sizeMb: 15000,
numberOfMessages: 30,
- attachmentsIncluded: true,
+ includeAttachments: true,
});
const component = getComponent();
await submitForm(component);
@@ -157,7 +154,7 @@ describe('', () => {
defaultProps.room,
ExportType.Beginning,
{
- attachmentsIncluded: false,
+ attachmentsIncluded: true,
maxSize: 15000 * 1024 * 1024,
numberOfMessages: 30,
},
@@ -194,7 +191,7 @@ describe('', () => {
});
it('does not render export format when set in ForceRoomExportParameters', () => {
- SettingsStoreMock.getValue.mockReturnValue({
+ ChatExportMock.getForceChatExportParameters.mockReturnValue({
format: ExportFormat.PlainText,
});
const component = getComponent();
@@ -215,7 +212,7 @@ describe('', () => {
});
it('does not render export type when set in ForceRoomExportParameters', () => {
- SettingsStoreMock.getValue.mockReturnValue({
+ ChatExportMock.getForceChatExportParameters.mockReturnValue({
range: ExportType.Beginning,
});
const component = getComponent();
@@ -306,7 +303,7 @@ describe('', () => {
});
it('does not render size limit input when set in ForceRoomExportParameters', () => {
- SettingsStoreMock.getValue.mockReturnValue({
+ ChatExportMock.getForceChatExportParameters.mockReturnValue({
sizeMb: 10000,
});
const component = getComponent();
@@ -317,7 +314,7 @@ describe('', () => {
* 2000mb size limit does not apply when higher limit is configured in config
*/
it('exports when size limit set in ForceRoomExportParameters is larger than 2000', async () => {
- SettingsStoreMock.getValue.mockReturnValue({
+ ChatExportMock.getForceChatExportParameters.mockReturnValue({
sizeMb: 10000,
});
const component = getComponent();
@@ -340,41 +337,12 @@ describe('', () => {
});
it('does not render input when set in ForceRoomExportParameters', () => {
- SettingsStoreMock.getValue.mockReturnValue({
+ ChatExportMock.getForceChatExportParameters.mockReturnValue({
includeAttachments: false,
});
const component = getComponent();
expect(getAttachmentsCheckbox(component).length).toBeFalsy();
});
});
-
- describe('getSafeForceRoomExportParameters()', () => {
- const testCases: [string, ForceRoomExportParameters, ForceRoomExportParameters][] = [
- ['setting is falsy', undefined, {}],
- ['setting is configured to string', 'test' as unknown, {}],
- ['setting is empty', {}, {}],
- ['format is not a valid ExportFormat', { format: 'mp3' }, {}],
- ['format is a valid ExportFormat', { format: ExportFormat.Html }, { format: ExportFormat.Html }],
- ['range is not a valid ExportType', { range: 'yesterday' }, {}],
- ['range is a valid ExportType', { range: ExportType.LastNMessages }, { range: ExportType.LastNMessages }],
- ['numberOfMessages is not a number', { numberOfMessages: 'test' }, {}],
- ['numberOfMessages is less than 1', { numberOfMessages: -1 }, {}],
- ['numberOfMessages is more than 100000000', { numberOfMessages: 9999999999 }, {}],
- ['numberOfMessages is valid', { numberOfMessages: 2000 }, { numberOfMessages: 2000 }],
- ['sizeMb is not a number', { sizeMb: 'test' }, {}],
- ['sizeMb is less than 1', { sizeMb: -1 }, {}],
- ['sizeMb is more than 100000', { sizeMb: Number.MAX_SAFE_INTEGER }, {}],
- ['sizeMb is valid', { sizeMb: 50000 }, { sizeMb: 50000 }],
- ['includeAttachments is not a boolean', { includeAttachments: 'yes' }, {}],
- ['includeAttachments is true', { includeAttachments: true }, { includeAttachments: true }],
- ['includeAttachments is false', { includeAttachments: false }, { includeAttachments: false }],
- ];
-
- it.each(testCases)('sanitizes correctly when %s', (_d, setting, expected) => {
- SettingsStoreMock.getValue.mockReturnValue(setting);
-
- expect(getSafeForceRoomExportParameters()).toEqual(expected);
- });
- });
});
From 02dbc601efbd9bb3ee60812f6ab8ebd1990ab07c Mon Sep 17 00:00:00 2001
From: Kerry Archibald
Date: Fri, 28 Jan 2022 14:32:07 +0100
Subject: [PATCH 08/11] move validateNumberInRange to utils
Signed-off-by: Kerry Archibald
---
src/components/views/dialogs/ExportDialog.tsx | 16 +++++++-----
src/utils/validate/index.ts | 1 +
src/utils/validate/numberInRange.ts | 9 +++++++
test/utils/validate/numberInRange-test.ts | 26 +++++++++++++++++++
4 files changed, 45 insertions(+), 7 deletions(-)
create mode 100644 src/utils/validate/index.ts
create mode 100644 src/utils/validate/numberInRange.ts
create mode 100644 test/utils/validate/numberInRange-test.ts
diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx
index 83a871e8dad..04f183c904d 100644
--- a/src/components/views/dialogs/ExportDialog.tsx
+++ b/src/components/views/dialogs/ExportDialog.tsx
@@ -40,16 +40,12 @@ import Exporter from "../../../utils/exportUtils/Exporter";
import Spinner from "../elements/Spinner";
import InfoDialog from "./InfoDialog";
import ChatExport from "../../../customisations/ChatExport";
+import { validateNumberInRange } from "../../../utils/validate";
interface IProps extends IDialogProps {
room: Room;
}
-const validateNumberInRange = (min: number, max: number) => (value?: string | number) => {
- const parsedSize = parseInt(value as string, 10);
- return !(isNaN(parsedSize) || min > parsedSize || parsedSize > max);
-};
-
interface ExportConfig {
exportFormat: ExportFormat;
exportType: ExportType;
@@ -203,7 +199,10 @@ const ExportDialog: React.FC = ({ room, onFinished }) => {
},
}, {
key: "number",
- test: ({ value }) => validateNumberInRange(1, 2000)(value),
+ test: ({ value }) => {
+ const parsedSize = parseInt(value as string, 10);
+ return validateNumberInRange(1, 2000)(parsedSize);
+ },
invalid: () => {
const min = 1;
const max = 2000;
@@ -238,7 +237,10 @@ const ExportDialog: React.FC = ({ room, onFinished }) => {
},
}, {
key: "number",
- test: ({ value }) => validateNumberInRange(1, 10 ** 8)(value),
+ test: ({ value }) => {
+ const parsedSize = parseInt(value as string, 10);
+ return validateNumberInRange(1, 10 ** 8)(parsedSize);
+ },
invalid: () => {
const min = 1;
const max = 10 ** 8;
diff --git a/src/utils/validate/index.ts b/src/utils/validate/index.ts
new file mode 100644
index 00000000000..f4357cbc17f
--- /dev/null
+++ b/src/utils/validate/index.ts
@@ -0,0 +1 @@
+export * from "./numberInRange";
diff --git a/src/utils/validate/numberInRange.ts b/src/utils/validate/numberInRange.ts
new file mode 100644
index 00000000000..dda5af8f07c
--- /dev/null
+++ b/src/utils/validate/numberInRange.ts
@@ -0,0 +1,9 @@
+
+/**
+ * Validates that a value is
+ * - a number
+ * - in a provided range (inclusive)
+ */
+export const validateNumberInRange = (min: number, max: number) => (value?: number) => {
+ return typeof value === 'number' && !(isNaN(value) || min > value || value > max);
+};
diff --git a/test/utils/validate/numberInRange-test.ts b/test/utils/validate/numberInRange-test.ts
new file mode 100644
index 00000000000..f7ad2e8c1c2
--- /dev/null
+++ b/test/utils/validate/numberInRange-test.ts
@@ -0,0 +1,26 @@
+import { validateNumberInRange } from '../../../src/utils/validate';
+
+describe('validateNumberInRange', () => {
+ const min = 1; const max = 10;
+ it('returns false when value is a not a number', () => {
+ expect(validateNumberInRange(min, max)('test' as unknown as number)).toEqual(false);
+ });
+ it('returns false when value is undefined', () => {
+ expect(validateNumberInRange(min, max)(undefined)).toEqual(false);
+ });
+ it('returns false when value is NaN', () => {
+ expect(validateNumberInRange(min, max)(NaN)).toEqual(false);
+ });
+ it('returns true when value is equal to min', () => {
+ expect(validateNumberInRange(min, max)(min)).toEqual(true);
+ });
+ it('returns true when value is equal to max', () => {
+ expect(validateNumberInRange(min, max)(max)).toEqual(true);
+ });
+ it('returns true when value is an int in range', () => {
+ expect(validateNumberInRange(min, max)(2)).toEqual(true);
+ });
+ it('returns true when value is a float in range', () => {
+ expect(validateNumberInRange(min, max)(2.2)).toEqual(true);
+ });
+});
From 4118e3e3d8295ede29e2e95da972768abc95f595 Mon Sep 17 00:00:00 2001
From: Kerry Archibald
Date: Fri, 28 Jan 2022 14:34:56 +0100
Subject: [PATCH 09/11] use nullish coalesce
Signed-off-by: Kerry Archibald
---
src/components/views/dialogs/ExportDialog.tsx | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/components/views/dialogs/ExportDialog.tsx b/src/components/views/dialogs/ExportDialog.tsx
index 04f183c904d..918bc733c12 100644
--- a/src/components/views/dialogs/ExportDialog.tsx
+++ b/src/components/views/dialogs/ExportDialog.tsx
@@ -67,12 +67,12 @@ interface ExportConfig {
const useExportFormState = (): ExportConfig => {
const config = ChatExport.getForceChatExportParameters();
- const [exportFormat, setExportFormat] = useState(config.format || ExportFormat.Html);
- const [exportType, setExportType] = useState(config.range || ExportType.Timeline);
+ const [exportFormat, setExportFormat] = useState(config.format ?? ExportFormat.Html);
+ const [exportType, setExportType] = useState(config.range ?? ExportType.Timeline);
const [includeAttachments, setAttachments] =
- useState(config.includeAttachments !== undefined && config.includeAttachments);
- const [numberOfMessages, setNumberOfMessages] = useState(config.numberOfMessages || 100);
- const [sizeLimit, setSizeLimit] = useState(config.sizeMb || 8);
+ useState(config.includeAttachments ?? false);
+ const [numberOfMessages, setNumberOfMessages] = useState(config.numberOfMessages ?? 100);
+ const [sizeLimit, setSizeLimit] = useState(config.sizeMb ?? 8);
return {
exportFormat,
From 00f049506d481f4c3b47bfbf3da5eb2d1ee2995f Mon Sep 17 00:00:00 2001
From: Kerry Archibald
Date: Mon, 31 Jan 2022 12:41:58 +0100
Subject: [PATCH 10/11] use 8gb size limit for customisation
Signed-off-by: Kerry Archibald
---
src/customisations/ChatExport.ts | 3 +++
src/utils/exportUtils/Exporter.ts | 2 +-
test/components/views/dialogs/ExportDialog-test.tsx | 4 ++--
test/utils/export-test.tsx | 4 ++--
4 files changed, 8 insertions(+), 5 deletions(-)
diff --git a/src/customisations/ChatExport.ts b/src/customisations/ChatExport.ts
index 6c69e55154f..938f3a1dc6f 100644
--- a/src/customisations/ChatExport.ts
+++ b/src/customisations/ChatExport.ts
@@ -20,8 +20,11 @@ export type ForceChatExportParameters = {
format?: ExportFormat;
range?: ExportType;
// must be < 10**8
+ // only used when range is 'LastNMessages'
+ // default is 100
numberOfMessages?: number;
includeAttachments?: boolean;
+ // maximum size of exported archive
// must be > 0 and < 100000
sizeMb?: number;
};
diff --git a/src/utils/exportUtils/Exporter.ts b/src/utils/exportUtils/Exporter.ts
index b55cb6d1bad..7b4a7bf342f 100644
--- a/src/utils/exportUtils/Exporter.ts
+++ b/src/utils/exportUtils/Exporter.ts
@@ -48,7 +48,7 @@ export default abstract class Exporter {
protected setProgressText: React.Dispatch>,
) {
if (exportOptions.maxSize < 1 * 1024 * 1024|| // Less than 1 MB
- exportOptions.maxSize > 100000 * 1024 * 1024 || // More than 100 GB
+ exportOptions.maxSize > 8000 * 1024 * 1024 || // More than 8 GB
exportOptions.numberOfMessages > 10**8
) {
throw new Error("Invalid export options");
diff --git a/test/components/views/dialogs/ExportDialog-test.tsx b/test/components/views/dialogs/ExportDialog-test.tsx
index db87a1c1c67..07d859358a4 100644
--- a/test/components/views/dialogs/ExportDialog-test.tsx
+++ b/test/components/views/dialogs/ExportDialog-test.tsx
@@ -141,7 +141,7 @@ describe('', () => {
ChatExportMock.getForceChatExportParameters.mockReturnValue({
format: ExportFormat.PlainText,
range: ExportType.Beginning,
- sizeMb: 15000,
+ sizeMb: 7000,
numberOfMessages: 30,
includeAttachments: true,
});
@@ -155,7 +155,7 @@ describe('', () => {
ExportType.Beginning,
{
attachmentsIncluded: true,
- maxSize: 15000 * 1024 * 1024,
+ maxSize: 7000 * 1024 * 1024,
numberOfMessages: 30,
},
]);
diff --git a/test/utils/export-test.tsx b/test/utils/export-test.tsx
index e0232c775d4..a9d0f00e2b4 100644
--- a/test/utils/export-test.tsx
+++ b/test/utils/export-test.tsx
@@ -200,9 +200,9 @@ describe('export', function() {
maxSize: 1024 * 1024 * 1024,
attachmentsIncluded: false,
}],
- ['maxSize exceeds 100GB', {
+ ['maxSize exceeds 8GB', {
numberOfMessages: -1,
- maxSize: 100001 * 1024 * 1024,
+ maxSize: 8001 * 1024 * 1024,
attachmentsIncluded: false,
}],
['maxSize is less than 1mb', {
From 506900cbb444b02d62d4785ad6a95e5d57910cb5 Mon Sep 17 00:00:00 2001
From: Kerry Archibald
Date: Mon, 31 Jan 2022 12:43:35 +0100
Subject: [PATCH 11/11] update comments
Signed-off-by: Kerry Archibald
---
src/customisations/ChatExport.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/customisations/ChatExport.ts b/src/customisations/ChatExport.ts
index 938f3a1dc6f..abb55a07482 100644
--- a/src/customisations/ChatExport.ts
+++ b/src/customisations/ChatExport.ts
@@ -25,7 +25,7 @@ export type ForceChatExportParameters = {
numberOfMessages?: number;
includeAttachments?: boolean;
// maximum size of exported archive
- // must be > 0 and < 100000
+ // must be > 0 and < 8000
sizeMb?: number;
};