]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18435 Migrate Project Key to RTl
authorstanislavh <stanislav.honcharov@sonarsource.com>
Thu, 13 Apr 2023 12:10:07 +0000 (14:10 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 14 Apr 2023 20:02:47 +0000 (20:02 +0000)
server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts
server/sonar-web/src/main/js/apps/projectKey/Key.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectKey/ProjectKeyApp.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectKey/__tests__/Key-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectKey/__tests__/ProjectKeyApp-it.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectKey/__tests__/UpdateForm-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/Key-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/UpdateForm-test.tsx.snap [deleted file]

index ba345fe6d089635007ef199b3d23035317b10131..359292c3f7ed14c4dc347a4a6b9e29eab3bd8673 100644 (file)
@@ -42,6 +42,7 @@ import {
   SourceViewerFile,
 } from '../../types/types';
 import {
+  changeKey,
   getChildren,
   getComponentData,
   getComponentForSourceViewer,
@@ -387,15 +388,16 @@ export default class ComponentsServiceMock {
     this.components = cloneDeep(this.defaultComponents);
     this.sourceFiles = cloneDeep(this.defaultSourceFiles);
 
-    (getComponentTree as jest.Mock).mockImplementation(this.handleGetComponentTree);
-    (getChildren as jest.Mock).mockImplementation(this.handleGetChildren);
-    (getTree as jest.Mock).mockImplementation(this.handleGetTree);
-    (getComponentData as jest.Mock).mockImplementation(this.handleGetComponentData);
-    (getComponentForSourceViewer as jest.Mock).mockImplementation(
-      this.handleGetComponentForSourceViewer
-    );
-    (getDuplications as jest.Mock).mockImplementation(this.handleGetDuplications);
-    (getSources as jest.Mock).mockImplementation(this.handleGetSources);
+    jest.mocked(getComponentTree).mockImplementation(this.handleGetComponentTree);
+    jest.mocked(getChildren).mockImplementation(this.handleGetChildren);
+    jest.mocked(getTree).mockImplementation(this.handleGetTree);
+    jest.mocked(getComponentData).mockImplementation(this.handleGetComponentData);
+    jest
+      .mocked(getComponentForSourceViewer)
+      .mockImplementation(this.handleGetComponentForSourceViewer);
+    jest.mocked(getDuplications).mockImplementation(this.handleGetDuplications);
+    jest.mocked(getSources).mockImplementation(this.handleGetSources);
+    jest.mocked(changeKey).mockImplementation(this.handleChangeKey);
   }
 
   findComponentTree = (key: string, from?: ComponentTree): ComponentTree | undefined => {
@@ -625,6 +627,15 @@ export default class ComponentsServiceMock {
     return this.reply(lines.slice(from - 1, to));
   };
 
+  handleChangeKey = (data: { from: string; to: string }) => {
+    const treeItem = this.components.find(({ component }) => component.key === data.from);
+    if (treeItem) {
+      treeItem.component.key = data.to;
+      return this.reply(undefined);
+    }
+    return Promise.reject({ status: 404, message: 'Component not found' });
+  };
+
   reply<T>(response: T): Promise<T> {
     return Promise.resolve(cloneDeep(response));
   }
diff --git a/server/sonar-web/src/main/js/apps/projectKey/Key.tsx b/server/sonar-web/src/main/js/apps/projectKey/Key.tsx
deleted file mode 100644 (file)
index 3015a71..0000000
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { Helmet } from 'react-helmet-async';
-import { changeKey } from '../../api/components';
-import withComponentContext from '../../app/components/componentContext/withComponentContext';
-import RecentHistory from '../../app/components/RecentHistory';
-import { Router, withRouter } from '../../components/hoc/withRouter';
-import { translate } from '../../helpers/l10n';
-import { Component } from '../../types/types';
-import UpdateForm from './UpdateForm';
-
-interface Props {
-  component: Component;
-  router: Router;
-}
-
-export class Key extends React.PureComponent<Props> {
-  handleChangeKey = (newKey: string) => {
-    return changeKey({ from: this.props.component.key, to: newKey }).then(() => {
-      RecentHistory.remove(this.props.component.key);
-      this.props.router.replace({ pathname: '/project/key', query: { id: newKey } });
-    });
-  };
-
-  render() {
-    const { component } = this.props;
-    return (
-      <div className="page page-limited" id="project-key">
-        <Helmet defer={false} title={translate('update_key.page')} />
-        <header className="page-header">
-          <h1 className="page-title">{translate('update_key.page')}</h1>
-          <div className="page-description">{translate('update_key.page.description')}</div>
-        </header>
-        <UpdateForm component={component} onKeyChange={this.handleChangeKey} />
-      </div>
-    );
-  }
-}
-
-export default withComponentContext(withRouter(Key));
diff --git a/server/sonar-web/src/main/js/apps/projectKey/ProjectKeyApp.tsx b/server/sonar-web/src/main/js/apps/projectKey/ProjectKeyApp.tsx
new file mode 100644 (file)
index 0000000..d1f7b8e
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { Helmet } from 'react-helmet-async';
+import { changeKey } from '../../api/components';
+import withComponentContext from '../../app/components/componentContext/withComponentContext';
+import RecentHistory from '../../app/components/RecentHistory';
+import { Router, withRouter } from '../../components/hoc/withRouter';
+import { translate } from '../../helpers/l10n';
+import { Component } from '../../types/types';
+import UpdateForm from './UpdateForm';
+
+interface Props {
+  component: Component;
+  router: Router;
+}
+
+function ProjectKeyApp({ component, router }: Props) {
+  const handleChangeKey = (newKey: string) => {
+    return changeKey({ from: component.key, to: newKey }).then(() => {
+      RecentHistory.remove(component.key);
+      router.replace({ pathname: '/project/key', query: { id: newKey } });
+    });
+  };
+
+  return (
+    <div className="page page-limited" id="project-key">
+      <Helmet defer={false} title={translate('update_key.page')} />
+      <header className="page-header">
+        <h1 className="page-title">{translate('update_key.page')}</h1>
+        <div className="page-description">{translate('update_key.page.description')}</div>
+      </header>
+      <UpdateForm component={component} onKeyChange={handleChangeKey} />
+    </div>
+  );
+}
+
+export default withComponentContext(withRouter(ProjectKeyApp));
diff --git a/server/sonar-web/src/main/js/apps/projectKey/__tests__/Key-test.tsx b/server/sonar-web/src/main/js/apps/projectKey/__tests__/Key-test.tsx
deleted file mode 100644 (file)
index 0c3767e..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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 { changeKey } from '../../../api/components';
-import { mockComponent } from '../../../helpers/mocks/component';
-import { Key } from '../Key';
-
-jest.mock('../../../api/components', () => ({
-  changeKey: jest.fn().mockResolvedValue(undefined),
-}));
-
-it('should render and change key', async () => {
-  const withRouterProps = { router: { replace: jest.fn() } as any };
-  const wrapper = shallow(
-    <Key component={mockComponent({ key: 'foo', name: 'Foo' })} {...withRouterProps} />
-  );
-  expect(wrapper).toMatchSnapshot();
-
-  wrapper.find('UpdateForm').prop<Function>('onKeyChange')('bar');
-  await new Promise(setImmediate);
-  expect(changeKey).toHaveBeenCalledWith({ from: 'foo', to: 'bar' });
-  expect(withRouterProps.router.replace).toHaveBeenCalledWith({
-    pathname: '/project/key',
-    query: { id: 'bar' },
-  });
-});
diff --git a/server/sonar-web/src/main/js/apps/projectKey/__tests__/ProjectKeyApp-it.tsx b/server/sonar-web/src/main/js/apps/projectKey/__tests__/ProjectKeyApp-it.tsx
new file mode 100644 (file)
index 0000000..d2e829c
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 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 { within } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { last } from 'lodash';
+import React from 'react';
+import { act } from 'react-dom/test-utils';
+import { Route } from 'react-router-dom';
+import { byRole } from 'testing-library-selector';
+import ComponentsServiceMock from '../../../api/mocks/ComponentsServiceMock';
+import { renderAppWithComponentContext } from '../../../helpers/testReactTestingUtils';
+import ProjectKeyApp from '../ProjectKeyApp';
+
+jest.mock('../../../api/components');
+
+const componentsMock = new ComponentsServiceMock();
+
+afterEach(() => {
+  componentsMock.reset();
+});
+
+it('can update project key', async () => {
+  const { ui, user } = getPageObjects();
+  const oldKey = componentsMock.components[0].component.key;
+  const newKey = 'NEW_KEY';
+  renderProjectKeyApp();
+
+  // Renders
+  expect(await ui.pageTitle.find()).toBeInTheDocument();
+
+  // Can type and reset to the old key value
+  expect(ui.newKeyInput.get()).toHaveValue(oldKey);
+  await user.clear(ui.newKeyInput.get());
+  await user.type(ui.newKeyInput.get(), newKey);
+  expect(ui.resetInputButton.get()).toBeEnabled();
+  await user.click(ui.resetInputButton.get());
+  expect(ui.newKeyInput.get()).toHaveValue(oldKey);
+
+  // Can update value
+  await user.clear(ui.newKeyInput.get());
+  await user.type(ui.newKeyInput.get(), newKey);
+  await user.click(ui.updateInputButton.get());
+  // Dialog should show old and new keys
+  expect(within(ui.updateKeyDialog.get()).getByText(oldKey)).toBeInTheDocument();
+  expect(within(ui.updateKeyDialog.get()).getByText(newKey)).toBeInTheDocument();
+  await act(async () => {
+    await user.click(last(ui.updateInputButton.getAll()) as HTMLElement);
+  });
+  expect(ui.updateInputButton.get()).toBeDisabled();
+
+  expect(ui.newKeyInput.get()).toHaveValue(newKey);
+});
+
+function renderProjectKeyApp() {
+  return renderAppWithComponentContext(
+    'project/key',
+    () => <Route path="project/key" element={<ProjectKeyApp />} />,
+    {},
+    { component: componentsMock.components[0].component }
+  );
+}
+
+function getPageObjects() {
+  const user = userEvent.setup();
+
+  const ui = {
+    pageTitle: byRole('heading', { name: 'update_key.page' }),
+    updateKeyDialog: byRole('dialog'),
+    newKeyInput: byRole('textbox'),
+    updateInputButton: byRole('button', { name: 'update_verb' }),
+    resetInputButton: byRole('button', { name: 'reset_verb' }),
+  };
+
+  return {
+    ui,
+    user,
+  };
+}
diff --git a/server/sonar-web/src/main/js/apps/projectKey/__tests__/UpdateForm-test.tsx b/server/sonar-web/src/main/js/apps/projectKey/__tests__/UpdateForm-test.tsx
deleted file mode 100644 (file)
index f39b5be..0000000
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 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, ShallowWrapper } from 'enzyme';
-import * as React from 'react';
-import ProjectKeyInput from '../../../components/common/ProjectKeyInput';
-import { Button, SubmitButton } from '../../../components/controls/buttons';
-import { mockComponent } from '../../../helpers/mocks/component';
-import { click, mockEvent } from '../../../helpers/testUtils';
-import UpdateForm, { UpdateFormProps } from '../UpdateForm';
-
-it('should render', () => {
-  expect(shallowRender()).toMatchSnapshot('default');
-  expect(getForm(shallowRender())).toMatchSnapshot('form');
-});
-
-// eslint-disable-next-line jest/expect-expect
-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);
-
-  // 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);
-});
-
-// eslint-disable-next-line jest/expect-expect
-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 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 }>
-) {
-  // eslint-disable-next-line jest/valid-expect
-  return expect(getForm(wrapper).find(button).props().disabled);
-}
-
-function expectProjectKeyInputValue(wrapper: ShallowWrapper) {
-  // eslint-disable-next-line jest/valid-expect
-  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} />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/Key-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/Key-test.tsx.snap
deleted file mode 100644 (file)
index 5b7c94d..0000000
+++ /dev/null
@@ -1,54 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render and change key 1`] = `
-<div
-  className="page page-limited"
-  id="project-key"
->
-  <Helmet
-    defer={false}
-    encodeSpecialCharacters={true}
-    prioritizeSeoTags={false}
-    title="update_key.page"
-  />
-  <header
-    className="page-header"
-  >
-    <h1
-      className="page-title"
-    >
-      update_key.page
-    </h1>
-    <div
-      className="page-description"
-    >
-      update_key.page.description
-    </div>
-  </header>
-  <UpdateForm
-    component={
-      {
-        "breadcrumbs": [],
-        "key": "foo",
-        "name": "Foo",
-        "qualifier": "TRK",
-        "qualityGate": {
-          "isDefault": true,
-          "key": "30",
-          "name": "Sonar way",
-        },
-        "qualityProfiles": [
-          {
-            "deleted": false,
-            "key": "my-qp",
-            "language": "ts",
-            "name": "Sonar way",
-          },
-        ],
-        "tags": [],
-      }
-    }
-    onKeyChange={[Function]}
-  />
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/UpdateForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectKey/__tests__/__snapshots__/UpdateForm-test.tsx.snap
deleted file mode 100644 (file)
index b52a9d3..0000000
+++ /dev/null
@@ -1,71 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-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_key.old_key
-        : 
-        <strong>
-          my-project
-        </strong>
-      </div>
-      <div
-        className="spacer-top"
-      >
-        update_key.new_key
-        : 
-        <strong />
-      </div>
-    </React.Fragment>
-  }
-  modalHeader="update_key.page"
-  onConfirm={[MockFunction]}
->
-  <Component />
-</ConfirmButton>
-`;
-
-exports[`should render: form 1`] = `
-<Fragment>
-  <form
-    onSubmit={[Function]}
-  >
-    <MandatoryFieldsExplanation
-      className="spacer-bottom"
-    />
-    <ProjectKeyInput
-      autofocus={true}
-      label="update_key.new_key"
-      onProjectKeyChange={[Function]}
-      placeholder="update_key.new_key"
-      projectKey="my-project"
-      touched={false}
-    />
-    <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>
-`;