]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-13192 Improve the validation messages when updating a project key
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Mon, 11 May 2020 15:07:32 +0000 (17:07 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 15 May 2020 20:03:42 +0000 (20:03 +0000)
server/sonar-web/src/main/js/apps/projectKey/UpdateForm.tsx
server/sonar-web/src/main/js/apps/projectKey/__tests__/UpdateForm-test.tsx
server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/UpdateForm-test.tsx.snap

index a2c2efea12a977d59d4849248e4315239428f0bf..585b9f3fda463fabcf149d9e31c211deca8bd5d5 100644 (file)
@@ -21,84 +21,79 @@ import * as React from 'react';
 import { Button, SubmitButton } from 'sonar-ui-common/components/controls/buttons';
 import ConfirmButton from 'sonar-ui-common/components/controls/ConfirmButton';
 import { translate, translateWithParameters } from 'sonar-ui-common/helpers/l10n';
+import ProjectKeyInput from '../../components/common/ProjectKeyInput';
+import { validateProjectKey } from '../../helpers/projects';
+import { ProjectKeyValidationResult } from '../../types/component';
 
-interface Props {
+export interface UpdateFormProps {
   component: Pick<T.Component, 'key' | 'name'>;
   onKeyChange: (newKey: string) => Promise<void>;
 }
 
-interface State {
-  newKey?: string;
-}
-
-export default class UpdateForm extends React.PureComponent<Props, State> {
-  state: State = {};
-
-  handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
-    const newKey = event.currentTarget.value;
-    this.setState({ newKey });
-  };
-
-  handleReset = () => {
-    this.setState({ newKey: undefined });
-  };
+export default function UpdateForm(props: UpdateFormProps) {
+  const { component } = props;
+  const [newKey, setNewKey] = React.useState<string | undefined>(undefined);
+  const value = newKey !== undefined ? newKey : component.key;
+  const hasChanged = value !== component.key;
 
-  render() {
-    const { component } = this.props;
-    const { newKey } = this.state;
-    const value = newKey != null ? newKey : component.key;
-    const hasChanged = value !== component.key;
+  const validationResult = validateProjectKey(value);
+  const error =
+    validationResult === ProjectKeyValidationResult.Valid
+      ? undefined
+      : translate('onboarding.create_project.project_key.error', validationResult);
 
-    return (
-      <ConfirmButton
-        confirmButtonText={translate('update_verb')}
-        confirmData={newKey}
-        modalBody={
-          <>
-            {translateWithParameters('update_key.are_you_sure_to_change_key', component.name)}
-            <div className="spacer-top">
-              {translate('update_key.old_key')}
-              {': '}
-              <strong>{component.key}</strong>
-            </div>
-            <div className="spacer-top">
-              {translate('update_key.new_key')}
-              {': '}
-              <strong>{newKey}</strong>
-            </div>
-          </>
-        }
-        modalHeader={translate('update_key.page')}
-        onConfirm={this.props.onKeyChange}>
-        {({ onFormSubmit }) => (
-          <form onSubmit={onFormSubmit}>
-            <input
-              className="input-super-large"
-              id="update-key-new-key"
-              onChange={this.handleChange}
-              placeholder={translate('update_key.new_key')}
-              required={true}
-              type="text"
-              value={value}
-            />
+  return (
+    <ConfirmButton
+      confirmButtonText={translate('update_verb')}
+      confirmData={newKey}
+      modalBody={
+        <>
+          {translateWithParameters('update_key.are_you_sure_to_change_key', component.name)}
+          <div className="spacer-top">
+            {translate('update_key.old_key')}
+            {': '}
+            <strong>{component.key}</strong>
+          </div>
+          <div className="spacer-top">
+            {translate('update_key.new_key')}
+            {': '}
+            <strong>{newKey}</strong>
+          </div>
+        </>
+      }
+      modalHeader={translate('update_key.page')}
+      onConfirm={props.onKeyChange}>
+      {({ onFormSubmit }) => (
+        <form onSubmit={onFormSubmit}>
+          <ProjectKeyInput
+            error={error}
+            label={translate('update_key.new_key')}
+            onProjectKeyChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+              setNewKey(e.currentTarget.value);
+            }}
+            touched={hasChanged}
+            placeholder={translate('update_key.new_key')}
+            projectKey={value}
+          />
 
-            <div className="spacer-top">
-              <SubmitButton disabled={!hasChanged} id="update-key-submit">
-                {translate('update_verb')}
-              </SubmitButton>
+          <div className="spacer-top">
+            <SubmitButton disabled={!hasChanged || error !== undefined} id="update-key-submit">
+              {translate('update_verb')}
+            </SubmitButton>
 
-              <Button
-                className="spacer-left"
-                disabled={!hasChanged}
-                id="update-key-reset"
-                onClick={this.handleReset}
-                type="reset">
-                {translate('reset_verb')}
-              </Button>
-            </div>
-          </form>
-        )}
-      </ConfirmButton>
-    );
-  }
+            <Button
+              className="spacer-left"
+              disabled={!hasChanged}
+              id="update-key-reset"
+              onClick={() => {
+                setNewKey(undefined);
+              }}
+              type="reset">
+              {translate('reset_verb')}
+            </Button>
+          </div>
+        </form>
+      )}
+    </ConfirmButton>
+  );
 }
