]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18436 Migrate Project Links to RTL
authorstanislavh <stanislav.honcharov@sonarsource.com>
Thu, 13 Apr 2023 12:10:37 +0000 (14:10 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 14 Apr 2023 20:02:47 +0000 (20:02 +0000)
17 files changed:
server/sonar-web/src/main/js/api/mocks/ProjectLinksServiceMock.ts [new file with mode: 0644]
server/sonar-web/src/main/js/app/utils/startReactApp.tsx
server/sonar-web/src/main/js/apps/projectLinks/App.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectLinks/LinkRow.tsx
server/sonar-web/src/main/js/apps/projectLinks/ProjectLinksApp.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectLinks/__tests__/App-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectLinks/__tests__/CreationModal-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectLinks/__tests__/Header-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectLinks/__tests__/LinkRow-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectLinks/__tests__/Table-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/App-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/CreationModal-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Header-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/LinkRow-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Table-test.tsx.snap [deleted file]
sonar-core/src/main/resources/org/sonar/l10n/core.properties

diff --git a/server/sonar-web/src/main/js/api/mocks/ProjectLinksServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/ProjectLinksServiceMock.ts
new file mode 100644 (file)
index 0000000..f564e9d
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * 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 { cloneDeep } from 'lodash';
+import { ProjectLink } from '../../types/types';
+import { createLink, deleteLink, getProjectLinks } from '../projectLinks';
+
+export default class ProjectLinksServiceMock {
+  projectLinks: ProjectLink[] = [];
+  idCounter: number = 0;
+
+  constructor() {
+    jest.mocked(getProjectLinks).mockImplementation(this.handleGetProjectLinks);
+    jest.mocked(createLink).mockImplementation(this.handleCreateLink);
+    jest.mocked(deleteLink).mockImplementation(this.handleDeleteLink);
+  }
+
+  handleGetProjectLinks = () => {
+    return this.reply(this.projectLinks);
+  };
+
+  handleCreateLink = ({ name, url }: { name: string; url: string }) => {
+    const link = {
+      id: `id${this.idCounter++}`,
+      name,
+      type: name,
+      url,
+    };
+    this.projectLinks.push(link);
+
+    return this.reply(link);
+  };
+
+  handleDeleteLink = (id: string) => {
+    this.projectLinks.filter((link) => link.id !== id);
+
+    return this.reply(undefined);
+  };
+
+  reset = () => {
+    this.projectLinks = [];
+  };
+
+  reply<T>(response: T): Promise<T> {
+    return Promise.resolve(cloneDeep(response));
+  }
+}
index 14e3c683fbbab03d926335e1edcd66282eea7428..c97a41582aa6f8641555f01559acf543c42785d7 100644 (file)
@@ -43,8 +43,8 @@ import projectBaselineRoutes from '../../apps/projectBaseline/routes';
 import projectBranchesRoutes from '../../apps/projectBranches/routes';
 import ProjectDeletionApp from '../../apps/projectDeletion/App';
 import projectDumpRoutes from '../../apps/projectDump/routes';
-import ProjectKeyApp from '../../apps/projectKey/Key';
-import ProjectLinksApp from '../../apps/projectLinks/App';
+import ProjectKeyApp from '../../apps/projectKey/ProjectKeyApp';
+import ProjectLinksApp from '../../apps/projectLinks/ProjectLinksApp';
 import projectQualityGateRoutes from '../../apps/projectQualityGate/routes';
 import projectQualityProfilesRoutes from '../../apps/projectQualityProfiles/routes';
 import projectsRoutes from '../../apps/projects/routes';
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/App.tsx b/server/sonar-web/src/main/js/apps/projectLinks/App.tsx
deleted file mode 100644 (file)
index 8533444..0000000
+++ /dev/null
@@ -1,107 +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 { createLink, deleteLink, getProjectLinks } from '../../api/projectLinks';
-import withComponentContext from '../../app/components/componentContext/withComponentContext';
-import DeferredSpinner from '../../components/ui/DeferredSpinner';
-import { translate } from '../../helpers/l10n';
-import { Component, ProjectLink } from '../../types/types';
-import Header from './Header';
-import Table from './Table';
-
-interface Props {
-  component: Component;
-}
-
-interface State {
-  links?: ProjectLink[];
-  loading: boolean;
-}
-
-export class App extends React.PureComponent<Props, State> {
-  mounted = false;
-  state: State = { loading: true };
-
-  componentDidMount() {
-    this.mounted = true;
-    this.fetchLinks();
-  }
-
-  componentDidUpdate(prevProps: Props) {
-    if (prevProps.component.key !== this.props.component.key) {
-      this.fetchLinks();
-    }
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  fetchLinks = () => {
-    this.setState({ loading: true });
-    getProjectLinks(this.props.component.key).then(
-      (links) => {
-        if (this.mounted) {
-          this.setState({ links, loading: false });
-        }
-      },
-      () => {
-        if (this.mounted) {
-          this.setState({ loading: false });
-        }
-      }
-    );
-  };
-
-  handleCreateLink = (name: string, url: string) => {
-    return createLink({ name, projectKey: this.props.component.key, url }).then((link) => {
-      if (this.mounted) {
-        this.setState(({ links = [] }) => ({
-          links: [...links, link],
-        }));
-      }
-    });
-  };
-
-  handleDeleteLink = (linkId: string) => {
-    return deleteLink(linkId).then(() => {
-      if (this.mounted) {
-        this.setState(({ links = [] }) => ({
-          links: links.filter((link) => link.id !== linkId),
-        }));
-      }
-    });
-  };
-
-  render() {
-    return (
-      <div className="page page-limited">
-        <Helmet defer={false} title={translate('project_links.page')} />
-        <Header onCreate={this.handleCreateLink} />
-        <DeferredSpinner loading={this.state.loading}>
-          {this.state.links && <Table links={this.state.links} onDelete={this.handleDeleteLink} />}
-        </DeferredSpinner>
-      </div>
-    );
-  }
-}
-
-export default withComponentContext(App);
index 6680aaca7a2934e5603def0a3ba59dd214d14e86..ad2162141667b47a49242aae93fca27e85e20257 100644 (file)
@@ -79,7 +79,11 @@ export default class LinkRow extends React.PureComponent<Props> {
         onConfirm={this.props.onDelete}
       >
         {({ onClick }) => (
-          <Button className="button-red js-delete-button" onClick={onClick}>
+          <Button
+            className="button-red js-delete-button"
+            aria-label={translateWithParameters('project_links.delete_x_link', link.name ?? '')}
+            onClick={onClick}
+          >
             {translate('delete')}
           </Button>
         )}
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/ProjectLinksApp.tsx b/server/sonar-web/src/main/js/apps/projectLinks/ProjectLinksApp.tsx
new file mode 100644 (file)
index 0000000..8da9634
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * 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 { createLink, deleteLink, getProjectLinks } from '../../api/projectLinks';
+import withComponentContext from '../../app/components/componentContext/withComponentContext';
+import DeferredSpinner from '../../components/ui/DeferredSpinner';
+import { translate } from '../../helpers/l10n';
+import { Component, ProjectLink } from '../../types/types';
+import Header from './Header';
+import Table from './Table';
+
+interface Props {
+  component: Component;
+}
+
+interface State {
+  links?: ProjectLink[];
+  loading: boolean;
+}
+
+export class ProjectLinksApp extends React.PureComponent<Props, State> {
+  mounted = false;
+  state: State = { loading: true };
+
+  componentDidMount() {
+    this.mounted = true;
+    this.fetchLinks();
+  }
+
+  componentDidUpdate(prevProps: Props) {
+    if (prevProps.component.key !== this.props.component.key) {
+      this.fetchLinks();
+    }
+  }
+
+  componentWillUnmount() {
+    this.mounted = false;
+  }
+
+  fetchLinks = () => {
+    const {
+      component: { key },
+    } = this.props;
+
+    this.setState({ loading: true });
+    getProjectLinks(key).then(
+      (links) => {
+        if (this.mounted) {
+          this.setState({ links, loading: false });
+        }
+      },
+      () => {
+        if (this.mounted) {
+          this.setState({ loading: false });
+        }
+      }
+    );
+  };
+
+  handleCreateLink = (name: string, url: string) => {
+    const {
+      component: { key },
+    } = this.props;
+
+    return createLink({ name, projectKey: key, url }).then((link) => {
+      if (this.mounted) {
+        this.setState(({ links = [] }) => ({
+          links: [...links, link],
+        }));
+      }
+    });
+  };
+
+  handleDeleteLink = (linkId: string) => {
+    return deleteLink(linkId).then(() => {
+      if (this.mounted) {
+        this.setState(({ links = [] }) => ({
+          links: links.filter((link) => link.id !== linkId),
+        }));
+      }
+    });
+  };
+
+  render() {
+    const { loading, links } = this.state;
+    return (
+      <div className="page page-limited">
+        <Helmet defer={false} title={translate('project_links.page')} />
+        <Header onCreate={this.handleCreateLink} />
+        <DeferredSpinner loading={loading}>
+          {links && <Table links={links} onDelete={this.handleDeleteLink} />}
+        </DeferredSpinner>
+      </div>
+    );
+  }
+}
+
+export default withComponentContext(ProjectLinksApp);
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/App-test.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/App-test.tsx
deleted file mode 100644 (file)
index f43a4b1..0000000
+++ /dev/null
@@ -1,77 +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 { createLink, deleteLink, getProjectLinks } from '../../../api/projectLinks';
-import { mockComponent } from '../../../helpers/mocks/component';
-import { waitAndUpdate } from '../../../helpers/testUtils';
-import { App } from '../App';
-
-// import { getProjectLinks, createLink, deleteLink } from '../../api/projectLinks';
-jest.mock('../../../api/projectLinks', () => ({
-  getProjectLinks: jest.fn().mockResolvedValue([
-    { id: '1', type: 'homepage', url: 'http://example.com' },
-    { id: '2', name: 'foo', type: 'foo', url: 'http://example.com/foo' },
-  ]),
-  createLink: jest
-    .fn()
-    .mockResolvedValue({ id: '3', name: 'bar', type: 'bar', url: 'http://example.com/bar' }),
-  deleteLink: jest.fn().mockResolvedValue(undefined),
-}));
-
-it('should fetch links and render', async () => {
-  const wrapper = shallow(<App component={mockComponent({ key: 'comp' })} />);
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-  expect(getProjectLinks).toHaveBeenCalledWith('comp');
-});
-
-it('should fetch links when component changes', async () => {
-  const wrapper = shallow(<App component={mockComponent({ key: 'comp' })} />);
-  await waitAndUpdate(wrapper);
-  expect(getProjectLinks).toHaveBeenLastCalledWith('comp');
-
-  wrapper.setProps({ component: { key: 'another' } });
-  expect(getProjectLinks).toHaveBeenLastCalledWith('another');
-});
-
-it('should create link', async () => {
-  const wrapper = shallow(<App component={mockComponent({ key: 'comp' })} />);
-  await waitAndUpdate(wrapper);
-
-  wrapper.find('Header').prop<Function>('onCreate')('bar', 'http://example.com/bar');
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-  expect(createLink).toHaveBeenCalledWith({
-    name: 'bar',
-    projectKey: 'comp',
-    url: 'http://example.com/bar',
-  });
-});
-
-it('should delete link', async () => {
-  const wrapper = shallow(<App component={mockComponent({ key: 'comp' })} />);
-  await waitAndUpdate(wrapper);
-
-  wrapper.find('Table').prop<Function>('onDelete')('foo');
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-  expect(deleteLink).toHaveBeenCalledWith('foo');
-});
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/CreationModal-test.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/CreationModal-test.tsx
deleted file mode 100644 (file)
index 7d6a806..0000000
+++ /dev/null
@@ -1,37 +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 { change, submit } from '../../../helpers/testUtils';
-import CreationModal from '../CreationModal';
-
-it('should create link', () => {
-  const onClose = jest.fn();
-  const onSubmit = jest.fn().mockResolvedValue(undefined);
-  const wrapper = shallow(<CreationModal onClose={onClose} onSubmit={onSubmit} />);
-  const form = wrapper.dive();
-
-  change(form.find('#create-link-name'), 'foo');
-  change(form.find('#create-link-url'), 'http://example.com/foo');
-  expect(form).toMatchSnapshot();
-
-  submit(wrapper);
-  expect(onSubmit).toHaveBeenCalledWith('foo', 'http://example.com/foo');
-});
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/Header-test.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/Header-test.tsx
deleted file mode 100644 (file)
index 652f194..0000000
+++ /dev/null
@@ -1,41 +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 { click } from '../../../helpers/testUtils';
-import Header from '../Header';
-
-it('should render', () => {
-  expect(shallow(<Header onCreate={jest.fn()} />)).toMatchSnapshot();
-});
-
-it('should open creation modal', () => {
-  const onCreate = jest.fn();
-  const wrapper = shallow(<Header onCreate={onCreate} />);
-  click(wrapper.find('Button'));
-  expect(wrapper.find('CreationModal').exists()).toBe(true);
-
-  wrapper.find('CreationModal').prop<Function>('onSubmit')('foo', 'http://example.com/foo');
-  expect(onCreate).toHaveBeenCalledWith('foo', 'http://example.com/foo');
-
-  wrapper.find('CreationModal').prop<Function>('onClose')();
-  wrapper.update();
-  expect(wrapper.find('CreationModal').exists()).toBe(false);
-});
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/LinkRow-test.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/LinkRow-test.tsx
deleted file mode 100644 (file)
index 8e6e159..0000000
+++ /dev/null
@@ -1,55 +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 LinkRow from '../LinkRow';
-
-it('should render provided link', () => {
-  expect(
-    shallow(
-      <LinkRow
-        link={{ id: '12', type: 'homepage', url: 'http://example.com' }}
-        onDelete={jest.fn()}
-      />
-    )
-  ).toMatchSnapshot();
-});
-
-it('should render custom link', () => {
-  expect(
-    shallow(
-      <LinkRow
-        link={{ id: '12', name: 'foo', type: 'foo', url: 'http://example.com' }}
-        onDelete={jest.fn()}
-      />
-    )
-  ).toMatchSnapshot();
-});
-
-it('should render dangerous code as plain text', () => {
-  expect(
-    shallow(
-      <LinkRow
-        link={{ id: '12', name: 'dangerous', type: 'dangerous', url: 'javascript:alert("Hello")' }}
-        onDelete={jest.fn()}
-      />
-    )
-  ).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx
new file mode 100644 (file)
index 0000000..317ccea
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * 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 userEvent from '@testing-library/user-event';
+import { last } from 'lodash';
+import React from 'react';
+import { Route } from 'react-router-dom';
+import { byRole, byText } from 'testing-library-selector';
+import ProjectLinksServiceMock from '../../../api/mocks/ProjectLinksServiceMock';
+import { mockComponent } from '../../../helpers/mocks/component';
+import { renderAppWithComponentContext } from '../../../helpers/testReactTestingUtils';
+import ProjectLinksApp from '../ProjectLinksApp';
+
+jest.mock('../../../api/projectLinks');
+
+const componentsMock = new ProjectLinksServiceMock();
+
+afterEach(() => {
+  componentsMock.reset();
+});
+
+it('renders project links app and can do CRUD operations', async () => {
+  const { ui } = getPageObjects();
+
+  const newLinkName1 = 'link1';
+  const newLinkName2 = 'issue';
+  renderProjectLinksApp();
+  await ui.appIsLoaded();
+
+  expect(ui.noResultsTable.get()).toBeInTheDocument();
+
+  // Create link
+  await ui.createLink(newLinkName1, 'https://link.com');
+  expect(ui.deleteLinkButton(newLinkName1).get()).toBeInTheDocument();
+  expect(ui.noResultsTable.query()).not.toBeInTheDocument();
+
+  // Create invalid link with provided type
+  await ui.createLink(newLinkName2, 'invalidurl');
+  expect(ui.deleteLinkButton(newLinkName2).query()).not.toBeInTheDocument();
+  expect(byText('project_links.issue').get()).toBeInTheDocument();
+
+  // Delete link
+  await ui.deleteLink(newLinkName1);
+  expect(ui.deleteLinkButton(newLinkName1).query()).not.toBeInTheDocument();
+});
+
+function renderProjectLinksApp() {
+  return renderAppWithComponentContext(
+    'project/links',
+    () => <Route path="project/links" element={<ProjectLinksApp />} />,
+    {},
+    { component: mockComponent() }
+  );
+}
+
+function getPageObjects() {
+  const user = userEvent.setup();
+
+  const ui = {
+    pageTitle: byRole('heading', { name: 'project_links.page' }),
+    noResultsTable: byText('no_results'),
+    createLinkButton: byRole('button', { name: 'create' }),
+    nameInput: byRole('textbox', { name: /project_links.name/ }),
+    urlInput: byRole('textbox', { name: /project_links.url/ }),
+    cancelDialogButton: byRole('button', { name: 'cancel' }),
+    deleteLinkButton: (name: string) =>
+      byRole('button', { name: `project_links.delete_x_link.${name}` }),
+    deleteButton: byRole('button', { name: 'delete' }),
+  };
+
+  async function appIsLoaded() {
+    expect(await ui.pageTitle.find()).toBeInTheDocument();
+  }
+
+  async function createLink(name: string, url: string) {
+    await user.click(ui.createLinkButton.get());
+    await user.type(ui.nameInput.get(), name);
+    await user.type(ui.urlInput.get(), url);
+    await user.click(last(ui.createLinkButton.getAll()) as HTMLElement);
+  }
+
+  async function deleteLink(name: string) {
+    await user.click(ui.deleteLinkButton(name).get());
+    await user.click(ui.deleteButton.get());
+  }
+
+  return {
+    ui: { ...ui, appIsLoaded, createLink, deleteLink },
+    user,
+  };
+}
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/Table-test.tsx b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/Table-test.tsx
deleted file mode 100644 (file)
index fe48fd3..0000000
+++ /dev/null
@@ -1,36 +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 Table from '../Table';
-
-it('should render', () => {
-  const links = [
-    { id: '1', type: 'homepage', url: 'http://example.com/homepage' },
-    { id: '2', type: 'issue', url: 'http://example.com/issue' },
-    { id: '3', name: 'foo', type: 'foo', url: 'http://example.com/foo' },
-    { id: '4', name: 'bar', type: 'bar', url: 'http://example.com/bar' },
-  ];
-  expect(shallow(<Table links={links} onDelete={jest.fn()} />)).toMatchSnapshot();
-});
-
-it('should render empty', () => {
-  expect(shallow(<Table links={[]} onDelete={jest.fn()} />)).toMatchSnapshot();
-});
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/App-test.tsx.snap
deleted file mode 100644 (file)
index 8274d51..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should create link 1`] = `
-<div
-  className="page page-limited"
->
-  <Helmet
-    defer={false}
-    encodeSpecialCharacters={true}
-    prioritizeSeoTags={false}
-    title="project_links.page"
-  />
-  <Header
-    onCreate={[Function]}
-  />
-  <DeferredSpinner
-    loading={false}
-  >
-    <Table
-      links={
-        [
-          {
-            "id": "1",
-            "type": "homepage",
-            "url": "http://example.com",
-          },
-          {
-            "id": "2",
-            "name": "foo",
-            "type": "foo",
-            "url": "http://example.com/foo",
-          },
-          {
-            "id": "3",
-            "name": "bar",
-            "type": "bar",
-            "url": "http://example.com/bar",
-          },
-        ]
-      }
-      onDelete={[Function]}
-    />
-  </DeferredSpinner>
-</div>
-`;
-
-exports[`should delete link 1`] = `
-<div
-  className="page page-limited"
->
-  <Helmet
-    defer={false}
-    encodeSpecialCharacters={true}
-    prioritizeSeoTags={false}
-    title="project_links.page"
-  />
-  <Header
-    onCreate={[Function]}
-  />
-  <DeferredSpinner
-    loading={false}
-  >
-    <Table
-      links={
-        [
-          {
-            "id": "1",
-            "type": "homepage",
-            "url": "http://example.com",
-          },
-          {
-            "id": "2",
-            "name": "foo",
-            "type": "foo",
-            "url": "http://example.com/foo",
-          },
-        ]
-      }
-      onDelete={[Function]}
-    />
-  </DeferredSpinner>
-</div>
-`;
-
-exports[`should fetch links and render 1`] = `
-<div
-  className="page page-limited"
->
-  <Helmet
-    defer={false}
-    encodeSpecialCharacters={true}
-    prioritizeSeoTags={false}
-    title="project_links.page"
-  />
-  <Header
-    onCreate={[Function]}
-  />
-  <DeferredSpinner
-    loading={false}
-  >
-    <Table
-      links={
-        [
-          {
-            "id": "1",
-            "type": "homepage",
-            "url": "http://example.com",
-          },
-          {
-            "id": "2",
-            "name": "foo",
-            "type": "foo",
-            "url": "http://example.com/foo",
-          },
-        ]
-      }
-      onDelete={[Function]}
-    />
-  </DeferredSpinner>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/CreationModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/CreationModal-test.tsx.snap
deleted file mode 100644 (file)
index 3eefed9..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should create link 1`] = `
-<Modal
-  contentLabel="project_links.create_new_project_link"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <form
-    onSubmit={[Function]}
-  >
-    <header
-      className="modal-head"
-    >
-      <h2>
-        project_links.create_new_project_link
-      </h2>
-    </header>
-    <div
-      className="modal-body"
-    >
-      <MandatoryFieldsExplanation
-        className="modal-field"
-      />
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="create-link-name"
-        >
-          project_links.name
-          <MandatoryFieldMarker />
-        </label>
-        <input
-          autoFocus={true}
-          id="create-link-name"
-          maxLength={128}
-          name="name"
-          onChange={[Function]}
-          required={true}
-          type="text"
-          value=""
-        />
-      </div>
-      <div
-        className="modal-field"
-      >
-        <label
-          htmlFor="create-link-url"
-        >
-          project_links.url
-          <MandatoryFieldMarker />
-        </label>
-        <input
-          id="create-link-url"
-          maxLength={128}
-          name="url"
-          onChange={[Function]}
-          required={true}
-          type="text"
-          value=""
-        />
-      </div>
-    </div>
-    <footer
-      className="modal-foot"
-    >
-      <DeferredSpinner
-        className="spacer-right"
-        loading={false}
-      />
-      <SubmitButton
-        disabled={false}
-        id="create-link-confirm"
-      >
-        create
-      </SubmitButton>
-      <ResetButtonLink
-        disabled={false}
-        onClick={[Function]}
-      >
-        cancel
-      </ResetButtonLink>
-    </footer>
-  </form>
-</Modal>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Header-test.tsx.snap
deleted file mode 100644 (file)
index 36966fb..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render 1`] = `
-<Fragment>
-  <header
-    className="page-header"
-  >
-    <h1
-      className="page-title"
-    >
-      project_links.page
-    </h1>
-    <div
-      className="page-actions"
-    >
-      <Button
-        id="create-project-link"
-        onClick={[Function]}
-      >
-        create
-      </Button>
-    </div>
-    <div
-      className="page-description"
-    >
-      project_links.page.description
-    </div>
-  </header>
-</Fragment>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/LinkRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/LinkRow-test.tsx.snap
deleted file mode 100644 (file)
index 1d479fd..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render custom link 1`] = `
-<tr
-  data-name="foo"
->
-  <td
-    className="nowrap"
-  >
-    <div>
-      <ProjectLinkIcon
-        className="little-spacer-right"
-        type="foo"
-      />
-      <div
-        className="display-inline-block text-top"
-      >
-        <span
-          className="js-name"
-        >
-          foo
-        </span>
-      </div>
-    </div>
-  </td>
-  <td
-    className="nowrap js-url"
-  >
-    <ForwardRef(Link)
-      target="_blank"
-      to="http://example.com"
-    >
-      http://example.com
-    </ForwardRef(Link)>
-  </td>
-  <td
-    className="thin nowrap"
-  >
-    <ConfirmButton
-      confirmButtonText="delete"
-      confirmData="12"
-      isDestructive={true}
-      modalBody="project_links.are_you_sure_to_delete_x_link.foo"
-      modalHeader="project_links.delete_project_link"
-      onConfirm={[MockFunction]}
-    >
-      <Component />
-    </ConfirmButton>
-  </td>
-</tr>
-`;
-
-exports[`should render dangerous code as plain text 1`] = `
-<tr
-  data-name="dangerous"
->
-  <td
-    className="nowrap"
-  >
-    <div>
-      <ProjectLinkIcon
-        className="little-spacer-right"
-        type="dangerous"
-      />
-      <div
-        className="display-inline-block text-top"
-      >
-        <span
-          className="js-name"
-        >
-          dangerous
-        </span>
-      </div>
-    </div>
-  </td>
-  <td
-    className="nowrap js-url"
-  >
-    javascript:alert("Hello")
-  </td>
-  <td
-    className="thin nowrap"
-  >
-    <ConfirmButton
-      confirmButtonText="delete"
-      confirmData="12"
-      isDestructive={true}
-      modalBody="project_links.are_you_sure_to_delete_x_link.dangerous"
-      modalHeader="project_links.delete_project_link"
-      onConfirm={[MockFunction]}
-    >
-      <Component />
-    </ConfirmButton>
-  </td>
-</tr>
-`;
-
-exports[`should render provided link 1`] = `
-<tr>
-  <td
-    className="nowrap"
-  >
-    <div>
-      <ProjectLinkIcon
-        className="little-spacer-right"
-        type="homepage"
-      />
-      <div
-        className="display-inline-block text-top"
-      >
-        <div>
-          <span
-            className="js-name"
-          >
-            project_links.homepage
-          </span>
-        </div>
-        <div
-          className="note little-spacer-top"
-        >
-          <span
-            className="js-type"
-          >
-            sonar.links.homepage
-          </span>
-        </div>
-      </div>
-    </div>
-  </td>
-  <td
-    className="nowrap js-url"
-  >
-    <ForwardRef(Link)
-      target="_blank"
-      to="http://example.com"
-    >
-      http://example.com
-    </ForwardRef(Link)>
-  </td>
-  <td
-    className="thin nowrap"
-  />
-</tr>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Table-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectLinks/__tests__/__snapshots__/Table-test.tsx.snap
deleted file mode 100644 (file)
index 7bc58db..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render 1`] = `
-<div
-  className="boxed-group boxed-group-inner"
->
-  <table
-    className="data zebra"
-    id="project-links"
-  >
-    <thead>
-      <tr>
-        <th
-          className="nowrap"
-        >
-          project_links.name
-        </th>
-        <th
-          className="nowrap width-100"
-        >
-          project_links.url
-        </th>
-        <th
-          className="thin"
-        >
-           
-        </th>
-      </tr>
-    </thead>
-    <tbody>
-      <LinkRow
-        key="1"
-        link={
-          {
-            "id": "1",
-            "type": "homepage",
-            "url": "http://example.com/homepage",
-          }
-        }
-        onDelete={[MockFunction]}
-      />
-      <LinkRow
-        key="2"
-        link={
-          {
-            "id": "2",
-            "type": "issue",
-            "url": "http://example.com/issue",
-          }
-        }
-        onDelete={[MockFunction]}
-      />
-      <LinkRow
-        key="4"
-        link={
-          {
-            "id": "4",
-            "name": "bar",
-            "type": "bar",
-            "url": "http://example.com/bar",
-          }
-        }
-        onDelete={[MockFunction]}
-      />
-      <LinkRow
-        key="3"
-        link={
-          {
-            "id": "3",
-            "name": "foo",
-            "type": "foo",
-            "url": "http://example.com/foo",
-          }
-        }
-        onDelete={[MockFunction]}
-      />
-    </tbody>
-  </table>
-</div>
-`;
-
-exports[`should render empty 1`] = `
-<div
-  className="note"
->
-  no_results
-</div>
-`;
index 3d879075e4b52528b791547b049ef6533c8df277..2192f4895ce0b81a09d3d206db853a4044899f77 100644 (file)
@@ -492,6 +492,7 @@ project_links.scm_dev=Developer connection
 project_links.create_new_project_link=Create New Project Link
 project_links.delete_project_link=Delete Project Link
 project_links.are_you_sure_to_delete_x_link=Are you sure you want to delete the "{0}" link?
+project_links.delete_x_link=Delete "{0}" link
 project_links.name=Name
 project_links.url=URL