From 18bc5536f4b7148d054f41868d4cb52a5c1d1257 Mon Sep 17 00:00:00 2001 From: janlengyel Date: Thu, 26 Oct 2023 15:28:27 +0200 Subject: [PATCH] [MM-52846] Migrate "components/autocomplete_selector.jsx" and tests to Typescript (#25056) * Migrate AutocompleteSelector and tests to ts * Remove commented code * More cleanup * Implemented suggestions * Revert formatting change * Remove default value and add undefined check * Remove optional from props with default values * Unify "Selected" type --- ...est.jsx => autocomplete_selector.test.tsx} | 8 +-- ...selector.jsx => autocomplete_selector.tsx} | 67 +++++++++++++------ .../dialog_element/dialog_element.tsx | 25 +++---- .../select_binding/select_binding.tsx | 11 ++- .../action_menu/action_menu.tsx | 8 +-- 5 files changed, 65 insertions(+), 54 deletions(-) rename webapp/channels/src/components/{autocomplete_selector.test.jsx => autocomplete_selector.test.tsx} (93%) rename webapp/channels/src/components/{autocomplete_selector.jsx => autocomplete_selector.tsx} (75%) diff --git a/webapp/channels/src/components/autocomplete_selector.test.jsx b/webapp/channels/src/components/autocomplete_selector.test.tsx similarity index 93% rename from webapp/channels/src/components/autocomplete_selector.test.jsx rename to webapp/channels/src/components/autocomplete_selector.test.tsx index 564a02c930..347e893ea8 100644 --- a/webapp/channels/src/components/autocomplete_selector.test.jsx +++ b/webapp/channels/src/components/autocomplete_selector.test.tsx @@ -52,7 +52,7 @@ describe('components/widgets/settings/AutocompleteSelector', () => { }); test('check snapshot with value prop and changing focus', () => { - const wrapper = shallow( + const wrapper = shallow( { `); - wrapper.instance().onChange({target: {value: 'value from input'}}); + wrapper.instance().onChange(({target: {value: 'value from input'} as HTMLInputElement})); wrapper.instance().onFocus(); expect(wrapper).toMatchInlineSnapshot(` @@ -136,7 +136,7 @@ describe('components/widgets/settings/AutocompleteSelector', () => { test('onSelected', () => { const onSelected = jest.fn(); - const wrapper = shallow( + const wrapper = shallow( { />, ); - const selected = {text: 'sometext', value: 'somevalue'}; + const selected = {text: 'sometext', value: 'somevalue', id: '', username: '', display_name: ''}; wrapper.instance().handleSelected(selected); expect(onSelected).toHaveBeenCalledTimes(1); diff --git a/webapp/channels/src/components/autocomplete_selector.jsx b/webapp/channels/src/components/autocomplete_selector.tsx similarity index 75% rename from webapp/channels/src/components/autocomplete_selector.jsx rename to webapp/channels/src/components/autocomplete_selector.tsx index b14a5ece91..477411147a 100644 --- a/webapp/channels/src/components/autocomplete_selector.jsx +++ b/webapp/channels/src/components/autocomplete_selector.tsx @@ -1,39 +1,62 @@ // 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 type {Channel} from '@mattermost/types/channels'; +import type {UserProfile} from '@mattermost/types/users'; + import SuggestionBox from 'components/suggestion/suggestion_box'; import SuggestionList from 'components/suggestion/suggestion_list'; -export default class AutocompleteSelector extends React.PureComponent { - static propTypes = { - providers: PropTypes.array.isRequired, - value: PropTypes.string.isRequired, - onSelected: PropTypes.func, - label: PropTypes.node, - labelClassName: PropTypes.string, - inputClassName: PropTypes.string, - helpText: PropTypes.node, - placeholder: PropTypes.string, - footer: PropTypes.node, - disabled: PropTypes.bool, - toggleFocus: PropTypes.func, - listComponent: PropTypes.elementType, - listPosition: PropTypes.string, - }; +import type ModalSuggestionList from './suggestion/modal_suggestion_list'; +import type Provider from './suggestion/provider'; +export type Option = { + text: string; + value: string; +}; +export type Selected = Option | UserProfile | Channel + +type Props = { + id: string; + providers: Provider[]; + value: string; + onSelected?: (selected: Selected) => void; + label?: React.ReactNode | string; + labelClassName: string; + inputClassName: string; + helpText?: React.ReactNode | string; + placeholder?: string; + footer?: Node; + disabled?: boolean; + toggleFocus?: ((focus: boolean) => void) | null; + listComponent: typeof SuggestionList | typeof ModalSuggestionList; + listPosition: string; +}; + +type State = { + input: string; + focused?: boolean; +}; + +type ChangeEvent = { + target: HTMLInputElement; +} + +export default class AutocompleteSelector extends React.PureComponent { static defaultProps = { - value: '', id: '', + value: '', labelClassName: '', inputClassName: '', listComponent: SuggestionList, listPosition: 'top', }; - constructor(props) { + suggestionRef?: HTMLElement; + + constructor(props: Props) { super(props); this.state = { @@ -41,7 +64,7 @@ export default class AutocompleteSelector extends React.PureComponent { }; } - onChange = (e) => { + onChange = (e: ChangeEvent) => { if (!e || !e.target) { return; } @@ -49,7 +72,7 @@ export default class AutocompleteSelector extends React.PureComponent { this.setState({input: e.target.value}); }; - handleSelected = (selected) => { + handleSelected = (selected: Selected) => { this.setState({input: ''}); if (this.props.onSelected) { @@ -63,7 +86,7 @@ export default class AutocompleteSelector extends React.PureComponent { }); }; - setSuggestionRef = (ref) => { + setSuggestionRef = (ref: HTMLElement) => { this.suggestionRef = ref; }; diff --git a/webapp/channels/src/components/interactive_dialog/dialog_element/dialog_element.tsx b/webapp/channels/src/components/interactive_dialog/dialog_element/dialog_element.tsx index 613cd41c9a..dda75c1d19 100644 --- a/webapp/channels/src/components/interactive_dialog/dialog_element/dialog_element.tsx +++ b/webapp/channels/src/components/interactive_dialog/dialog_element/dialog_element.tsx @@ -7,10 +7,12 @@ import {FormattedMessage} from 'react-intl'; import type {UserAutocomplete} from '@mattermost/types/autocomplete'; import type {Channel} from '@mattermost/types/channels'; import type {ServerError} from '@mattermost/types/errors'; +import type {UserProfile} from '@mattermost/types/users'; import type {ActionResult} from 'mattermost-redux/types/actions'; import AutocompleteSelector from 'components/autocomplete_selector'; +import type {Option, Selected} from 'components/autocomplete_selector'; import GenericChannelProvider from 'components/suggestion/generic_channel_provider'; import GenericUserProvider from 'components/suggestion/generic_user_provider'; import MenuActionProvider from 'components/suggestion/menu_action_provider'; @@ -52,14 +54,6 @@ type State = { value: string; } -type Selected = { - id: string; - username: string; - display_name: string; - value: string; - text: string; -} - export default class DialogElement extends React.PureComponent { private providers: Provider[]; @@ -94,14 +88,17 @@ export default class DialogElement extends React.PureComponent { const {name, dataSource} = this.props; if (dataSource === 'users') { - this.props.onChange(name, selected.id); - this.setState({value: selected.username}); + const user = selected as UserProfile; + this.props.onChange(name, user.id); + this.setState({value: user.username}); } else if (dataSource === 'channels') { - this.props.onChange(name, selected.id); - this.setState({value: selected.display_name}); + const channel = selected as Channel; + this.props.onChange(name, channel.id); + this.setState({value: channel.display_name}); } else { - this.props.onChange(name, selected.value); - this.setState({value: selected.text}); + const option = selected as Option; + this.props.onChange(name, option.value); + this.setState({value: option.text}); } }; diff --git a/webapp/channels/src/components/post_view/embedded_bindings/select_binding/select_binding.tsx b/webapp/channels/src/components/post_view/embedded_bindings/select_binding/select_binding.tsx index 88b4e3abcc..b7a90c3e29 100644 --- a/webapp/channels/src/components/post_view/embedded_bindings/select_binding/select_binding.tsx +++ b/webapp/channels/src/components/post_view/embedded_bindings/select_binding/select_binding.tsx @@ -13,6 +13,7 @@ import {AppBindingLocations, AppCallResponseTypes} from 'mattermost-redux/consta import type {ActionResult} from 'mattermost-redux/types/actions'; import AutocompleteSelector from 'components/autocomplete_selector'; +import type {Option, Selected} from 'components/autocomplete_selector'; import PostContext from 'components/post_view/post_context'; import MenuActionProvider from 'components/suggestion/menu_action_provider'; @@ -20,11 +21,6 @@ import {createCallContext} from 'utils/apps'; import type {HandleBindingClick, OpenAppsModal, PostEphemeralCallResponseForPost} from 'types/apps'; -type Option = { - text: string; - value: string; -}; - type Props = { intl: IntlShape; post: Post; @@ -84,8 +80,9 @@ class SelectBinding extends React.PureComponent { this.state = {}; } - handleSelected = async (selected: Option) => { - if (!selected) { + handleSelected = async (selected: Selected) => { + //this component expects selected to be of type Option + if (!selected || !('value' in selected)) { return; } diff --git a/webapp/channels/src/components/post_view/message_attachments/action_menu/action_menu.tsx b/webapp/channels/src/components/post_view/message_attachments/action_menu/action_menu.tsx index 8c3d3874a9..e78a8cf630 100644 --- a/webapp/channels/src/components/post_view/message_attachments/action_menu/action_menu.tsx +++ b/webapp/channels/src/components/post_view/message_attachments/action_menu/action_menu.tsx @@ -7,6 +7,7 @@ import type {Channel} from '@mattermost/types/channels'; import type {UserProfile} from '@mattermost/types/users'; import AutocompleteSelector from 'components/autocomplete_selector'; +import type {Option, Selected} from 'components/autocomplete_selector'; import PostContext from 'components/post_view/post_context'; import GenericChannelProvider from 'components/suggestion/generic_channel_provider'; import GenericUserProvider from 'components/suggestion/generic_user_provider'; @@ -14,15 +15,8 @@ import MenuActionProvider from 'components/suggestion/menu_action_provider'; import type {OwnProps, PropsFromRedux} from './index'; -type Option = { - text: string; - value: string; -}; - type Provider = GenericUserProvider | GenericChannelProvider | MenuActionProvider; -type Selected = Option | UserProfile | Channel; - export type Props = OwnProps & PropsFromRedux; type State = {