Browse Source

SONAR-11321 Improve project page manual fields validate

tags/7.5
Grégoire Aubert 5 years ago
parent
commit
e86e8c1fe6
16 changed files with 516 additions and 146 deletions
  1. 9
    0
      server/sonar-web/src/main/js/api/components.ts
  2. 145
    0
      server/sonar-web/src/main/js/apps/create/components/ProjectKeyInput.tsx
  3. 101
    0
      server/sonar-web/src/main/js/apps/create/components/ProjectNameInput.tsx
  4. 1
    1
      server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationNameInput-test.tsx
  5. 61
    0
      server/sonar-web/src/main/js/apps/create/components/__tests__/ProjectKeyInput-test.tsx
  6. 37
    0
      server/sonar-web/src/main/js/apps/create/components/__tests__/ProjectNameInput-test.tsx
  7. 28
    0
      server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/ProjectKeyInput-test.tsx.snap
  8. 27
    0
      server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/ProjectNameInput-test.tsx.snap
  9. 33
    53
      server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx
  10. 5
    4
      server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx
  11. 6
    62
      server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap
  12. 9
    3
      server/sonar-web/src/main/js/components/controls/ValidationInput.tsx
  13. 1
    0
      server/sonar-web/src/main/js/components/controls/__tests__/ValidationInput-test.tsx
  14. 33
    17
      server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ValidationInput-test.tsx.snap
  15. 13
    5
      server/sonar-web/src/main/js/helpers/testUtils.ts
  16. 7
    1
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

+ 9
- 0
server/sonar-web/src/main/js/api/components.ts View File

@@ -164,6 +164,15 @@ export function getTree(data: {
return getJSON('/api/components/tree', data).catch(throwGlobalError);
}

export function doesComponentExists(
data: { component: string } & BranchParameters
): Promise<boolean> {
return getJSON('/api/components/show', data).then(
({ component }) => component !== undefined,
() => false
);
}

export function getComponentShow(data: { component: string } & BranchParameters): Promise<any> {
return getJSON('/api/components/show', data).catch(throwGlobalError);
}

+ 145
- 0
server/sonar-web/src/main/js/apps/create/components/ProjectKeyInput.tsx View File

@@ -0,0 +1,145 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as classNames from 'classnames';
import { debounce } from 'lodash';
import ValidationInput from '../../../components/controls/ValidationInput';
import { doesComponentExists } from '../../../api/components';
import { translate } from '../../../helpers/l10n';

interface Props {
className?: string;
initialValue?: string;
onChange: (value: string | undefined) => void;
}

interface State {
editing: boolean;
error?: string;
touched: boolean;
validating: boolean;
value: string;
}

export default class ProjectKeyInput extends React.PureComponent<Props, State> {
mounted = false;
constructor(props: Props) {
super(props);
this.state = { error: undefined, editing: false, touched: false, validating: false, value: '' };
this.checkFreeKey = debounce(this.checkFreeKey, 250);
}

componentDidMount() {
this.mounted = true;
if (this.props.initialValue !== undefined) {
this.setState({ value: this.props.initialValue });
this.validateKey(this.props.initialValue);
}
}

componentWillUnmount() {
this.mounted = false;
}

checkFreeKey = (key: string) => {
this.setState({ validating: true });
return doesComponentExists({ component: key })
.then(alreadyExist => {
if (this.mounted) {
if (!alreadyExist) {
this.setState({ error: undefined, validating: false });
this.props.onChange(key);
} else {
this.setState({
error: translate('onboarding.create_project.project_key.taken'),
touched: true,
validating: false
});
this.props.onChange(undefined);
}
}
})
.catch(() => {
if (this.mounted) {
this.setState({ error: undefined, validating: false });
this.props.onChange(key);
}
});
};

handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.currentTarget;
this.setState({ touched: true, value });
this.validateKey(value);
};

handleBlur = () => {
this.setState({ editing: false });
};

handleFocus = () => {
this.setState({ editing: true });
};

validateKey(key: string) {
if (key.length > 400 || !/^[\w-.:]*[a-zA-Z]+[\w-.:]*$/.test(key)) {
this.setState({
error: translate('onboarding.create_project.project_key.error'),
touched: true
});
this.props.onChange(undefined);
} else {
this.checkFreeKey(key);
}
}

render() {
const isInvalid = this.state.touched && !this.state.editing && this.state.error !== undefined;
const isValid = this.state.touched && !this.state.validating && this.state.error === undefined;
return (
<ValidationInput
className={this.props.className}
description={translate('onboarding.create_project.project_key.description')}
error={this.state.error}
help={translate('onboarding.create_project.project_key.help')}
id="project-key"
isInvalid={isInvalid}
isValid={isValid}
label={translate('onboarding.create_project.project_key')}
required={true}>
<input
autoFocus={true}
className={classNames('input-super-large', {
'is-invalid': isInvalid,
'is-valid': isValid
})}
id="project-key"
maxLength={400}
minLength={1}
onBlur={this.handleBlur}
onChange={this.handleChange}
onFocus={this.handleFocus}
type="text"
value={this.state.value}
/>
</ValidationInput>
);
}
}

