Fix component types of 'components/widgets/settings/text_setting.tsx' (#23919)

Этот коммит содержится в:
M-ZubairAhmed
2023-07-12 16:25:05 +05:30
коммит произвёл GitHub
родитель 98da282f7c
Коммит 5e3c03a0a8
15 изменённых файлов: 136 добавлений и 276 удалений

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

@@ -76,7 +76,6 @@ exports[`components/BleveSettings should match snapshot, disabled 1`] = `
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="" value=""
/> />
<div <div
@@ -336,7 +335,6 @@ exports[`components/BleveSettings should match snapshot, enabled 1`] = `
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="bleve.idx" value="bleve.idx"
/> />
<div <div

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

@@ -126,7 +126,6 @@ exports[`components/PushSettings should match snapshot, licensed 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="E.g.: \\"https://push-test.mattermost.com\\"" placeholder="E.g.: \\"https://push-test.mattermost.com\\""
setByEnv={false} setByEnv={false}
type="input"
value="https://push.mattermost.com" value="https://push.mattermost.com"
/> />
<AdminTextSetting <AdminTextSetting
@@ -262,7 +261,6 @@ exports[`components/PushSettings should match snapshot, unlicensed 1`] = `
onChange={[Function]} onChange={[Function]}
placeholder="E.g.: \\"https://push-test.mattermost.com\\"" placeholder="E.g.: \\"https://push-test.mattermost.com\\""
setByEnv={false} setByEnv={false}
type="input"
value="https://push.mattermost.com" value="https://push.mattermost.com"
/> />
<AdminTextSetting <AdminTextSetting

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

@@ -160,7 +160,6 @@ export default class BleveSettings extends AdminSettings<Props, State> {
onChange={this.handleSettingChanged} onChange={this.handleSettingChanged}
setByEnv={this.isSetByEnv('BleveSettings.IndexDir')} setByEnv={this.isSetByEnv('BleveSettings.IndexDir')}
disabled={this.props.isDisabled} disabled={this.props.isDisabled}
type='input'
/> />
<div className='form-group'> <div className='form-group'>
<label <label

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

@@ -275,7 +275,6 @@ exports[`components/PluginManagement should match snapshot 1`] = `
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div
@@ -624,7 +623,6 @@ exports[`components/PluginManagement should match snapshot when \`Enable Marketp
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div
@@ -1069,7 +1067,6 @@ exports[`components/PluginManagement should match snapshot when \`Enable Remote
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div
@@ -1420,7 +1417,6 @@ exports[`components/PluginManagement should match snapshot when \`Require Signat
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div
@@ -1769,7 +1765,6 @@ exports[`components/PluginManagement should match snapshot, No installed plugins
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div
@@ -2122,7 +2117,6 @@ exports[`components/PluginManagement should match snapshot, allow insecure URL e
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div
@@ -2473,7 +2467,6 @@ exports[`components/PluginManagement should match snapshot, disabled 1`] = `
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
</SettingsGroup> </SettingsGroup>
@@ -2797,7 +2790,6 @@ exports[`components/PluginManagement should match snapshot, text entered into th
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div
@@ -3153,7 +3145,6 @@ exports[`components/PluginManagement should match snapshot, upload disabled 1`]
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div
@@ -3502,7 +3493,6 @@ exports[`components/PluginManagement should match snapshot, with installed plugi
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div
@@ -3914,7 +3904,6 @@ exports[`components/PluginManagement should match snapshot, with installed plugi
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div
@@ -4294,7 +4283,6 @@ exports[`components/PluginManagement should match snapshot, with installed plugi
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div
@@ -4674,7 +4662,6 @@ exports[`components/PluginManagement should match snapshot, with installed plugi
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div
@@ -5054,7 +5041,6 @@ exports[`components/PluginManagement should match snapshot, with installed plugi
} }
onChange={[Function]} onChange={[Function]}
setByEnv={false} setByEnv={false}
type="input"
value="marketplace.example.com" value="marketplace.example.com"
/> />
<div <div

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

@@ -1235,7 +1235,6 @@ export default class PluginManagement extends AdminSettings<Props, State> {
/> />
<TextSetting <TextSetting
id={'marketplaceUrl'} id={'marketplaceUrl'}
type={'input'}
label={ label={
<FormattedMessage <FormattedMessage
id='admin.plugins.settings.marketplaceUrl' id='admin.plugins.settings.marketplaceUrl'

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

@@ -359,7 +359,6 @@ export default class PushSettings extends AdminSettings<Props, State> {
onChange={this.handleChange} onChange={this.handleChange}
disabled={this.props.isDisabled || this.state.pushNotificationServerType !== PUSH_NOTIFICATIONS_CUSTOM} disabled={this.props.isDisabled || this.state.pushNotificationServerType !== PUSH_NOTIFICATIONS_CUSTOM}
setByEnv={this.isSetByEnv('EmailSettings.PushNotificationServer')} setByEnv={this.isSetByEnv('EmailSettings.PushNotificationServer')}
type='input'
/> />
<TextSetting <TextSetting
id='maxNotificationsPerChannel' id='maxNotificationsPerChannel'

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

@@ -9,24 +9,22 @@ import AdminTextSetting from './text_setting';
describe('components/admin_console/TextSetting', () => { describe('components/admin_console/TextSetting', () => {
test('render component with required props', () => { test('render component with required props', () => {
const onChange = jest.fn();
renderWithIntl( renderWithIntl(
<AdminTextSetting <AdminTextSetting
id='string.id' id='some.id'
label='some label' label='some label'
value='some value' value='some value'
onChange={onChange} onChange={jest.fn()}
setByEnv={false} setByEnv={false}
labelClassName='' labelClassName=''
inputClassName='' inputClassName=''
maxLength={-1} maxLength={-1}
resizable={true} resizable={true}
type='input'
/>, />,
); );
screen.getByText('some label', {exact: false}); screen.getByText('some label', {exact: false});
expect(screen.getByTestId('string.idinput')).toHaveProperty('id', 'string.id'); expect(screen.getByTestId('some.idinput')).toHaveProperty('id', 'some.id');
expect(screen.getByTestId('string.idinput')).toHaveValue('some value'); expect(screen.getByTestId('some.idinput')).toHaveValue('some value');
}); });
}); });

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

@@ -3,7 +3,7 @@
import React from 'react'; import React from 'react';
import TextSetting, {WidgetTextSettingProps} from 'components/widgets/settings/text_setting'; import TextSetting, {Props as WidgetTextSettingProps} from 'components/widgets/settings/text_setting';
import SetByEnv from './set_by_env'; import SetByEnv from './set_by_env';

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

@@ -8,8 +8,6 @@ import {AppField} from '@mattermost/types/apps';
import TextSetting from 'components/widgets/settings/text_setting'; import TextSetting from 'components/widgets/settings/text_setting';
import Markdown from 'components/markdown';
import AppsFormField, {Props} from './apps_form_field'; import AppsFormField, {Props} from './apps_form_field';
import AppsFormSelectField from './apps_form_select_field'; import AppsFormSelectField from './apps_form_select_field';
@@ -37,23 +35,6 @@ describe('components/apps_form/apps_form_field/AppsFormField', () => {
performLookup: jest.fn(), performLookup: jest.fn(),
}; };
const baseTextSettingProps = {
inputClassName: '',
label: (
<React.Fragment>
{textField.modal_label}
{false}
</React.Fragment>
),
maxLength: 100,
placeholder: 'The hint',
resizable: false,
type: 'input',
value: '',
id: baseDialogTextProps.name,
helpText: (<Markdown message='The description'/>),
};
it('subtype blank - optional field', () => { it('subtype blank - optional field', () => {
const wrapper = shallow( const wrapper = shallow(
<AppsFormField <AppsFormField
@@ -65,18 +46,8 @@ describe('components/apps_form/apps_form_field/AppsFormField', () => {
}} }}
/>, />,
); );
expect(wrapper.matchesElement(
<TextSetting expect(wrapper.find(TextSetting).props().type).toEqual('text');
{...baseTextSettingProps}
label={(
<React.Fragment>
{textField.modal_label}
{<span className='light'>{' (optional)'}</span>}
</React.Fragment>
)}
type='input'
/>,
)).toEqual(true);
}); });
it('subtype blank', () => { it('subtype blank', () => {
@@ -86,12 +57,7 @@ describe('components/apps_form/apps_form_field/AppsFormField', () => {
/>, />,
); );
expect(wrapper.matchesElement( expect(wrapper.find(TextSetting).props().type).toEqual('text');
<TextSetting
{...baseTextSettingProps}
type='input'
/>,
)).toEqual(true);
}); });
it('subtype email', () => { it('subtype email', () => {
@@ -104,30 +70,8 @@ describe('components/apps_form/apps_form_field/AppsFormField', () => {
}} }}
/>, />,
); );
expect(wrapper.matchesElement(
<TextSetting
{...baseTextSettingProps}
type='email'
/>,
)).toEqual(true);
});
it('subtype invalid', () => { expect(wrapper.find(TextSetting).props().type).toEqual('email');
const wrapper = shallow(
<AppsFormField
{...baseDialogTextProps}
field={{
...textField,
subtype: 'invalid',
}}
/>,
);
expect(wrapper.matchesElement(
<TextSetting
{...baseTextSettingProps}
type='input'
/>,
)).toEqual(true);
}); });
it('subtype password', () => { it('subtype password', () => {
@@ -140,12 +84,8 @@ describe('components/apps_form/apps_form_field/AppsFormField', () => {
}} }}
/>, />,
); );
expect(wrapper.matchesElement(
<TextSetting expect(wrapper.find(TextSetting).props().type).toEqual('password');
{...baseTextSettingProps}
type='password'
/>,
)).toEqual(true);
}); });
}); });

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

