[MM-47113] Migrate "components/interactive_dialog/interactive_dialog.jsx" and tests to Typescript (#25026)

Этот коммит содержится в:
Sudhanva-Nadiger
2023-10-28 12:29:50 +05:30
коммит произвёл GitHub
родитель ee3b5e6810
Коммит 1043aa5330
2 изменённых файлов: 88 добавлений и 56 удалений

Просмотреть файл

@@ -6,14 +6,21 @@ import React from 'react';
import {Modal} from 'react-bootstrap';
import {Provider} from 'react-redux';
import type {DialogElement as TDialogElement} from '@mattermost/types/integrations';
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
import mockStore from 'tests/test_store';
import EmojiMap from 'utils/emoji_map';
import InteractiveDialog from './interactive_dialog.jsx';
import type {Props} from './interactive_dialog';
import InteractiveDialog from './interactive_dialog';
const submitEvent = {
preventDefault: jest.fn(),
} as unknown as React.FormEvent<HTMLFormElement>;
describe('components/interactive_dialog/InteractiveDialog', () => {
const baseProps = {
const baseProps: Props = {
url: 'http://example.com',
callbackId: 'abc',
elements: [],
@@ -22,9 +29,9 @@ describe('components/interactive_dialog/InteractiveDialog', () => {
submitLabel: 'Yes',
notifyOnCancel: true,
state: 'some state',
onExited: () => {},
onExited: jest.fn(),
actions: {
submitInteractiveDialog: () => ({}),
submitInteractiveDialog: jest.fn(),
},
emojiMap: new EmojiMap(new Map()),
};
@@ -34,14 +41,12 @@ describe('components/interactive_dialog/InteractiveDialog', () => {
const props = {
...baseProps,
actions: {
submitInteractiveDialog: () => ({
data: {error: 'This is an error.'},
}),
submitInteractiveDialog: jest.fn().mockResolvedValue({data: {error: 'This is an error.'}}),
},
};
const wrapper = shallow(<InteractiveDialog {...props}/>);
const wrapper = shallow<InteractiveDialog>(<InteractiveDialog {...props}/>);
await wrapper.instance().handleSubmit({preventDefault: jest.fn()});
await wrapper.instance().handleSubmit(submitEvent);
const expected = (
<div className='error-text'>
@@ -52,8 +57,8 @@ describe('components/interactive_dialog/InteractiveDialog', () => {
});
test('should not appear when submit does not return an error', async () => {
const wrapper = shallow(<InteractiveDialog {...baseProps}/>);
await wrapper.instance().handleSubmit({preventDefault: jest.fn()});
const wrapper = shallow<InteractiveDialog>(<InteractiveDialog {...baseProps}/>);
await wrapper.instance().handleSubmit(submitEvent);
expect(wrapper.find(Modal.Footer).exists('.error-text')).toBe(false);
});
@@ -61,7 +66,7 @@ describe('components/interactive_dialog/InteractiveDialog', () => {
describe('default select element in Interactive Dialog', () => {
test('should be enabled by default', () => {
const selectElement = {
const selectElement: TDialogElement = {
data_source: '',
default: 'opt3',
display_name: 'Option Selector',
@@ -73,10 +78,15 @@ describe('components/interactive_dialog/InteractiveDialog', () => {
{text: 'Option3', value: 'opt3'},
],
type: 'select',
subtype: '',
placeholder: '',
help_text: '',
min_length: 0,
max_length: 0,
};
const {elements, ...rest} = baseProps;
elements.push(selectElement);
elements?.push(selectElement);
const props = {
...rest,
elements,
@@ -93,19 +103,25 @@ describe('components/interactive_dialog/InteractiveDialog', () => {
});
describe('bool element in Interactive Dialog', () => {
const element = {
const element: TDialogElement = {
data_source: '',
display_name: 'Boolean Selector',
name: 'somebool',
optional: false,
type: 'bool',
placeholder: 'Subscribe?',
subtype: '',
default: '',
help_text: '',
min_length: 0,
max_length: 0,
options: [],
};
const {elements, ...rest} = baseProps;
const props = {
...rest,
elements: [
...elements,
...elements || [],
element,
],
};
@@ -122,9 +138,9 @@ describe('components/interactive_dialog/InteractiveDialog', () => {
testCases.forEach((testCase) => test(`should interpret ${testCase.description}`, () => {
if (testCase.default === undefined) {
delete element.default;
delete (element as any).default;
} else {
element.default = testCase.default;
(element as any).default = testCase.default;
}
const store = mockStore({});

Просмотреть файл

@@ -1,11 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import PropTypes from 'prop-types';
import React from 'react';
import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl';
import type {DialogSubmission, DialogElement as TDialogElement} from '@mattermost/types/integrations';
import type {ActionFunc} from 'mattermost-redux/types/actions';
import {
checkDialogElementForError,
checkIfErrorsMatchElements,
@@ -13,41 +15,48 @@ import {
import SpinnerButton from 'components/spinner_button';
import type EmojiMap from 'utils/emoji_map';
import {localizeMessage} from 'utils/utils';
import DialogElement from './dialog_element';
import DialogIntroductionText from './dialog_introduction_text';
export default class InteractiveDialog extends React.PureComponent {
static propTypes = {
url: PropTypes.string.isRequired,
callbackId: PropTypes.string,
elements: PropTypes.arrayOf(PropTypes.object),
title: PropTypes.string.isRequired,
introductionText: PropTypes.string,
iconUrl: PropTypes.string,
submitLabel: PropTypes.string,
notifyOnCancel: PropTypes.bool,
state: PropTypes.string,
onExited: PropTypes.func,
actions: PropTypes.shape({
submitInteractiveDialog: PropTypes.func.isRequired,
}).isRequired,
emojiMap: PropTypes.object.isRequired,
export type Props = {
url: string;
callbackId?: string;
elements?: TDialogElement[];
title: string;
introductionText?: string;
iconUrl?: string;
submitLabel?: string;
notifyOnCancel?: boolean;
state?: string;
onExited?: () => void;
actions: {
submitInteractiveDialog: (submission: DialogSubmission) => ActionFunc;
};
emojiMap: EmojiMap;
}
constructor(props) {
type State = {
show: boolean;
values: Record<string, string | number | boolean>;
error: string | null;
errors: Record<string, JSX.Element>;
submitting: boolean;
}
export default class InteractiveDialog extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
const values = {};
const values: Record<string, string | number | boolean> = {};
if (props.elements != null) {
props.elements.forEach((e) => {
if (e.type === 'bool') {
values[e.name] =
e.default === true ||
String(e.default).toLowerCase() === 'true';
values[e.name] = String(e.default).toLowerCase() === 'true';
} else {
values[e.name] = e.default || null;
values[e.name] = e.default ?? null;
}
});
}
@@ -61,12 +70,13 @@ export default class InteractiveDialog extends React.PureComponent {
};
}
handleSubmit = async (e) => {
handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
const {elements} = this.props;
const values = this.state.values;
const errors = {};
const errors: Record<string, JSX.Element> = {};
if (elements) {
elements.forEach((elem) => {
const error = checkDialogElementForError(
@@ -93,18 +103,20 @@ export default class InteractiveDialog extends React.PureComponent {
const {url, callbackId, state} = this.props;
const dialog = {
const dialog: DialogSubmission = {
url,
callback_id: callbackId,
state,
submission: values,
callback_id: callbackId ?? '',
state: state ?? '',
submission: values as { [x: string]: string },
user_id: '',
channel_id: '',
team_id: '',
cancelled: false,
};
this.setState({submitting: true});
const {data} = await this.props.actions.submitInteractiveDialog(
dialog,
);
const {data}: any = await this.props.actions.submitInteractiveDialog(dialog) ?? {};
this.setState({submitting: false});
@@ -139,11 +151,15 @@ export default class InteractiveDialog extends React.PureComponent {
const {url, callbackId, state, notifyOnCancel} = this.props;
if (!submitted && notifyOnCancel) {
const dialog = {
const dialog: DialogSubmission = {
url,
callback_id: callbackId,
state,
callback_id: callbackId ?? '',
state: state ?? '',
cancelled: true,
user_id: '',
channel_id: '',
team_id: '',
submission: {},
};
this.props.actions.submitInteractiveDialog(dialog);
@@ -152,7 +168,7 @@ export default class InteractiveDialog extends React.PureComponent {
this.setState({show: false});
};
onChange = (name, value) => {
onChange = (name: string, value: string) => {
const values = {...this.state.values, [name]: value};
this.setState({values});
};
@@ -166,12 +182,13 @@ export default class InteractiveDialog extends React.PureComponent {
elements,
} = this.props;
let submitText = (
let submitText: JSX.Element | string = (
<FormattedMessage
id='interactive_dialog.submit'
defaultMessage='Submit'
/>
);
if (submitLabel) {
submitText = submitLabel;
}
@@ -207,7 +224,7 @@ export default class InteractiveDialog extends React.PureComponent {
>
<Modal.Header
closeButton={true}
style={{borderBottom: elements == null && '0px'}}
style={{borderBottom: elements == null ? '0px' : undefined}}
>
<Modal.Title
componentClass='h1'
@@ -239,7 +256,6 @@ export default class InteractiveDialog extends React.PureComponent {
helpText={e.help_text}
errorText={this.state.errors[e.name]}
placeholder={e.placeholder}
minLength={e.min_length}
maxLength={e.max_length}
dataSource={e.data_source}
optional={e.optional}