+ 101
- 0
server/sonar-web/src/main/js/apps/create/components/ProjectNameInput.tsx View File

@@ -0,0 +1,101 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 * as classNames from 'classnames';
import ValidationInput from '../../../components/controls/ValidationInput';
import { translate } from '../../../helpers/l10n';

interface Props {
className?: string;
initialValue?: string;
onChange: (value: string | undefined) => void;
}

interface State {
editing: boolean;
error?: string;
touched: boolean;
value: string;
}

export default class ProjectNameInput extends React.PureComponent<Props, State> {
state: State = { error: undefined, editing: false, touched: false, value: '' };

componentDidMount() {
if (this.props.initialValue) {
const error = this.validateName(this.props.initialValue);
this.setState({ error, touched: Boolean(error), value: this.props.initialValue });
}
}

handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
const { value } = event.currentTarget;
const error = this.validateName(value);
this.setState({ error, touched: true, value });
this.props.onChange(error === undefined ? value : undefined);
};

handleBlur = () => {
this.setState({ editing: false });
};

handleFocus = () => {
this.setState({ editing: true });
};

validateName(name: string) {
if (name.length > 255) {
return translate('onboarding.create_project.display_name.error');
}
return undefined;
}

render() {
const isInvalid = this.state.touched && !this.state.editing && this.state.error !== undefined;
const isValid = this.state.touched && this.state.error === undefined && this.state.value !== '';
return (
<ValidationInput
className={this.props.className}
description={translate('onboarding.create_project.display_name.description')}
error={this.state.error}
id="project-name"
isInvalid={isInvalid}
isValid={isValid}
label={translate('onboarding.create_project.display_name')}
required={true}>
<input
className={classNames('input-super-large', {
'is-invalid': isInvalid,
'is-valid': isValid
})}
id="project-name"
maxLength={500}
minLength={1}
onBlur={this.handleBlur}
onChange={this.handleChange}
onFocus={this.handleFocus}
required={true}
type="text"
value={this.state.value}
/>
</ValidationInput>
);
}
}

+ 1
- 1
server/sonar-web/src/main/js/apps/create/components/__tests__/OrganizationNameInput-test.tsx View File

@@ -28,7 +28,7 @@ it('should render correctly', () => {
expect(wrapper.find('ValidationInput').prop('isValid')).toMatchSnapshot();
});

