]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19613 Project Information tests
authorViktor Vorona <viktor.vorona@sonarsource.com>
Mon, 26 Jun 2023 06:49:07 +0000 (08:49 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 26 Jun 2023 20:03:55 +0000 (20:03 +0000)
17 files changed:
server/sonar-web/src/main/js/api/mocks/ComponentsServiceMock.ts
server/sonar-web/src/main/js/api/mocks/MeasuresServiceMock.ts
server/sonar-web/src/main/js/api/mocks/NotificationsMock.ts
server/sonar-web/src/main/js/api/mocks/ProjectBadgesServiceMock.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/api/mocks/ProjectLinksServiceMock.ts
server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx
server/sonar-web/src/main/js/apps/projectInformation/ProjectInformationApp.tsx
server/sonar-web/src/main/js/apps/projectInformation/__tests__/ProjectInformationApp-it.tsx
server/sonar-web/src/main/js/apps/projectInformation/about/AboutProject.tsx
server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaDescription.tsx
server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaHome.tsx
server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaLinks.tsx
server/sonar-web/src/main/js/apps/projectInformation/about/components/MetaTags.tsx
server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaHome-test.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaTags-test.tsx
server/sonar-web/src/main/js/apps/projectLinks/__tests__/ProjectLinksApp-it.tsx
server/sonar-web/src/main/js/components/icons/ProjectLinkIcon.tsx

index 1a9eb241a1fe2d2a6ba921c25673a1c7907f11d7..e6100375decba076cd077ece704ae404fbc7d908 100644 (file)
@@ -46,6 +46,8 @@ import {
   getDuplications,
   getSources,
   getTree,
+  setApplicationTags,
+  setProjectTags,
 } from '../components';
 import {
   ComponentTree,
@@ -95,6 +97,8 @@ export default class ComponentsServiceMock {
     jest.mocked(changeKey).mockImplementation(this.handleChangeKey);
     jest.mocked(getComponentLeaves).mockImplementation(this.handleGetComponentLeaves);
     jest.mocked(getBreadcrumbs).mockImplementation(this.handleGetBreadcrumbs);
+    jest.mocked(setProjectTags).mockImplementation(this.handleSetProjectTags);
+    jest.mocked(setApplicationTags).mockImplementation(this.handleSetApplicationTags);
   }
 
   findComponentTree = (key: string, from?: ComponentTree) => {
@@ -369,7 +373,25 @@ export default class ComponentsServiceMock {
     return this.reply([...(base.ancestors as ComponentRaw[]), base.component as ComponentRaw]);
   };
 
-  reply<T>(response: T): Promise<T> {
-    return Promise.resolve(cloneDeep(response));
+  handleSetProjectTags: typeof setProjectTags = ({ project, tags }) => {
+    const base = this.findComponentTree(project);
+    if (base !== undefined) {
+      base.component.tags = tags.split(',');
+    }
+    return this.reply();
+  };
+
+  handleSetApplicationTags: typeof setApplicationTags = ({ application, tags }) => {
+    const base = this.findComponentTree(application);
+    if (base !== undefined) {
+      base.component.tags = tags.split(',');
+    }
+    return this.reply();
+  };
+
+  reply<T>(): Promise<void>;
+  reply<T>(response: T): Promise<T>;
+  reply<T>(response?: T): Promise<T | void> {
+    return Promise.resolve(response ? cloneDeep(response) : undefined);
   }
 }
index 2e5ed685cc159598a461a27768c980647fb16d8d..ca1785671fd7f1b145368c86172f8b266bb69905 100644 (file)
@@ -62,6 +62,10 @@ export class MeasuresServiceMock {
     return this.#measures;
   };
 
+  setComponents = (components: ComponentTree) => {
+    this.#components = components;
+  };
+
   findComponentTree = (key: string, from?: ComponentTree): ComponentTree => {
     const recurse = (node: ComponentTree): ComponentTree | undefined => {
       if (node.component.key === key) {
index 3d09914d94db3859e30259e3e40403d0ce595fef..9bf0ac6290fa884d334433ea838106959d0ff053 100644 (file)
@@ -27,6 +27,8 @@ import {
 } from '../../types/notifications';
 import { addNotification, getNotifications, removeNotification } from '../notifications';
 
+jest.mock('../notifications');
+
 /* Constants */
 const channels = ['EmailNotificationChannel'];
 const defaultNotifications: Notification[] = [
diff --git a/server/sonar-web/src/main/js/api/mocks/ProjectBadgesServiceMock.tsx b/server/sonar-web/src/main/js/api/mocks/ProjectBadgesServiceMock.tsx
new file mode 100644 (file)
index 0000000..495f001
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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 { getProjectBadgesToken, renewProjectBadgesToken } from '../project-badges';
+
+jest.mock('../project-badges');
+jest.mock('../project-badges');
+
+const defaultToken = 'sqb_2b5052cef8eac91a921ac71be9227a27f6b6b38b';
+
+export class ProjectBadgesServiceMock {
+  token: string;
+
+  constructor() {
+    this.token = defaultToken;
+
+    jest.mocked(getProjectBadgesToken).mockImplementation(this.handleGetProjectBadgesToken);
+    jest.mocked(renewProjectBadgesToken).mockImplementation(this.handleRenewProjectBadgesToken);
+  }
+
+  handleGetProjectBadgesToken = () => {
+    return Promise.resolve(this.token);
+  };
+
+  handleRenewProjectBadgesToken = () => {
+    const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
+    this.token =
+      'sqb-' +
+      new Array(40)
+        .fill(null)
+        .map(() => chars.charAt(Math.floor(Math.random() * chars.length)))
+        .join('');
+
+    return Promise.resolve(this.token);
+  };
+
+  reset = () => {
+    this.token = defaultToken;
+  };
+}
index f564e9d42aa80c5e7b941f22ba7c2d62bea4d162..cca040fda84b0863f76197083de8187dd7d544e0 100644 (file)
@@ -21,6 +21,8 @@ import { cloneDeep } from 'lodash';
 import { ProjectLink } from '../../types/types';
 import { createLink, deleteLink, getProjectLinks } from '../projectLinks';
 
+jest.mock('../projectLinks');
+
 export default class ProjectLinksServiceMock {
   projectLinks: ProjectLink[] = [];
   idCounter: number = 0;
index 173e9fa7723fbb91321c6375afc8ab77e0a24f8b..4fbc29fdf32cbfc2e300b14876e191bb45cd56a9 100644 (file)
@@ -36,7 +36,6 @@ import { CurrentUser } from '../../../types/users';
 import routes from '../routes';
 
 jest.mock('../../../api/user-tokens');
-jest.mock('../../../api/notifications');
 
 jest.mock('../../../helpers/preferences', () => ({
   getKeyboardShortcutEnabled: jest.fn().mockResolvedValue(true),
index 223c019c707ed5bf9948056a070ba1dd966d44fa..6498c2f743acb1e35a4f7af5936437d55b42d470 100644 (file)
@@ -50,7 +50,7 @@ interface State {
   measures?: Measure[];
 }
 
-export class ProjectInformationApp extends React.PureComponent<Props, State> {
+class ProjectInformationApp extends React.PureComponent<Props, State> {
   mounted = false;
   state: State = {};
 
index cebd49ea64cd4e470fb780922996257bd77aa52d..78da836c88b8756addd8cdce876fc64a8a2bf99e 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
+import { screen } from '@testing-library/react';
+import BranchesServiceMock from '../../../api/mocks/BranchesServiceMock';
+import CodingRulesServiceMock from '../../../api/mocks/CodingRulesServiceMock';
 import ComponentsServiceMock from '../../../api/mocks/ComponentsServiceMock';
+import { MeasuresServiceMock } from '../../../api/mocks/MeasuresServiceMock';
+import NotificationsMock from '../../../api/mocks/NotificationsMock';
+import { ProjectBadgesServiceMock } from '../../../api/mocks/ProjectBadgesServiceMock';
+import ProjectLinksServiceMock from '../../../api/mocks/ProjectLinksServiceMock';
+import { mockComponent } from '../../../helpers/mocks/component';
+import { mockCurrentUser, mockLoggedInUser, mockMeasure } from '../../../helpers/testMocks';
 import { renderAppWithComponentContext } from '../../../helpers/testReactTestingUtils';
 import { byRole } from '../../../helpers/testSelector';
+import { ComponentQualifier, Visibility } from '../../../types/component';
+import { MetricKey } from '../../../types/metrics';
+import { Component } from '../../../types/types';
+import { CurrentUser } from '../../../types/users';
 import routes from '../routes';
 
+jest.mock('../../../api/rules');
+jest.mock('../../../api/issues');
+jest.mock('../../../api/quality-profiles');
+jest.mock('../../../api/users');
+jest.mock('../../../api/web-api', () => ({
+  fetchWebApi: () => Promise.resolve([]),
+}));
+
 const componentsMock = new ComponentsServiceMock();
+const measuresHandler = new MeasuresServiceMock();
+const linksHandler = new ProjectLinksServiceMock();
+const rulesHandler = new CodingRulesServiceMock();
+const badgesHandler = new ProjectBadgesServiceMock();
+const notificationsHandler = new NotificationsMock();
+const branchesHandler = new BranchesServiceMock();
 
 const ui = {
   projectPageTitle: byRole('heading', { name: 'project.info.title' }),
   applicationPageTitle: byRole('heading', { name: 'application.info.title' }),
   qualityGateList: byRole('list', { name: 'project.info.quality_gate' }),
-  qualityProfilesList: byRole('list', { name: 'project.info.qualit_profiles' }),
+  qualityProfilesList: byRole('list', { name: 'overview.quality_profiles' }),
+  externalLinksList: byRole('list', { name: 'overview.external_links' }),
   link: byRole('link'),
   tags: byRole('generic', { name: /tags:/ }),
   size: byRole('link', { name: /project.info.see_more_info_on_x_locs/ }),
   newKeyInput: byRole('textbox'),
   updateInputButton: byRole('button', { name: 'update_verb' }),
   resetInputButton: byRole('button', { name: 'reset_verb' }),
+  projectHomeCheckbox: byRole('checkbox', { name: 'project.info.make_home.label' }),
+  applicationHomeCheckbox: byRole('checkbox', { name: 'application.info.make_home.label' }),
 };
 
 afterEach(() => {
   componentsMock.reset();
+  measuresHandler.reset();
+  linksHandler.reset();
+  rulesHandler.reset();
+  badgesHandler.reset();
+  notificationsHandler.reset();
+  branchesHandler.reset();
+});
+
+it('should show fields for project', async () => {
+  measuresHandler.registerComponentMeasures({
+    'my-project': { [MetricKey.ncloc]: mockMeasure({ metric: MetricKey.ncloc, value: '1000' }) },
+  });
+  linksHandler.projectLinks = [{ id: '1', name: 'test', type: '', url: 'http://test.com' }];
+  renderProjectInformationApp(
+    {
+      visibility: Visibility.Private,
+      description: 'Test description',
+      tags: ['bar'],
+    },
+    mockLoggedInUser()
+  );
+  expect(await ui.projectPageTitle.find()).toBeInTheDocument();
+  expect(ui.qualityGateList.get()).toBeInTheDocument();
+  expect(ui.link.getAll(ui.qualityGateList.get())).toHaveLength(1);
+  expect(ui.link.getAll(ui.qualityProfilesList.get())).toHaveLength(1);
+  expect(ui.link.getAll(ui.externalLinksList.get())).toHaveLength(1);
+  expect(screen.getByText('Test description')).toBeInTheDocument();
+  expect(screen.getByText('my-project')).toBeInTheDocument();
+  expect(screen.getByText('visibility.private')).toBeInTheDocument();
+  expect(ui.tags.get()).toHaveTextContent('bar');
+  expect(ui.size.get()).toHaveTextContent('1short_number_suffix.k');
+  expect(ui.projectHomeCheckbox.get()).toBeInTheDocument();
+  expect(ui.applicationHomeCheckbox.query()).not.toBeInTheDocument();
+});
+
+it('should show application fields', async () => {
+  measuresHandler.registerComponentMeasures({
+    'my-project': {
+      [MetricKey.ncloc]: mockMeasure({ metric: MetricKey.ncloc, value: '1000' }),
+      [MetricKey.projects]: mockMeasure({ metric: MetricKey.projects, value: '2' }),
+    },
+  });
+  renderProjectInformationApp(
+    {
+      qualifier: ComponentQualifier.Application,
+      visibility: Visibility.Private,
+      description: 'Test description',
+      tags: ['bar'],
+    },
+    mockLoggedInUser()
+  );
+  expect(await ui.applicationPageTitle.find()).toBeInTheDocument();
+  expect(ui.qualityGateList.query()).not.toBeInTheDocument();
+  expect(ui.qualityProfilesList.query()).not.toBeInTheDocument();
+  expect(ui.externalLinksList.query()).not.toBeInTheDocument();
+  expect(screen.getByText('Test description')).toBeInTheDocument();
+  expect(screen.getByText('my-project')).toBeInTheDocument();
+  expect(screen.getByText('visibility.private')).toBeInTheDocument();
+  expect(ui.tags.get()).toHaveTextContent('bar');
+  expect(ui.size.get()).toHaveTextContent('1short_number_suffix.k');
+  expect(screen.getByRole('link', { name: '2' })).toBeInTheDocument();
+  expect(ui.applicationHomeCheckbox.get()).toBeInTheDocument();
+  expect(ui.projectHomeCheckbox.query()).not.toBeInTheDocument();
+});
+
+it('should hide some fields for application', async () => {
+  renderProjectInformationApp({
+    qualifier: ComponentQualifier.Application,
+  });
+  expect(await ui.applicationPageTitle.find()).toBeInTheDocument();
+  expect(screen.getByText('application.info.empty_description')).toBeInTheDocument();
+  expect(screen.queryByText(/visibility/)).not.toBeInTheDocument();
+  expect(ui.tags.get()).toHaveTextContent('no_tags');
+  expect(ui.applicationHomeCheckbox.query()).not.toBeInTheDocument();
+});
+
+it('should not show field that is not configured', async () => {
+  renderProjectInformationApp({
+    qualityGate: undefined,
+    qualityProfiles: [],
+  });
+  expect(await ui.projectPageTitle.find()).toBeInTheDocument();
+  expect(ui.qualityGateList.query()).not.toBeInTheDocument();
+  expect(ui.qualityProfilesList.query()).not.toBeInTheDocument();
+  expect(screen.queryByText(/visibility/)).not.toBeInTheDocument();
+  expect(ui.tags.get()).toHaveTextContent('no_tags');
+  expect(screen.getByText('project.info.empty_description')).toBeInTheDocument();
+  expect(ui.projectHomeCheckbox.query()).not.toBeInTheDocument();
 });
 
-it('can update project key', async () => {
-  renderProjectInformationApp();
+it('should hide visibility if public', async () => {
+  renderProjectInformationApp({
+    visibility: Visibility.Public,
+    qualityGate: undefined,
+    qualityProfiles: [],
+  });
   expect(await ui.projectPageTitle.find()).toBeInTheDocument();
+  expect(ui.qualityGateList.query()).not.toBeInTheDocument();
+  expect(ui.qualityProfilesList.query()).not.toBeInTheDocument();
+  expect(screen.queryByText(/visibility/)).not.toBeInTheDocument();
+  expect(ui.tags.get()).toHaveTextContent('no_tags');
+  expect(screen.getByText('project.info.empty_description')).toBeInTheDocument();
+  expect(ui.projectHomeCheckbox.query()).not.toBeInTheDocument();
 });
 
-function renderProjectInformationApp() {
+function renderProjectInformationApp(
+  overrides: Partial<Component> = {},
+  currentUser: CurrentUser = mockCurrentUser()
+) {
+  const component = mockComponent(overrides);
+  componentsMock.registerComponent(component, [componentsMock.components[0].component]);
+  measuresHandler.setComponents({ component, ancestors: [], children: [] });
   return renderAppWithComponentContext(
     'project/information',
     routes,
-    {},
-    { component: componentsMock.components[0].component }
+    { currentUser },
+    { component }
   );
 }
index 09dd1ec8bed3c76ecec793bf1e924d4f89a1cafb..ee9f650da39e841823646c967ede957a3b0413c0 100644 (file)
@@ -96,7 +96,7 @@ export default function AboutProject(props: AboutProjectProps) {
         <MetaTags component={component} onComponentChange={props.onComponentChange} />
       </ProjectInformationSection>
 
-      <ProjectInformationSection>
+      <ProjectInformationSection last={!isLoggedIn(currentUser) && (isApp || !links?.length)}>
         <MetaSize component={component} measures={measures} />
       </ProjectInformationSection>
 
@@ -124,7 +124,7 @@ function ProjectInformationSection(props: PropsWithChildren<ProjectInformationSe
   const { children, className, last = false } = props;
   return (
     <>
-      <div className={classNames('sw-py-6', className)}>{children}</div>
+      <div className={classNames('sw-py-4', className)}>{children}</div>
       {!last && <BasicSeparator />}
     </>
   );
index 9a9e3e5e4a86e9c4213999ae9a92bde284b7c430..3f440c494f97f456cca4e8601ec5e9ff4392aa96 100644 (file)
@@ -30,11 +30,10 @@ export default function MetaDescription({ description, isApp }: Props) {
   return (
     <>
       <h3>{translate('project.info.description')}</h3>
-      {description ? (
-        <p className="it__project-description">{description}</p>
-      ) : (
-        <TextMuted text={translate(isApp ? 'application' : 'project', 'info.empty_description')} />
-      )}
+      <TextMuted
+        className="it__project-description"
+        text={description ?? translate(isApp ? 'application' : 'project', 'info.empty_description')}
+      />
     </>
   );
 }
index cf79ab1f0b55f684cde0416a3e7da2a403bd9747..6ad8e44ca6d8cb24e10155f813b1d4d3fe4bbcdc 100644 (file)
@@ -37,7 +37,7 @@ export default function MetaHome({ componentKey, currentUser, isApp }: MetaHomeP
   const { updateCurrentUserHomepage } = useContext(CurrentUserContext);
   const currentPage: HomePage = {
     component: componentKey,
-    type: 'PROJECT',
+    type: isApp ? 'APPLICATION' : 'PROJECT',
     branch: undefined,
   };
 
index 15cc747d867aa2257a69f0e8e74d8fb3c05da07d..4b111cf94689039dc65bbbfc3ccfecb4daaec3a0 100644 (file)
@@ -32,8 +32,8 @@ export default function MetaLinks({ links }: Props) {
 
   return (
     <>
-      <h3>{translate('overview.external_links')}</h3>
-      <ul className="project-info-list">
+      <h3 id="external-links">{translate('overview.external_links')}</h3>
+      <ul className="project-info-list" aria-labelledby="external-links">
         {orderedLinks.map((link) => (
           <MetaLink miui key={link.id} link={link} />
         ))}
index 68abe4393c5dd9bf9a828c95d25fa07429b65d52..68ce51c2bc10a0fc7d316f7057ef6e2f26bd2c23 100644 (file)
@@ -71,7 +71,7 @@ export default function MetaTags(props: Props) {
       <Tags
         allowUpdate={canUpdateTags()}
         ariaTagsListLabel={translate('tags')}
-        className="js-issue-edit-tags"
+        className="project-info-tags"
         emptyText={translate('no_tags')}
         overlay={<MetaTagsSelector selectedTags={tags} setProjectTags={handleSetProjectTags} />}
         popupPlacement={PopupPlacement.Bottom}
diff --git a/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaHome-test.tsx b/server/sonar-web/src/main/js/apps/projectInformation/about/components/__tests__/MetaHome-test.tsx
new file mode 100644 (file)
index 0000000..97e2047
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * 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 { act } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import * as React from 'react';
+import { setHomePage } from '../../../../../api/users';
+import { CurrentUserContext } from '../../../../../app/components/current-user/CurrentUserContext';
+import { mockLoggedInUser } from '../../../../../helpers/testMocks';
+import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
+import { byRole } from '../../../../../helpers/testSelector';
+import { LoggedInUser, isLoggedIn } from '../../../../../types/users';
+import MetaHome from '../MetaHome';
+
+jest.mock('../../../../../api/users', () => ({
+  setHomePage: jest.fn().mockImplementation(() => Promise.resolve()),
+}));
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
+const ui = {
+  checkbox: byRole('checkbox', { name: /info.make_home.label/ }),
+};
+
+it('should update homepage for project', async () => {
+  const user = userEvent.setup();
+  renderMetaHome(
+    mockLoggedInUser({
+      homepage: { component: 'test-project', type: 'PROJECT', branch: undefined },
+    })
+  );
+
+  expect(await ui.checkbox.find()).toBeInTheDocument();
+  expect(ui.checkbox.get()).not.toBeChecked();
+  await act(() => user.click(ui.checkbox.get()));
+  expect(ui.checkbox.get()).toBeChecked();
+  expect(jest.mocked(setHomePage)).toHaveBeenCalledWith({
+    component: 'my-project',
+    type: 'PROJECT',
+    branch: undefined,
+  });
+  await act(() => user.click(ui.checkbox.get()));
+  expect(jest.mocked(setHomePage)).toHaveBeenCalledWith({ type: 'PROJECTS' });
+  expect(await ui.checkbox.find()).not.toBeChecked();
+});
+
+it('should update homepage for application', async () => {
+  const user = userEvent.setup();
+  renderMetaHome(
+    mockLoggedInUser({
+      homepage: { component: 'test-project', type: 'PROJECT', branch: undefined },
+    }),
+    'test',
+    true
+  );
+
+  expect(await ui.checkbox.find()).toBeInTheDocument();
+  expect(ui.checkbox.get()).not.toBeChecked();
+  await act(() => user.click(ui.checkbox.get()));
+  expect(ui.checkbox.get()).toBeChecked();
+  expect(jest.mocked(setHomePage)).toHaveBeenCalledWith({
+    component: 'test',
+    type: 'APPLICATION',
+    branch: undefined,
+  });
+});
+
+function TestComponent({
+  user,
+  componentKey,
+  isApp,
+}: {
+  user: LoggedInUser;
+  componentKey: string;
+  isApp?: boolean;
+}) {
+  const [currentUser, setCurrentUser] = React.useState(user);
+  return (
+    <CurrentUserContext.Provider
+      value={{
+        currentUser,
+        updateCurrentUserHomepage: (homepage) => {
+          setCurrentUser({ ...currentUser, homepage });
+        },
+        updateDismissedNotices: () => {},
+      }}
+    >
+      <CurrentUserContext.Consumer>
+        {({ currentUser }) =>
+          isLoggedIn(currentUser) ? (
+            <MetaHome componentKey={componentKey} currentUser={currentUser} isApp={isApp} />
+          ) : null
+        }
+      </CurrentUserContext.Consumer>
+    </CurrentUserContext.Provider>
+  );
+}
+
+function renderMetaHome(
+  currentUser: LoggedInUser,
+  componentKey: string = 'my-project',
+  isApp?: boolean
+) {
+  return renderComponent(
+    <TestComponent user={currentUser} componentKey={componentKey} isApp={isApp} />
+  );
+}
index 88511e5e1936f24ab69f89312f08c801dc29ebeb..4199cd484e5d91d01ad5d4c33fa341d846b19639 100644 (file)
@@ -78,6 +78,8 @@ it('should allow to edit tags for a project', async () => {
 
   expect(setProjectTags).toHaveBeenCalled();
   expect(setApplicationTags).not.toHaveBeenCalled();
+  await user.click(document.body);
+  expect(screen.queryByRole('checkbox')).not.toBeInTheDocument();
 });
 
 it('should set tags for an app', async () => {
index 0eb160dbf01314ed72a539a06b3d3a8a2c55cda5..2eb01730c51019101ee4e966eedffb1475189224 100644 (file)
@@ -27,8 +27,6 @@ import { renderAppWithComponentContext } from '../../../helpers/testReactTesting
 import { byRole, byText } from '../../../helpers/testSelector';
 import ProjectLinksApp from '../ProjectLinksApp';
 
-jest.mock('../../../api/projectLinks');
-
 const componentsMock = new ProjectLinksServiceMock();
 
 afterEach(() => {
index 9392d6400e9d34e5c35625bc4722692bc4d965ab..0290f6d1f424e401125cb50669dc5532267ccc65 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { LinkExternalIcon } from '@primer/octicons-react';
-import { HomeIcon } from 'design-system';
-import * as React from 'react';
+import {
+  FileIcon,
+  HomeIcon,
+  LinkExternalIcon,
+  IconProps as MIUIIconProps,
+  PulseIcon,
+  SyncIcon,
+} from '@primer/octicons-react';
+import React, { FC } from 'react';
 import BugTrackerIcon from './BugTrackerIcon';
 import ContinuousIntegrationIcon from './ContinuousIntegrationIcon';
 import DetachIcon from './DetachIcon';
@@ -37,16 +43,16 @@ export default function ProjectLinkIcon({
   type,
   ...iconProps
 }: IconProps & ProjectLinkIconProps) {
-  const getIcon = (): any => {
+  const getIcon = (): FC<IconProps | MIUIIconProps> => {
     switch (type) {
       case 'issue':
-        return BugTrackerIcon;
+        return miui ? PulseIcon : BugTrackerIcon;
       case 'homepage':
         return miui ? HomeIcon : HouseIcon;
       case 'ci':
-        return ContinuousIntegrationIcon;
+        return miui ? SyncIcon : ContinuousIntegrationIcon;
       case 'scm':
-        return SCMIcon;
+        return miui ? FileIcon : SCMIcon;
       default:
         return miui ? LinkExternalIcon : DetachIcon;
     }