]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18440 RTL Migration Project Management
authorViktor Vorona <viktor.vorona@sonarsource.com>
Thu, 15 Jun 2023 14:31:15 +0000 (16:31 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 15 Jun 2023 20:03:02 +0000 (20:03 +0000)
45 files changed:
server/sonar-web/src/main/js/api/components.ts
server/sonar-web/src/main/js/api/mocks/PermissionsServiceMock.ts
server/sonar-web/src/main/js/api/mocks/ProjectsManagementServiceMock.ts [new file with mode: 0644]
server/sonar-web/src/main/js/api/mocks/SettingsServiceMock.ts
server/sonar-web/src/main/js/api/permissions.ts
server/sonar-web/src/main/js/api/project-management.ts [new file with mode: 0644]
server/sonar-web/src/main/js/app/components/nav/global/__tests__/GlobalNavMenu-test.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/Manual-it.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/ManualProjectCreate-test.tsx
server/sonar-web/src/main/js/apps/create/project/manual/ManualProjectCreate.tsx
server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/PermissionTemplatesApp-it.tsx
server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/PermissionTemplatesApp-it.tsx.snap
server/sonar-web/src/main/js/apps/permissions/project/components/__tests__/PermissionsProject-it.tsx
server/sonar-web/src/main/js/apps/projectDeletion/Form.tsx
server/sonar-web/src/main/js/apps/projectDeletion/__tests__/Form-test.tsx
server/sonar-web/src/main/js/apps/projectsManagement/ChangeDefaultVisibilityForm.tsx
server/sonar-web/src/main/js/apps/projectsManagement/CreateProjectForm.tsx
server/sonar-web/src/main/js/apps/projectsManagement/DeleteModal.tsx
server/sonar-web/src/main/js/apps/projectsManagement/ProjectManagementApp.tsx
server/sonar-web/src/main/js/apps/projectsManagement/ProjectRow.tsx
server/sonar-web/src/main/js/apps/projectsManagement/ProjectRowActions.tsx
server/sonar-web/src/main/js/apps/projectsManagement/Projects.tsx
server/sonar-web/src/main/js/apps/projectsManagement/RestoreAccessModal.tsx
server/sonar-web/src/main/js/apps/projectsManagement/Search.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeDefaultVisibilityForm-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ChangeDefaultVisibilityForm-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/apps/projectsManagement/routes.tsx
server/sonar-web/src/main/js/helpers/mocks/projects.ts

index 7c05e3ec95c2b6a40418204e3f9561a3142839d9..e57e665cfa5e2eb7d3c9da33934d6faeea7e956e 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import { throwGlobalError } from '../helpers/error';
-import { getJSON, post, postJSON, RequestData } from '../helpers/request';
+import { getJSON, post, RequestData } from '../helpers/request';
 import { BranchParameters } from '../types/branch-like';
 import {
   ComponentQualifier,
@@ -54,16 +54,6 @@ export interface ProjectBase {
   visibility: Visibility;
 }
 
-export interface Project extends ProjectBase {
-  id: string;
-  lastAnalysisDate?: string;
-}
-
-export interface SearchProjectsParameters extends BaseSearchProjectsParameters {
-  p?: number;
-  ps?: number;
-}
-
 export interface ComponentRaw {
   key: string;
   name: string;
@@ -76,52 +66,6 @@ export interface ComponentRaw {
   needIssueSync?: boolean;
 }
 
-export function getComponents(parameters: SearchProjectsParameters): Promise<{
-  components: Project[];
-  paging: Paging;
-}> {
-  return getJSON('/api/projects/search', parameters);
-}
-
-export function bulkDeleteProjects(
-  parameters: BaseSearchProjectsParameters
-): Promise<void | Response> {
-  return post('/api/projects/bulk_delete', parameters).catch(throwGlobalError);
-}
-
-export function deleteProject(project: string): Promise<void | Response> {
-  return post('/api/projects/delete', { project }).catch(throwGlobalError);
-}
-
-export function deletePortfolio(portfolio: string): Promise<void | Response> {
-  return post('/api/views/delete', { key: portfolio }).catch(throwGlobalError);
-}
-
-export function setupManualProjectCreation(data: {
-  name: string;
-  project: string;
-  mainBranch: string;
-  visibility?: Visibility;
-}) {
-  return (newCodeDefinitionType?: string, newCodeDefinitionValue?: string) =>
-    createProject({
-      ...data,
-      newCodeDefinitionType,
-      newCodeDefinitionValue,
-    });
-}
-
-export function createProject(data: {
-  name: string;
-  project: string;
-  mainBranch: string;
-  visibility?: Visibility;
-  newCodeDefinitionType?: string;
-  newCodeDefinitionValue?: string;
-}): Promise<{ project: ProjectBase }> {
-  return postJSON('/api/projects/create', data).catch(throwGlobalError);
-}
-
 export function searchProjectTags(data?: { ps?: number; q?: string }): Promise<any> {
   return getJSON('/api/project_tags/search', data).catch(throwGlobalError);
 }
index 4d7b322fd669a13d5c8e5fd5030294aefe422cce..9a99f4b19dacd3829a5ae76be3ff4537df6d275b 100644 (file)
@@ -39,11 +39,11 @@ import {
   deletePermissionTemplate,
   getGlobalPermissionsGroups,
   getGlobalPermissionsUsers,
-  getPermissionsGroupsForComponent,
-  getPermissionsUsersForComponent,
   getPermissionTemplateGroups,
-  getPermissionTemplates,
   getPermissionTemplateUsers,
+  getPermissionTemplates,
+  getPermissionsGroupsForComponent,
+  getPermissionsUsersForComponent,
   grantPermissionToGroup,
   grantPermissionToUser,
   grantTemplatePermissionToGroup,
@@ -64,7 +64,12 @@ const defaultUsers = [
   mockPermissionUser({
     login: 'gooduser1',
     name: 'John',
-    permissions: [Permissions.IssueAdmin, Permissions.SecurityHotspotAdmin, Permissions.Browse],
+    permissions: [
+      Permissions.IssueAdmin,
+      Permissions.SecurityHotspotAdmin,
+      Permissions.Browse,
+      Permissions.Admin,
+    ],
   }),
   mockPermissionUser({
     login: 'gooduser2',
@@ -251,7 +256,11 @@ export default class PermissionsServiceMock {
 
     const users =
       q && q.length >= MIN_QUERY_LENGTH
-        ? this.#users.filter((user) => user.name.toLowerCase().includes(q.toLowerCase()))
+        ? this.#users.filter(
+            (user) =>
+              user.name.toLowerCase().includes(q.toLowerCase()) ||
+              user.login.toLowerCase().includes(q.toLowerCase())
+          )
         : this.#users;
 
     const usersChunked = chunk(
diff --git a/server/sonar-web/src/main/js/api/mocks/ProjectsManagementServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/ProjectsManagementServiceMock.ts
new file mode 100644 (file)
index 0000000..29e2e0e
--- /dev/null
@@ -0,0 +1,184 @@
+/*
+ * 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 { mockProject } from '../../helpers/mocks/projects';
+import { ComponentQualifier, Visibility } from '../../types/component';
+import { SettingsKey } from '../../types/settings';
+import {
+  Project,
+  bulkDeleteProjects,
+  changeProjectDefaultVisibility,
+  createProject,
+  deletePortfolio,
+  deleteProject,
+  getComponents,
+} from '../project-management';
+import SettingsServiceMock from './SettingsServiceMock';
+
+jest.mock('../project-management');
+
+const defaultProject = [
+  mockProject({ key: 'project1', name: 'Project 1' }),
+  mockProject({ key: 'project2', name: 'Project 2', visibility: Visibility.Private }),
+  mockProject({ key: 'project3', name: 'Project 3', lastAnalysisDate: undefined }),
+  mockProject({ key: 'projectProvisioned', name: 'Project 4' }),
+  mockProject({ key: 'portfolio1', name: 'Portfolio 1', qualifier: ComponentQualifier.Portfolio }),
+  mockProject({
+    key: 'portfolio2',
+    name: 'Portfolio 2',
+    qualifier: ComponentQualifier.Portfolio,
+    visibility: Visibility.Private,
+  }),
+  mockProject({
+    key: 'portfolio3',
+    name: 'Portfolio 3',
+    qualifier: ComponentQualifier.Portfolio,
+    lastAnalysisDate: undefined,
+  }),
+  mockProject({
+    key: 'application1',
+    name: 'Application 1',
+    qualifier: ComponentQualifier.Application,
+  }),
+  mockProject({
+    key: 'application2',
+    name: 'Application 2',
+    qualifier: ComponentQualifier.Application,
+    visibility: Visibility.Private,
+  }),
+  mockProject({
+    key: 'application3',
+    name: 'Application 3',
+    qualifier: ComponentQualifier.Application,
+    lastAnalysisDate: undefined,
+  }),
+];
+
+export default class ProjectManagementServiceMock {
+  #projects: Project[];
+  #settingsService: SettingsServiceMock;
+
+  constructor(settingsService: SettingsServiceMock) {
+    this.#projects = cloneDeep(defaultProject);
+    this.#settingsService = settingsService;
+    jest.mocked(getComponents).mockImplementation(this.handleGetComponents);
+    jest.mocked(createProject).mockImplementation(this.handleCreateProject);
+    jest.mocked(bulkDeleteProjects).mockImplementation(this.handleBulkDeleteProjects);
+    jest.mocked(deleteProject).mockImplementation(this.handleDeleteProject);
+    jest.mocked(deletePortfolio).mockImplementation(this.handleDeletePortfolio);
+    jest
+      .mocked(changeProjectDefaultVisibility)
+      .mockImplementation(this.handleChangeProjectDefaultVisibility);
+  }
+
+  setProjects = (projects: Project[]) => {
+    this.#projects = cloneDeep(projects);
+  };
+
+  handleGetComponents: typeof getComponents = (params) => {
+    const pageIndex = params.p || 1;
+    const pageSize = params.ps || 50;
+    const components = this.#projects.filter((item) => {
+      if (params.qualifiers && item.qualifier !== params.qualifiers) {
+        return false;
+      }
+      if (params.visibility && item.visibility !== params.visibility) {
+        return false;
+      }
+      if (
+        params.analyzedBefore &&
+        (!item.lastAnalysisDate ||
+          new Date(item.lastAnalysisDate) > new Date(params.analyzedBefore))
+      ) {
+        return false;
+      }
+      if (params.onProvisionedOnly && !item.key.includes('Provisioned')) {
+        return false;
+      }
+      if (
+        params.q &&
+        !item.key.toLowerCase().includes(params.q.toLowerCase()) &&
+        !item.name.toLowerCase().includes(params.q.toLowerCase())
+      ) {
+        return false;
+      }
+      return true;
+    });
+
+    return this.reply({
+      components: components.slice((pageIndex - 1) * pageSize, pageSize * pageIndex),
+      paging: { pageIndex, pageSize, total: components.length },
+    });
+  };
+
+  handleCreateProject: typeof createProject = ({ project, name, visibility }) => {
+    this.#projects.unshift(
+      mockProject({
+        key: project,
+        name,
+        visibility,
+        lastAnalysisDate: undefined,
+      })
+    );
+    return this.reply({ project: this.#projects[0] });
+  };
+
+  handleBulkDeleteProjects: typeof bulkDeleteProjects = ({ projects }) => {
+    if (projects === undefined) {
+      return Promise.reject();
+    }
+
+    this.#projects = this.#projects.filter((item) => !projects.split(',').includes(item.key));
+    return this.reply();
+  };
+
+  handleDeleteProject: typeof deleteProject = (key) => {
+    const projectToDelete = this.#projects.find((item) => key === item.key);
+    if (projectToDelete?.qualifier !== ComponentQualifier.Project) {
+      return Promise.reject();
+    }
+    this.#projects = this.#projects.filter((p) => p.key !== key);
+    return this.reply();
+  };
+
+  handleDeletePortfolio: typeof deletePortfolio = (key) => {
+    const portfolioToDelete = this.#projects.find((item) => key === item.key);
+    if (portfolioToDelete?.qualifier !== ComponentQualifier.Portfolio) {
+      return Promise.reject();
+    }
+    this.#projects = this.#projects.filter((p) => p.key !== key);
+    return this.reply();
+  };
+
+  handleChangeProjectDefaultVisibility: typeof changeProjectDefaultVisibility = (visibility) => {
+    this.#settingsService.set(SettingsKey.DefaultProjectVisibility, visibility);
+    return this.reply();
+  };
+
+  reset = () => {
+    this.#projects = cloneDeep(defaultProject);
+  };
+
+  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 d5ca5119c96df080facebd699700f3b19e9a04cc..aefca24cfd01c9190186da06b50760f014d4e020 100644 (file)
@@ -134,7 +134,7 @@ export default class SettingsServiceMock {
 
   handleGetValue = (data: { key: string; component?: string } & BranchParameters) => {
     const setting = this.#settingValues.find((s) => s.key === data.key) as SettingValue;
-    return this.reply(setting);
+    return this.reply(setting ?? {});
   };
 
   handleGetValues = (data: { keys: string[]; component?: string } & BranchParameters) => {
index dfde7efcc220437d411cc9181564d9db9023d3de..733fabbb9fc3b741e8c69f58eb2cd311a708641a 100644 (file)
@@ -227,11 +227,3 @@ export function changeProjectVisibility(
 ): Promise<void | Response> {
   return post('/api/projects/update_visibility', { project, visibility }).catch(throwGlobalError);
 }
-
-export function changeProjectDefaultVisibility(
-  projectVisibility: Visibility
-): Promise<void | Response> {
-  return post('/api/projects/update_default_visibility', { projectVisibility }).catch(
-    throwGlobalError
-  );
-}
diff --git a/server/sonar-web/src/main/js/api/project-management.ts b/server/sonar-web/src/main/js/api/project-management.ts
new file mode 100644 (file)
index 0000000..c560ff1
--- /dev/null
@@ -0,0 +1,117 @@
+/*
+ * 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 { throwGlobalError } from '../helpers/error';
+import { getJSON, post, postJSON } from '../helpers/request';
+import { ComponentQualifier, Visibility } from '../types/component';
+import { Paging } from '../types/types';
+
+export interface BaseSearchProjectsParameters {
+  analyzedBefore?: string;
+  onProvisionedOnly?: boolean;
+  projects?: string;
+  q?: string;
+  qualifiers?: string;
+  visibility?: Visibility;
+}
+
+export interface ProjectBase {
+  key: string;
+  name: string;
+  qualifier:
+    | ComponentQualifier.Application
+    | ComponentQualifier.Portfolio
+    | ComponentQualifier.Project;
+  visibility: Visibility;
+}
+
+export interface Project extends ProjectBase {
+  lastAnalysisDate?: string;
+}
+
+export interface SearchProjectsParameters extends BaseSearchProjectsParameters {
+  p?: number;
+  ps?: number;
+}
+
+export interface ComponentRaw {
+  key: string;
+  name: string;
+  isFavorite?: boolean;
+  analysisDate?: string;
+  qualifier: ComponentQualifier;
+  tags: string[];
+  visibility: Visibility;
+  leakPeriodDate?: string;
+  needIssueSync?: boolean;
+}
+
+export function getComponents(parameters: SearchProjectsParameters): Promise<{
+  components: Project[];
+  paging: Paging;
+}> {
+  return getJSON('/api/projects/search', parameters);
+}
+
+export function bulkDeleteProjects(
+  parameters: BaseSearchProjectsParameters
+): Promise<void | Response> {
+  return post('/api/projects/bulk_delete', parameters).catch(throwGlobalError);
+}
+
+export function deleteProject(project: string): Promise<void | Response> {
+  return post('/api/projects/delete', { project }).catch(throwGlobalError);
+}
+
+export function deletePortfolio(portfolio: string): Promise<void | Response> {
+  return post('/api/views/delete', { key: portfolio }).catch(throwGlobalError);
+}
+
+export function createProject(data: {
+  name: string;
+  project: string;
+  mainBranch: string;
+  visibility?: Visibility;
+  newCodeDefinitionType?: string;
+  newCodeDefinitionValue?: string;
+}): Promise<{ project: ProjectBase }> {
+  return postJSON('/api/projects/create', data).catch(throwGlobalError);
+}
+
+export function setupManualProjectCreation(data: {
+  name: string;
+  project: string;
+  mainBranch: string;
+  visibility?: Visibility;
+}) {
+  return (newCodeDefinitionType?: string, newCodeDefinitionValue?: string) =>
+    createProject({
+      ...data,
+      newCodeDefinitionType,
+      newCodeDefinitionValue,
+    });
+}
+
+export function changeProjectDefaultVisibility(
+  projectVisibility: Visibility
+): Promise<void | Response> {
+  return post('/api/projects/update_default_visibility', { projectVisibility }).catch(
+    throwGlobalError
+  );
+}
index d2592e507567a0a8f14e566d7c5dd7995068c352..08dbe83c815e701189ac751011f08e01707ffedb 100644 (file)
@@ -29,10 +29,10 @@ it('should work with extensions', () => {
     qualifiers: ['TRK'],
   });
 
-  const currentUser = {
+  const currentUser = mockCurrentUser({
     isLoggedIn: false,
     dismissedNotices: {},
-  };
+  });
   renderGlobalNavMenu({ appState, currentUser });
   expect(screen.getByText('more')).toBeInTheDocument();
 });
@@ -43,10 +43,10 @@ it('should show administration menu if the user has the rights', () => {
     globalPages: [],
     qualifiers: ['TRK'],
   });
-  const currentUser = {
+  const currentUser = mockCurrentUser({
     isLoggedIn: false,
     dismissedNotices: {},
-  };
+  });
 
   renderGlobalNavMenu({ appState, currentUser });
   expect(screen.getByText('layout.settings')).toBeInTheDocument();
index 7fc9af55881fc0e9f526c292c15273482337a269..dfa41c26dd85ad4b5cb949c13564226f4dec0782 100644 (file)
@@ -32,11 +32,13 @@ import CreateProjectPage, { CreateProjectPageProps } from '../CreateProjectPage'
 
 jest.mock('../../../../api/alm-settings');
 jest.mock('../../../../api/newCodePeriod');
-jest.mock('../../../../api/components', () => ({
-  ...jest.requireActual('../../../../api/components'),
+jest.mock('../../../../api/project-management', () => ({
   setupManualProjectCreation: jest
     .fn()
     .mockReturnValue(() => Promise.resolve({ project: mockProject() })),
+}));
+jest.mock('../../../../api/components', () => ({
+  ...jest.requireActual('../../../../api/components'),
   doesComponentExists: jest
     .fn()
     .mockImplementation(({ component }) => Promise.resolve(component === 'exists')),
index 1bf27b9b4899c6469c294b0c156ebeabc5b5db16..479b6df5c16572b420327acd7bf21674480d1326 100644 (file)
@@ -29,8 +29,11 @@ const ui = {
   nextButton: byRole('button', { name: 'next' }),
 };
 
-jest.mock('../../../../api/components', () => ({
+jest.mock('../../../../api/project-management', () => ({
   setupManualProjectCreation: jest.fn(),
+}));
+
+jest.mock('../../../../api/components', () => ({
   doesComponentExists: jest
     .fn()
     .mockImplementation(({ component }) => Promise.resolve(component === 'exists')),
index ef344ed928d9d1180e455f628fa72b0552b98f87..c6b0bfc89dfe897666119d6c6471bb8643f7c7b3 100644 (file)
@@ -21,7 +21,8 @@ import classNames from 'classnames';
 import { debounce, isEmpty } from 'lodash';
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import { doesComponentExists, setupManualProjectCreation } from '../../../../api/components';
+import { doesComponentExists } from '../../../../api/components';
+import { setupManualProjectCreation } from '../../../../api/project-management';
 import { getValue } from '../../../../api/settings';
 import DocLink from '../../../../components/common/DocLink';
 import ProjectKeyInput from '../../../../components/common/ProjectKeyInput';
index 412d3bd93ff0320cf78264f406d94a59d1b1cd4e..e3710f2ed8350a26ef15b06f1f2f448a6e83fa21 100644 (file)
@@ -286,7 +286,7 @@ describe('filtering', () => {
 
     expect(screen.getAllByRole('row').length).toBe(12);
     await ui.toggleFilterByPermission(Permissions.Admin);
-    expect(screen.getAllByRole('row').length).toBe(2);
+    expect(screen.getAllByRole('row').length).toBe(3);
     await ui.toggleFilterByPermission(Permissions.Admin);
     expect(screen.getAllByRole('row').length).toBe(12);
   });
index e05b7037600234022dc99ef4c047a1c1bead14da..82f709663db2be910f383ccc790b1c63a9956863 100644 (file)
@@ -1,6 +1,6 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`rendering should render the list of templates: Permission Template 1: admin 1`] = `"0  user(s)1 group(s)"`;
+exports[`rendering should render the list of templates: Permission Template 1: admin 1`] = `"1  user(s)1 group(s)"`;
 
 exports[`rendering should render the list of templates: Permission Template 1: codeviewer 1`] = `"0  user(s)0 group(s)"`;
 
index 568fa687c7b3aa2413e39d6739b1d58b39ac23b7..d76dad4e0ea28b41bfec5a1ef60fdbd4104275c7 100644 (file)
@@ -105,7 +105,7 @@ describe('filtering', () => {
 
     expect(screen.getAllByRole('row').length).toBe(11);
     await ui.toggleFilterByPermission(Permissions.Admin);
-    expect(screen.getAllByRole('row').length).toBe(2);
+    expect(screen.getAllByRole('row').length).toBe(3);
     await ui.toggleFilterByPermission(Permissions.Admin);
     expect(screen.getAllByRole('row').length).toBe(11);
   });
index 777510ac1e8d7e0c75a848d98ae18624fea7e009..b9c387f966494cf86b3ca099c637759d31cce7d9 100644 (file)
@@ -19,9 +19,9 @@
  */
 import * as React from 'react';
 import { deleteApplication } from '../../api/application';
-import { deletePortfolio, deleteProject } from '../../api/components';
-import { Button } from '../../components/controls/buttons';
+import { deletePortfolio, deleteProject } from '../../api/project-management';
 import ConfirmButton from '../../components/controls/ConfirmButton';
+import { Button } from '../../components/controls/buttons';
 import { Router, withRouter } from '../../components/hoc/withRouter';
 import { addGlobalSuccessMessage } from '../../helpers/globalMessages';
 import { translate, translateWithParameters } from '../../helpers/l10n';
index 9e83d7e2c9fcbdaf7d5a7ad6017ee90003783a32..393786ce3f0a754a092fc90ba27f78b53f2c593f 100644 (file)
 import { shallow } from 'enzyme';
 import * as React from 'react';
 import { deleteApplication } from '../../../api/application';
-import { deletePortfolio, deleteProject } from '../../../api/components';
+import { deletePortfolio, deleteProject } from '../../../api/project-management';
 import { mockRouter } from '../../../helpers/testMocks';
 import { Form } from '../Form';
 
-jest.mock('../../../api/components', () => ({
+jest.mock('../../../api/project-management', () => ({
   deleteProject: jest.fn().mockResolvedValue(undefined),
   deletePortfolio: jest.fn().mockResolvedValue(undefined),
 }));
index 65da87ca632ea2a57ad71af482ea03b4d2085bcd..242ef8f106fb3c9b877e8c0b8b90d82c280a1b5c 100644 (file)
@@ -18,9 +18,9 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { Button, ResetButtonLink } from '../../components/controls/buttons';
 import Modal from '../../components/controls/Modal';
 import Radio from '../../components/controls/Radio';
+import { Button, ResetButtonLink } from '../../components/controls/buttons';
 import { Alert } from '../../components/ui/Alert';
 import { translate } from '../../helpers/l10n';
 import { Visibility } from '../../types/component';
@@ -51,10 +51,12 @@ export default class ChangeDefaultVisibilityForm extends React.PureComponent<Pro
   };
 
   render() {
+    const header = translate('settings.projects.change_visibility_form.header');
+
     return (
-      <Modal contentLabel="modal form" onRequestClose={this.props.onClose}>
+      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
         <header className="modal-head">
-          <h2>{translate('settings.projects.change_visibility_form.header')}</h2>
+          <h2>{header}</h2>
         </header>
 
         <div className="modal-body">
index f8d669f835a028d9b804c0a773ace3b7c4866e63..c7809034a3d1960b3288638aca427cdd0ca4ea16 100644 (file)
  */
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import { createProject } from '../../api/components';
+import { createProject } from '../../api/project-management';
 import { getValue } from '../../api/settings';
 import Link from '../../components/common/Link';
 import VisibilitySelector from '../../components/common/VisibilitySelector';
-import { ResetButtonLink, SubmitButton } from '../../components/controls/buttons';
 import Modal from '../../components/controls/Modal';
+import { ResetButtonLink, SubmitButton } from '../../components/controls/buttons';
 import { Alert } from '../../components/ui/Alert';
 import MandatoryFieldMarker from '../../components/ui/MandatoryFieldMarker';
 import MandatoryFieldsExplanation from '../../components/ui/MandatoryFieldsExplanation';
@@ -130,13 +130,14 @@ export default class CreateProjectForm extends React.PureComponent<Props, State>
   render() {
     const { defaultProjectVisibility } = this.props;
     const { createdProject } = this.state;
+    const header = translate('qualifiers.create.TRK');
 
     return (
-      <Modal contentLabel="modal form" onRequestClose={this.props.onClose}>
+      <Modal contentLabel={header} onRequestClose={this.props.onClose}>
         {createdProject ? (
           <div>
             <header className="modal-head">
-              <h2>{translate('qualifiers.create.TRK')}</h2>
+              <h2>{header}</h2>
             </header>
 
             <div className="modal-body">
@@ -168,7 +169,7 @@ export default class CreateProjectForm extends React.PureComponent<Props, State>
         ) : (
           <form id="create-project-form" onSubmit={this.handleFormSubmit}>
             <header className="modal-head">
-              <h2>{translate('qualifiers.create.TRK')}</h2>
+              <h2>{header}</h2>
             </header>
 
             <div className="modal-body">
index 343747b4906b19dc9c04f257802b90fe79e2fe7a..d272fa225052db899a9d2ee22f823ca3f634ddc5 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { bulkDeleteProjects } from '../../api/components';
+import { bulkDeleteProjects } from '../../api/project-management';
 import Modal from '../../components/controls/Modal';
 import { ResetButtonLink, SubmitButton } from '../../components/controls/buttons';
 import { Alert } from '../../components/ui/Alert';
index 730d75391341416fff8e513e60f6118862d1705b..780a6a167750d29572945204cee3c5958f2a0499 100644 (file)
 import { debounce, uniq, without } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
-import { Project, getComponents } from '../../api/components';
-import { changeProjectDefaultVisibility } from '../../api/permissions';
+import {
+  Project,
+  changeProjectDefaultVisibility,
+  getComponents,
+} from '../../api/project-management';
 import { getValue } from '../../api/settings';
 import withCurrentUserContext from '../../app/components/current-user/withCurrentUserContext';
 import ListFooter from '../../components/controls/ListFooter';
@@ -61,7 +64,7 @@ interface State {
 const DEBOUNCE_DELAY = 250;
 const PAGE_SIZE = 50;
 
-export class ProjectManagementApp extends React.PureComponent<Props, State> {
+class ProjectManagementApp extends React.PureComponent<Props, State> {
   mounted = false;
 
   constructor(props: Props) {
index 8125bf3fbf9d96b12acb351508e6621360b6eb6a..026f6cac3cbfdf6cd056a183f900e5b17fc2697b 100644 (file)
@@ -18,7 +18,7 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { Project } from '../../api/components';
+import { Project } from '../../api/project-management';
 import Link from '../../components/common/Link';
 import PrivacyBadgeContainer from '../../components/common/PrivacyBadgeContainer';
 import Checkbox from '../../components/controls/Checkbox';
index 7fff9e79f9ddb9171e0878de9856e9dd4e0af19b..b41ac29b0b369aafb455a09e71fa02ce8fa8c6e3 100644 (file)
@@ -18,8 +18,8 @@
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
-import { Project } from '../../api/components';
 import { getComponentNavigation } from '../../api/navigation';
+import { Project } from '../../api/project-management';
 import ActionsDropdown, { ActionsDropdownItem } from '../../components/controls/ActionsDropdown';
 import DeferredSpinner from '../../components/ui/DeferredSpinner';
 import { translate, translateWithParameters } from '../../helpers/l10n';
index 9c9f8d2f634450e82e9a0b3aebbe5614a76aa9da..447e1a64d3d77a1799c48dc22e3bfa44848be4d5 100644 (file)
@@ -19,7 +19,7 @@
  */
 import classNames from 'classnames';
 import * as React from 'react';
-import { Project } from '../../api/components';
+import { Project } from '../../api/project-management';
 import { translate } from '../../helpers/l10n';
 import { LoggedInUser } from '../../types/users';
 import ProjectRow from './ProjectRow';
index da6e250ea576fa706125434db78e8bd6a4117b9f..ee64532c43e119ab740db21c23f7d8e2115bf7a1 100644 (file)
  */
 import * as React from 'react';
 import { FormattedMessage } from 'react-intl';
-import { Project } from '../../api/components';
 import { grantPermissionToUser } from '../../api/permissions';
-import { ResetButtonLink, SubmitButton } from '../../components/controls/buttons';
+import { Project } from '../../api/project-management';
 import Modal from '../../components/controls/Modal';
+import { ResetButtonLink, SubmitButton } from '../../components/controls/buttons';
 import { translate } from '../../helpers/l10n';
 import { LoggedInUser } from '../../types/users';
 
index 0e5e903e1a6e7e9fdf5243a9638198a626d9a7a0..30fd95ab1b5bf51262d5ae3563342483655b86b7 100644 (file)
@@ -20,7 +20,7 @@
 import { sortBy } from 'lodash';
 import * as React from 'react';
 import { components, OptionProps, SingleValueProps } from 'react-select';
-import { Project } from '../../api/components';
+import { Project } from '../../api/project-management';
 import withAppStateContext from '../../app/components/app-state/withAppStateContext';
 import { Button } from '../../components/controls/buttons';
 import Checkbox from '../../components/controls/Checkbox';
@@ -63,7 +63,7 @@ interface State {
 
 const QUALIFIERS_ORDER = ['TRK', 'VW', 'APP'];
 
-export class Search extends React.PureComponent<Props, State> {
+class Search extends React.PureComponent<Props, State> {
   state: State = { bulkApplyTemplateModal: false, deleteModal: false };
 
   getQualifierOptions = () => {
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/BulkApplyTemplateModal-test.tsx
deleted file mode 100644 (file)
index 7be41f7..0000000
+++ /dev/null
@@ -1,122 +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 { parseDate } from '../../../helpers/dates';
-import { click, waitAndUpdate } from '../../../helpers/testUtils';
-import BulkApplyTemplateModal, { Props } from '../BulkApplyTemplateModal';
-
-jest.mock('../../../api/permissions', () => ({
-  bulkApplyTemplate: jest.fn(() => Promise.resolve()),
-  getPermissionTemplates: jest.fn(() => Promise.resolve({ permissionTemplates: [] })),
-}));
-
-const bulkApplyTemplate = require('../../../api/permissions').bulkApplyTemplate as jest.Mock<any>;
-const getPermissionTemplates = require('../../../api/permissions')
-  .getPermissionTemplates as jest.Mock<any>;
-
-beforeEach(() => {
-  bulkApplyTemplate.mockClear();
-  getPermissionTemplates.mockClear();
-});
-
-it('fetches permission templates on component mount', () => {
-  shallow(render());
-  expect(getPermissionTemplates).toHaveBeenCalled();
-});
-
-it('bulk applies template to all results', async () => {
-  const wrapper = shallow(render());
-  (wrapper.instance() as BulkApplyTemplateModal).mounted = true;
-  expect(wrapper).toMatchSnapshot();
-
-  wrapper.setState({
-    loading: false,
-    permissionTemplate: 'foo',
-    permissionTemplates: [
-      { id: 'foo', name: 'Foo' },
-      { id: 'bar', name: 'Bar' },
-    ],
-  });
-  expect(wrapper).toMatchSnapshot();
-
-  click(wrapper.find('SubmitButton'));
-  expect(bulkApplyTemplate).toHaveBeenCalledWith({
-    analyzedBefore: '2017-04-08T00:00:00+0000',
-    onProvisionedOnly: true,
-    q: 'bla',
-    qualifiers: 'TRK',
-    templateId: 'foo',
-  });
-  expect(wrapper).toMatchSnapshot();
-
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('bulk applies template to selected results', async () => {
-  const wrapper = shallow(render({ qualifier: 'VW', selection: ['proj1', 'proj2'] }));
-  (wrapper.instance() as BulkApplyTemplateModal).mounted = true;
-  expect(wrapper).toMatchSnapshot();
-
-  wrapper.setState({
-    loading: false,
-    permissionTemplate: 'foo',
-    permissionTemplates: [
-      { id: 'foo', name: 'Foo' },
-      { id: 'bar', name: 'Bar' },
-    ],
-  });
-  expect(wrapper).toMatchSnapshot();
-
-  click(wrapper.find('SubmitButton'));
-  expect(wrapper).toMatchSnapshot();
-  await new Promise(setImmediate);
-  expect(bulkApplyTemplate).toHaveBeenCalledWith({
-    projects: 'proj1,proj2',
-    qualifiers: 'VW',
-    templateId: 'foo',
-  });
-
-  wrapper.update();
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('closes', () => {
-  const onClose = jest.fn();
-  const wrapper = shallow(render({ onClose }));
-  click(wrapper.find('ResetButtonLink'));
-  expect(onClose).toHaveBeenCalled();
-});
-
-function render(props?: { [P in keyof Props]?: Props[P] }) {
-  return (
-    <BulkApplyTemplateModal
-      analyzedBefore={parseDate('2017-04-08T00:00:00.000Z')}
-      onClose={jest.fn()}
-      provisioned
-      qualifier="TRK"
-      query="bla"
-      selection={[]}
-      total={17}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeDefaultVisibilityForm-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ChangeDefaultVisibilityForm-test.tsx
deleted file mode 100644 (file)
index e6763d0..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 Radio from '../../../components/controls/Radio';
-import { click } from '../../../helpers/testUtils';
-import { Visibility } from '../../../types/component';
-import ChangeDefaultVisibilityForm from '../ChangeDefaultVisibilityForm';
-
-it('closes', () => {
-  const onClose = jest.fn();
-  const wrapper = shallowRender({ onClose });
-  click(wrapper.find('.js-modal-close'));
-  expect(onClose).toHaveBeenCalled();
-});
-
-it('changes visibility', () => {
-  const onConfirm = jest.fn();
-  const wrapper = shallowRender({ onConfirm });
-  expect(wrapper).toMatchSnapshot();
-
-  wrapper.find(Radio).first().props().onCheck(Visibility.Private);
-  expect(wrapper).toMatchSnapshot();
-
-  click(wrapper.find('.js-confirm'));
-  expect(onConfirm).toHaveBeenCalledWith(Visibility.Private);
-});
-
-function shallowRender(props: Partial<ChangeDefaultVisibilityForm['props']> = {}) {
-  return shallow(
-    <ChangeDefaultVisibilityForm
-      defaultVisibility={Visibility.Public}
-      onClose={jest.fn()}
-      onConfirm={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/CreateProjectForm-test.tsx
deleted file mode 100644 (file)
index 72643b0..0000000
+++ /dev/null
@@ -1,82 +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 { render, screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import * as React from 'react';
-import { createProject } from '../../../api/components';
-import CreateProjectForm from '../CreateProjectForm';
-
-jest.mock('../../../api/components', () => ({
-  createProject: jest.fn().mockResolvedValue({}),
-  doesComponentExists: jest
-    .fn()
-    .mockImplementation(({ component }) => Promise.resolve(component === 'exists')),
-}));
-
-jest.mock('../../../api/settings', () => ({
-  getValue: jest.fn().mockResolvedValue({ value: 'main' }),
-}));
-
-beforeEach(() => {
-  jest.clearAllMocks();
-});
-
-it('should render all inputs and create a project', async () => {
-  const user = userEvent.setup();
-  renderCreateProjectForm();
-
-  await user.type(
-    screen.getByRole('textbox', {
-      name: 'onboarding.create_project.display_name field_required',
-    }),
-    'ProjectName'
-  );
-
-  await user.type(
-    screen.getByRole('textbox', {
-      name: 'onboarding.create_project.project_key field_required',
-    }),
-    'ProjectKey'
-  );
-
-  expect(
-    screen.getByRole('textbox', {
-      name: 'onboarding.create_project.main_branch_name field_required',
-    })
-  ).toHaveValue('main');
-
-  await user.type(
-    screen.getByRole('textbox', {
-      name: 'onboarding.create_project.main_branch_name field_required',
-    }),
-    '{Control>}a{/Control}{Backspace}ProjectMainBranch'
-  );
-
-  await user.click(screen.getByRole('button', { name: 'create' }));
-  expect(createProject).toHaveBeenCalledWith({
-    name: 'ProjectName',
-    project: 'ProjectKey',
-    mainBranch: 'ProjectMainBranch',
-  });
-});
-
-function renderCreateProjectForm(props: Partial<CreateProjectForm['props']> = {}) {
-  render(<CreateProjectForm onClose={jest.fn()} onProjectCreated={jest.fn()} {...props} />);
-}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/DeleteModal-test.tsx
deleted file mode 100644 (file)
index 7e9accb..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.
- */
-/* eslint-disable import/first */
-jest.mock('../../../api/components', () => ({
-  bulkDeleteProjects: jest.fn(() => Promise.resolve()),
-}));
-
-import { shallow } from 'enzyme';
-import * as React from 'react';
-import { parseDate } from '../../../helpers/dates';
-import { click } from '../../../helpers/testUtils';
-import DeleteModal, { Props } from '../DeleteModal';
-
-const bulkDeleteProjects = require('../../../api/components').bulkDeleteProjects as jest.Mock<any>;
-
-beforeEach(() => {
-  bulkDeleteProjects.mockClear();
-});
-
-it('deletes all projects', async () => {
-  const onConfirm = jest.fn();
-  const wrapper = shallowRender({ onConfirm });
-  (wrapper.instance() as DeleteModal).mounted = true;
-  expect(wrapper).toMatchSnapshot();
-
-  click(wrapper.find('SubmitButton'));
-  expect(wrapper).toMatchSnapshot();
-  expect(bulkDeleteProjects).toHaveBeenCalledWith({
-    analyzedBefore: '2017-04-08T00:00:00+0000',
-    onProvisionedOnly: undefined,
-    q: 'bla',
-    qualifiers: 'TRK',
-  });
-
-  await new Promise(setImmediate);
-  expect(onConfirm).toHaveBeenCalled();
-});
-
-it('deletes selected projects', async () => {
-  const onConfirm = jest.fn();
-  const wrapper = shallowRender({ onConfirm, selection: ['proj1', 'proj2'] });
-  (wrapper.instance() as DeleteModal).mounted = true;
-  expect(wrapper).toMatchSnapshot();
-
-  click(wrapper.find('SubmitButton'));
-  expect(wrapper).toMatchSnapshot();
-  expect(bulkDeleteProjects).toHaveBeenCalledWith({ projects: 'proj1,proj2' });
-
-  await new Promise(setImmediate);
-  expect(onConfirm).toHaveBeenCalled();
-});
-
-it('closes', () => {
-  const onClose = jest.fn();
-  const wrapper = shallowRender({ onClose });
-  click(wrapper.find('ResetButtonLink'));
-  expect(onClose).toHaveBeenCalled();
-});
-
-function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
-  return shallow(
-    <DeleteModal
-      analyzedBefore={parseDate('2017-04-08T00:00:00.000Z')}
-      onClose={jest.fn()}
-      onConfirm={jest.fn()}
-      provisioned={false}
-      qualifier="TRK"
-      query="bla"
-      selection={[]}
-      total={17}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Header-test.tsx
deleted file mode 100644 (file)
index c3930a0..0000000
+++ /dev/null
@@ -1,67 +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 { Visibility } from '../../../types/component';
-import Header, { Props } from '../Header';
-
-jest.mock('../../../helpers/system', () => ({
-  getReactDomContainerSelector: jest.fn(() => '#content'),
-}));
-
-it('renders', () => {
-  expect(shallowRender()).toMatchSnapshot('undefined visibility');
-  expect(shallowRender({ defaultProjectVisibility: Visibility.Public })).toMatchSnapshot('default');
-});
-
-it('creates project', () => {
-  const onProjectCreate = jest.fn();
-  const wrapper = shallowRender({ onProjectCreate });
-  click(wrapper.find('#create-project'));
-  expect(onProjectCreate).toHaveBeenCalledWith();
-});
-
-it('changes default visibility', () => {
-  const onChangeDefaultProjectVisibility = jest.fn();
-  const wrapper = shallowRender({ onChangeDefaultProjectVisibility });
-
-  click(wrapper.find('.js-change-visibility'));
-
-  const modalWrapper = wrapper.find('ChangeDefaultVisibilityForm');
-  expect(modalWrapper).toMatchSnapshot();
-  modalWrapper.prop<Function>('onConfirm')(Visibility.Private);
-  expect(onChangeDefaultProjectVisibility).toHaveBeenCalledWith(Visibility.Private);
-
-  modalWrapper.prop<Function>('onClose')();
-  wrapper.update();
-  expect(wrapper.find('ChangeDefaultVisibilityForm').exists()).toBe(false);
-});
-
-function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
-  return shallow(
-    <Header
-      hasProvisionPermission
-      onChangeDefaultProjectVisibility={jest.fn()}
-      onProjectCreate={jest.fn()}
-      {...props}
-    />
-  );
-}
index 66aee4b45ace749cb5d6dbb0c31501b6a3e03a52..1a8fac665f0993dc799986dda1b793077830db68 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, within } from '@testing-library/react';
+import { screen, waitFor, within } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
-import { getComponents, SearchProjectsParameters } from '../../../api/components';
+import selectEvent from 'react-select-event';
 import PermissionsServiceMock from '../../../api/mocks/PermissionsServiceMock';
+import ProjectManagementServiceMock from '../../../api/mocks/ProjectsManagementServiceMock';
+import SettingsServiceMock from '../../../api/mocks/SettingsServiceMock';
+import { mockComponent } from '../../../helpers/mocks/component';
+import { mockProject } from '../../../helpers/mocks/projects';
+import { mockAppState, mockCurrentUser } from '../../../helpers/testMocks';
 import { renderAppWithAdminContext } from '../../../helpers/testReactTestingUtils';
-import { ComponentQualifier, Visibility } from '../../../types/component';
+import { byPlaceholderText, byRole, byText } from '../../../helpers/testSelector';
+import { AppState } from '../../../types/appstate';
+import { ComponentQualifier } from '../../../types/component';
+import { Permissions } from '../../../types/permissions';
+import { GlobalSettingKeys } from '../../../types/settings';
+import { LoggedInUser } from '../../../types/users';
 import routes from '../routes';
 
-jest.mock('../../../api/components', () => ({
-  getComponents: jest.fn().mockResolvedValue({
-    paging: { total: 0 },
-    components: [],
-  }),
-}));
-
-jest.mock('../../../api/settings', () => ({
-  getValue: jest.fn().mockResolvedValue({ value: 'public' }),
-}));
-
-const components = mockComponents(11);
-
-let serviceMock: PermissionsServiceMock;
-beforeAll(() => {
-  serviceMock = new PermissionsServiceMock();
-});
-
-afterEach(() => {
-  serviceMock.reset();
-});
+let login: string;
 
-describe('Bulk Apply', () => {
-  (getComponents as jest.Mock).mockImplementation(getComponentsImplementation(10));
+const permissionsHandler = new PermissionsServiceMock();
+const settingsHandler = new SettingsServiceMock();
+const handler = new ProjectManagementServiceMock(settingsHandler);
 
-  it('should work as expected', async () => {
-    const user = userEvent.setup();
-
-    renderGlobalBackgroundTasksApp();
-
-    const bulkApplyButton = await screen.findByRole('button', {
-      name: 'permission_templates.bulk_apply_permission_template',
+jest.mock('../../../api/navigation', () => ({
+  getComponentNavigation: jest.fn().mockImplementation(async ({ component }) => {
+    const canBrowseProjectResponse = await permissionsHandler.handleGetPermissionUsersForComponent({
+      projectKey: component,
+      q: login,
+      permission: Permissions.Browse,
+    });
+    const showPermissionsResponse = await permissionsHandler.handleGetPermissionUsersForComponent({
+      projectKey: component,
+      q: login,
+      permission: Permissions.Admin,
     });
 
-    expect(bulkApplyButton).toBeDisabled();
-
-    const projects = getProjects();
+    return Promise.resolve(
+      mockComponent({
+        configuration: {
+          canBrowseProject: canBrowseProjectResponse.users.length > 0,
+          showPermissions: showPermissionsResponse.users.length > 0,
+        },
+      })
+    );
+  }),
+}));
 
-    expect(projects).toHaveLength(10);
+const ui = {
+  row: byRole('row'),
+  firstProjectActions: byRole('button', {
+    name: 'projects_management.show_actions_for_x.Project 1',
+  }),
+  editPermissions: byRole('link', { name: 'edit_permissions' }),
+  applyPermissionTemplate: byRole('button', { name: 'projects_role.apply_template' }),
+  restoreAccess: byRole('button', { name: 'global_permissions.restore_access' }),
+  editPermissionsPage: byText('/project_roles?id=project1'),
+
+  apply: byRole('button', { name: 'apply' }),
+  cancel: byRole('button', { name: 'cancel' }),
+  delete: byRole('button', { name: 'delete' }),
+  create: byRole('button', { name: 'create' }),
+  close: byRole('button', { name: 'close' }),
+  restore: byRole('button', { name: 'restore' }),
+  checkbox: byRole('checkbox'),
+  deleteProjects: byRole('button', {
+    name: /permission_templates.(select_to_delete|delete_selected)/,
+  }),
+  showMore: byRole('button', { name: 'show_more' }),
+  checkAll: byRole('checkbox', { name: 'check_all' }),
+  uncheckAll: byRole('checkbox', { name: 'uncheck_all' }),
+  bulkApplyButton: byRole('button', {
+    name: 'permission_templates.bulk_apply_permission_template',
+  }),
+  createProject: byRole('button', {
+    name: 'qualifiers.create.TRK',
+  }),
 
-    await user.click(screen.getByRole('button', { name: 'show_more' }));
+  visibilityFilter: byRole('combobox', { name: 'projects_management.filter_by_visibility' }),
+  qualifierFilter: byRole('combobox', { name: 'projects_management.filter_by_component' }),
+  analysisDateFilter: byPlaceholderText('last_analysis_before'),
+  provisionedFilter: byRole('checkbox', {
+    name: 'provisioning.only_provisioned provisioning.only_provisioned.tooltip',
+  }),
+  searchFilter: byRole('searchbox', { name: 'search.search_by_name_or_key' }),
 
-    expect(getProjects()).toHaveLength(11);
+  defaultVisibility: byText('settings.projects.default_visibility_of_new_projects'),
 
-    await user.click(screen.getByRole('checkbox', { name: 'check_all' }));
+  createDialog: byRole('dialog', { name: 'qualifiers.create.TRK' }),
+  displayNameInput: byRole('textbox', {
+    name: 'onboarding.create_project.display_name field_required',
+  }),
+  projectKeyInput: byRole('textbox', {
+    name: 'onboarding.create_project.project_key field_required',
+  }),
+  mainBranchNameInput: byRole('textbox', {
+    name: 'onboarding.create_project.main_branch_name field_required',
+  }),
+  privateVisibility: byRole('radio', { name: 'visibility.private' }),
+  successMsg: byText('projects_management.project_has_been_successfully_created'),
 
-    expect(bulkApplyButton).toBeEnabled();
+  bulkApplyDialog: byRole('dialog', {
+    name: 'permission_templates.bulk_apply_permission_template',
+  }),
+  applyTemplateDialog: byRole('dialog', {
+    name: 'projects_role.apply_template_to_x.Project 1',
+  }),
+  selectTemplate: byRole('combobox', { name: 'template field_required' }),
 
-    await user.click(bulkApplyButton);
+  deleteDialog: byRole('dialog', { name: 'qualifiers.delete.TRK' }),
 
-    let modal = await screen.findByRole('dialog');
+  changeDefaultVisibilityDialog: byRole('dialog', {
+    name: 'settings.projects.change_visibility_form.header',
+  }),
+  editDefaultVisibility: byRole('button', {
+    name: 'settings.projects.change_visibility_form.label',
+  }),
+  visibilityPublicRadio: byRole('radio', {
+    name: 'visibility.public visibility.public.description.short',
+  }),
+  submitDefaultVisibilityChange: byRole('button', {
+    name: 'settings.projects.change_visibility_form.submit',
+  }),
 
-    expect(modal).toBeInTheDocument();
-    expect(
-      within(modal).getByText(
-        'permission_templates.bulk_apply_permission_template.apply_to_selected.11'
-      )
-    ).toBeInTheDocument();
+  restoreAccessDialog: byRole('dialog', {
+    name: 'global_permissions.restore_access',
+  }),
+};
 
-    await user.click(within(modal).getByRole('button', { name: 'apply' }));
+beforeAll(() => {
+  jest.useFakeTimers({
+    advanceTimers: true,
+    now: new Date('2019-01-05T07:08:59Z'),
+  });
+});
 
-    expect(
-      await screen.findByText('bulk apply permission template error message')
-    ).toBeInTheDocument();
-    expect(await screen.findByRole('dialog')).toBeInTheDocument();
+afterEach(() => {
+  permissionsHandler.reset();
+  settingsHandler.reset();
+  handler.reset();
+});
 
-    await user.click(within(modal).getByRole('button', { name: 'cancel' }));
+it('should filter projects', async () => {
+  const user = userEvent.setup();
+  renderProjectManagementApp();
+  await waitFor(() => expect(ui.row.getAll()).toHaveLength(5));
+  await selectEvent.select(ui.visibilityFilter.get(), 'visibility.public');
+  expect(ui.row.getAll()).toHaveLength(4);
+  await user.click(ui.analysisDateFilter.get());
+  await user.click(await screen.findByRole('gridcell', { name: '5' }));
+  expect(ui.row.getAll()).toHaveLength(3);
+  await user.click(ui.provisionedFilter.get());
+  expect(ui.row.getAll()).toHaveLength(2);
+  expect(ui.row.getAll()[1]).toHaveTextContent('Project 4');
+  await selectEvent.select(ui.qualifierFilter.get(), 'qualifiers.VW');
+  expect(ui.provisionedFilter.query()).not.toBeInTheDocument();
+  expect(ui.row.getAll()).toHaveLength(2);
+  expect(ui.row.getAll()[1]).toHaveTextContent('Portfolio 1');
+  await selectEvent.select(ui.qualifierFilter.get(), 'qualifiers.APP');
+  expect(ui.provisionedFilter.query()).not.toBeInTheDocument();
+  expect(ui.row.getAll()).toHaveLength(2);
+  expect(ui.row.getAll()[1]).toHaveTextContent('Application 1');
+});
 
-    const checkboxes = screen.getAllByRole('checkbox');
-    await user.click(checkboxes[8]);
-    await user.click(checkboxes[9]);
+it('should search by text', async () => {
+  const user = userEvent.setup();
+  renderProjectManagementApp();
+  await waitFor(() => expect(ui.row.getAll()).toHaveLength(5));
+  await user.type(ui.searchFilter.get(), 'provision');
+  expect(ui.row.getAll()).toHaveLength(2);
+  await selectEvent.select(ui.qualifierFilter.get(), 'qualifiers.VW');
+  expect(ui.row.getAll()).toHaveLength(4);
+  expect(ui.searchFilter.get()).toHaveValue('');
+  await user.type(ui.searchFilter.get(), 'Portfolio 2');
+  expect(ui.row.getAll()).toHaveLength(2);
+  await selectEvent.select(ui.qualifierFilter.get(), 'qualifiers.APP');
+  expect(ui.row.getAll()).toHaveLength(4);
+  expect(ui.searchFilter.get()).toHaveValue('');
+  await user.type(ui.searchFilter.get(), 'Application 3');
+  expect(ui.row.getAll()).toHaveLength(2);
+});
 
-    await user.click(bulkApplyButton);
+it('should hide quilifier filter', () => {
+  renderProjectManagementApp({ qualifiers: [ComponentQualifier.Project] });
+  expect(ui.qualifierFilter.query()).not.toBeInTheDocument();
+});
 
-    modal = await screen.findByRole('dialog');
+it('should hide create Project button', () => {
+  renderProjectManagementApp();
+  expect(ui.createProject.query()).not.toBeInTheDocument();
+});
 
-    expect(modal).toBeInTheDocument();
-    expect(
-      within(modal).getByText(
-        'permission_templates.bulk_apply_permission_template.apply_to_selected.9'
-      )
-    ).toBeInTheDocument();
+it('should delete projects, but not Portfolios or Applications', async () => {
+  const user = userEvent.setup();
+  renderProjectManagementApp();
+  expect(await ui.deleteProjects.find()).toBeDisabled();
+  expect(ui.row.getAll()).toHaveLength(5);
+  await user.click(ui.checkbox.get(ui.row.getAll()[1]));
+  await user.click(ui.checkbox.get(ui.row.getAll()[2]));
+  expect(ui.deleteProjects.get()).toBeEnabled();
+  await user.click(ui.deleteProjects.get());
+  expect(ui.deleteDialog.get()).toBeInTheDocument();
+  expect(
+    within(ui.deleteDialog.get()).getByText('projects_management.delete_selected_warning.2')
+  ).toBeInTheDocument();
+  await user.click(ui.delete.get(ui.deleteDialog.get()));
+  expect(ui.row.getAll()).toHaveLength(3);
+});
 
-    await user.click(within(modal).getByRole('button', { name: 'apply' }));
+it('should bulk apply permission templates to projects', async () => {
+  const user = userEvent.setup();
+  handler.setProjects(
+    Array.from({ length: 11 }, (_, i) => mockProject({ key: i.toString(), name: `Test ${i}` }))
+  );
+  renderProjectManagementApp();
+
+  expect(await ui.bulkApplyButton.find()).toBeDisabled();
+  const projects = ui.row.getAll().slice(1);
+  expect(projects).toHaveLength(11);
+  await user.click(ui.checkAll.get());
+  expect(ui.bulkApplyButton.get()).toBeEnabled();
+
+  await user.click(ui.bulkApplyButton.get());
+  expect(await ui.bulkApplyDialog.find()).toBeInTheDocument();
+  expect(
+    within(ui.bulkApplyDialog.get()).getByText(
+      'permission_templates.bulk_apply_permission_template.apply_to_selected.11'
+    )
+  ).toBeInTheDocument();
+
+  await user.click(ui.apply.get(ui.bulkApplyDialog.get()));
+  expect(
+    await screen.findByText('bulk apply permission template error message')
+  ).toBeInTheDocument();
+  expect(ui.bulkApplyDialog.get()).toBeInTheDocument();
+
+  await user.click(ui.cancel.get(ui.bulkApplyDialog.get()));
+
+  await user.click(ui.uncheckAll.get());
+  await user.click(ui.checkbox.get(projects[8]));
+  await user.click(ui.checkbox.get(projects[9]));
+  await user.click(ui.checkbox.get(projects[10]));
+  await user.click(ui.checkbox.get(projects[9])); // uncheck one
+  await user.click(ui.bulkApplyButton.get());
+
+  expect(await ui.bulkApplyDialog.find()).toBeInTheDocument();
+  expect(
+    within(ui.bulkApplyDialog.get()).getByText(
+      'permission_templates.bulk_apply_permission_template.apply_to_selected.2'
+    )
+  ).toBeInTheDocument();
+  await selectEvent.select(
+    ui.selectTemplate.get(ui.bulkApplyDialog.get()),
+    'Permission Template 2'
+  );
+  await user.click(ui.apply.get(ui.bulkApplyDialog.get()));
+
+  expect(
+    await within(ui.bulkApplyDialog.get()).findByText('projects_role.apply_template.success')
+  ).toBeInTheDocument();
+});
 
-    modal = await screen.findByRole('dialog');
-    expect(
-      await within(modal).findByText('projects_role.apply_template.success')
-    ).toBeInTheDocument();
-  });
+it('should load more and change the filter without caching old pages', async () => {
+  const user = userEvent.setup();
+  handler.setProjects([
+    ...Array.from({ length: 60 }, (_, i) =>
+      mockProject({
+        key: ComponentQualifier.Project + i.toString(),
+        name: `Project ${i}`,
+        qualifier: ComponentQualifier.Project,
+      })
+    ),
+    ...Array.from({ length: 60 }, (_, i) =>
+      mockProject({
+        key: ComponentQualifier.Portfolio + i.toString(),
+        name: `Portfolio ${i}`,
+        qualifier: ComponentQualifier.Portfolio,
+      })
+    ),
+    ...Array.from({ length: 60 }, (_, i) =>
+      mockProject({
+        key: ComponentQualifier.Application + i.toString(),
+        name: `Application ${i}`,
+        qualifier: ComponentQualifier.Application,
+      })
+    ),
+  ]);
+  renderProjectManagementApp();
+  await waitFor(() => expect(ui.row.getAll()).toHaveLength(51));
+  await user.click(ui.showMore.get());
+  let rows = ui.row.getAll();
+  expect(rows).toHaveLength(61);
+  expect(rows[1]).toHaveTextContent('Project 0');
+  expect(rows[60]).toHaveTextContent('Project 59');
+  await selectEvent.select(ui.qualifierFilter.get(), 'qualifiers.VW');
+  rows = ui.row.getAll();
+  expect(rows).toHaveLength(51);
+  expect(rows[1]).toHaveTextContent('Portfolio 0');
+  await user.click(ui.showMore.get());
+  rows = ui.row.getAll();
+  expect(rows).toHaveLength(61);
+  expect(rows[1]).toHaveTextContent('Portfolio 0');
+  expect(rows[60]).toHaveTextContent('Portfolio 59');
 });
 
-function getProjects() {
-  // Remove the first row (header)
-  return screen.getAllByRole('row').slice(1);
-}
+it('should create project', async () => {
+  settingsHandler.set(GlobalSettingKeys.MainBranchName, 'main');
+  const user = userEvent.setup();
+  renderProjectManagementApp({}, { permissions: { global: [Permissions.ProjectCreation] } });
+  await waitFor(() => expect(ui.row.getAll()).toHaveLength(5));
+  await user.click(await ui.createProject.find());
+  let dialog = ui.createDialog.get();
+  expect(dialog).toBeInTheDocument();
+  expect(ui.privateVisibility.get(dialog)).not.toBeChecked();
+  await user.click(ui.privateVisibility.get(dialog));
+  expect(ui.privateVisibility.get(dialog)).not.toBeChecked();
+  await user.click(ui.cancel.get(dialog));
+
+  expect(await ui.defaultVisibility.find()).toBeInTheDocument();
+  expect(ui.defaultVisibility.get()).toHaveTextContent('—');
+  await user.click(ui.editDefaultVisibility.get());
+  expect(await ui.changeDefaultVisibilityDialog.find()).toBeInTheDocument();
+  await user.click(ui.visibilityPublicRadio.get(ui.changeDefaultVisibilityDialog.get()));
+  await user.click(ui.submitDefaultVisibilityChange.get(ui.changeDefaultVisibilityDialog.get()));
+  expect(ui.changeDefaultVisibilityDialog.query()).not.toBeInTheDocument();
+  expect(ui.defaultVisibility.get()).toHaveTextContent('visibility.public');
+
+  await user.click(await ui.createProject.find());
+  dialog = ui.createDialog.get();
+  expect(dialog).toBeInTheDocument();
+  await user.click(ui.privateVisibility.get(dialog));
+  expect(ui.privateVisibility.get(dialog)).toBeChecked();
+  await user.type(ui.displayNameInput.get(dialog), 'a Test');
+  await user.type(ui.projectKeyInput.get(dialog), 'test');
+  expect(ui.mainBranchNameInput.get(dialog)).toHaveValue('main');
+  await user.click(ui.create.get(dialog));
+  expect(ui.successMsg.get(dialog)).toBeInTheDocument();
+  await user.click(ui.close.get(dialog));
+  expect(ui.row.getAll()).toHaveLength(6);
+  expect(ui.row.getAll()[1]).toHaveTextContent('qualifier.TRKa Testvisibility.privatetest—');
+});
 
-function getComponentsImplementation(overridePageSize?: number) {
-  return (params: SearchProjectsParameters) => {
-    const pageSize = overridePageSize ?? params.ps ?? 50;
-    const startIndex = ((params.p ?? 1) - 1) * pageSize; // artifically bump the page size to 500
-    return Promise.resolve({
-      paging: {
-        total: 1001,
-      },
-      components: components.slice(startIndex, startIndex + pageSize),
-    });
-  };
-}
+it('should edit permissions of single project', async () => {
+  const user = userEvent.setup();
+  renderProjectManagementApp();
+  await user.click(await ui.firstProjectActions.find());
+  expect(ui.restoreAccess.query()).not.toBeInTheDocument();
+  expect(ui.editPermissions.get()).toBeInTheDocument();
+  await user.click(ui.editPermissions.get());
 
-function mockComponents(n: number) {
-  const results = [];
+  expect(await ui.editPermissionsPage.find()).toBeInTheDocument();
+});
 
-  for (let i = 0; i < n; i++) {
-    results.push({
-      key: `project-${i + 1}`,
-      name: `Project ${i + 1}`,
-      qualifier: ComponentQualifier.Project,
-      visibility: Visibility.Private,
-    });
-  }
+it('should apply template for single object', async () => {
+  const user = userEvent.setup();
+  renderProjectManagementApp();
+  await user.click(await ui.firstProjectActions.find());
+  await user.click(ui.applyPermissionTemplate.get());
+
+  expect(ui.applyTemplateDialog.get()).toBeInTheDocument();
+  await selectEvent.select(
+    ui.selectTemplate.get(ui.applyTemplateDialog.get()),
+    'Permission Template 2'
+  );
+  await user.click(ui.apply.get(ui.applyTemplateDialog.get()));
+
+  expect(
+    await within(ui.applyTemplateDialog.get()).findByText('projects_role.apply_template.success')
+  ).toBeInTheDocument();
+});
 
-  return results;
-}
+it('should restore access to admin', async () => {
+  const user = userEvent.setup();
+  renderProjectManagementApp({}, { login: 'gooduser2' });
+  await user.click(await ui.firstProjectActions.find());
+  expect(await ui.restoreAccess.find()).toBeInTheDocument();
+  expect(ui.editPermissions.query()).not.toBeInTheDocument();
+  await user.click(ui.restoreAccess.get());
+  expect(ui.restoreAccessDialog.get()).toBeInTheDocument();
+  await user.click(ui.restore.get(ui.restoreAccessDialog.get()));
+  expect(ui.restoreAccessDialog.query()).not.toBeInTheDocument();
+  await user.click(await ui.firstProjectActions.find());
+  expect(ui.restoreAccess.query()).not.toBeInTheDocument();
+  expect(ui.editPermissions.get()).toBeInTheDocument();
+});
 
-function renderGlobalBackgroundTasksApp() {
-  renderAppWithAdminContext('admin/projects_management', routes, {});
+function renderProjectManagementApp(
+  overrides: Partial<AppState> = {},
+  user: Partial<LoggedInUser> = {}
+) {
+  login = user?.login ?? 'gooduser1';
+  renderAppWithAdminContext('admin/projects_management', routes, {
+    appState: mockAppState({
+      qualifiers: [
+        ComponentQualifier.Project,
+        ComponentQualifier.Application,
+        ComponentQualifier.Portfolio,
+      ],
+      ...overrides,
+    }),
+    currentUser: mockCurrentUser(user),
+  });
 }
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-test.tsx
deleted file mode 100644 (file)
index ee38280..0000000
+++ /dev/null
@@ -1,185 +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 { getComponents } from '../../../api/components';
-import { changeProjectDefaultVisibility } from '../../../api/permissions';
-import { getValue } from '../../../api/settings';
-import { mockLoggedInUser } from '../../../helpers/testMocks';
-import { waitAndUpdate } from '../../../helpers/testUtils';
-import { ComponentQualifier, Visibility } from '../../../types/component';
-import { ProjectManagementApp, Props } from '../ProjectManagementApp';
-
-jest.mock('lodash', () => {
-  const lodash = jest.requireActual('lodash');
-  lodash.debounce =
-    (fn: Function) =>
-    (...args: any[]) =>
-      fn(args);
-  return lodash;
-});
-
-jest.mock('../../../api/components', () => ({
-  getComponents: jest.fn().mockResolvedValue({ paging: { total: 0 }, components: [] }),
-}));
-
-jest.mock('../../../api/permissions', () => ({
-  changeProjectDefaultVisibility: jest.fn().mockResolvedValue({}),
-}));
-
-jest.mock('../../../api/settings', () => ({
-  getValue: jest.fn().mockResolvedValue({ value: 'public' }),
-}));
-
-const defaultSearchParameters = {
-  p: undefined,
-  ps: 50,
-  q: undefined,
-};
-
-beforeEach(() => {
-  jest.clearAllMocks();
-});
-
-it('fetches all projects on mount', async () => {
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  expect(getComponents).toHaveBeenLastCalledWith({
-    ...defaultSearchParameters,
-    qualifiers: ComponentQualifier.Project,
-  });
-  expect(getValue).toHaveBeenCalled();
-  expect(wrapper.state().defaultProjectVisibility).toBe(Visibility.Public);
-});
-
-it('selects provisioned', () => {
-  const wrapper = shallowRender();
-  wrapper.find('withAppStateContext(Search)').prop<Function>('onProvisionedChanged')(true);
-  expect(getComponents).toHaveBeenLastCalledWith({
-    ...defaultSearchParameters,
-    onProvisionedOnly: true,
-    qualifiers: 'TRK',
-  });
-});
-
-it('changes qualifier and resets provisioned', () => {
-  const wrapper = shallowRender();
-  wrapper.setState({ provisioned: true });
-  wrapper.find('withAppStateContext(Search)').prop<Function>('onQualifierChanged')('VW');
-  expect(getComponents).toHaveBeenLastCalledWith({ ...defaultSearchParameters, qualifiers: 'VW' });
-});
-
-it('searches', () => {
-  const wrapper = shallowRender();
-  wrapper.find('withAppStateContext(Search)').prop<Function>('onSearch')('foo');
-  expect(getComponents).toHaveBeenLastCalledWith({
-    ...defaultSearchParameters,
-    q: 'foo',
-    qualifiers: 'TRK',
-  });
-});
-
-it('should handle date filtering', () => {
-  const wrapper = shallowRender();
-  wrapper.find('withAppStateContext(Search)').prop<Function>('onDateChanged')(
-    '2019-11-14T06:55:02.663Z'
-  );
-  expect(getComponents).toHaveBeenCalledWith({
-    ...defaultSearchParameters,
-    qualifiers: 'TRK',
-    analyzedBefore: '2019-11-14',
-  });
-});
-
-it('should handle default project visibility change', async () => {
-  const wrapper = shallowRender();
-
-  await waitAndUpdate(wrapper);
-
-  expect(wrapper.state().defaultProjectVisibility).toBe(Visibility.Public);
-  wrapper.instance().handleDefaultProjectVisibilityChange(Visibility.Private);
-
-  expect(changeProjectDefaultVisibility).toHaveBeenCalledWith(Visibility.Private);
-  await waitAndUpdate(wrapper);
-  expect(wrapper.state().defaultProjectVisibility).toBe(Visibility.Private);
-});
-
-it('loads more', () => {
-  const wrapper = shallowRender();
-  wrapper.find('ListFooter').prop<Function>('loadMore')();
-  expect(getComponents).toHaveBeenLastCalledWith({
-    ...defaultSearchParameters,
-    p: 2,
-    qualifiers: 'TRK',
-  });
-});
-
-it('selects and deselects projects', async () => {
-  (getComponents as jest.Mock).mockImplementation(() =>
-    Promise.resolve({ paging: { total: 2 }, components: [{ key: 'foo' }, { key: 'bar' }] })
-  );
-  const wrapper = shallowRender();
-  await new Promise(setImmediate);
-
-  wrapper.find('Projects').prop<Function>('onProjectSelected')('foo');
-  expect(wrapper.state('selection')).toEqual(['foo']);
-
-  wrapper.find('Projects').prop<Function>('onProjectSelected')('bar');
-  expect(wrapper.state('selection')).toEqual(['foo', 'bar']);
-
-  // should not select already selected project
-  wrapper.find('Projects').prop<Function>('onProjectSelected')('bar');
-  expect(wrapper.state('selection')).toEqual(['foo', 'bar']);
-
-  wrapper.find('Projects').prop<Function>('onProjectDeselected')('foo');
-  expect(wrapper.state('selection')).toEqual(['bar']);
-
-  wrapper.find('withAppStateContext(Search)').prop<Function>('onAllDeselected')();
-  expect(wrapper.state('selection')).toEqual([]);
-
-  wrapper.find('withAppStateContext(Search)').prop<Function>('onAllSelected')();
-  expect(wrapper.state('selection')).toEqual(['foo', 'bar']);
-});
-
-it('creates project', () => {
-  const wrapper = shallowRender();
-  expect(wrapper.find('CreateProjectForm').exists()).toBe(false);
-
-  wrapper.find('Header').prop<Function>('onProjectCreate')();
-  wrapper.update();
-  expect(wrapper.find('CreateProjectForm').exists()).toBe(true);
-
-  wrapper.find('CreateProjectForm').prop<Function>('onProjectCreated')();
-  wrapper.update();
-  expect((getComponents as jest.Mock).mock.calls).toHaveLength(2);
-
-  wrapper.find('CreateProjectForm').prop<Function>('onClose')();
-  wrapper.update();
-  expect(wrapper.find('CreateProjectForm').exists()).toBe(false);
-});
-
-function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
-  return shallow<ProjectManagementApp>(
-    <ProjectManagementApp
-      currentUser={mockLoggedInUser({ login: 'foo', permissions: { global: ['provisioning'] } })}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRow-test.tsx
deleted file mode 100644 (file)
index e1a9026..0000000
+++ /dev/null
@@ -1,60 +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 { ComponentQualifier, Visibility } from '../../../types/component';
-import ProjectRow from '../ProjectRow';
-
-const project = {
-  key: 'project',
-  name: 'Project',
-  qualifier: ComponentQualifier.Project,
-  visibility: Visibility.Private,
-};
-
-it('renders', () => {
-  expect(shallowRender()).toMatchSnapshot();
-  expect(
-    shallowRender({ project: { ...project, lastAnalysisDate: '2017-04-08T00:00:00.000Z' } })
-  ).toMatchSnapshot('with lastAnalysisDate');
-  expect(
-    shallowRender({ project: { ...project, qualifier: ComponentQualifier.Portfolio } })
-  ).toMatchSnapshot('portfolio');
-});
-
-it('checks project', () => {
-  const onProjectCheck = jest.fn();
-  const wrapper = shallowRender({ onProjectCheck });
-  wrapper.find('Checkbox').prop<Function>('onCheck')(false);
-  expect(onProjectCheck).toHaveBeenCalledWith(project, false);
-});
-
-function shallowRender(props?: any) {
-  return shallow(
-    <ProjectRow
-      currentUser={{ login: 'foo' }}
-      onApplyTemplate={jest.fn()}
-      onProjectCheck={jest.fn()}
-      project={project}
-      selected
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectRowActions-test.tsx
deleted file mode 100644 (file)
index 819bd7a..0000000
+++ /dev/null
@@ -1,133 +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 { getComponentNavigation } from '../../../api/navigation';
-import { mockLoggedInUser } from '../../../helpers/testMocks';
-import { click, waitAndUpdate } from '../../../helpers/testUtils';
-import { ComponentQualifier, Visibility } from '../../../types/component';
-import ProjectRowActions, { Props } from '../ProjectRowActions';
-
-jest.mock('../../../api/navigation', () => ({
-  getComponentNavigation: jest.fn().mockResolvedValue({}),
-}));
-
-beforeEach(() => {
-  jest.clearAllMocks();
-});
-
-it('renders correctly', () => {
-  const wrapper = shallowRender();
-  expect(wrapper).toMatchSnapshot();
-});
-
-describe('restore access', () => {
-  beforeAll(() => {
-    (getComponentNavigation as jest.Mock).mockResolvedValue({
-      configuration: {
-        canBrowseProject: false,
-        showPermissions: false,
-      },
-    });
-  });
-
-  it('shows the restore access action', async () => {
-    const wrapper = shallowRender();
-    wrapper.instance().handleDropdownOpen();
-    await waitAndUpdate(wrapper);
-
-    expect(getComponentNavigation).toHaveBeenCalledWith({ component: 'foo' });
-    expect(wrapper.find('.js-restore-access').exists()).toBe(true);
-  });
-
-  it('shows the restore access modal', async () => {
-    const wrapper = shallowRender();
-    wrapper.instance().handleDropdownOpen();
-    await waitAndUpdate(wrapper);
-
-    click(wrapper.find('.js-restore-access'));
-    expect(wrapper.find('RestoreAccessModal')).toMatchSnapshot();
-
-    wrapper.instance().handleRestoreAccessDone();
-    await waitAndUpdate(wrapper);
-    expect(wrapper.find('.js-restore-access').exists()).toBe(false);
-    expect(wrapper.find('RestoreAccessModal').exists()).toBe(false);
-  });
-
-  it('also shows the restore access when browse permission is missing', async () => {
-    (getComponentNavigation as jest.Mock).mockResolvedValueOnce({
-      configuration: { canBrowseProject: false, showPermissions: true },
-    });
-
-    const wrapper = shallowRender();
-    wrapper.instance().handleDropdownOpen();
-    await waitAndUpdate(wrapper);
-
-    expect(getComponentNavigation).toHaveBeenCalledWith({ component: 'foo' });
-    expect(wrapper.find('.js-restore-access').exists()).toBe(true);
-  });
-});
-
-describe('permissions', () => {
-  beforeAll(() => {
-    (getComponentNavigation as jest.Mock).mockResolvedValue({
-      configuration: {
-        canBrowseProject: true,
-        showPermissions: true,
-      },
-    });
-  });
-
-  it('shows the update permissions action', async () => {
-    const wrapper = shallowRender();
-    wrapper.instance().handleDropdownOpen();
-    await waitAndUpdate(wrapper);
-    expect(wrapper.find('.js-edit-permissions').exists()).toBe(true);
-  });
-
-  it('shows the apply permission template modal', async () => {
-    const wrapper = shallowRender();
-    wrapper.instance().handleDropdownOpen();
-    await waitAndUpdate(wrapper);
-
-    click(wrapper.find('.js-apply-template'));
-    expect(wrapper.find('ApplyTemplate')).toMatchSnapshot();
-
-    wrapper.instance().handleApplyTemplateClose();
-    await waitAndUpdate(wrapper);
-    expect(wrapper.find('ApplyTemplate').exists()).toBe(false);
-  });
-});
-
-function shallowRender(props: Partial<Props> = {}) {
-  return shallow<ProjectRowActions>(
-    <ProjectRowActions
-      currentUser={mockLoggedInUser()}
-      project={{
-        id: 'foo',
-        key: 'foo',
-        name: 'Foo',
-        qualifier: ComponentQualifier.Project,
-        visibility: Visibility.Private,
-      }}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Projects-test.tsx
deleted file mode 100644 (file)
index aa3c008..0000000
+++ /dev/null
@@ -1,57 +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 { ComponentQualifier, Visibility } from '../../../types/component';
-import Projects from '../Projects';
-
-const projects = [
-  { key: 'a', name: 'A', qualifier: ComponentQualifier.Project, visibility: Visibility.Public },
-  { key: 'b', name: 'B', qualifier: ComponentQualifier.Project, visibility: Visibility.Public },
-];
-const selection = ['a'];
-
-it('renders list of projects', () => {
-  expect(shallowRender({ projects, selection })).toMatchSnapshot();
-});
-
-it('selects and deselects project', () => {
-  const onProjectDeselected = jest.fn();
-  const onProjectSelected = jest.fn();
-  const wrapper = shallowRender({ onProjectDeselected, onProjectSelected, projects });
-
-  wrapper.find('ProjectRow').first().prop<Function>('onProjectCheck')(projects[0], true);
-  expect(onProjectSelected).toHaveBeenCalledWith('a');
-
-  wrapper.find('ProjectRow').first().prop<Function>('onProjectCheck')(projects[0], false);
-  expect(onProjectDeselected).toHaveBeenCalledWith('a');
-});
-
-function shallowRender(props?: any) {
-  return shallow(
-    <Projects
-      currentUser={{ login: 'foo' }}
-      onProjectDeselected={jest.fn()}
-      onProjectSelected={jest.fn()}
-      selection={[]}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/Search-test.tsx
deleted file mode 100644 (file)
index d9de391..0000000
+++ /dev/null
@@ -1,160 +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 { mockReactSelectOptionProps } from '../../../helpers/mocks/react-select';
-import { mockAppState } from '../../../helpers/testMocks';
-import { click } from '../../../helpers/testUtils';
-import { Props, Search } from '../Search';
-
-it('renders', () => {
-  expect(shallowRender()).toMatchSnapshot();
-});
-
-it('disables the delete and bulk apply buttons unless a project is selected', () => {
-  const wrapper = shallowRender();
-  expect(wrapper.find('Button.js-delete').prop('disabled')).toBe(true);
-  expect(wrapper.find('Button.js-bulk-apply-permission-template').prop('disabled')).toBe(true);
-
-  wrapper.setProps({ selection: ['foo'] });
-  expect(wrapper.find('Button.js-delete').prop('disabled')).toBe(false);
-  expect(wrapper.find('Button.js-bulk-apply-permission-template').prop('disabled')).toBe(false);
-});
-
-it('render qualifiers filter', () => {
-  expect(
-    shallowRender({ appState: mockAppState({ qualifiers: ['TRK', 'VW', 'APP'] }) })
-  ).toMatchSnapshot();
-});
-
-it('updates qualifier', () => {
-  const onQualifierChanged = jest.fn();
-  const wrapper = shallowRender({
-    onQualifierChanged,
-    appState: mockAppState({ qualifiers: ['TRK', 'VW', 'APP'] }),
-  });
-  wrapper.find('Select[name="projects-qualifier"]').simulate('change', {
-    value: 'VW',
-  });
-  expect(onQualifierChanged).toHaveBeenCalledWith('VW');
-});
-
-it('renders optionrenderer and singlevaluerenderer', () => {
-  const wrapper = shallowRender({
-    appState: mockAppState({ qualifiers: ['TRK', 'VW', 'APP'] }),
-  });
-  const OptionRendererer = wrapper.instance().optionRenderer;
-  const SingleValueRendererer = wrapper.instance().singleValueRenderer;
-  expect(
-    shallow(<OptionRendererer {...mockReactSelectOptionProps({ label: 'Val', value: 'val' })} />)
-  ).toMatchSnapshot('option renderer');
-  expect(
-    shallow(
-      <SingleValueRendererer {...mockReactSelectOptionProps({ label: 'Val', value: 'val' })} />
-    )
-  ).toMatchSnapshot('single value renderer');
-});
-
-it('selects provisioned', () => {
-  const onProvisionedChanged = jest.fn();
-  const wrapper = shallowRender({ onProvisionedChanged });
-  wrapper.find('Checkbox[id="projects-provisioned"]').prop<Function>('onCheck')(true);
-  expect(onProvisionedChanged).toHaveBeenCalledWith(true);
-});
-
-it('does not render provisioned filter for portfolios', () => {
-  const wrapper = shallowRender();
-  expect(wrapper.find('Checkbox[id="projects-provisioned"]').exists()).toBe(true);
-  wrapper.setProps({ qualifiers: 'VW' });
-  expect(wrapper.find('Checkbox[id="projects-provisioned"]').exists()).toBe(false);
-});
-
-it('updates analysis date', () => {
-  const onDateChanged = jest.fn();
-  const wrapper = shallowRender({ onDateChanged });
-
-  wrapper.find('DateInput').prop<Function>('onChange')('2017-04-08T00:00:00.000Z');
-  expect(onDateChanged).toHaveBeenCalledWith('2017-04-08T00:00:00.000Z');
-
-  wrapper.find('DateInput').prop<Function>('onChange')(undefined);
-  expect(onDateChanged).toHaveBeenCalledWith(undefined);
-});
-
-it('searches', () => {
-  const onSearch = jest.fn();
-  const wrapper = shallowRender({ onSearch });
-  wrapper.find('SearchBox').prop<Function>('onChange')('foo');
-  expect(onSearch).toHaveBeenCalledWith('foo');
-});
-
-it('checks all or none projects', () => {
-  const onAllDeselected = jest.fn();
-  const onAllSelected = jest.fn();
-  const wrapper = shallowRender({ onAllDeselected, onAllSelected });
-
-  wrapper.find('Checkbox[id="projects-selection"]').prop<Function>('onCheck')(true);
-  expect(onAllSelected).toHaveBeenCalled();
-
-  wrapper.find('Checkbox[id="projects-selection"]').prop<Function>('onCheck')(false);
-  expect(onAllDeselected).toHaveBeenCalled();
-});
-
-it('deletes projects', () => {
-  const onDeleteProjects = jest.fn();
-  const wrapper = shallowRender({ onDeleteProjects, selection: ['foo', 'bar'] });
-  click(wrapper.find('.js-delete'));
-  expect(wrapper.find('DeleteModal')).toMatchSnapshot();
-  wrapper.find('DeleteModal').prop<Function>('onConfirm')();
-  expect(onDeleteProjects).toHaveBeenCalled();
-});
-
-it('bulk applies permission template', () => {
-  const wrapper = shallowRender({});
-  click(wrapper.find('.js-bulk-apply-permission-template'));
-  expect(wrapper.find('BulkApplyTemplateModal')).toMatchSnapshot();
-  wrapper.find('BulkApplyTemplateModal').prop<Function>('onClose')();
-  wrapper.update();
-  expect(wrapper.find('BulkApplyTemplateModal').exists()).toBe(false);
-});
-
-function shallowRender(props?: { [P in keyof Props]?: Props[P] }) {
-  return shallow<Search>(
-    <Search
-      analyzedBefore={undefined}
-      onAllDeselected={jest.fn()}
-      onAllSelected={jest.fn()}
-      onDateChanged={jest.fn()}
-      onDeleteProjects={jest.fn()}
-      onProvisionedChanged={jest.fn()}
-      onQualifierChanged={jest.fn()}
-      onSearch={jest.fn()}
-      onVisibilityChanged={jest.fn()}
-      projects={[]}
-      provisioned={false}
-      qualifiers="TRK"
-      query=""
-      ready
-      selection={[]}
-      appState={mockAppState({ qualifiers: ['TRK'] })}
-      total={17}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/BulkApplyTemplateModal-test.tsx.snap
deleted file mode 100644 (file)
index 2e0bb0a..0000000
+++ /dev/null
@@ -1,447 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`bulk applies template to all results 1`] = `
-<Modal
-  contentLabel="permission_templates.bulk_apply_permission_template"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      permission_templates.bulk_apply_permission_template
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <i
-      className="spinner"
-    />
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <ResetButtonLink
-      onClick={[MockFunction]}
-    >
-      cancel
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
-
-exports[`bulk applies template to all results 2`] = `
-<Modal
-  contentLabel="permission_templates.bulk_apply_permission_template"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      permission_templates.bulk_apply_permission_template
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <MandatoryFieldsExplanation
-      className="spacer-bottom"
-    />
-    <Alert
-      variant="warning"
-    >
-      permission_templates.bulk_apply_permission_template.apply_to_all.17
-    </Alert>
-    <div
-      className="modal-field"
-    >
-      <label
-        htmlFor="bulk-apply-template-input"
-      >
-        template
-        <MandatoryFieldMarker />
-      </label>
-      <Select
-        id="bulk-apply-template"
-        inputId="bulk-apply-template-input"
-        isDisabled={false}
-        onChange={[Function]}
-        options={
-          [
-            {
-              "label": "Foo",
-              "value": "foo",
-            },
-            {
-              "label": "Bar",
-              "value": "bar",
-            },
-          ]
-        }
-        value={
-          {
-            "label": "Foo",
-            "value": "foo",
-          }
-        }
-      />
-    </div>
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <SubmitButton
-      disabled={false}
-      onClick={[Function]}
-    >
-      apply
-    </SubmitButton>
-    <ResetButtonLink
-      onClick={[MockFunction]}
-    >
-      cancel
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
-
-exports[`bulk applies template to all results 3`] = `
-<Modal
-  contentLabel="permission_templates.bulk_apply_permission_template"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      permission_templates.bulk_apply_permission_template
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <MandatoryFieldsExplanation
-      className="spacer-bottom"
-    />
-    <Alert
-      variant="warning"
-    >
-      permission_templates.bulk_apply_permission_template.apply_to_all.17
-    </Alert>
-    <div
-      className="modal-field"
-    >
-      <label
-        htmlFor="bulk-apply-template-input"
-      >
-        template
-        <MandatoryFieldMarker />
-      </label>
-      <Select
-        id="bulk-apply-template"
-        inputId="bulk-apply-template-input"
-        isDisabled={true}
-        onChange={[Function]}
-        options={
-          [
-            {
-              "label": "Foo",
-              "value": "foo",
-            },
-            {
-              "label": "Bar",
-              "value": "bar",
-            },
-          ]
-        }
-        value={
-          {
-            "label": "Foo",
-            "value": "foo",
-          }
-        }
-      />
-    </div>
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <i
-      className="spinner spacer-right"
-    />
-    <SubmitButton
-      disabled={true}
-      onClick={[Function]}
-    >
-      apply
-    </SubmitButton>
-    <ResetButtonLink
-      onClick={[MockFunction]}
-    >
-      cancel
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
-
-exports[`bulk applies template to all results 4`] = `
-<Modal
-  contentLabel="permission_templates.bulk_apply_permission_template"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      permission_templates.bulk_apply_permission_template
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <Alert
-      variant="success"
-    >
-      projects_role.apply_template.success
-    </Alert>
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <ResetButtonLink
-      onClick={[MockFunction]}
-    >
-      close
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
-
-exports[`bulk applies template to selected results 1`] = `
-<Modal
-  contentLabel="permission_templates.bulk_apply_permission_template"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      permission_templates.bulk_apply_permission_template
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <i
-      className="spinner"
-    />
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <ResetButtonLink
-      onClick={[MockFunction]}
-    >
-      cancel
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
-
-exports[`bulk applies template to selected results 2`] = `
-<Modal
-  contentLabel="permission_templates.bulk_apply_permission_template"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      permission_templates.bulk_apply_permission_template
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <MandatoryFieldsExplanation
-      className="spacer-bottom"
-    />
-    <Alert
-      variant="warning"
-    >
-      permission_templates.bulk_apply_permission_template.apply_to_selected.2
-    </Alert>
-    <div
-      className="modal-field"
-    >
-      <label
-        htmlFor="bulk-apply-template-input"
-      >
-        template
-        <MandatoryFieldMarker />
-      </label>
-      <Select
-        id="bulk-apply-template"
-        inputId="bulk-apply-template-input"
-        isDisabled={false}
-        onChange={[Function]}
-        options={
-          [
-            {
-              "label": "Foo",
-              "value": "foo",
-            },
-            {
-              "label": "Bar",
-              "value": "bar",
-            },
-          ]
-        }
-        value={
-          {
-            "label": "Foo",
-            "value": "foo",
-          }
-        }
-      />
-    </div>
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <SubmitButton
-      disabled={false}
-      onClick={[Function]}
-    >
-      apply
-    </SubmitButton>
-    <ResetButtonLink
-      onClick={[MockFunction]}
-    >
-      cancel
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
-
-exports[`bulk applies template to selected results 3`] = `
-<Modal
-  contentLabel="permission_templates.bulk_apply_permission_template"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      permission_templates.bulk_apply_permission_template
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <MandatoryFieldsExplanation
-      className="spacer-bottom"
-    />
-    <Alert
-      variant="warning"
-    >
-      permission_templates.bulk_apply_permission_template.apply_to_selected.2
-    </Alert>
-    <div
-      className="modal-field"
-    >
-      <label
-        htmlFor="bulk-apply-template-input"
-      >
-        template
-        <MandatoryFieldMarker />
-      </label>
-      <Select
-        id="bulk-apply-template"
-        inputId="bulk-apply-template-input"
-        isDisabled={true}
-        onChange={[Function]}
-        options={
-          [
-            {
-              "label": "Foo",
-              "value": "foo",
-            },
-            {
-              "label": "Bar",
-              "value": "bar",
-            },
-          ]
-        }
-        value={
-          {
-            "label": "Foo",
-            "value": "foo",
-          }
-        }
-      />
-    </div>
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <i
-      className="spinner spacer-right"
-    />
-    <SubmitButton
-      disabled={true}
-      onClick={[Function]}
-    >
-      apply
-    </SubmitButton>
-    <ResetButtonLink
-      onClick={[MockFunction]}
-    >
-      cancel
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
-
-exports[`bulk applies template to selected results 4`] = `
-<Modal
-  contentLabel="permission_templates.bulk_apply_permission_template"
-  onRequestClose={[MockFunction]}
-  size="small"
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      permission_templates.bulk_apply_permission_template
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <Alert
-      variant="success"
-    >
-      projects_role.apply_template.success
-    </Alert>
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <ResetButtonLink
-      onClick={[MockFunction]}
-    >
-      close
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ChangeDefaultVisibilityForm-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ChangeDefaultVisibilityForm-test.tsx.snap
deleted file mode 100644 (file)
index a669b4a..0000000
+++ /dev/null
@@ -1,159 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`changes visibility 1`] = `
-<Modal
-  contentLabel="modal form"
-  onRequestClose={[MockFunction]}
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      settings.projects.change_visibility_form.header
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <div
-      className="big-spacer-bottom"
-      key="public"
-    >
-      <Radio
-        checked={true}
-        onCheck={[Function]}
-        value="public"
-      >
-        <div>
-          visibility.public
-          <p
-            className="text-muted spacer-top"
-          >
-            visibility.public.description.short
-          </p>
-        </div>
-      </Radio>
-    </div>
-    <div
-      className="big-spacer-bottom"
-      key="private"
-    >
-      <Radio
-        checked={false}
-        onCheck={[Function]}
-        value="private"
-      >
-        <div>
-          visibility.private
-          <p
-            className="text-muted spacer-top"
-          >
-            visibility.private.description.short
-          </p>
-        </div>
-      </Radio>
-    </div>
-    <Alert
-      variant="warning"
-    >
-      settings.projects.change_visibility_form.warning
-    </Alert>
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <Button
-      className="js-confirm"
-      onClick={[Function]}
-      type="submit"
-    >
-      settings.projects.change_visibility_form.submit
-    </Button>
-    <ResetButtonLink
-      className="js-modal-close"
-      onClick={[MockFunction]}
-    >
-      cancel
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
-
-exports[`changes visibility 2`] = `
-<Modal
-  contentLabel="modal form"
-  onRequestClose={[MockFunction]}
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      settings.projects.change_visibility_form.header
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <div
-      className="big-spacer-bottom"
-      key="public"
-    >
-      <Radio
-        checked={false}
-        onCheck={[Function]}
-        value="public"
-      >
-        <div>
-          visibility.public
-          <p
-            className="text-muted spacer-top"
-          >
-            visibility.public.description.short
-          </p>
-        </div>
-      </Radio>
-    </div>
-    <div
-      className="big-spacer-bottom"
-      key="private"
-    >
-      <Radio
-        checked={true}
-        onCheck={[Function]}
-        value="private"
-      >
-        <div>
-          visibility.private
-          <p
-            className="text-muted spacer-top"
-          >
-            visibility.private.description.short
-          </p>
-        </div>
-      </Radio>
-    </div>
-    <Alert
-      variant="warning"
-    >
-      settings.projects.change_visibility_form.warning
-    </Alert>
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <Button
-      className="js-confirm"
-      onClick={[Function]}
-      type="submit"
-    >
-      settings.projects.change_visibility_form.submit
-    </Button>
-    <ResetButtonLink
-      className="js-modal-close"
-      onClick={[MockFunction]}
-    >
-      cancel
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/DeleteModal-test.tsx.snap
deleted file mode 100644 (file)
index 662bae2..0000000
+++ /dev/null
@@ -1,171 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`deletes all projects 1`] = `
-<Modal
-  contentLabel="qualifiers.delete.TRK"
-  onRequestClose={[MockFunction]}
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      qualifiers.delete.TRK
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <Alert
-      variant="warning"
-    >
-      projects_management.delete_all_warning.17
-    </Alert>
-    qualifiers.delete_confirm.TRK
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <SubmitButton
-      className="button-red"
-      disabled={false}
-      onClick={[Function]}
-    >
-      delete
-    </SubmitButton>
-    <ResetButtonLink
-      onClick={[MockFunction]}
-    >
-      cancel
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
-
-exports[`deletes all projects 2`] = `
-<Modal
-  contentLabel="qualifiers.delete.TRK"
-  onRequestClose={[MockFunction]}
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      qualifiers.delete.TRK
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <Alert
-      variant="warning"
-    >
-      projects_management.delete_all_warning.17
-    </Alert>
-    qualifiers.delete_confirm.TRK
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <i
-      className="spinner spacer-right"
-    />
-    <SubmitButton
-      className="button-red"
-      disabled={true}
-      onClick={[Function]}
-    >
-      delete
-    </SubmitButton>
-    <ResetButtonLink
-      onClick={[MockFunction]}
-    >
-      cancel
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
-
-exports[`deletes selected projects 1`] = `
-<Modal
-  contentLabel="qualifiers.delete.TRK"
-  onRequestClose={[MockFunction]}
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      qualifiers.delete.TRK
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <Alert
-      variant="warning"
-    >
-      projects_management.delete_selected_warning.2
-    </Alert>
-    qualifiers.delete_confirm.TRK
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <SubmitButton
-      className="button-red"
-      disabled={false}
-      onClick={[Function]}
-    >
-      delete
-    </SubmitButton>
-    <ResetButtonLink
-      onClick={[MockFunction]}
-    >
-      cancel
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
-
-exports[`deletes selected projects 2`] = `
-<Modal
-  contentLabel="qualifiers.delete.TRK"
-  onRequestClose={[MockFunction]}
->
-  <header
-    className="modal-head"
-  >
-    <h2>
-      qualifiers.delete.TRK
-    </h2>
-  </header>
-  <div
-    className="modal-body"
-  >
-    <Alert
-      variant="warning"
-    >
-      projects_management.delete_selected_warning.2
-    </Alert>
-    qualifiers.delete_confirm.TRK
-  </div>
-  <footer
-    className="modal-foot"
-  >
-    <i
-      className="spinner spacer-right"
-    />
-    <SubmitButton
-      className="button-red"
-      disabled={true}
-      onClick={[Function]}
-    >
-      delete
-    </SubmitButton>
-    <ResetButtonLink
-      onClick={[MockFunction]}
-    >
-      cancel
-    </ResetButtonLink>
-  </footer>
-</Modal>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Header-test.tsx.snap
deleted file mode 100644 (file)
index 839270b..0000000
+++ /dev/null
@@ -1,99 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`changes default visibility 1`] = `
-<ChangeDefaultVisibilityForm
-  defaultVisibility="public"
-  onClose={[Function]}
-  onConfirm={[MockFunction]}
-/>
-`;
-
-exports[`renders: default 1`] = `
-<header
-  className="page-header"
->
-  <h1
-    className="page-title"
-  >
-    projects_management
-  </h1>
-  <div
-    className="page-actions"
-  >
-    <span
-      className="big-spacer-right"
-    >
-      <span
-        className="text-middle"
-      >
-        settings.projects.default_visibility_of_new_projects
-         
-        <strong>
-          visibility.public
-        </strong>
-      </span>
-      <EditButton
-        aria-label="settings.projects.change_visibility_form.label"
-        className="js-change-visibility spacer-left button-small"
-        onClick={[Function]}
-      />
-    </span>
-    <Button
-      id="create-project"
-      onClick={[MockFunction]}
-    >
-      qualifiers.create.TRK
-    </Button>
-  </div>
-  <p
-    className="page-description"
-  >
-    projects_management.page.description
-  </p>
-</header>
-`;
-
-exports[`renders: undefined visibility 1`] = `
-<header
-  className="page-header"
->
-  <h1
-    className="page-title"
-  >
-    projects_management
-  </h1>
-  <div
-    className="page-actions"
-  >
-    <span
-      className="big-spacer-right"
-    >
-      <span
-        className="text-middle"
-      >
-        settings.projects.default_visibility_of_new_projects
-         
-        <strong>
-          —
-        </strong>
-      </span>
-      <EditButton
-        aria-label="settings.projects.change_visibility_form.label"
-        className="js-change-visibility spacer-left button-small"
-        onClick={[Function]}
-      />
-    </span>
-    <Button
-      id="create-project"
-      onClick={[MockFunction]}
-    >
-      qualifiers.create.TRK
-    </Button>
-  </div>
-  <p
-    className="page-description"
-  >
-    projects_management.page.description
-  </p>
-</header>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRow-test.tsx.snap
deleted file mode 100644 (file)
index e59f26b..0000000
+++ /dev/null
@@ -1,279 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders 1`] = `
-<tr
-  data-project-key="project"
->
-  <td
-    className="thin"
-  >
-    <Checkbox
-      checked={true}
-      label="projects_management.select_project.Project"
-      onCheck={[Function]}
-      thirdState={false}
-    />
-  </td>
-  <td
-    className="nowrap hide-overflow project-row-text-cell"
-  >
-    <ForwardRef(Link)
-      className="link-no-underline"
-      to={
-        {
-          "pathname": "/dashboard",
-          "search": "?id=project",
-        }
-      }
-    >
-      <QualifierIcon
-        className="little-spacer-right"
-        qualifier="TRK"
-      />
-      <Tooltip
-        overlay="Project"
-        placement="left"
-      >
-        <span>
-          Project
-        </span>
-      </Tooltip>
-    </ForwardRef(Link)>
-  </td>
-  <td
-    className="thin nowrap"
-  >
-    <PrivacyBadgeContainer
-      qualifier="TRK"
-      visibility="private"
-    />
-  </td>
-  <td
-    className="nowrap hide-overflow project-row-text-cell"
-  >
-    <Tooltip
-      overlay="project"
-      placement="left"
-    >
-      <span
-        className="note"
-      >
-        project
-      </span>
-    </Tooltip>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <span
-      className="note"
-    >
-      —
-    </span>
-  </td>
-  <td
-    className="thin nowrap"
-  >
-    <ProjectRowActions
-      currentUser={
-        {
-          "login": "foo",
-        }
-      }
-      project={
-        {
-          "key": "project",
-          "name": "Project",
-          "qualifier": "TRK",
-          "visibility": "private",
-        }
-      }
-    />
-  </td>
-</tr>
-`;
-
-exports[`renders: portfolio 1`] = `
-<tr
-  data-project-key="project"
->
-  <td
-    className="thin"
-  >
-    <Checkbox
-      checked={true}
-      label="projects_management.select_project.Project"
-      onCheck={[Function]}
-      thirdState={false}
-    />
-  </td>
-  <td
-    className="nowrap hide-overflow project-row-text-cell"
-  >
-    <ForwardRef(Link)
-      className="link-no-underline"
-      to={
-        {
-          "pathname": "/portfolio",
-          "search": "?id=project",
-        }
-      }
-    >
-      <QualifierIcon
-        className="little-spacer-right"
-        qualifier="VW"
-      />
-      <Tooltip
-        overlay="Project"
-        placement="left"
-      >
-        <span>
-          Project
-        </span>
-      </Tooltip>
-    </ForwardRef(Link)>
-  </td>
-  <td
-    className="thin nowrap"
-  >
-    <PrivacyBadgeContainer
-      qualifier="VW"
-      visibility="private"
-    />
-  </td>
-  <td
-    className="nowrap hide-overflow project-row-text-cell"
-  >
-    <Tooltip
-      overlay="project"
-      placement="left"
-    >
-      <span
-        className="note"
-      >
-        project
-      </span>
-    </Tooltip>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <span
-      className="note"
-    >
-      —
-    </span>
-  </td>
-  <td
-    className="thin nowrap"
-  >
-    <ProjectRowActions
-      currentUser={
-        {
-          "login": "foo",
-        }
-      }
-      project={
-        {
-          "key": "project",
-          "name": "Project",
-          "qualifier": "VW",
-          "visibility": "private",
-        }
-      }
-    />
-  </td>
-</tr>
-`;
-
-exports[`renders: with lastAnalysisDate 1`] = `
-<tr
-  data-project-key="project"
->
-  <td
-    className="thin"
-  >
-    <Checkbox
-      checked={true}
-      label="projects_management.select_project.Project"
-      onCheck={[Function]}
-      thirdState={false}
-    />
-  </td>
-  <td
-    className="nowrap hide-overflow project-row-text-cell"
-  >
-    <ForwardRef(Link)
-      className="link-no-underline"
-      to={
-        {
-          "pathname": "/dashboard",
-          "search": "?id=project",
-        }
-      }
-    >
-      <QualifierIcon
-        className="little-spacer-right"
-        qualifier="TRK"
-      />
-      <Tooltip
-        overlay="Project"
-        placement="left"
-      >
-        <span>
-          Project
-        </span>
-      </Tooltip>
-    </ForwardRef(Link)>
-  </td>
-  <td
-    className="thin nowrap"
-  >
-    <PrivacyBadgeContainer
-      qualifier="TRK"
-      visibility="private"
-    />
-  </td>
-  <td
-    className="nowrap hide-overflow project-row-text-cell"
-  >
-    <Tooltip
-      overlay="project"
-      placement="left"
-    >
-      <span
-        className="note"
-      >
-        project
-      </span>
-    </Tooltip>
-  </td>
-  <td
-    className="thin nowrap text-right"
-  >
-    <DateFormatter
-      date="2017-04-08T00:00:00.000Z"
-    />
-  </td>
-  <td
-    className="thin nowrap"
-  >
-    <ProjectRowActions
-      currentUser={
-        {
-          "login": "foo",
-        }
-      }
-      project={
-        {
-          "key": "project",
-          "lastAnalysisDate": "2017-04-08T00:00:00.000Z",
-          "name": "Project",
-          "qualifier": "TRK",
-          "visibility": "private",
-        }
-      }
-    />
-  </td>
-</tr>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/ProjectRowActions-test.tsx.snap
deleted file mode 100644 (file)
index f892bad..0000000
+++ /dev/null
@@ -1,60 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`permissions shows the apply permission template modal 1`] = `
-<ApplyTemplate
-  onClose={[Function]}
-  project={
-    {
-      "id": "foo",
-      "key": "foo",
-      "name": "Foo",
-      "qualifier": "TRK",
-      "visibility": "private",
-    }
-  }
-/>
-`;
-
-exports[`renders correctly 1`] = `
-<Fragment>
-  <ActionsDropdown
-    label="projects_management.show_actions_for_x.Foo"
-    onOpen={[Function]}
-  >
-    <ActionsDropdownItem
-      className="js-apply-template"
-      onClick={[Function]}
-    >
-      projects_role.apply_template
-    </ActionsDropdownItem>
-  </ActionsDropdown>
-</Fragment>
-`;
-
-exports[`restore access shows the restore access modal 1`] = `
-<RestoreAccessModal
-  currentUser={
-    {
-      "dismissedNotices": {
-        "educationPrinciples": false,
-      },
-      "groups": [],
-      "isLoggedIn": true,
-      "login": "luke",
-      "name": "Skywalker",
-      "scmAccounts": [],
-    }
-  }
-  onClose={[Function]}
-  onRestoreAccess={[Function]}
-  project={
-    {
-      "id": "foo",
-      "key": "foo",
-      "name": "Foo",
-      "qualifier": "TRK",
-      "visibility": "private",
-    }
-  }
-/>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Projects-test.tsx.snap
deleted file mode 100644 (file)
index 31f831a..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders list of projects 1`] = `
-<div
-  className="boxed-group boxed-group-inner"
->
-  <table
-    className="data zebra new-loading"
-    id="projects-management-page-projects"
-  >
-    <thead>
-      <tr>
-        <th />
-        <th>
-          name
-        </th>
-        <th />
-        <th>
-          key
-        </th>
-        <th
-          className="thin nowrap text-right"
-        >
-          last_analysis
-        </th>
-        <th />
-      </tr>
-    </thead>
-    <tbody>
-      <ProjectRow
-        currentUser={
-          {
-            "login": "foo",
-          }
-        }
-        key="a"
-        onProjectCheck={[Function]}
-        project={
-          {
-            "key": "a",
-            "name": "A",
-            "qualifier": "TRK",
-            "visibility": "public",
-          }
-        }
-        selected={true}
-      />
-      <ProjectRow
-        currentUser={
-          {
-            "login": "foo",
-          }
-        }
-        key="b"
-        onProjectCheck={[Function]}
-        project={
-          {
-            "key": "b",
-            "name": "B",
-            "qualifier": "TRK",
-            "visibility": "public",
-          }
-        }
-        selected={false}
-      />
-    </tbody>
-  </table>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectsManagement/__tests__/__snapshots__/Search-test.tsx.snap
deleted file mode 100644 (file)
index e505f08..0000000
+++ /dev/null
@@ -1,315 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`bulk applies permission template 1`] = `
-<BulkApplyTemplateModal
-  onClose={[Function]}
-  provisioned={false}
-  qualifier="TRK"
-  query=""
-  selection={[]}
-  total={17}
-/>
-`;
-
-exports[`deletes projects 1`] = `
-<DeleteModal
-  onClose={[Function]}
-  onConfirm={[Function]}
-  provisioned={false}
-  qualifier="TRK"
-  query=""
-  selection={
-    [
-      "foo",
-      "bar",
-    ]
-  }
-  total={17}
-/>
-`;
-
-exports[`render qualifiers filter 1`] = `
-<div
-  className="big-spacer-bottom"
->
-  <div
-    className="projects-management-search"
-  >
-    <div>
-      <Checkbox
-        checked={false}
-        id="projects-selection"
-        onCheck={[Function]}
-        thirdState={false}
-        title="check_all"
-      />
-    </div>
-    <Select
-      aria-label="projects_management.filter_by_component"
-      className="input-medium it__project-qualifier-select"
-      components={
-        {
-          "Option": [Function],
-          "SingleValue": [Function],
-        }
-      }
-      isDisabled={false}
-      isSearchable={false}
-      name="projects-qualifier"
-      onChange={[Function]}
-      options={
-        [
-          {
-            "label": "qualifiers.TRK",
-            "value": "TRK",
-          },
-          {
-            "label": "qualifiers.VW",
-            "value": "VW",
-          },
-          {
-            "label": "qualifiers.APP",
-            "value": "APP",
-          },
-        ]
-      }
-      value={
-        {
-          "label": "qualifiers.TRK",
-          "value": "TRK",
-        }
-      }
-    />
-    <DateInput
-      inputClassName="input-medium"
-      name="analyzed-before"
-      onChange={[MockFunction]}
-      placeholder="last_analysis_before"
-    />
-    <Select
-      aria-label="projects_management.filter_by_visibility"
-      className="input-small"
-      isDisabled={false}
-      isSearchable={false}
-      name="projects-visibility"
-      onChange={[Function]}
-      options={
-        [
-          {
-            "label": "visibility.both",
-            "value": "all",
-          },
-          {
-            "label": "visibility.public",
-            "value": "public",
-          },
-          {
-            "label": "visibility.private",
-            "value": "private",
-          },
-        ]
-      }
-      value={
-        {
-          "label": "visibility.both",
-          "value": "all",
-        }
-      }
-    />
-    <div>
-      <Checkbox
-        checked={false}
-        className="link-checkbox-control"
-        id="projects-provisioned"
-        onCheck={[MockFunction]}
-        thirdState={false}
-      >
-        <span
-          className="text-middle little-spacer-left"
-        >
-          provisioning.only_provisioned
-        </span>
-        <HelpTooltip
-          className="spacer-left"
-          overlay="provisioning.only_provisioned.tooltip"
-        />
-      </Checkbox>
-    </div>
-    <div
-      className="flex-grow"
-    >
-      <SearchBox
-        minLength={3}
-        onChange={[MockFunction]}
-        placeholder="search.search_by_name_or_key"
-        value=""
-      />
-    </div>
-    <div
-      className="bulk-actions"
-    >
-      <Button
-        className="js-bulk-apply-permission-template"
-        disabled={true}
-        onClick={[Function]}
-      >
-        permission_templates.bulk_apply_permission_template
-      </Button>
-      <Button
-        className="js-delete spacer-left button-red"
-        disabled={true}
-        onClick={[Function]}
-        title="permission_templates.select_to_delete"
-      >
-        delete
-      </Button>
-    </div>
-  </div>
-</div>
-`;
-
-exports[`renders 1`] = `
-<div
-  className="big-spacer-bottom"
->
-  <div
-    className="projects-management-search"
-  >
-    <div>
-      <Checkbox
-        checked={false}
-        id="projects-selection"
-        onCheck={[Function]}
-        thirdState={false}
-        title="check_all"
-      />
-    </div>
-    <DateInput
-      inputClassName="input-medium"
-      name="analyzed-before"
-      onChange={[MockFunction]}
-      placeholder="last_analysis_before"
-    />
-    <Select
-      aria-label="projects_management.filter_by_visibility"
-      className="input-small"
-      isDisabled={false}
-      isSearchable={false}
-      name="projects-visibility"
-      onChange={[Function]}
-      options={
-        [
-          {
-            "label": "visibility.both",
-            "value": "all",
-          },
-          {
-            "label": "visibility.public",
-            "value": "public",
-          },
-          {
-            "label": "visibility.private",
-            "value": "private",
-          },
-        ]
-      }
-      value={
-        {
-          "label": "visibility.both",
-          "value": "all",
-        }
-      }
-    />
-    <div>
-      <Checkbox
-        checked={false}
-        className="link-checkbox-control"
-        id="projects-provisioned"
-        onCheck={[MockFunction]}
-        thirdState={false}
-      >
-        <span
-          className="text-middle little-spacer-left"
-        >
-          provisioning.only_provisioned
-        </span>
-        <HelpTooltip
-          className="spacer-left"
-          overlay="provisioning.only_provisioned.tooltip"
-        />
-      </Checkbox>
-    </div>
-    <div
-      className="flex-grow"
-    >
-      <SearchBox
-        minLength={3}
-        onChange={[MockFunction]}
-        placeholder="search.search_by_name_or_key"
-        value=""
-      />
-    </div>
-    <div
-      className="bulk-actions"
-    >
-      <Button
-        className="js-bulk-apply-permission-template"
-        disabled={true}
-        onClick={[Function]}
-      >
-        permission_templates.bulk_apply_permission_template
-      </Button>
-      <Button
-        className="js-delete spacer-left button-red"
-        disabled={true}
-        onClick={[Function]}
-        title="permission_templates.select_to_delete"
-      >
-        delete
-      </Button>
-    </div>
-  </div>
-</div>
-`;
-
-exports[`renders optionrenderer and singlevaluerenderer: option renderer 1`] = `
-<Option
-  data={
-    {
-      "label": "Val",
-      "value": "val",
-    }
-  }
->
-  <div
-    className="display-flex-center"
-  >
-    <QualifierIcon
-      className="little-spacer-right"
-      qualifier="val"
-    />
-    Val
-  </div>
-</Option>
-`;
-
-exports[`renders optionrenderer and singlevaluerenderer: single value renderer 1`] = `
-<SingleValue
-  data={
-    {
-      "label": "Val",
-      "value": "val",
-    }
-  }
->
-  <div
-    className="display-flex-center"
-  >
-    <QualifierIcon
-      className="little-spacer-right"
-      qualifier="val"
-    />
-    Val
-  </div>
-</SingleValue>
-`;
index 9672b0ae9bbee4bc42d5dc4d09e55a50a4229d4a..9fa5482ffe4ea6001599230db7a07d181646740a 100644 (file)
@@ -21,6 +21,6 @@ import React from 'react';
 import { Route } from 'react-router-dom';
 import ProjectManagementApp from './ProjectManagementApp';
 
-export const routes = () => <Route path="projects_management" element={<ProjectManagementApp />} />;
+const routes = () => <Route path="projects_management" element={<ProjectManagementApp />} />;
 
 export default routes;
index 84e1a61b35119cdf194c3ef39a6c19cd3b7793ff..cacb364b3c16af37ec5cebe3412c51ba8c87d34d 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 { Project } from '../../apps/projects/types';
+import { Project } from '../../api/project-management';
 import { ComponentQualifier, Visibility } from '../../types/component';
 
 export function mockProject(overrides: Partial<Project> = {}): Project {
   return {
     key: 'foo',
     name: 'Foo',
-    measures: {},
     qualifier: ComponentQualifier.Project,
-    tags: [],
     visibility: Visibility.Public,
+    lastAnalysisDate: '2019-01-04T09:51:48Z',
     ...overrides,
   };
 }