@@ -0,0 +1,71 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { Button } from 'sonar-ui-common/components/controls/buttons'; | |||
import { Alert } from 'sonar-ui-common/components/ui/Alert'; | |||
import { translate } from 'sonar-ui-common/helpers/l10n'; | |||
import { DefaultSpecializedInputProps } from '../../utils'; | |||
const JSON_SPACE_SIZE = 4; | |||
interface State { | |||
formatError: boolean; | |||
} | |||
export default class InputForJSON extends React.PureComponent<DefaultSpecializedInputProps, State> { | |||
state: State = { formatError: false }; | |||
handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => { | |||
this.setState({ formatError: false }); | |||
this.props.onChange(event.target.value); | |||
}; | |||
format = () => { | |||
const { value } = this.props; | |||
try { | |||
if (value) { | |||
this.props.onChange(JSON.stringify(JSON.parse(value), undefined, JSON_SPACE_SIZE)); | |||
} | |||
} catch (e) { | |||
this.setState({ formatError: true }); | |||
} | |||
}; | |||
render() { | |||
const { formatError } = this.state; | |||
return ( | |||
<div className="display-flex-end"> | |||
<textarea | |||
className="settings-large-input text-top monospaced spacer-right" | |||
name={this.props.name} | |||
onChange={this.handleInputChange} | |||
rows={5} | |||
value={this.props.value || ''} | |||
/> | |||
<div> | |||
{formatError && <Alert variant="info">{translate('settings.json.format_error')} </Alert>} | |||
<Button className="spacer-top" onClick={this.format}> | |||
{translate('settings.json.format')} | |||
</Button> | |||
</div> | |||
</div> | |||
); | |||
} | |||
} |
@@ -26,6 +26,7 @@ import { | |||
isDefaultOrInherited | |||
} from '../../utils'; | |||
import InputForBoolean from './InputForBoolean'; | |||
import InputForJSON from './InputForJSON'; | |||
import InputForNumber from './InputForNumber'; | |||
import InputForPassword from './InputForPassword'; | |||
import InputForSingleSelectList from './InputForSingleSelectList'; | |||
@@ -37,7 +38,7 @@ const typeMapping: { | |||
} = { | |||
STRING: InputForString, | |||
TEXT: InputForText, | |||
JSON: InputForText, | |||
JSON: InputForJSON, | |||
PASSWORD: InputForPassword, | |||
BOOLEAN: InputForBoolean, | |||
INTEGER: InputForNumber, |
@@ -0,0 +1,72 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2021 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { change } from 'sonar-ui-common/helpers/testUtils'; | |||
import { DefaultSpecializedInputProps } from '../../../utils'; | |||
import InputForJSON from '../InputForJSON'; | |||
it('should render correctly', () => { | |||
expect(shallowRender()).toMatchSnapshot(); | |||
}); | |||
it('should call onChange', () => { | |||
const onChange = jest.fn(); | |||
const wrapper = shallowRender({ onChange }); | |||
change(wrapper.find('textarea'), '{"a": 1}'); | |||
expect(onChange).toBeCalledWith('{"a": 1}'); | |||
}); | |||
it('should handle formatting for invalid JSON', () => { | |||
const onChange = jest.fn(); | |||
const wrapper = shallowRender({ onChange, value: '{"a": 1b}' }); | |||
wrapper.instance().format(); | |||
expect(onChange).not.toBeCalled(); | |||
expect(wrapper.state().formatError).toBe(true); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
it('should handle formatting for valid JSON', () => { | |||
const onChange = jest.fn(); | |||
const wrapper = shallowRender({ onChange, value: '{"a": 1}' }); | |||
wrapper.instance().format(); | |||
expect(onChange).toBeCalledWith(`{ | |||
"a": 1 | |||
}`); | |||
expect(wrapper.state().formatError).toBe(false); | |||
}); | |||
it('should handle ignore formatting if empty', () => { | |||
const onChange = jest.fn(); | |||
const wrapper = shallowRender({ onChange, value: '' }); | |||
wrapper.instance().format(); | |||
expect(onChange).not.toBeCalled(); | |||
expect(wrapper.state().formatError).toBe(false); | |||
}); | |||
function shallowRender(props: Partial<DefaultSpecializedInputProps> = {}) { | |||
return shallow<InputForJSON>( | |||
<InputForJSON isDefault={false} name="foo" onChange={jest.fn()} value="" {...props} /> | |||
); | |||
} |
@@ -0,0 +1,51 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should handle formatting for invalid JSON 1`] = ` | |||
<div | |||
className="display-flex-end" | |||
> | |||
<textarea | |||
className="settings-large-input text-top monospaced spacer-right" | |||
name="foo" | |||
onChange={[Function]} | |||
rows={5} | |||
value="{\\"a\\": 1b}" | |||
/> | |||
<div> | |||
<Alert | |||
variant="info" | |||
> | |||
settings.json.format_error | |||
</Alert> | |||
<Button | |||
className="spacer-top" | |||
onClick={[Function]} | |||
> | |||
settings.json.format | |||
</Button> | |||
</div> | |||
</div> | |||
`; | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="display-flex-end" | |||
> | |||
<textarea | |||
className="settings-large-input text-top monospaced spacer-right" | |||
name="foo" | |||
onChange={[Function]} | |||
rows={5} | |||
value="" | |||
/> | |||
<div> | |||
<Button | |||
className="spacer-top" | |||
onClick={[Function]} | |||
> | |||
settings.json.format | |||
</Button> | |||
</div> | |||
</div> | |||
`; |
@@ -1050,6 +1050,9 @@ settings.default.password=<password> | |||
settings.reset_confirm.title=Reset Setting | |||
settings.reset_confirm.description=Are you sure that you want to reset this setting? | |||
settings.json.format=Format JSON | |||
settings.json.format_error=Formatting requires valid JSON. Please fix it and retry. | |||
settings.analysis_scope.wildcards.introduction=You can use the following wildcards. | |||
settings.analysis_scope.wildcards.zero_more_char=Match zero or more characters | |||
settings.analysis_scope.wildcards.zero_more_dir=Match zero or more directories |