diff --git a/webapp/channels/src/components/integrations/__snapshots__/abstract_oauth_app.test.jsx.snap b/webapp/channels/src/components/integrations/__snapshots__/abstract_oauth_app.test.tsx.snap
similarity index 97%
rename from webapp/channels/src/components/integrations/__snapshots__/abstract_oauth_app.test.jsx.snap
rename to webapp/channels/src/components/integrations/__snapshots__/abstract_oauth_app.test.tsx.snap
index 4c8da97d1f..a52c83bc56 100644
--- a/webapp/channels/src/components/integrations/__snapshots__/abstract_oauth_app.test.jsx.snap
+++ b/webapp/channels/src/components/integrations/__snapshots__/abstract_oauth_app.test.tsx.snap
@@ -113,7 +113,7 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot 1`] = `
@@ -296,7 +296,9 @@ https://test.com/callback2"
id="Footer"
/>
- renderExtra
+
+ renderExtra
+
@@ -416,7 +418,7 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot, display
- renderExtra
+
+ renderExtra
+
diff --git a/webapp/channels/src/components/integrations/abstract_oauth_app.test.jsx b/webapp/channels/src/components/integrations/abstract_oauth_app.test.tsx
similarity index 71%
rename from webapp/channels/src/components/integrations/abstract_oauth_app.test.jsx
rename to webapp/channels/src/components/integrations/abstract_oauth_app.test.tsx
index 649c556b2d..40068dfaca 100644
--- a/webapp/channels/src/components/integrations/abstract_oauth_app.test.jsx
+++ b/webapp/channels/src/components/integrations/abstract_oauth_app.test.tsx
@@ -2,13 +2,14 @@
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
-import React from 'react';
+import React, {type ChangeEvent} from 'react';
import {FormattedMessage} from 'react-intl';
-import AbstractOAuthApp from 'components/integrations/abstract_oauth_app.jsx';
+import AbstractOAuthApp from 'components/integrations/abstract_oauth_app';
+
+import {TestHelper} from 'utils/test_helper';
describe('components/integrations/AbstractOAuthApp', () => {
- const team = {name: 'test'};
const header = {id: 'Header', defaultMessage: 'Header'};
const footer = {id: 'Footer', defaultMessage: 'Footer'};
const loading = {id: 'Loading', defaultMessage: 'Loading'};
@@ -25,19 +26,23 @@ describe('components/integrations/AbstractOAuthApp', () => {
update_at: 1501365458934,
callback_urls: ['https://test.com/callback', 'https://test.com/callback2'],
};
+
const action = jest.fn().mockImplementation(
() => {
- return new Promise((resolve) => {
+ return new Promise((resolve) => {
process.nextTick(() => resolve());
});
},
);
+
+ const team = TestHelper.getTeamMock({name: 'test', id: initialApp.id});
+
const baseProps = {
team,
header,
footer,
loading,
- renderExtra: 'renderExtra',
+ renderExtra: {'renderExtra'}
,
serverError: '',
initialApp,
action: jest.fn(),
@@ -82,81 +87,89 @@ describe('components/integrations/AbstractOAuthApp', () => {
test('should have correct state when updateName is called', () => {
const props = {...baseProps, action};
- const wrapper = shallow(
+ const wrapper = shallow(
,
);
-
- wrapper.instance().updateName({target: {value: 'new name'}});
+ const evt = {preventDefault: jest.fn(), target: {value: 'new name'}} as unknown as ChangeEvent;
+ wrapper.instance().updateName(evt);
expect(wrapper.state('name')).toEqual('new name');
-
- wrapper.instance().updateName({target: {value: 'other name'}});
- expect(wrapper.state('name')).toEqual('other name');
+ const evt2 = {preventDefault: jest.fn(), target: {value: 'another name'}} as unknown as ChangeEvent;
+ wrapper.instance().updateName(evt2);
+ expect(wrapper.state('name')).toEqual('another name');
});
test('should have correct state when updateTrusted is called', () => {
const props = {...baseProps, action};
- const wrapper = shallow(
+ const wrapper = shallow(
,
);
- wrapper.instance().updateTrusted({target: {value: 'false'}});
+ const evt = {preventDefault: jest.fn(), target: {value: 'false'}} as unknown as ChangeEvent;
+ wrapper.instance().updateTrusted(evt);
expect(wrapper.state('is_trusted')).toEqual(false);
- wrapper.instance().updateTrusted({target: {value: 'true'}});
+ const evt2 = {preventDefault: jest.fn(), target: {value: 'true'}} as unknown as ChangeEvent;
+ wrapper.instance().updateTrusted(evt2);
expect(wrapper.state('is_trusted')).toEqual(true);
});
test('should have correct state when updateDescription is called', () => {
const props = {...baseProps, action};
- const wrapper = shallow(
+ const wrapper = shallow(
,
);
- wrapper.instance().updateDescription({target: {value: 'new description'}});
+ const evt = {preventDefault: jest.fn(), target: {value: 'new description'}} as unknown as ChangeEvent;
+ wrapper.instance().updateDescription(evt);
expect(wrapper.state('description')).toEqual('new description');
- wrapper.instance().updateDescription({target: {value: 'another description'}});
+ const evt2 = {preventDefault: jest.fn(), target: {value: 'another description'}} as unknown as ChangeEvent;
+ wrapper.instance().updateDescription(evt2);
expect(wrapper.state('description')).toEqual('another description');
});
test('should have correct state when updateHomepage is called', () => {
const props = {...baseProps, action};
- const wrapper = shallow(
+ const wrapper = shallow(
,
);
- wrapper.instance().updateHomepage({target: {value: 'new homepage'}});
+ const evt = {preventDefault: jest.fn(), target: {value: 'new homepage'}} as unknown as ChangeEvent;
+ wrapper.instance().updateHomepage(evt);
expect(wrapper.state('homepage')).toEqual('new homepage');
- wrapper.instance().updateHomepage({target: {value: 'another homepage'}});
+ const evt2 = {preventDefault: jest.fn(), target: {value: 'another homepage'}} as unknown as ChangeEvent;
+ wrapper.instance().updateHomepage(evt2);
expect(wrapper.state('homepage')).toEqual('another homepage');
});
test('should have correct state when updateIconUrl is called', () => {
const props = {...baseProps, action};
- const wrapper = shallow(
+ const wrapper = shallow(
,
);
wrapper.setState({has_icon: true});
- wrapper.instance().updateIconUrl({target: {value: 'https://test.com/new_icon_url'}});
+ const evt = {preventDefault: jest.fn(), target: {value: 'https://test.com/new_icon_url'}} as unknown as ChangeEvent;
+ wrapper.instance().updateIconUrl(evt);
expect(wrapper.state('icon_url')).toEqual('https://test.com/new_icon_url');
expect(wrapper.state('has_icon')).toEqual(false);
wrapper.setState({has_icon: true});
- wrapper.instance().updateIconUrl({target: {value: 'https://test.com/another_icon_url'}});
+ const evt2 = {preventDefault: jest.fn(), target: {value: 'https://test.com/another_icon_url'}} as unknown as ChangeEvent;
+ wrapper.instance().updateIconUrl(evt2);
expect(wrapper.state('icon_url')).toEqual('https://test.com/another_icon_url');
expect(wrapper.state('has_icon')).toEqual(false);
});
test('should have correct state when handleSubmit is called', () => {
const props = {...baseProps, action};
- const wrapper = shallow(
+ const wrapper = shallow(
,
);
const newState = {saving: false, name: 'name', description: 'description', homepage: 'homepage'};
- const evt = {preventDefault: jest.fn()};
+ const evt = {preventDefault: jest.fn()} as any;
wrapper.setState({saving: true});
wrapper.instance().handleSubmit(evt);
expect(evt.preventDefault).toHaveBeenCalled();
diff --git a/webapp/channels/src/components/integrations/abstract_oauth_app.jsx b/webapp/channels/src/components/integrations/abstract_oauth_app.tsx
similarity index 87%
rename from webapp/channels/src/components/integrations/abstract_oauth_app.jsx
rename to webapp/channels/src/components/integrations/abstract_oauth_app.tsx
index e985b54039..bfe3eb738c 100644
--- a/webapp/channels/src/components/integrations/abstract_oauth_app.jsx
+++ b/webapp/channels/src/components/integrations/abstract_oauth_app.tsx
@@ -1,11 +1,15 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
-import PropTypes from 'prop-types';
+import type {ChangeEvent, FormEvent} from 'react';
import React from 'react';
import {FormattedMessage} from 'react-intl';
+import type {MessageDescriptor} from 'react-intl';
import {Link} from 'react-router-dom';
+import type {OAuthApp} from '@mattermost/types/integrations';
+import type {Team} from '@mattermost/types/teams';
+
import {Permissions} from 'mattermost-redux/constants';
import BackstageHeader from 'components/backstage/components/backstage_header';
@@ -15,60 +19,76 @@ import SpinnerButton from 'components/spinner_button';
import {localizeMessage} from 'utils/utils';
-export default class AbstractOAuthApp extends React.PureComponent {
- static propTypes = {
+type Props = {
- /**
- * The current team
- */
- team: PropTypes.object.isRequired,
+ /**
+ * The current team
+ */
+ team: Team;
- /**
- * The header text to render, has id and defaultMessage
- */
- header: PropTypes.object.isRequired,
+ /**
+ * The header text to render, has id and defaultMessage
+ */
+ header: MessageDescriptor;
- /**
- * The footer text to render, has id and defaultMessage
- */
- footer: PropTypes.object.isRequired,
+ /**
+ * The footer text to render, has id and defaultMessage
+ */
+ footer: MessageDescriptor;
- /**
- * The spinner loading text to render, has id and defaultMessage
- */
- loading: PropTypes.object.isRequired,
+ /**
+ * The spinner loading text to render, has id and defaultMessage
+ */
+ loading: MessageDescriptor;
- /**
- * Any extra component/node to render
- */
- renderExtra: PropTypes.node.isRequired,
+ /**
+ * Any extra component/node to render
+ */
+ renderExtra?: JSX.Element;
- /**
- * The server error text after a failed action
- */
- serverError: PropTypes.string.isRequired,
+ /**
+ * The server error text after a failed action
+ */
+ serverError: string;
- /**
- * The OAuthApp used to set the initial state
- */
- initialApp: PropTypes.object,
+ /**
+ * The OAuthApp used to set the initial state
+ */
+ initialApp?: OAuthApp;
- /**
- * The async function to run when the action button is pressed
- */
- action: PropTypes.func.isRequired,
- };
+ /**
+ * The async function to run when the action button is pressed
+ */
+ action: (app: OAuthApp) => Promise;
- constructor(props) {
+}
+
+type State = {
+ name: string;
+ description: string;
+ homepage: string;
+ icon_url: string;
+ callbackUrls: string;
+ is_trusted: boolean;
+ has_icon: boolean;
+ saving: boolean;
+ clientError: JSX.Element | null | string;
+};
+
+export default class AbstractOAuthApp extends React.PureComponent {
+ private image: HTMLImageElement;
+ private icon_url: React.RefObject;
+
+ constructor(props: Props) {
super(props);
this.image = new Image();
this.image.onload = this.imageLoaded;
this.icon_url = React.createRef();
- this.state = this.getStateFromApp(this.props.initialApp || {});
+ this.state = this.getStateFromApp(this.props.initialApp || {} as OAuthApp);
}
- getStateFromApp = (app) => {
+ getStateFromApp = (app: OAuthApp) => {
return {
name: app.name || '',
description: app.description || '',
@@ -83,13 +103,15 @@ export default class AbstractOAuthApp extends React.PureComponent {
};
imageLoaded = () => {
- this.setState({
- has_icon: true,
- icon_url: this.icon_url.current.value,
- });
+ if (this.icon_url.current?.value) {
+ this.setState({
+ has_icon: true,
+ icon_url: this.icon_url.current.value,
+ });
+ }
};
- handleSubmit = (e) => {
+ handleSubmit = (e: FormEvent) => {
e.preventDefault();
if (this.state.saving) {
@@ -173,36 +195,36 @@ export default class AbstractOAuthApp extends React.PureComponent {
description: this.state.description,
is_trusted: this.state.is_trusted,
icon_url: this.state.icon_url,
- };
+ } as OAuthApp;
this.props.action(app).then(() => this.setState({saving: false}));
};
- updateName = (e) => {
+ updateName = (e: ChangeEvent) => {
this.setState({
name: e.target.value,
});
};
- updateTrusted = (e) => {
+ updateTrusted = (e: ChangeEvent) => {
this.setState({
is_trusted: e.target.value === 'true',
});
};
- updateDescription = (e) => {
+ updateDescription = (e: ChangeEvent) => {
this.setState({
description: e.target.value,
});
};
- updateHomepage = (e) => {
+ updateHomepage = (e: ChangeEvent) => {
this.setState({
homepage: e.target.value,
});
};
- updateIconUrl = (e) => {
+ updateIconUrl = (e: ChangeEvent) => {
this.setState({
has_icon: false,
icon_url: e.target.value,
@@ -210,7 +232,7 @@ export default class AbstractOAuthApp extends React.PureComponent {
this.image.src = e.target.value;
};
- updateCallbackUrls = (e) => {
+ updateCallbackUrls = (e: ChangeEvent) => {
this.setState({
callbackUrls: e.target.value,
});
@@ -315,7 +337,7 @@ export default class AbstractOAuthApp extends React.PureComponent {