it('should have an error when description is too long', () => {
it('should have an error when name is too long', () => {
expect(
shallow(<OrganizationNameInput initialValue={'x'.repeat(256)} onChange={jest.fn()} />)
.find('ValidationInput')

+ 61
- 0
server/sonar-web/src/main/js/apps/create/components/__tests__/ProjectKeyInput-test.tsx View File

@@ -0,0 +1,61 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
import ProjectKeyInput from '../ProjectKeyInput';
import { doesComponentExists } from '../../../../api/components';
import { waitAndUpdate } from '../../../../helpers/testUtils';

jest.mock('../../../../api/components', () => ({
doesComponentExists: jest.fn().mockResolvedValue(false)
}));

beforeEach(() => {
(doesComponentExists as jest.Mock<any>).mockClear();
});

it('should render correctly', () => {
const wrapper = shallow(<ProjectKeyInput initialValue="key" onChange={jest.fn()} />);
expect(wrapper).toMatchSnapshot();
wrapper.setState({ touched: true });
expect(wrapper.find('ValidationInput').prop('isValid')).toMatchSnapshot();
});

it('should not display any status when the key is not defined', async () => {
const wrapper = shallow(<ProjectKeyInput onChange={jest.fn()} />);
await waitAndUpdate(wrapper);
expect(wrapper.find('ValidationInput').prop('isInvalid')).toBe(false);
expect(wrapper.find('ValidationInput').prop('isValid')).toBe(false);
});

it('should have an error when the key is invalid', async () => {
const wrapper = shallow(
<ProjectKeyInput initialValue="KEy-with#speci@l_char" onChange={jest.fn()} />
);
await waitAndUpdate(wrapper);
expect(wrapper.find('ValidationInput').prop('isInvalid')).toBe(true);
});

it('should have an error when the key already exists', async () => {
(doesComponentExists as jest.Mock<any>).mockResolvedValue(true);
const wrapper = shallow(<ProjectKeyInput initialValue="" onChange={jest.fn()} />);
await waitAndUpdate(wrapper);
expect(wrapper.find('ValidationInput').prop('isInvalid')).toBe(true);
});

+ 37
- 0
server/sonar-web/src/main/js/apps/create/components/__tests__/ProjectNameInput-test.tsx View File

@@ -0,0 +1,37 @@
/*
* SonarQube
* Copyright (C) 2009-2018 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 { shallow } from 'enzyme';
import ProjectNameInput from '../ProjectNameInput';

it('should render correctly', () => {
const wrapper = shallow(<ProjectNameInput initialValue="Project Name" onChange={jest.fn()} />);
expect(wrapper).toMatchSnapshot();
wrapper.setState({ touched: true });
expect(wrapper.find('ValidationInput').prop('isValid')).toMatchSnapshot();
});

it('should have an error when name is too long', () => {
expect(
shallow(<ProjectNameInput initialValue={'x'.repeat(501)} onChange={jest.fn()} />)
.find('ValidationInput')
.prop('isInvalid')
).toBe(true);
});

+ 28
- 0
server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/ProjectKeyInput-test.tsx.snap View File

@@ -0,0 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<ValidationInput
description="onboarding.create_project.project_key.description"
help="onboarding.create_project.project_key.help"
id="project-key"
isInvalid={false}
isValid={false}
label="onboarding.create_project.project_key"
required={true}
>
<input
autoFocus={true}
className="input-super-large"
id="project-key"
maxLength={400}
minLength={1}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
type="text"
value="key"
/>
</ValidationInput>
`;

exports[`should render correctly 2`] = `true`;

+ 27
- 0
server/sonar-web/src/main/js/apps/create/components/__tests__/__snapshots__/ProjectNameInput-test.tsx.snap View File

@@ -0,0 +1,27 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<ValidationInput
description="onboarding.create_project.display_name.description"
id="project-name"
isInvalid={false}
isValid={false}
label="onboarding.create_project.display_name"
required={true}
>
<input
className="input-super-large"
id="project-name"
maxLength={500}
minLength={1}
onBlur={[Function]}
onChange={[Function]}
onFocus={[Function]}
required={true}
type="text"
value="Project Name"
/>
</ValidationInput>
`;

exports[`should render correctly 2`] = `true`;

+ 33
- 53
server/sonar-web/src/main/js/apps/create/project/ManualProjectCreate.tsx View File

@@ -21,9 +21,11 @@ import * as React from 'react';
import OrganizationInput from './OrganizationInput';
import DeferredSpinner from '../../../components/common/DeferredSpinner';
import { SubmitButton } from '../../../components/ui/buttons';
import { createProject } from '../../../api/components';
import { LoggedInUser, Organization } from '../../../app/types';
import { translate } from '../../../helpers/l10n';
import { createProject } from '../../../api/components';
import ProjectKeyInput from '../components/ProjectKeyInput';
import ProjectNameInput from '../components/ProjectNameInput';

interface Props {
currentUser: LoggedInUser;
@@ -33,20 +35,20 @@ interface Props {
}

interface State {
projectName: string;
projectKey: string;
projectName?: string;
projectKey?: string;
selectedOrganization: string;
submitting: boolean;
}

type ValidState = State & Required<Pick<State, 'projectName' | 'projectKey'>>;

export default class ManualProjectCreate extends React.PureComponent<Props, State> {
mounted = false;

constructor(props: Props) {
super(props);
this.state = {
projectName: '',
projectKey: '',
selectedOrganization: this.getInitialSelectedOrganization(props),
submitting: false
};
@@ -60,6 +62,10 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
this.mounted = false;
}

canSubmit(state: State): state is ValidState {
return Boolean(state.projectKey && state.projectName && state.selectedOrganization);
}

getInitialSelectedOrganization(props: Props) {
if (props.organization) {
return props.organization;
@@ -72,14 +78,13 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat

handleFormSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();

if (this.isValid()) {
const { projectKey, projectName, selectedOrganization } = this.state;
const { state } = this;
if (this.canSubmit(state)) {
this.setState({ submitting: true });
createProject({
project: projectKey,
name: projectName,
organization: selectedOrganization
project: state.projectKey,
name: state.projectName,
organization: state.selectedOrganization
}).then(
({ project }) => this.props.onProjectCreate([project.key]),
() => {
@@ -95,17 +100,12 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
this.setState({ selectedOrganization: key });
};

handleProjectNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ projectName: event.currentTarget.value });
};

handleProjectKeyChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this.setState({ projectKey: event.currentTarget.value });
handleProjectNameChange = (projectName?: string) => {
this.setState({ projectName });
};

isValid = () => {
const { projectKey, projectName, selectedOrganization } = this.state;
return Boolean(projectKey && projectName && selectedOrganization);
handleProjectKeyChange = (projectKey?: string) => {
this.setState({ projectKey });
};

render() {
@@ -118,39 +118,19 @@ export default class ManualProjectCreate extends React.PureComponent<Props, Stat
organization={this.state.selectedOrganization}
organizations={this.props.userOrganizations}
/>
<div className="form-field">
<label htmlFor="project-name">
{translate('onboarding.create_project.project_name')}
<em className="mandatory">*</em>
</label>
<input
className="input-super-large"
id="project-name"
maxLength={400}
minLength={1}
onChange={this.handleProjectNameChange}
required={true}
type="text"
value={this.state.projectName}
/>
</div>
<div className="form-field">
<label htmlFor="project-key">
{translate('onboarding.create_project.project_key')}
<em className="mandatory">*</em>
</label>
<input
className="input-super-large"
id="project-key"
maxLength={400}
minLength={1}
onChange={this.handleProjectKeyChange}
required={true}
type="text"
value={this.state.projectKey}
/>
</div>
<SubmitButton disabled={!this.isValid() || submitting}>{translate('setup')}</SubmitButton>
<ProjectKeyInput
className="form-field"
initialValue={this.state.projectKey}
onChange={this.handleProjectKeyChange}
/>
<ProjectNameInput
className="form-field"
initialValue={this.state.projectName}
onChange={this.handleProjectNameChange}
/>
<SubmitButton disabled={!this.canSubmit(this.state) || submitting}>
{translate('setup')}
</SubmitButton>
<DeferredSpinner className="spacer-left" loading={submitting} />
</form>
</>

+ 5
- 4
server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx View File

@@ -39,11 +39,12 @@ it('should correctly create a project', async () => {
const onProjectCreate = jest.fn();
const wrapper = getWrapper({ onProjectCreate });
wrapper.find('withRouter(OrganizationInput)').prop<Function>('onChange')({ key: 'foo' });
change(wrapper.find('#project-name'), 'Bar');
expect(wrapper.find('SubmitButton')).toMatchSnapshot();

change(wrapper.find('#project-key'), 'bar');
expect(wrapper.find('SubmitButton')).toMatchSnapshot();
change(wrapper.find('ProjectKeyInput'), 'bar');
expect(wrapper.find('SubmitButton').prop('disabled')).toBe(true);

change(wrapper.find('ProjectNameInput'), 'Bar');
expect(wrapper.find('SubmitButton').prop('disabled')).toBe(false);

submit(wrapper.find('form'));
expect(createProject).toBeCalledWith({ project: 'bar', name: 'Bar', organization: 'foo' });

+ 6
- 62
server/sonar-web/src/main/js/apps/create/project/__tests__/__snapshots__/ManualProjectCreate-test.tsx.snap View File

@@ -1,21 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should correctly create a project 1`] = `
<SubmitButton
disabled={true}
>
setup
</SubmitButton>
`;

exports[`should correctly create a project 2`] = `
<SubmitButton
disabled={false}
>
setup
</SubmitButton>
`;

exports[`should render correctly 1`] = `
<Fragment>
<form
@@ -37,54 +21,14 @@ exports[`should render correctly 1`] = `
]
}
/>
<div
<ProjectKeyInput
className="form-field"
>
<label
htmlFor="project-name"
>
onboarding.create_project.project_name
<em
className="mandatory"
>
*
</em>
</label>
<input
className="input-super-large"
id="project-name"
maxLength={400}
minLength={1}
onChange={[Function]}
required={true}
type="text"
value=""
/>
</div>
<div
onChange={[Function]}
/>
<ProjectNameInput
className="form-field"
>
<label
htmlFor="project-key"
>
onboarding.create_project.project_key
<em
className="mandatory"
>
*
</em>
</label>
<input
className="input-super-large"
id="project-key"
maxLength={400}
minLength={1}
onChange={[Function]}
required={true}
type="text"
value=""
/>
</div>
onChange={[Function]}
/>
<SubmitButton
disabled={true}
>

+ 9
- 3
server/sonar-web/src/main/js/components/controls/ValidationInput.tsx View File

@@ -18,13 +18,16 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import HelpTooltip from './HelpTooltip';
import AlertErrorIcon from '../icons-components/AlertErrorIcon';
import AlertSuccessIcon from '../icons-components/AlertSuccessIcon';

interface Props {
description?: string;
children: React.ReactNode;
className?: string;
error: string | undefined;
help?: string;
id: string;
isInvalid: boolean;
isValid: boolean;
@@ -35,10 +38,13 @@ interface Props {
export default function ValidationInput(props: Props) {
const hasError = props.isInvalid && props.error !== undefined;
return (
<div>
<div className={props.className}>
<label htmlFor={props.id}>
<strong>{props.label}</strong>
{props.required && <em className="mandatory">*</em>}
<span className="text-middle">
<strong>{props.label}</strong>
{props.required && <em className="mandatory">*</em>}
</span>
{props.help && <HelpTooltip className="spacer-left" overlay={props.help} />}
</label>
<div className="little-spacer-top spacer-bottom">
{props.children}

+ 1
- 0
server/sonar-web/src/main/js/components/controls/__tests__/ValidationInput-test.tsx View File

@@ -27,6 +27,7 @@ it('should render', () => {
<ValidationInput
description="My description"
error={undefined}
help="Help message"
id="field-id"
isInvalid={false}
isValid={false}

+ 33
- 17
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ValidationInput-test.tsx.snap View File

@@ -5,14 +5,22 @@ exports[`should render 1`] = `
<label
htmlFor="field-id"
>
<strong>
Field label
</strong>
<em
className="mandatory"
<span
className="text-middle"
>
*
</em>
<strong>
Field label
</strong>
<em
className="mandatory"
>
*
</em>
</span>
<HelpTooltip
className="spacer-left"
overlay="Help message"
/>
</label>
<div
className="little-spacer-top spacer-bottom"
@@ -32,14 +40,18 @@ exports[`should render when valid 1`] = `
<label
htmlFor="field-id"
>
<strong>
Field label
</strong>
<em
className="mandatory"
<span
className="text-middle"
>
*
</em>
<strong>
Field label
</strong>
<em
className="mandatory"
>
*
</em>
</span>
</label>
<div
className="little-spacer-top spacer-bottom"
@@ -62,9 +74,13 @@ exports[`should render with error 1`] = `
<label
htmlFor="field-id"
>
<strong>
Field label
</strong>
<span
className="text-middle"
>
<strong>
Field label
</strong>
</span>
</label>
<div
className="little-spacer-top spacer-bottom"

+ 13
- 5
server/sonar-web/src/main/js/helpers/testUtils.ts View File

@@ -52,11 +52,19 @@ export function submit(element: ShallowWrapper | ReactWrapper): void {
}

export function change(element: ShallowWrapper | ReactWrapper, value: string, event = {}): void {
element.simulate('change', {
target: { value },
currentTarget: { value },
...event
});
// `type()` returns a component constructor for a composite element and string for DOM nodes
if (typeof element.type() === 'function') {
element.prop<Function>('onChange')(value);
// TODO find out if `root` is a public api
// https://github.com/airbnb/enzyme/blob/master/packages/enzyme/src/ReactWrapper.js#L109
(element as any).root().update();
} else {
element.simulate('change', {
target: { value },
currentTarget: { value },
...event
});
}
}

export function keydown(keyCode: number): void {

+ 7
- 1
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -2725,7 +2725,13 @@ onboarding.create_project.install_app_description.bitbucket=We need you to insta
onboarding.create_project.install_app_description.github=We need you to install the SonarCloud GitHub application on one of your organization in order to select which repositories you want to analyze.
onboarding.create_project.organization=Organization
onboarding.create_project.project_key=Project key
onboarding.create_project.project_name=Project name
onboarding.create_project.project_key.description=Up to 400 characters. All letters, digits, dash, underscore, point or colon.
onboarding.create_project.project_key.error=The provided value doesn't match the expected format.
onboarding.create_project.project_key.help=Your project key is a unique identifier for your project.
onboarding.create_project.project_key.taken=This project key is already taken.
onboarding.create_project.display_name=Display name
onboarding.create_project.display_name.error=The provided value doesn't match the expected format.
onboarding.create_project.display_name.description=Up to 500 characters
onboarding.create_project.repository_imported=Already imported: {link}
onboarding.create_project.see_project=See the project
onboarding.create_project.select_repositories=Select repositories

Loading…
Cancel
Save