[MM-47113] Migrate "components/interactive_dialog/interactive_dialog.jsx" and tests to Typescript (#25026)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
ee3b5e6810
Коммит
1043aa5330
@@ -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}
|
||||
Ссылка в новой задаче
Block a user