index 7fb59ff2f2bc3c306eb9c0a8fb6f4e6c8dfef68c..c88048f5a0a8f022f722dd266b5781ba11351f42 100644 (file)
  */
 import { shallow, ShallowWrapper } from 'enzyme';
 import * as React from 'react';
-import { change, click } from 'sonar-ui-common/helpers/testUtils';
-import UpdateForm from '../UpdateForm';
+import { Button, SubmitButton } from 'sonar-ui-common/components/controls/buttons';
+import { click } from 'sonar-ui-common/helpers/testUtils';
+import ProjectKeyInput from '../../../components/common/ProjectKeyInput';
+import { mockComponent, mockEvent } from '../../../helpers/testMocks';
+import UpdateForm, { UpdateFormProps } from '../UpdateForm';
 
 it('should render', () => {
-  const wrapper = shallow(
-    <UpdateForm component={{ key: 'foo', name: 'Foo' }} onKeyChange={jest.fn()} />
-  );
-  expect(getInner(wrapper)).toMatchSnapshot();
+  expect(shallowRender()).toMatchSnapshot('default');
+  expect(getForm(shallowRender())).toMatchSnapshot('form');
+});
+
+it('should correctly update the form', () => {
+  const component = mockComponent();
+  const wrapper = shallowRender({ component });
+  expectButtonDisabled(wrapper, Button).toBe(true);
+  expectButtonDisabled(wrapper, SubmitButton).toBe(true);
+
+  // Changing the key should unlock the form.
+  changeInput(wrapper, 'bar');
+  expectProjectKeyInputValue(wrapper).toBe('bar');
+  expectButtonDisabled(wrapper, Button).toBe(false);
+  expectButtonDisabled(wrapper, SubmitButton).toBe(false);
 
-  change(getInner(wrapper).find('input'), 'bar');
-  expect(getInner(wrapper)).toMatchSnapshot();
+  // Changing it back again should lock the form.
+  changeInput(wrapper, component.key);
+  expectProjectKeyInputValue(wrapper).toBe(component.key);
+  expectButtonDisabled(wrapper, Button).toBe(true);
+  expectButtonDisabled(wrapper, SubmitButton).toBe(true);
+});
 
-  click(getInner(wrapper).find('Button'));
-  expect(getInner(wrapper)).toMatchSnapshot();
+it('should correctly reset the form', () => {
+  const component = mockComponent();
+  const wrapper = shallowRender({ component });
+  changeInput(wrapper, 'bar');
+  click(getForm(wrapper).find(Button));
+  expectProjectKeyInputValue(wrapper).toBe(component.key);
 });
 