@@ -108,18 +108,13 @@ export default class AppsFormField extends React.PureComponent<Props> {
} }
} }
let textType: InputTypes = 'input';
if (subtype && TextSetting.validTypes.includes(subtype)) {
textType = subtype as InputTypes;
}
const textValue = value as string; const textValue = value as string;
return ( return (
<TextSetting <TextSetting
autoFocus={this.props.autoFocus} autoFocus={this.props.autoFocus}
id={name} id={name}
disabled={field.readonly} disabled={field.readonly}
type={textType} type={subtype as InputTypes}
label={displayNameContent} label={displayNameContent}
maxLength={maxLength} maxLength={maxLength}
value={textValue || ''} value={textValue || ''}

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

@@ -21,18 +21,7 @@ describe('components/interactive_dialog/DialogElement', () => {
}, },
onChange: jest.fn(), onChange: jest.fn(),
}; };
const baseTextSettingProps = {
id: baseDialogProps.name,
maxLength: 100,
resizable: false,
value: '',
label: (
<React.Fragment>
{baseDialogProps.displayName}
<span className='error-text'>{' *'}</span>
</React.Fragment>
),
};
it('subtype blank', () => { it('subtype blank', () => {
const wrapper = shallow( const wrapper = shallow(
<DialogElement <DialogElement
@@ -40,12 +29,8 @@ describe('components/interactive_dialog/DialogElement', () => {
subtype='' subtype=''
/>, />,
); );
expect(wrapper.matchesElement(
<TextSetting expect(wrapper.find(TextSetting).props().type).toEqual('text');
{...baseTextSettingProps}
type='input'
/>,
)).toEqual(true);
}); });
it('subtype email', () => { it('subtype email', () => {
@@ -55,27 +40,7 @@ describe('components/interactive_dialog/DialogElement', () => {
subtype='email' subtype='email'
/>, />,
); );
expect(wrapper.matchesElement( expect(wrapper.find(TextSetting).props().type).toEqual('email');
<TextSetting
{...baseTextSettingProps}
type='email'
/>,
)).toEqual(true);
});
it('subtype invalid', () => {
const wrapper = shallow(
<DialogElement
{...baseDialogProps}
subtype='invalid'
/>,
);
expect(wrapper.matchesElement(
<TextSetting
{...baseTextSettingProps}
type='input'
/>,
)).toEqual(true);
}); });
it('subtype password', () => { it('subtype password', () => {
@@ -85,12 +50,7 @@ describe('components/interactive_dialog/DialogElement', () => {
subtype='password' subtype='password'
/>, />,
); );
expect(wrapper.matchesElement( expect(wrapper.find(TextSetting).props().type).toEqual('password');
<TextSetting
{...baseTextSettingProps}
type='password'
/>,
)).toEqual(true);
}); });
describe('radioSetting', () => { describe('radioSetting', () => {

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

@@ -116,10 +116,10 @@ export default class DialogElement extends React.PureComponent<Props, State> {
errorText, errorText,
optional, optional,
options, options,
type,
maxLength,
} = this.props; } = this.props;
let {type, maxLength} = this.props;
let displayNameContent: React.ReactNode = displayName; let displayNameContent: React.ReactNode = displayName;
if (optional) { if (optional) {
displayNameContent = ( displayNameContent = (
@@ -155,16 +155,11 @@ export default class DialogElement extends React.PureComponent<Props, State> {
} }
if (type === 'text' || type === 'textarea') { if (type === 'text' || type === 'textarea') {
let textSettingMaxLength;
if (type === 'text') { if (type === 'text') {
maxLength = maxLength || TEXT_DEFAULT_MAX_LENGTH; textSettingMaxLength = maxLength || TEXT_DEFAULT_MAX_LENGTH;
if (subtype && TextSetting.validTypes.includes(subtype)) {
type = subtype;
} else {
type = 'input';
}
} else { } else {
maxLength = maxLength || TEXTAREA_DEFAULT_MAX_LENGTH; textSettingMaxLength = maxLength || TEXTAREA_DEFAULT_MAX_LENGTH;
} }
const textValue = value as string; const textValue = value as string;
@@ -172,9 +167,9 @@ export default class DialogElement extends React.PureComponent<Props, State> {
<TextSetting <TextSetting
autoFocus={this.props.autoFocus} autoFocus={this.props.autoFocus}
id={name} id={name}
type={type as InputTypes} type={subtype as InputTypes || 'text'}
label={displayNameContent} label={displayNameContent}
maxLength={maxLength} maxLength={textSettingMaxLength}
value={textValue || ''} value={textValue || ''}
placeholder={placeholder} placeholder={placeholder}
helpText={helpTextContent} helpText={helpTextContent}

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

@@ -0,0 +1,40 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`components/widgets/settings/TextSetting render component with required props 1`] = `
<Setting
inputClassName=""
inputId="string.id"
label="some label"
labelClassName=""
>
<input
className="form-control"
data-testid="string.idinput"
id="string.id"
maxLength={-1}
onChange={[Function]}
type="text"
value="some value"
/>
</Setting>
`;
exports[`components/widgets/settings/TextSetting render with textarea type 1`] = `
<Setting
inputClassName=""
inputId="string.id"
label="some label"
labelClassName=""
>
<textarea
className="form-control"
data-testid="string.idinput"
dir="auto"
id="string.id"
maxLength={-1}
onChange={[Function]}
rows={5}
value="some value"
/>
</Setting>
`;

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

@@ -17,24 +17,8 @@ describe('components/widgets/settings/TextSetting', () => {
onChange={onChange} onChange={onChange}
/>, />,
); );
expect(wrapper).toMatchInlineSnapshot(`
<Setting expect(wrapper).toMatchSnapshot();
inputClassName=""
inputId="string.id"
label="some label"
labelClassName=""
>
<input
className="form-control"
data-testid="string.idinput"
id="string.id"
maxLength={-1}
onChange={[Function]}
type="input"
value="some value"
/>
</Setting>
`);
}); });
test('render with textarea type', () => { test('render with textarea type', () => {
@@ -48,26 +32,8 @@ describe('components/widgets/settings/TextSetting', () => {
onChange={onChange} onChange={onChange}
/>, />,
); );
expect(wrapper).toMatchInlineSnapshot(`
<Setting expect(wrapper).toMatchSnapshot();
inputClassName=""
inputId="string.id"
label="some label"
labelClassName=""
>
<textarea
className="form-control"
data-testid="string.idinput"
dir="auto"
id="string.id"
maxLength={-1}
onChange={[Function]}
rows={5}
style={Object {}}
value="some value"
/>
</Setting>
`);
}); });
test('onChange', () => { test('onChange', () => {

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

@@ -1,108 +1,95 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import React from 'react'; import React, {ChangeEvent, ReactNode} from 'react';
import Setting from './setting'; import Setting from './setting';
export type InputTypes = 'input' | 'textarea' | 'number' | 'email' | 'tel' | 'url' | 'password' const INPUT_TYPES = ['text', 'textarea', 'number', 'email', 'tel', 'url', 'password'] as const;
export type InputTypes = typeof INPUT_TYPES[number];
export type WidgetTextSettingProps = { export type Props = {
id: string; id: string;
label: React.ReactNode; label: ReactNode;
labelClassName?: string; labelClassName?: string;
placeholder?: string; placeholder?: string;
helpText?: React.ReactNode; helpText?: ReactNode;
footer?: React.ReactNode; footer?: ReactNode;
value: string | number; value: string | number;
inputClassName?: string; inputClassName?: string;
maxLength?: number; maxLength?: number;
resizable?: boolean; resizable?: boolean;
onChange(name: string, value: any): void; onChange(id: string, value: any): void;
disabled?: boolean; disabled?: boolean;
type: InputTypes;
// This is a custom prop that is not part of the HTML input element type
type?: InputTypes;
autoFocus?: boolean; autoFocus?: boolean;
} }
// Since handle change is read from input and textarea element function TextSetting(props: Props) {
type HandleChangeTypes = React.ChangeEventHandler<HTMLInputElement | HTMLTextAreaElement> const {labelClassName = '', inputClassName = '', maxLength = -1, resizable = true, type = 'text'} = props;
export default class TextSetting extends React.PureComponent<WidgetTextSettingProps> { function handleChange(event: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) {
public static validTypes: string[] = ['input', 'textarea', 'number', 'email', 'tel', 'url', 'password']; if (props.type === 'number') {
props.onChange(props.id, parseInt(event.target.value, 10));
public static defaultProps: Partial<WidgetTextSettingProps> = {
labelClassName: '',
inputClassName: '',
type: 'input',
maxLength: -1, // A negative number allows for values of any length
resizable: true,
};
private handleChange: HandleChangeTypes = (e) => {
if (this.props.type === 'number') {
this.props.onChange(this.props.id, parseInt(e.target.value, 10));
} else { } else {
this.props.onChange(this.props.id, e.target.value); props.onChange(props.id, event.target.value);
} }
}; }
public render(): JSX.Element { let input = null;
const {resizable} = this.props; if (type === 'textarea') {
let {type} = this.props; input = (
let input = null; <textarea
id={props.id}
data-testid={`${props.id}input`} // a lot of our e2e test rely on 'input' being in the test id if it's a text/textarea input
className='form-control'
autoFocus={props.autoFocus}
dir='auto'
rows={5}
placeholder={props.placeholder}
style={resizable === false ? {resize: 'none'} : undefined}
value={props.value}
maxLength={maxLength}
onChange={handleChange}
disabled={props.disabled}
/>
);
} else {
const inputType = INPUT_TYPES.includes(type) ? type : 'text';
if (type === 'textarea') { // a lot of our e2e test rely on 'input' being in the test id if it's a text/textarea input
let style = {}; const testId = inputType === 'text' ? `${props.id}input` : `${props.id}${inputType}`;
if (!resizable) {
style = Object.assign({}, {resize: 'none'});
}
input = ( input = (
<textarea <input
autoFocus={this.props.autoFocus} id={props.id}
data-testid={this.props.id + 'input'} data-testid={testId}
id={this.props.id} className='form-control'
dir='auto' autoFocus={props.autoFocus}
style={style} type={inputType}
className='form-control' placeholder={props.placeholder}
rows={5} value={props.value}
placeholder={this.props.placeholder} maxLength={maxLength}
value={this.props.value} onChange={handleChange}
maxLength={this.props.maxLength} disabled={props.disabled}
onChange={this.handleChange} />
disabled={this.props.disabled}
/>
);
} else {
type = ['input', 'email', 'tel', 'number', 'url', 'password'].includes(type) ? type : 'input';
input = (
<input
autoFocus={this.props.autoFocus}
data-testid={this.props.id + type}
id={this.props.id}
className='form-control'
type={type}
placeholder={this.props.placeholder}
value={this.props.value}
maxLength={this.props.maxLength}
onChange={this.handleChange}
disabled={this.props.disabled}
/>
);
}
return (
<Setting
label={this.props.label}
labelClassName={this.props.labelClassName}
inputClassName={this.props.inputClassName}
helpText={this.props.helpText}
inputId={this.props.id}
footer={this.props.footer}
>
{input}
</Setting>
); );
} }
return (
<Setting
label={props.label}
labelClassName={labelClassName}
inputClassName={inputClassName}
helpText={props.helpText}
inputId={props.id}
footer={props.footer}
>
{input}
</Setting>
);
} }
export default TextSetting;