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

Commit 3eca71b

Browse files
author
Kerry
authored
Unit test ExportDialog (#7619)
* add test ids to dialog buttons Signed-off-by: Kerry Archibald <[email protected]> * unit test ExportDialog Signed-off-by: Kerry Archibald <[email protected]> * remove extra snapshot Signed-off-by: Kerry Archibald <[email protected]> * fix bad snapshots Signed-off-by: Kerry Archibald <[email protected]> * remove wrappers from snapshot Signed-off-by: Kerry Archibald <[email protected]>
1 parent 50f8c61 commit 3eca71b

File tree

5 files changed

+3175
-3
lines changed

5 files changed

+3175
-3
lines changed

src/components/views/dialogs/ExportDialog.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
138138
},
139139
invalid: () => {
140140
const min = 1;
141-
const max = 10 ** 8;
141+
const max = 2000;
142142
return _t("Enter a number between %(min)s and %(max)s", {
143143
min,
144144
max,
@@ -239,6 +239,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
239239
if (exportType === ExportType.LastNMessages) {
240240
messageCount = (
241241
<Field
242+
id="message-count"
242243
element="input"
243244
type="number"
244245
value={numberOfMessages.toString()}
@@ -335,6 +336,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
335336
</span>
336337

337338
<Field
339+
id="export-type"
338340
element="select"
339341
value={exportType}
340342
onChange={(e) => {
@@ -350,6 +352,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
350352
</span>
351353

352354
<Field
355+
id="size-limit"
353356
type="number"
354357
autoComplete="off"
355358
onValidate={onValidateSize}
@@ -361,6 +364,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
361364
/>
362365

363366
<StyledCheckbox
367+
id="include-attachments"
364368
checked={includeAttachments}
365369
onChange={(e) =>
366370
setAttachments(
@@ -372,7 +376,7 @@ const ExportDialog: React.FC<IProps> = ({ room, onFinished }) => {
372376
</StyledCheckbox>
373377
</div>
374378
{ isExporting ? (
375-
<div className="mx_ExportDialog_progress">
379+
<div data-test-id='export-progress' className="mx_ExportDialog_progress">
376380
<Spinner w={24} h={24} />
377381
<p>
378382
{ exportProgressText }

src/components/views/elements/DialogButtons.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export default class DialogButtons extends React.Component<IProps> {
8383
cancelButton = <button
8484
// important: the default type is 'submit' and this button comes before the
8585
// primary in the DOM so will get form submissions unless we make it not a submit.
86+
data-test-id="dialog-cancel-button"
8687
type="button"
8788
onClick={this.onCancelClick}
8889
className={this.props.cancelButtonClass}
@@ -103,6 +104,7 @@ export default class DialogButtons extends React.Component<IProps> {
103104
{ cancelButton }
104105
{ this.props.children }
105106
<button type={this.props.primaryIsSubmit ? 'submit' : 'button'}
107+
data-test-id="dialog-primary-button"
106108
className={primaryButtonClassName}
107109
onClick={this.props.onPrimaryButtonClick}
108110
autoFocus={this.props.focus}

src/components/views/elements/StyledCheckbox.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export enum CheckboxStyle {
2828
interface IProps extends React.InputHTMLAttributes<HTMLInputElement> {
2929
inputRef?: React.RefObject<HTMLInputElement>;
3030
kind?: CheckboxStyle;
31+
id?: string;
3132
}
3233

3334
interface IState {
@@ -44,7 +45,7 @@ export default class StyledCheckbox extends React.PureComponent<IProps, IState>
4445
constructor(props: IProps) {
4546
super(props);
4647
// 56^10 so unlikely chance of collision.
47-
this.id = "checkbox_" + randomString(10);
48+
this.id = this.props.id || "checkbox_" + randomString(10);
4849
}
4950

5051
public render() {
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/*
2+
Copyright 2022 The Matrix.org Foundation C.I.C.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
import React from 'react';
18+
import { mount } from 'enzyme';
19+
import '../../../skinned-sdk';
20+
import { act } from "react-dom/test-utils";
21+
import { Room } from 'matrix-js-sdk';
22+
23+
import ExportDialog from '../../../../src/components/views/dialogs/ExportDialog';
24+
import { ExportType, ExportFormat } from '../../../../src/utils/exportUtils/exportUtils';
25+
import { createTestClient, mkStubRoom } from '../../../test-utils';
26+
import { MatrixClientPeg } from '../../../../src/MatrixClientPeg';
27+
import HTMLExporter from "../../../../src/utils/exportUtils/HtmlExport";
28+
29+
jest.useFakeTimers();
30+
31+
const mockHtmlExporter = ({
32+
export: jest.fn().mockResolvedValue({}),
33+
});
34+
jest.mock("../../../../src/utils/exportUtils/HtmlExport", () => jest.fn());
35+
36+
describe('<ExportDialog />', () => {
37+
const mockClient = createTestClient();
38+
jest.spyOn(MatrixClientPeg, 'get').mockReturnValue(mockClient);
39+
40+
const roomId = 'test:test.org';
41+
const defaultProps = {
42+
room: mkStubRoom(roomId, 'test', mockClient) as unknown as Room,
43+
onFinished: jest.fn(),
44+
};
45+
46+
const getComponent = (props = {}) => mount(<ExportDialog {...defaultProps} {...props} />);
47+
48+
const getSizeInput = (component) => component.find('input[id="size-limit"]');
49+
const getExportTypeInput = (component) => component.find('select[id="export-type"]');
50+
const getAttachmentsCheckbox = (component) => component.find('input[id="include-attachments"]');
51+
const getMessageCountInput = (component) => component.find('input[id="message-count"]');
52+
const getExportFormatInput = (component, format) => component.find(`input[id="exportFormat-${format}"]`);
53+
const getPrimaryButton = (component) => component.find('[data-test-id="dialog-primary-button"]');
54+
const getSecondaryButton = (component) => component.find('[data-test-id="dialog-cancel-button"]');
55+
56+
const submitForm = async (component) => act(async () => {
57+
getPrimaryButton(component).simulate('click');
58+
component.setProps({});
59+
});
60+
const selectExportFormat = async (component, format: ExportFormat) => act(async () => {
61+
getExportFormatInput(component, format).simulate('change');
62+
component.setProps({});
63+
});
64+
const selectExportType = async (component, type: ExportType) => act(async () => {
65+
getExportTypeInput(component).simulate('change', { target: { value: type } });
66+
component.setProps({});
67+
});
68+
const setMessageCount = async (component, count: number) => act(async () => {
69+
getMessageCountInput(component).simulate('change', { target: { value: count } });
70+
component.setProps({});
71+
});
72+
73+
const setSizeLimit = async (component, limit: number) => act(async () => {
74+
getSizeInput(component).simulate('change', { target: { value: limit } });
75+
component.setProps({});
76+
});
77+
78+
const setIncludeAttachments = async (component, checked) => act(async () => {
79+
getAttachmentsCheckbox(component).simulate('change', { target: { checked } });
80+
component.setProps({});
81+
});
82+
83+
beforeEach(() => {
84+
(HTMLExporter as jest.Mock).mockImplementation(jest.fn().mockReturnValue(mockHtmlExporter));
85+
mockHtmlExporter.export.mockClear();
86+
});
87+
88+
it('renders export dialog', () => {
89+
const component = getComponent();
90+
expect(component.find('.mx_ExportDialog')).toMatchSnapshot();
91+
});
92+
93+
it('calls onFinished when cancel button is clicked', () => {
94+
const onFinished = jest.fn();
95+
const component = getComponent({ onFinished });
96+
act(() => {
97+
getSecondaryButton(component).simulate('click');
98+
});
99+
expect(onFinished).toHaveBeenCalledWith(false);
100+
});
101+
102+
it('exports room on submit', async () => {
103+
const component = getComponent();
104+
await submitForm(component);
105+
106+
// 4th arg is an component function
107+
const exportConstructorProps = (HTMLExporter as jest.Mock).mock.calls[0].slice(0, 3);
108+
expect(exportConstructorProps).toEqual([
109+
defaultProps.room,
110+
ExportType.Timeline,
111+
{
112+
attachmentsIncluded: false,
113+
maxSize: 8388608, // 8MB to bytes
114+
numberOfMessages: 100,
115+
},
116+
]);
117+
expect(mockHtmlExporter.export).toHaveBeenCalled();
118+
});
119+
120+
it('renders success screen when export is finished', async () => {
121+
const component = getComponent();
122+
await submitForm(component);
123+
component.setProps({});
124+
125+
jest.runAllTimers();
126+
127+
expect(component.find('.mx_InfoDialog .mx_Dialog_content')).toMatchSnapshot();
128+
});
129+
130+
describe('export format', () => {
131+
it('renders export format with html selected by default', () => {
132+
const component = getComponent();
133+
expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeTruthy();
134+
});
135+
136+
it('sets export format on radio button click', async () => {
137+
const component = getComponent();
138+
await selectExportFormat(component, ExportFormat.PlainText);
139+
expect(getExportFormatInput(component, ExportFormat.PlainText).props().checked).toBeTruthy();
140+
expect(getExportFormatInput(component, ExportFormat.Html).props().checked).toBeFalsy();
141+
});
142+
});
143+
144+
describe('export type', () => {
145+
it('renders export type with timeline selected by default', () => {
146+
const component = getComponent();
147+
expect(getExportTypeInput(component).props().value).toEqual(ExportType.Timeline);
148+
});
149+
150+
it('sets export type on change', async () => {
151+
const component = getComponent();
152+
await selectExportType(component, ExportType.Beginning);
153+
expect(getExportTypeInput(component).props().value).toEqual(ExportType.Beginning);
154+
});
155+
156+
it('does not render message count input', async () => {
157+
const component = getComponent();
158+
expect(getMessageCountInput(component).length).toBeFalsy();
159+
});
160+
161+
it('renders message count input with default value 100 when export type is lastNMessages', async () => {
162+
const component = getComponent();
163+
await selectExportType(component, ExportType.LastNMessages);
164+
expect(getMessageCountInput(component).props().value).toEqual("100");
165+
});
166+
167+
it('sets message count on change', async () => {
168+
const component = getComponent();
169+
await selectExportType(component, ExportType.LastNMessages);
170+
await setMessageCount(component, 10);
171+
expect(getMessageCountInput(component).props().value).toEqual("10");
172+
});
173+
174+
it('does not export when export type is lastNMessages and message count is falsy', async () => {
175+
const component = getComponent();
176+
await selectExportType(component, ExportType.LastNMessages);
177+
await setMessageCount(component, 0);
178+
await submitForm(component);
179+
180+
expect(mockHtmlExporter.export).not.toHaveBeenCalled();
181+
});
182+
183+
it('does not export when export type is lastNMessages and message count is more than max', async () => {
184+
const component = getComponent();
185+
await selectExportType(component, ExportType.LastNMessages);
186+
await setMessageCount(component, 99999999999);
187+
await submitForm(component);
188+
189+
expect(mockHtmlExporter.export).not.toHaveBeenCalled();
190+
});
191+
192+
it('exports when export type is NOT lastNMessages and message count is falsy', async () => {
193+
const component = getComponent();
194+
await selectExportType(component, ExportType.LastNMessages);
195+
await setMessageCount(component, 0);
196+
await selectExportType(component, ExportType.Timeline);
197+
await submitForm(component);
198+
199+
expect(mockHtmlExporter.export).toHaveBeenCalled();
200+
});
201+
});
202+
203+
describe('size limit', () => {
204+
it('renders size limit input with default value', () => {
205+
const component = getComponent();
206+
expect(getSizeInput(component).props().value).toEqual("8");
207+
});
208+
209+
it('updates size limit on change', async () => {
210+
const component = getComponent();
211+
await setSizeLimit(component, 20);
212+
expect(getSizeInput(component).props().value).toEqual("20");
213+
});
214+
215+
it('does not export when size limit is falsy', async () => {
216+
const component = getComponent();
217+
await setSizeLimit(component, 0);
218+
await submitForm(component);
219+
220+
expect(mockHtmlExporter.export).not.toHaveBeenCalled();
221+
});
222+
223+
it('does not export when size limit is larger than max', async () => {
224+
const component = getComponent();
225+
await setSizeLimit(component, 2001);
226+
await submitForm(component);
227+
228+
expect(mockHtmlExporter.export).not.toHaveBeenCalled();
229+
});
230+
231+
it('exports when size limit is max', async () => {
232+
const component = getComponent();
233+
await setSizeLimit(component, 2000);
234+
await submitForm(component);
235+
236+
expect(mockHtmlExporter.export).toHaveBeenCalled();
237+
});
238+
});
239+
240+
describe('include attachements', () => {
241+
it('renders input with default value of false', () => {
242+
const component = getComponent();
243+
expect(getAttachmentsCheckbox(component).props().checked).toEqual(false);
244+
});
245+
246+
it('updates include attachments on change', async () => {
247+
const component = getComponent();
248+
await setIncludeAttachments(component, true);
249+
expect(getAttachmentsCheckbox(component).props().checked).toEqual(true);
250+
});
251+
});
252+
});
253+

0 commit comments

Comments
 (0)