-function getInner(wrapper: ShallowWrapper) {
-  // TODO find a better way to do this
+function getForm(wrapper: ShallowWrapper) {
+  // We're wrapper by a <ConfirmButton>. Dive twice to get the actual form.
   return wrapper.dive().dive();
 }
+
+function expectButtonDisabled(
+  wrapper: ShallowWrapper,
+  button: React.ComponentType<{ disabled?: boolean }>
+) {
+  return expect(
+    getForm(wrapper)
+      .find(button)
+      .props().disabled
+  );
+}
+
+function expectProjectKeyInputValue(wrapper: ShallowWrapper) {
+  return expect(
+    getForm(wrapper)
+      .find(ProjectKeyInput)
+      .props().projectKey
+  );
+}
+
+function changeInput(wrapper: ShallowWrapper, value: string) {
+  getForm(wrapper)
+    .find(ProjectKeyInput)
+    .props()
+    .onProjectKeyChange(mockEvent({ currentTarget: { value } }));
+}
+
+function shallowRender(props: Partial<UpdateFormProps> = {}) {
+  return shallow<UpdateFormProps>(
+    <UpdateForm component={mockComponent()} onKeyChange={jest.fn()} {...props} />
+  );
+}
index 0a009777e05f353769af0f4af64cd8e3b9a4cb32..ed6ab1774aa6bb59307e55e6213b163c2c1972ba 100644 (file)
@@ -1,92 +1,47 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`should render 1`] = `
-<Fragment>
-  <form
-    onSubmit={[Function]}
-  >
-    <input
-      className="input-super-large"
-      id="update-key-new-key"
-      onChange={[Function]}
-      placeholder="update_key.new_key"
-      required={true}
-      type="text"
-      value="foo"
-    />
-    <div
-      className="spacer-top"
-    >
-      <SubmitButton
-        disabled={true}
-        id="update-key-submit"
-      >
-        update_verb
-      </SubmitButton>
-      <Button
-        className="spacer-left"
-        disabled={true}
-        id="update-key-reset"
-        onClick={[Function]}
-        type="reset"
-      >
-        reset_verb
-      </Button>
-    </div>
-  </form>
-</Fragment>
-`;
-
-exports[`should render 2`] = `
-<Fragment>
-  <form
-    onSubmit={[Function]}
-  >
-    <input
-      className="input-super-large"
-      id="update-key-new-key"
-      onChange={[Function]}
-      placeholder="update_key.new_key"
-      required={true}
-      type="text"
-      value="bar"
-    />
-    <div
-      className="spacer-top"
-    >
-      <SubmitButton
-        disabled={false}
-        id="update-key-submit"
+exports[`should render: default 1`] = `
+<ConfirmButton
+  confirmButtonText="update_verb"
+  modalBody={
+    <React.Fragment>
+      update_key.are_you_sure_to_change_key.MyProject
+      <div
+        className="spacer-top"
       >
-        update_verb
-      </SubmitButton>
-      <Button
-        className="spacer-left"
-        disabled={false}
-        id="update-key-reset"
-        onClick={[Function]}
-        type="reset"
+        update_key.old_key
+        : 
+        <strong>
+          my-project
+        </strong>
+      </div>
+      <div
+        className="spacer-top"
       >
-        reset_verb
-      </Button>
-    </div>
-  </form>
-</Fragment>
+        update_key.new_key
+        : 
+        <strong />
+      </div>
+    </React.Fragment>
+  }
+  modalHeader="update_key.page"
+  onConfirm={[MockFunction]}
+>
+  <Component />
+</ConfirmButton>
 `;
 
-exports[`should render 3`] = `
+exports[`should render: form 1`] = `
 <Fragment>
   <form
     onSubmit={[Function]}
   >
-    <input
-      className="input-super-large"
-      id="update-key-new-key"
-      onChange={[Function]}
+    <ProjectKeyInput
+      label="update_key.new_key"
+      onProjectKeyChange={[Function]}
       placeholder="update_key.new_key"
-      required={true}
-      type="text"
-      value="foo"
+      projectKey="my-project"
+      touched={false}
     />
     <div
       className="spacer-top"