[MM-47094] Migrate "components/integrations/abstract_oauth_app.jsx" and tests to Typescript (#24529)
Этот коммит содержится в:
@@ -113,7 +113,7 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot 1`] = `
|
||||
<input
|
||||
className="form-control"
|
||||
id="name"
|
||||
maxLength="64"
|
||||
maxLength={64}
|
||||
onChange={[Function]}
|
||||
type="text"
|
||||
value="testApp"
|
||||
@@ -146,7 +146,7 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot 1`] = `
|
||||
<input
|
||||
className="form-control"
|
||||
id="description"
|
||||
maxLength="512"
|
||||
maxLength={512}
|
||||
onChange={[Function]}
|
||||
type="text"
|
||||
value="testing"
|
||||
@@ -179,7 +179,7 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot 1`] = `
|
||||
<input
|
||||
className="form-control"
|
||||
id="homepage"
|
||||
maxLength="256"
|
||||
maxLength={256}
|
||||
onChange={[Function]}
|
||||
type="url"
|
||||
value="https://test.com"
|
||||
@@ -212,7 +212,7 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot 1`] = `
|
||||
<input
|
||||
className="form-control"
|
||||
id="icon_url"
|
||||
maxLength="512"
|
||||
maxLength={512}
|
||||
onChange={[Function]}
|
||||
type="url"
|
||||
value="https://test.com/icon"
|
||||
@@ -245,9 +245,9 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot 1`] = `
|
||||
<textarea
|
||||
className="form-control"
|
||||
id="callbackUrls"
|
||||
maxLength="1024"
|
||||
maxLength={1024}
|
||||
onChange={[Function]}
|
||||
rows="3"
|
||||
rows={3}
|
||||
value="https://test.com/callback
|
||||
https://test.com/callback2"
|
||||
/>
|
||||
@@ -296,7 +296,9 @@ https://test.com/callback2"
|
||||
id="Footer"
|
||||
/>
|
||||
</SpinnerButton>
|
||||
renderExtra
|
||||
<div>
|
||||
renderExtra
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -416,7 +418,7 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot, display
|
||||
<input
|
||||
className="form-control"
|
||||
id="name"
|
||||
maxLength="64"
|
||||
maxLength={64}
|
||||
onChange={[Function]}
|
||||
type="text"
|
||||
value="testApp"
|
||||
@@ -449,7 +451,7 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot, display
|
||||
<input
|
||||
className="form-control"
|
||||
id="description"
|
||||
maxLength="512"
|
||||
maxLength={512}
|
||||
onChange={[Function]}
|
||||
type="text"
|
||||
value="testing"
|
||||
@@ -482,7 +484,7 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot, display
|
||||
<input
|
||||
className="form-control"
|
||||
id="homepage"
|
||||
maxLength="256"
|
||||
maxLength={256}
|
||||
onChange={[Function]}
|
||||
type="url"
|
||||
value="https://test.com"
|
||||
@@ -515,7 +517,7 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot, display
|
||||
<input
|
||||
className="form-control"
|
||||
id="icon_url"
|
||||
maxLength="512"
|
||||
maxLength={512}
|
||||
onChange={[Function]}
|
||||
type="url"
|
||||
value="https://test.com/icon"
|
||||
@@ -548,9 +550,9 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot, display
|
||||
<textarea
|
||||
className="form-control"
|
||||
id="callbackUrls"
|
||||
maxLength="1024"
|
||||
maxLength={1024}
|
||||
onChange={[Function]}
|
||||
rows="3"
|
||||
rows={3}
|
||||
value=""
|
||||
/>
|
||||
<div
|
||||
@@ -601,7 +603,9 @@ exports[`components/integrations/AbstractOAuthApp should match snapshot, display
|
||||
id="Footer"
|
||||
/>
|
||||
</SpinnerButton>
|
||||
renderExtra
|
||||
<div>
|
||||
renderExtra
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -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<void>((resolve) => {
|
||||
process.nextTick(() => resolve());
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
const team = TestHelper.getTeamMock({name: 'test', id: initialApp.id});
|
||||
|
||||
const baseProps = {
|
||||
team,
|
||||
header,
|
||||
footer,
|
||||
loading,
|
||||
renderExtra: 'renderExtra',
|
||||
renderExtra: <div>{'renderExtra'}</div>,
|
||||
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<AbstractOAuthApp>(
|
||||
<AbstractOAuthApp {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.instance().updateName({target: {value: 'new name'}});
|
||||
const evt = {preventDefault: jest.fn(), target: {value: 'new name'}} as unknown as ChangeEvent<HTMLInputElement>;
|
||||
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<HTMLInputElement>;
|
||||
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<AbstractOAuthApp>(
|
||||
<AbstractOAuthApp {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.instance().updateTrusted({target: {value: 'false'}});
|
||||
const evt = {preventDefault: jest.fn(), target: {value: 'false'}} as unknown as ChangeEvent<HTMLInputElement>;
|
||||
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<HTMLInputElement>;
|
||||
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<AbstractOAuthApp>(
|
||||
<AbstractOAuthApp {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.instance().updateDescription({target: {value: 'new description'}});
|
||||
const evt = {preventDefault: jest.fn(), target: {value: 'new description'}} as unknown as ChangeEvent<HTMLInputElement>;
|
||||
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<HTMLInputElement>;
|
||||
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<AbstractOAuthApp>(
|
||||
<AbstractOAuthApp {...props}/>,
|
||||
);
|
||||
|
||||
wrapper.instance().updateHomepage({target: {value: 'new homepage'}});
|
||||
const evt = {preventDefault: jest.fn(), target: {value: 'new homepage'}} as unknown as ChangeEvent<HTMLInputElement>;
|
||||
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<HTMLInputElement>;
|
||||
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<AbstractOAuthApp>(
|
||||
<AbstractOAuthApp {...props}/>,
|
||||
);
|
||||
|
||||
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<HTMLInputElement>;
|
||||
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<HTMLInputElement>;
|
||||
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<AbstractOAuthApp>(
|
||||
<AbstractOAuthApp {...props}/>,
|
||||
);
|
||||
|
||||
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();
|
||||
@@ -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<void>;
|
||||
|
||||
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<Props, State> {
|
||||
private image: HTMLImageElement;
|
||||
private icon_url: React.RefObject<HTMLInputElement>;
|
||||
|
||||
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<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
name: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
updateTrusted = (e) => {
|
||||
updateTrusted = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
is_trusted: e.target.value === 'true',
|
||||
});
|
||||
};
|
||||
|
||||
updateDescription = (e) => {
|
||||
updateDescription = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
description: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
updateHomepage = (e) => {
|
||||
updateHomepage = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({
|
||||
homepage: e.target.value,
|
||||
});
|
||||
};
|
||||
|
||||
updateIconUrl = (e) => {
|
||||
updateIconUrl = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
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<HTMLTextAreaElement>) => {
|
||||
this.setState({
|
||||
callbackUrls: e.target.value,
|
||||
});
|
||||
@@ -315,7 +337,7 @@ export default class AbstractOAuthApp extends React.PureComponent {
|
||||
<input
|
||||
id='name'
|
||||
type='text'
|
||||
maxLength='64'
|
||||
maxLength={64}
|
||||
className='form-control'
|
||||
value={this.state.name}
|
||||
onChange={this.updateName}
|
||||
@@ -342,7 +364,7 @@ export default class AbstractOAuthApp extends React.PureComponent {
|
||||
<input
|
||||
id='description'
|
||||
type='text'
|
||||
maxLength='512'
|
||||
maxLength={512}
|
||||
className='form-control'
|
||||
value={this.state.description}
|
||||
onChange={this.updateDescription}
|
||||
@@ -369,7 +391,7 @@ export default class AbstractOAuthApp extends React.PureComponent {
|
||||
<input
|
||||
id='homepage'
|
||||
type='url'
|
||||
maxLength='256'
|
||||
maxLength={256}
|
||||
className='form-control'
|
||||
value={this.state.homepage}
|
||||
onChange={this.updateHomepage}
|
||||
@@ -397,7 +419,7 @@ export default class AbstractOAuthApp extends React.PureComponent {
|
||||
id='icon_url'
|
||||
ref={this.icon_url}
|
||||
type='url'
|
||||
maxLength='512'
|
||||
maxLength={512}
|
||||
className='form-control'
|
||||
value={this.state.icon_url}
|
||||
onChange={this.updateIconUrl}
|
||||
@@ -423,8 +445,8 @@ export default class AbstractOAuthApp extends React.PureComponent {
|
||||
<div className='col-md-5 col-sm-8'>
|
||||
<textarea
|
||||
id='callbackUrls'
|
||||
rows='3'
|
||||
maxLength='1024'
|
||||
rows={3}
|
||||
maxLength={1024}
|
||||
className='form-control'
|
||||
value={this.state.callbackUrls}
|
||||
onChange={this.updateCallbackUrls}
|
||||
@@ -455,7 +477,7 @@ export default class AbstractOAuthApp extends React.PureComponent {
|
||||
className='btn btn-primary'
|
||||
type='submit'
|
||||
spinning={this.state.saving}
|
||||
spinningText={localizeMessage(this.props.loading.id, this.props.loading.defaultMessage)}
|
||||
spinningText={localizeMessage(this.props.loading?.id || '', (this.props.loading?.defaultMessage || '') as string)}
|
||||
onClick={this.handleSubmit}
|
||||
id='saveOauthApp'
|
||||
>
|
||||
@@ -21,7 +21,6 @@ exports[`components/integrations/AddOAuthApp should match snapshot 1`] = `
|
||||
"id": "installed_oauth_apps.saving",
|
||||
}
|
||||
}
|
||||
renderExtra=""
|
||||
serverError=""
|
||||
team={
|
||||
Object {
|
||||
|
||||
@@ -11,7 +11,7 @@ import type {ActionResult} from 'mattermost-redux/types/actions.js';
|
||||
|
||||
import {t} from 'utils/i18n';
|
||||
|
||||
import AbstractOAuthApp from '../abstract_oauth_app.jsx';
|
||||
import AbstractOAuthApp from '../abstract_oauth_app';
|
||||
|
||||
const HEADER = {id: t('add_oauth_app.header'), defaultMessage: 'Add'};
|
||||
const FOOTER = {id: t('installed_oauth_apps.save'), defaultMessage: 'Save'};
|
||||
@@ -58,7 +58,6 @@ const AddOAuthApp = ({team, actions}: Props): JSX.Element => {
|
||||
header={HEADER}
|
||||
footer={FOOTER}
|
||||
loading={LOADING}
|
||||
renderExtra={''}
|
||||
action={addOAuthApp}
|
||||
serverError={serverError}
|
||||
/>
|
||||
|
||||
@@ -14,7 +14,7 @@ import LoadingScreen from 'components/loading_screen';
|
||||
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
|
||||
import AbstractOAuthApp from '../abstract_oauth_app.jsx';
|
||||
import AbstractOAuthApp from '../abstract_oauth_app';
|
||||
|
||||
const HEADER = {id: 'integrations.edit', defaultMessage: 'Edit'};
|
||||
const FOOTER = {id: 'update_incoming_webhook.update', defaultMessage: 'Update'};
|
||||
|
||||
Ссылка в новой задаче
Block a user