]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18448 Migrate the components/issue tests to RTL
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Tue, 28 Mar 2023 13:05:44 +0000 (15:05 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 30 Mar 2023 20:03:07 +0000 (20:03 +0000)
55 files changed:
server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx
server/sonar-web/src/main/js/components/issue/Issue.css
server/sonar-web/src/main/js/components/issue/Issue.tsx
server/sonar-web/src/main/js/components/issue/IssueView.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/__tests__/IssueView-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/issue-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/__tests__/issue-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
server/sonar-web/src/main/js/components/issue/components/IssueChangelog.tsx
server/sonar-web/src/main/js/components/issue/components/IssueChangelogDiff.tsx
server/sonar-web/src/main/js/components/issue/components/IssueCommentLine.tsx
server/sonar-web/src/main/js/components/issue/components/IssueMessageTags.tsx
server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
server/sonar-web/src/main/js/components/issue/components/IssueView.tsx [new file with mode: 0644]
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueActionsBar-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueAssign-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelog-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelogDiff-test.tsx
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentLine-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueSeverity-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTransition-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueChangelog-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueChangelogDiff-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentLine-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueSeverity-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTransition-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueType-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/popups/ChangelogPopup.tsx
server/sonar-web/src/main/js/components/issue/popups/CommentForm.tsx
server/sonar-web/src/main/js/components/issue/popups/SimilarIssuesPopup.tsx
server/sonar-web/src/main/js/components/issue/popups/__tests__/ChangelogPopup-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentDeletePopup-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentPopup-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/popups/__tests__/SetAssigneePopup-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/popups/__tests__/SimilarIssuesPopup-test.tsx [deleted file]
server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/ChangelogPopup-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/CommentDeletePopup-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetAssigneePopup-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SimilarIssuesPopup-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/helpers/constants.ts
server/sonar-web/src/main/js/helpers/mocks/issues.ts
server/sonar-web/src/main/js/helpers/testMocks.ts
server/sonar-web/src/main/js/types/issues.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index f9f348b8a164584f36d506ae90c878d4dae32f19..1809ed0dbc10c794f4a592264ef94f8dd355154c 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 { cloneDeep, keyBy, times } from 'lodash';
+import { cloneDeep, keyBy, times, uniqueId } from 'lodash';
 import { RuleDescriptionSections } from '../../apps/coding-rules/rule';
+import { mockIssueChangelog } from '../../helpers/mocks/issues';
 import { mockSnippetsByComponent } from '../../helpers/mocks/sources';
 import { RequestData } from '../../helpers/request';
 import { getStandards } from '../../helpers/security-standard';
 import {
-  mockCurrentUser,
   mockLoggedInUser,
   mockPaging,
   mockRawIssue,
@@ -31,10 +31,12 @@ import {
 } from '../../helpers/testMocks';
 import {
   ASSIGNEE_ME,
+  IssueActions,
   IssueResolution,
   IssueScope,
   IssueSeverity,
   IssueStatus,
+  IssueTransition,
   IssueType,
   RawFacet,
   RawIssue,
@@ -49,12 +51,13 @@ import {
   RuleDetails,
   SnippetsByComponent,
 } from '../../types/types';
-import { NoticeType } from '../../types/users';
+import { LoggedInUser, NoticeType } from '../../types/users';
 import {
   addIssueComment,
   bulkChangeIssues,
   deleteIssueComment,
   editIssueComment,
+  getIssueChangelog,
   getIssueFlowSnippets,
   searchIssues,
   searchIssueTags,
@@ -97,11 +100,13 @@ interface IssueData {
 
 export default class IssuesServiceMock {
   isAdmin = false;
+  currentUser: LoggedInUser;
   standards?: Standards;
   defaultList: IssueData[];
   list: IssueData[];
 
   constructor() {
+    this.currentUser = mockLoggedInUser();
     this.defaultList = [
       {
         issue: mockRawIssue(false, {
@@ -336,7 +341,7 @@ export default class IssuesServiceMock {
       },
       {
         issue: mockRawIssue(false, {
-          actions: ['set_type', 'set_tags', 'comment', 'set_severity', 'assign'],
+          actions: Object.values(IssueActions),
           transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
           key: 'issue2',
           component: 'foo:test2.js',
@@ -391,7 +396,7 @@ export default class IssuesServiceMock {
       },
       {
         issue: mockRawIssue(false, {
-          actions: ['set_type', 'set_tags', 'comment', 'set_severity', 'assign'],
+          actions: Object.values(IssueActions),
           transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
           key: 'issue4',
           component: 'foo:test2.js',
@@ -444,19 +449,29 @@ export default class IssuesServiceMock {
     (getCurrentUser as jest.Mock).mockImplementation(this.handleGetCurrentUser);
     (dismissNotice as jest.Mock).mockImplementation(this.handleDismissNotification);
     (setIssueType as jest.Mock).mockImplementation(this.handleSetIssueType);
-    (setIssueAssignee as jest.Mock).mockImplementation(this.handleSetIssueAssignee);
+    jest.mocked(setIssueAssignee).mockImplementation(this.handleSetIssueAssignee);
     (setIssueSeverity as jest.Mock).mockImplementation(this.handleSetIssueSeverity);
     (setIssueTransition as jest.Mock).mockImplementation(this.handleSetIssueTransition);
     (setIssueTags as jest.Mock).mockImplementation(this.handleSetIssueTags);
-    (addIssueComment as jest.Mock).mockImplementation(this.handleAddComment);
-    (editIssueComment as jest.Mock).mockImplementation(this.handleEditComment);
-    (deleteIssueComment as jest.Mock).mockImplementation(this.handleDeleteComment);
+    jest.mocked(addIssueComment).mockImplementation(this.handleAddComment);
+    jest.mocked(editIssueComment).mockImplementation(this.handleEditComment);
+    jest.mocked(deleteIssueComment).mockImplementation(this.handleDeleteComment);
     (searchUsers as jest.Mock).mockImplementation(this.handleSearchUsers);
     (searchIssueTags as jest.Mock).mockImplementation(this.handleSearchIssueTags);
+    jest.mocked(getIssueChangelog).mockImplementation(this.handleGetIssueChangelog);
   }
 
   reset = () => {
     this.list = cloneDeep(this.defaultList);
+    this.currentUser = mockLoggedInUser();
+  };
+
+  setCurrentUser = (user: LoggedInUser) => {
+    this.currentUser = user;
+  };
+
+  setIssueList = (list: IssueData[]) => {
+    this.list = list;
   };
 
   async getStandards(): Promise<Standards> {
@@ -703,7 +718,7 @@ export default class IssuesServiceMock {
   };
 
   handleGetCurrentUser = () => {
-    return this.reply(mockCurrentUser());
+    return this.reply(this.currentUser);
   };
 
   handleDismissNotification = (noticeType: NoticeType) => {
@@ -723,27 +738,45 @@ export default class IssuesServiceMock {
   };
 
   handleSetIssueAssignee = (data: { issue: string; assignee?: string }) => {
-    return this.getActionsResponse({ assignee: data.assignee }, data.issue);
+    return this.getActionsResponse(
+      { assignee: data.assignee === '_me' ? this.currentUser.login : data.assignee },
+      data.issue
+    );
   };
 
   handleSetIssueTransition = (data: { issue: string; transition: string }) => {
-    const statusMap: { [key: string]: string } = {
-      confirm: 'CONFIRMED',
-      unconfirm: 'REOPENED',
-      resolve: 'RESOLVED',
-      wontfix: 'RESOLVED',
-      falsepositive: 'RESOLVED',
+    const statusMap: { [key: string]: IssueStatus } = {
+      [IssueTransition.Confirm]: IssueStatus.Confirmed,
+      [IssueTransition.UnConfirm]: IssueStatus.Reopened,
+      [IssueTransition.Resolve]: IssueStatus.Resolved,
+      [IssueTransition.WontFix]: IssueStatus.Resolved,
+      [IssueTransition.FalsePositive]: IssueStatus.Resolved,
     };
-    const transitionMap: Dict<string[]> = {
-      REOPENED: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
-      OPEN: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
-      CONFIRMED: ['resolve', 'unconfirm', 'falsepositive', 'wontfix'],
-      RESOLVED: ['reopen'],
+    const transitionMap: Dict<IssueTransition[]> = {
+      [IssueStatus.Reopened]: [
+        IssueTransition.Confirm,
+        IssueTransition.Resolve,
+        IssueTransition.FalsePositive,
+        IssueTransition.WontFix,
+      ],
+      [IssueStatus.Open]: [
+        IssueTransition.Confirm,
+        IssueTransition.Resolve,
+        IssueTransition.FalsePositive,
+        IssueTransition.WontFix,
+      ],
+      [IssueStatus.Confirmed]: [
+        IssueTransition.Resolve,
+        IssueTransition.UnConfirm,
+        IssueTransition.FalsePositive,
+        IssueTransition.WontFix,
+      ],
+      [IssueStatus.Resolved]: [IssueTransition.Reopen],
     };
 
     const resolutionMap: Dict<string> = {
-      wontfix: IssueResolution.WontFix,
-      falsepositive: IssueResolution.FalsePositive,
+      [IssueTransition.WontFix]: IssueResolution.WontFix,
+      [IssueTransition.FalsePositive]: IssueResolution.FalsePositive,
     };
 
     return this.getActionsResponse(
@@ -762,14 +795,13 @@ export default class IssuesServiceMock {
   };
 
   handleAddComment = (data: { issue: string; text: string }) => {
-    // For comment its little more complex to get comment Id
     return this.getActionsResponse(
       {
         comments: [
           {
             createdAt: '2022-07-28T11:30:04+0200',
             htmlText: data.text,
-            key: '1234',
+            key: uniqueId(),
             login: 'admin',
             markdown: data.text,
             updatable: true,
@@ -781,31 +813,40 @@ export default class IssuesServiceMock {
   };
 
   handleEditComment = (data: { comment: string; text: string }) => {
-    // For comment its little more complex to get comment Id
+    const issueKey = this.list.find((i) => i.issue.comments?.some((c) => c.key === data.comment))
+      ?.issue.key;
+    if (!issueKey) {
+      throw new Error(`Couldn't find issue for comment ${data.comment}`);
+    }
     return this.getActionsResponse(
       {
         comments: [
           {
             createdAt: '2022-07-28T11:30:04+0200',
             htmlText: data.text,
-            key: '1234',
+            key: data.comment,
             login: 'admin',
             markdown: data.text,
             updatable: true,
           },
         ],
       },
-      'issue2'
+      issueKey
     );
   };
 
-  handleDeleteComment = () => {
-    // For comment its little more complex to get comment Id
+  handleDeleteComment = (data: { comment: string }) => {
+    const issue = this.list.find((i) =>
+      i.issue.comments?.some((c) => c.key === data.comment)
+    )?.issue;
+    if (!issue) {
+      throw new Error(`Couldn't find issue for comment ${data.comment}`);
+    }
     return this.getActionsResponse(
       {
-        comments: [],
+        comments: issue.comments?.filter((c) => c.key !== data.comment),
       },
-      'issue2'
+      issue.key
     );
   };
 
@@ -817,6 +858,33 @@ export default class IssuesServiceMock {
     return this.reply(['accessibility', 'android']);
   };
 
+  handleGetIssueChangelog = (_issue: string) => {
+    return this.reply({
+      changelog: [
+        mockIssueChangelog({
+          creationDate: '2018-09-01',
+          diffs: [
+            {
+              key: 'status',
+              newValue: IssueStatus.Reopened,
+              oldValue: IssueStatus.Confirmed,
+            },
+          ],
+        }),
+        mockIssueChangelog({
+          creationDate: '2018-10-01',
+          diffs: [
+            {
+              key: 'assign',
+              newValue: 'darth.vader',
+              oldValue: 'luke.skywalker',
+            },
+          ],
+        }),
+      ],
+    });
+  };
+
   getActionsResponse = (overrides: Partial<RawIssue>, issueKey: string) => {
     const issueDataSelected = this.list.find((l) => l.issue.key === issueKey);
 
index 2cef9341265fde5103d0eb89c6c9f5e5c0ae361d..fbe22d96bd02813b7be1a86e51fe7f9e1484ecfb 100644 (file)
@@ -290,8 +290,10 @@ describe('issues app', () => {
     // Improve this to include all the bulk change fonctionality
     it('should be able to bulk change', async () => {
       const user = userEvent.setup();
+      const currentUser = mockLoggedInUser();
       issuesHandler.setIsAdmin(true);
-      renderIssueApp(mockLoggedInUser());
+      issuesHandler.setCurrentUser(currentUser);
+      renderIssueApp(currentUser);
 
       // Check that the bulk button has correct behavior
       expect(screen.getByRole('button', { name: 'bulk_change' })).toBeDisabled();
@@ -481,7 +483,9 @@ describe('issues app', () => {
 
     it('should allow to set creation date', async () => {
       const user = userEvent.setup();
-      renderIssueApp(mockLoggedInUser());
+      const currentUser = mockLoggedInUser();
+      issuesHandler.setCurrentUser(currentUser);
+      renderIssueApp(currentUser);
       await waitOnDataLoaded();
 
       // Select a specific date range such that only one issue matches
@@ -503,7 +507,9 @@ describe('issues app', () => {
 
     it('should allow to only show my issues', async () => {
       const user = userEvent.setup();
-      renderIssueApp(mockLoggedInUser());
+      const currentUser = mockLoggedInUser();
+      issuesHandler.setCurrentUser(currentUser);
+      renderIssueApp(currentUser);
       await waitOnDataLoaded();
 
       // By default, it should show all issues
@@ -895,7 +901,7 @@ describe('issues item', () => {
 
     expect(
       screen.getByRole('heading', {
-        name: 'Issue with tags sonar-lint-icon issue.resolution.badge.DEPRECATED',
+        name: 'Issue with tags issue.quick_fix_available_with_sonarlint_no_link issue.resolution.badge.DEPRECATED',
       })
     ).toBeInTheDocument();
   });
index 1fe474bf951efb4ea0d4aa6b02abcc5022ff6c0d..eec00a0a1b56cbec22958d1ebe88c853d56bd255 100644 (file)
@@ -39,7 +39,8 @@ import {
 } from '../../../helpers/measures';
 import { getBranchLikeUrl } from '../../../helpers/urls';
 import { BranchLike } from '../../../types/branch-like';
-import { IssueType as IssueTypeEnum } from '../../../types/issues';
+import { IssueSeverity, IssueType as IssueTypeEnum } from '../../../types/issues';
+import { MetricType } from '../../../types/metrics';
 import { FacetValue, IssueType, MeasureEnhanced, SourceViewerFile } from '../../../types/types';
 import Measure from '../../measure/Measure';
 import SeverityHelper from '../../shared/SeverityHelper';
@@ -199,7 +200,9 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
                           <IssueTypeIcon className="little-spacer-right" query={f.val} />
                           {translate('issue.type', f.val)}
                         </span>
-                        <span className="measure-value">{formatMeasure(f.count, 'SHORT_INT')}</span>
+                        <span className="measure-value">
+                          {formatMeasure(f.count, MetricType.ShortInteger)}
+                        </span>
                       </div>
                     )
                   )}
@@ -207,14 +210,18 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
               )}
               {severitiesFacet && (
                 <div className="measures">
-                  {sortBy(severitiesFacet, (f) => SEVERITIES.indexOf(f.val)).map((f) => (
-                    <div className="measure measure-one-line" key={f.val}>
-                      <span className="measure-name">
-                        <SeverityHelper severity={f.val} />
-                      </span>
-                      <span className="measure-value">{formatMeasure(f.count, 'SHORT_INT')}</span>
-                    </div>
-                  ))}
+                  {sortBy(severitiesFacet, (f) => SEVERITIES.indexOf(f.val as IssueSeverity)).map(
+                    (f) => (
+                      <div className="measure measure-one-line" key={f.val}>
+                        <span className="measure-name">
+                          <SeverityHelper severity={f.val} />
+                        </span>
+                        <span className="measure-value">
+                          {formatMeasure(f.count, MetricType.ShortInteger)}
+                        </span>
+                      </div>
+                    )
+                  )}
                 </div>
               )}
               {tagsFacet && (
@@ -225,7 +232,9 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
                         <TagsIcon className="little-spacer-right" />
                         {f.val}
                       </span>
-                      <span className="measure-value">{formatMeasure(f.count, 'SHORT_INT')}</span>
+                      <span className="measure-value">
+                        {formatMeasure(f.count, MetricType.ShortInteger)}
+                      </span>
                     </div>
                   ))}
                 </div>
index b9fd5495faafe88a48533913666cf7237db2616a..5be5920c71f4c237cc86295bc2e94e9a801447f5 100644 (file)
   cursor: initial;
 }
 
-.issue.hotspot {
-  background-color: var(--hotspotBgColor);
-}
-
 .issue.selected,
 .issue-message-box.selected {
   box-shadow: none;
index fbb18e9dd06c47160e205e3006b1f474d7c4b378..0dd933c582b91ec58854f91ef5e40cd93123630b 100644 (file)
@@ -25,8 +25,8 @@ import { getKeyboardShortcutEnabled } from '../../helpers/preferences';
 import { BranchLike } from '../../types/branch-like';
 import { Issue as TypeIssue } from '../../types/types';
 import { updateIssue } from './actions';
+import IssueView from './components/IssueView';
 import './Issue.css';
-import IssueView from './IssueView';
 
 interface Props {
   branchLike?: BranchLike;
diff --git a/server/sonar-web/src/main/js/components/issue/IssueView.tsx b/server/sonar-web/src/main/js/components/issue/IssueView.tsx
deleted file mode 100644 (file)
index 09a2ae2..0000000
+++ /dev/null
@@ -1,156 +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 classNames from 'classnames';
-import * as React from 'react';
-import { deleteIssueComment, editIssueComment } from '../../api/issues';
-import Checkbox from '../../components/controls/Checkbox';
-import { translate, translateWithParameters } from '../../helpers/l10n';
-import { BranchLike } from '../../types/branch-like';
-import { Issue } from '../../types/types';
-import { updateIssue } from './actions';
-import IssueActionsBar from './components/IssueActionsBar';
-import IssueCommentLine from './components/IssueCommentLine';
-import IssueTitleBar from './components/IssueTitleBar';
-
-interface Props {
-  branchLike?: BranchLike;
-  checked?: boolean;
-  currentPopup?: string;
-  displayWhyIsThisAnIssue?: boolean;
-  displayLocationsCount?: boolean;
-  displayLocationsLink?: boolean;
-  issue: Issue;
-  onAssign: (login: string) => void;
-  onChange: (issue: Issue) => void;
-  onCheck?: (issue: string) => void;
-  onClick?: (issueKey: string) => void;
-  onFilter?: (property: string, issue: Issue) => void;
-  selected: boolean;
-  togglePopup: (popup: string, show: boolean | void) => void;
-}
-
-export default class IssueView extends React.PureComponent<Props> {
-  handleCheck = () => {
-    if (this.props.onCheck) {
-      this.props.onCheck(this.props.issue.key);
-    }
-  };
-
-  handleBoxClick = (event: React.MouseEvent<HTMLDivElement>) => {
-    if (!isClickable(event.target as HTMLElement) && this.props.onClick) {
-      event.preventDefault();
-      this.handleDetailClick();
-    }
-  };
-
-  handleDetailClick = () => {
-    if (this.props.onClick) {
-      this.props.onClick(this.props.issue.key);
-    }
-  };
-
-  editComment = (comment: string, text: string) => {
-    updateIssue(this.props.onChange, editIssueComment({ comment, text }));
-  };
-
-  deleteComment = (comment: string) => {
-    updateIssue(this.props.onChange, deleteIssueComment({ comment }));
-  };
-
-  render() {
-    const {
-      issue,
-      branchLike,
-      checked,
-      currentPopup,
-      displayWhyIsThisAnIssue,
-      displayLocationsLink,
-      displayLocationsCount,
-    } = this.props;
-
-    const hasCheckbox = this.props.onCheck != null;
-
-    const issueClass = classNames('issue', {
-      'no-click': this.props.onClick === undefined,
-      hotspot: issue.type === 'SECURITY_HOTSPOT',
-      'issue-with-checkbox': hasCheckbox,
-      selected: this.props.selected,
-    });
-
-    return (
-      <div
-        className={issueClass}
-        onClick={this.handleBoxClick}
-        role="region"
-        aria-label={issue.message}
-      >
-        {hasCheckbox && (
-          <Checkbox
-            checked={checked || false}
-            className="issue-checkbox-container"
-            onCheck={this.handleCheck}
-            label={translateWithParameters('issues.action_select.label', issue.message)}
-            title={translate('issues.action_select')}
-          />
-        )}
-        <IssueTitleBar
-          branchLike={branchLike}
-          onClick={this.handleDetailClick}
-          currentPopup={currentPopup}
-          displayLocationsCount={displayLocationsCount}
-          displayLocationsLink={displayLocationsLink}
-          displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
-          issue={issue}
-          onFilter={this.props.onFilter}
-          togglePopup={this.props.togglePopup}
-        />
-        <IssueActionsBar
-          className="padded-left"
-          currentPopup={currentPopup}
-          issue={issue}
-          onAssign={this.props.onAssign}
-          onChange={this.props.onChange}
-          togglePopup={this.props.togglePopup}
-        />
-        {issue.comments && issue.comments.length > 0 && (
-          <div className="issue-comments">
-            {issue.comments.map((comment) => (
-              <IssueCommentLine
-                comment={comment}
-                key={comment.key}
-                onDelete={this.deleteComment}
-                onEdit={this.editComment}
-              />
-            ))}
-          </div>
-        )}
-      </div>
-    );
-  }
-}
-
-function isClickable(node: HTMLElement | undefined | null): boolean {
-  if (!node) {
-    return false;
-  }
-  const clickableTags = ['A', 'BUTTON', 'INPUT', 'TEXTAREA'];
-  const tagName = (node.tagName || '').toUpperCase();
-  return clickableTags.includes(tagName) || isClickable(node.parentElement);
-}
diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx b/server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx
new file mode 100644 (file)
index 0000000..0f6d979
--- /dev/null
@@ -0,0 +1,554 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+import { act, screen, within } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { omit, pick } from 'lodash';
+import * as React from 'react';
+import { byRole, byText } from 'testing-library-selector';
+import IssuesServiceMock from '../../../api/mocks/IssuesServiceMock';
+import { KeyboardKeys } from '../../../helpers/keycodes';
+import { mockIssueComment } from '../../../helpers/mocks/issues';
+import { mockIssue, mockLoggedInUser, mockRawIssue } from '../../../helpers/testMocks';
+import { findTooltipWithContent, renderApp } from '../../../helpers/testReactTestingUtils';
+import {
+  IssueActions,
+  IssueSeverity,
+  IssueStatus,
+  IssueTransition,
+  IssueType,
+} from '../../../types/issues';
+import { RuleStatus } from '../../../types/rules';
+import { IssueComment } from '../../../types/types';
+import Issue from '../Issue';
+
+jest.mock('../../../api/issues');
+jest.mock('../../../api/rules');
+jest.mock('../../../api/users');
+
+jest.mock('../../../helpers/preferences', () => ({
+  getKeyboardShortcutEnabled: jest.fn(() => true),
+}));
+
+const issuesHandler = new IssuesServiceMock();
+
+beforeEach(() => {
+  issuesHandler.reset();
+});
+
+describe('rendering', () => {
+  it('should render correctly with comments', () => {
+    const { ui } = getPageObject();
+    renderIssue({ issue: mockIssue(false, { comments: [mockIssueCommentPosted4YearsAgo()] }) });
+
+    const comments = within(ui.commentsList());
+    expect(comments.getByText('Leïa Skywalker')).toBeInTheDocument();
+    expect(comments.getByRole('listitem')).toHaveTextContent('This is a comment, bud');
+    expect(comments.getByRole('listitem')).toHaveTextContent('issue.comment.posted_on4 years ago');
+  });
+
+  it('should render correctly for locations, issue message, line, permalink, why, and effort', async () => {
+    const { ui } = getPageObject();
+    const issue = mockIssue(true, { effort: '2 days', message: 'This is an issue' });
+    const onClick = jest.fn();
+    renderIssue({ issue, displayLocationsCount: true, displayWhyIsThisAnIssue: true, onClick });
+
+    expect(ui.locationsBadge(7).get()).toBeInTheDocument();
+    expect(ui.lineInfo(26).get()).toBeInTheDocument();
+    expect(ui.permalink.get()).toHaveAttribute(
+      'href',
+      `/project/issues?issues=${issue.key}&open=${issue.key}&id=${issue.project}`
+    );
+    expect(ui.whyLink.get()).toBeInTheDocument();
+    expect(ui.effort('2 days').get()).toBeInTheDocument();
+    await ui.clickIssueMessage();
+    expect(onClick).toHaveBeenCalledWith(issue.key);
+  });
+
+  it.each([RuleStatus.Deprecated, RuleStatus.Removed])(
+    'should render correctly for a %s rule',
+    (ruleStatus) => {
+      const { ui } = getPageObject();
+      renderIssue({ issue: mockIssue(false, { ruleStatus }) });
+      expect(ui.ruleStatusBadge(ruleStatus).get()).toBeInTheDocument();
+    }
+  );
+
+  it('should render correctly for external rule engines', () => {
+    renderIssue({ issue: mockIssue(true, { externalRuleEngine: 'ESLINT' }) });
+    expect(screen.getByText('ESLINT')).toBeInTheDocument();
+  });
+
+  it('should render the SonarLint icon correctly', () => {
+    renderIssue({ issue: mockIssue(false, { quickFixAvailable: true }) });
+    expect(
+      findTooltipWithContent('issue.quick_fix_available_with_sonarlint_no_link')
+    ).toBeInTheDocument();
+  });
+
+  it('should render correctly with a checkbox', async () => {
+    const { ui } = getPageObject();
+    const onCheck = jest.fn();
+    const issue = mockIssue();
+    renderIssue({ onCheck, issue });
+    await ui.toggleCheckbox();
+    expect(onCheck).toHaveBeenCalledWith(issue.key);
+  });
+
+  it('should correctly render the changelog', async () => {
+    const { ui } = getPageObject();
+    renderIssue();
+
+    await ui.showChangelog();
+    expect(
+      ui.changelogRow('status', IssueStatus.Confirmed, IssueStatus.Reopened).get()
+    ).toBeInTheDocument();
+    expect(ui.changelogRow('assign', 'luke.skywalker', 'darth.vader').get()).toBeInTheDocument();
+  });
+});
+
+describe('updating', () => {
+  it('should allow updating the type', async () => {
+    const { ui } = getPageObject();
+    const issue = mockRawIssue(false, {
+      type: IssueType.Bug,
+      actions: [IssueActions.SetType],
+    });
+    issuesHandler.setIssueList([{ issue, snippets: {} }]);
+    renderIssue({ issue: mockIssue(false, { ...pick(issue, 'actions', 'key', 'type') }) });
+
+    await ui.updateType(IssueType.Bug, IssueType.CodeSmell);
+    expect(ui.updateTypeBtn(IssueType.CodeSmell).get()).toBeInTheDocument();
+  });
+
+  it('should allow updating the severity', async () => {
+    const { ui } = getPageObject();
+    const issue = mockRawIssue(false, {
+      severity: IssueSeverity.Blocker,
+      actions: [IssueActions.SetSeverity],
+    });
+    issuesHandler.setIssueList([{ issue, snippets: {} }]);
+    renderIssue({ issue: mockIssue(false, { ...pick(issue, 'actions', 'key', 'severity') }) });
+
+    await ui.updateSeverity(IssueSeverity.Blocker, IssueSeverity.Minor);
+    expect(ui.updateSeverityBtn(IssueSeverity.Minor).get()).toBeInTheDocument();
+  });
+
+  it('should allow updating the status', async () => {
+    const { ui } = getPageObject();
+    const issue = mockRawIssue(false, {
+      status: IssueStatus.Open,
+      transitions: [IssueTransition.Confirm, IssueTransition.UnConfirm],
+    });
+    issuesHandler.setIssueList([{ issue, snippets: {} }]);
+    renderIssue({
+      issue: mockIssue(false, { ...pick(issue, 'key', 'status', 'transitions') }),
+    });
+
+    await ui.updateStatus(IssueStatus.Open, IssueTransition.Confirm);
+    expect(ui.updateStatusBtn(IssueStatus.Confirmed).get()).toBeInTheDocument();
+  });
+
+  it('should allow assigning', async () => {
+    const { ui } = getPageObject();
+    const issue = mockRawIssue(false, {
+      assignee: 'leia',
+      actions: [IssueActions.Assign],
+    });
+    issuesHandler.setIssueList([{ issue, snippets: {} }]);
+    renderIssue({
+      issue: mockIssue(false, { ...pick(issue, 'actions', 'key', 'assignee') }),
+    });
+
+    await ui.updateAssignee('leia', 'Skywalker');
+    expect(ui.updateAssigneeBtn('luke').get()).toBeInTheDocument();
+  });
+
+  it('should allow commenting', async () => {
+    const { ui } = getPageObject();
+    const issue = mockRawIssue(false, {
+      actions: [IssueActions.Comment],
+    });
+    issuesHandler.setIssueList([{ issue, snippets: {} }]);
+    renderIssue({ issue: mockIssue(false, { ...pick(issue, 'actions', 'key') }) });
+
+    // Create
+    await ui.addComment('Original content');
+    const comments = within(ui.commentsList());
+    expect(comments.getByRole('listitem')).toHaveTextContent('Original content');
+
+    // Update
+    await ui.updateComment('New content');
+    expect(comments.getByRole('listitem')).toHaveTextContent('New content');
+
+    // Delete
+    await ui.deleteComment();
+    expect(comments.getByRole('listitem')).toHaveTextContent('New content');
+  });
+
+  it('should allow updating the tags', async () => {
+    const { ui } = getPageObject();
+    const issue = mockRawIssue(false, {
+      tags: [],
+      actions: [IssueActions.SetTags],
+    });
+    issuesHandler.setIssueList([{ issue, snippets: {} }]);
+    renderIssue({ issue: mockIssue(false, { ...pick(issue, 'actions', 'key', 'tags') }) });
+
+    await ui.addTag('accessibility');
+    await ui.addTag('android', ['accessibility']);
+    expect(ui.updateTagsBtn(['accessibility', 'android']).get()).toBeInTheDocument();
+  });
+});
+
+it('should correctly handle keyboard shortcuts', async () => {
+  const { ui } = getPageObject();
+  const onCheck = jest.fn();
+  const issue = mockRawIssue(false, {
+    actions: Object.values(IssueActions),
+    assignee: 'luke',
+    transitions: [IssueTransition.Confirm, IssueTransition.UnConfirm],
+  });
+  issuesHandler.setIssueList([{ issue, snippets: {} }]);
+  issuesHandler.setCurrentUser(mockLoggedInUser({ login: 'leia', name: 'Organa' }));
+  renderIssue({
+    onCheck,
+    selected: true,
+    issue: mockIssue(false, { ...pick(issue, 'actions', 'key', 'assignee', 'transitions') }),
+  });
+
+  await ui.pressTransitionShortcut();
+  expect(ui.setStatusBtn(IssueTransition.UnConfirm).get()).toBeInTheDocument();
+  await ui.pressDismissShortcut();
+
+  await ui.pressAssignShortcut();
+  expect(ui.setAssigneeBtn(/Organa/).get()).toBeInTheDocument();
+  await ui.pressDismissShortcut();
+
+  await ui.pressSeverityShortcut();
+  expect(ui.setSeverityBtn(IssueSeverity.Minor).get()).toBeInTheDocument();
+  await ui.pressDismissShortcut();
+
+  await ui.pressCommentShortcut();
+  expect(ui.commentTextInput.get()).toBeInTheDocument();
+  await ui.pressDismissShortcut();
+
+  await ui.pressTagsShortcut();
+  expect(ui.tagsSearchInput.get()).toBeInTheDocument();
+  await ui.pressDismissShortcut();
+
+  await ui.pressCheckShortcut();
+  expect(onCheck).toHaveBeenCalled();
+
+  expect(ui.updateAssigneeBtn('luke').get()).toBeInTheDocument();
+  await ui.pressAssignToMeShortcut();
+  expect(ui.updateAssigneeBtn('leia').get()).toBeInTheDocument();
+});
+
+it('should correctly handle similar issues filtering', async () => {
+  const { ui, user } = getPageObject();
+  const onFilter = jest.fn();
+  const issue = mockIssue(false, {
+    ruleName: 'Rule Foo',
+    tags: ['accessibility', 'owasp'],
+    projectName: 'Project Bar',
+    componentLongName: 'main.js',
+  });
+  renderIssue({ onFilter, issue });
+
+  await ui.showSimilarIssues();
+  await user.click(ui.similarIssueTypeLink.get());
+  expect(onFilter).toHaveBeenLastCalledWith('type', issue);
+
+  await ui.showSimilarIssues();
+  await user.click(ui.similarIssueSeverityLink.get());
+  expect(onFilter).toHaveBeenLastCalledWith('severity', issue);
+
+  await ui.showSimilarIssues();
+  await user.click(ui.similarIssueStatusLink.get());
+  expect(onFilter).toHaveBeenLastCalledWith('status', issue);
+
+  await ui.showSimilarIssues();
+  await user.click(ui.similarIssueResolutionLink.get());
+  expect(onFilter).toHaveBeenLastCalledWith('resolution', issue);
+
+  await ui.showSimilarIssues();
+  await user.click(ui.similarIssueAssigneeLink.get());
+  expect(onFilter).toHaveBeenLastCalledWith('assignee', issue);
+
+  await ui.showSimilarIssues();
+  await user.click(ui.similarIssueRuleLink.get());
+  expect(onFilter).toHaveBeenLastCalledWith('rule', issue);
+
+  await ui.showSimilarIssues();
+  await user.click(ui.similarIssueTagLink('accessibility').get());
+  expect(onFilter).toHaveBeenLastCalledWith('tag###accessibility', issue);
+
+  await ui.showSimilarIssues();
+  await user.click(ui.similarIssueTagLink('owasp').get());
+  expect(onFilter).toHaveBeenLastCalledWith('tag###owasp', issue);
+
+  await ui.showSimilarIssues();
+  await user.click(ui.similarIssueProjectLink.get());
+  expect(onFilter).toHaveBeenLastCalledWith('project', issue);
+
+  await ui.showSimilarIssues();
+  await user.click(ui.similarIssueFileLink.get());
+  expect(onFilter).toHaveBeenLastCalledWith('file', issue);
+});
+
+function mockIssueCommentPosted4YearsAgo(overrides: Partial<IssueComment> = {}) {
+  const date = new Date();
+  date.setFullYear(date.getFullYear() - 4);
+  return mockIssueComment({
+    authorName: 'Leïa Skywalker',
+    createdAt: date.toISOString(),
+    ...overrides,
+  });
+}
+
+function getPageObject() {
+  const user = userEvent.setup();
+
+  const selectors = {
+    // Issue
+    ruleStatusBadge: (status: RuleStatus) => byText(`issue.resolution.badge.${status}`),
+    locationsBadge: (count: number) => byText(count),
+    lineInfo: (line: number) => byText(`L${line}`),
+    permalink: byRole('link', { name: 'permalink' }),
+    effort: (effort: string) => byText(`issue.x_effort.${effort}`),
+    whyLink: byRole('link', { name: 'issue.why_this_issue.long' }),
+    checkbox: byRole('checkbox'),
+    issueMessageBtn: byRole('button', { name: 'This is an issue' }),
+
+    // Changelog
+    toggleChangelogBtn: byRole('button', {
+      name: /issue.changelog.found_on_x_show_more/,
+    }),
+    changelogRow: (key: string, oldValue: string, newValue: string) =>
+      byRole('row', {
+        name: new RegExp(
+          `issue\\.changelog\\.changed_to\\.issue\\.changelog\\.field\\.${key}\\.${newValue} \\(issue\\.changelog\\.was\\.${oldValue}\\)`
+        ),
+      }),
+
+    // Similar issues
+    toggleSimilarIssuesBtn: byRole('button', { name: 'issue.filter_similar_issues' }),
+    similarIssueTypeLink: byRole('button', { name: 'issue.type.BUG' }),
+    similarIssueSeverityLink: byRole('button', { name: 'severity.MAJOR' }),
+    similarIssueStatusLink: byRole('button', { name: 'issue.status.OPEN' }),
+    similarIssueResolutionLink: byRole('button', { name: 'unresolved' }),
+    similarIssueAssigneeLink: byRole('button', { name: 'unassigned' }),
+    similarIssueRuleLink: byRole('button', { name: 'Rule Foo' }),
+    similarIssueTagLink: (name: string) => byRole('button', { name }),
+    similarIssueProjectLink: byRole('button', { name: 'qualifier.TRK Project Bar' }),
+    similarIssueFileLink: byRole('button', { name: 'qualifier.FIL main.js' }),
+
+    // Comment
+    commentsList: () => {
+      const list = byRole('list')
+        .getAll()
+        .find((el) => el.getAttribute('data-testid') === 'issue-comments');
+      if (list === undefined) {
+        throw new Error('Could not find comments list');
+      }
+      return list;
+    },
+    commentAddBtn: byRole('button', { name: 'issue.comment.add_comment' }),
+    commentEditBtn: byRole('button', { name: 'issue.comment.edit' }),
+    commentTextInput: byRole('textbox', { name: 'issue.comment.enter_comment' }),
+    commentSaveBtn: byRole('button', { name: 'issue.comment.formlink' }),
+    commentUpdateBtn: byRole('button', { name: 'save' }),
+    commentDeleteBtn: byRole('button', { name: 'issue.comment.delete' }),
+    commentConfirmDeleteBtn: byRole('button', { name: 'delete' }),
+
+    // Type
+    updateTypeBtn: (currentType: IssueType) =>
+      byRole('button', { name: `issue.type.type_x_click_to_change.issue.type.${currentType}` }),
+    setTypeBtn: (type: IssueType) => byRole('button', { name: `issue.type.${type}` }),
+
+    // Severity
+    updateSeverityBtn: (currentSeverity: IssueSeverity) =>
+      byRole('button', {
+        name: `issue.severity.severity_x_click_to_change.severity.${currentSeverity}`,
+      }),
+    setSeverityBtn: (severity: IssueSeverity) => byRole('button', { name: `severity.${severity}` }),
+
+    // Status
+    updateStatusBtn: (currentStatus: IssueStatus) =>
+      byRole('button', {
+        name: `issue.transition.status_x_click_to_change.issue.status.${currentStatus}`,
+      }),
+    setStatusBtn: (transition: IssueTransition) =>
+      byRole('button', { name: `issue.transition.${transition}` }),
+
+    // Assignee
+    assigneeSearchInput: byRole('searchbox'),
+    updateAssigneeBtn: (currentAssignee: string) =>
+      byRole('button', {
+        name: `issue.assign.assigned_to_x_click_to_change.${currentAssignee}`,
+      }),
+    setAssigneeBtn: (name: RegExp) => byRole('button', { name }),
+
+    // Tags
+    tagsSearchInput: byRole('searchbox'),
+    updateTagsBtn: (currentTags?: string[]) =>
+      byRole('button', {
+        name: `tags_list_x.${currentTags ? currentTags.join(', ') : 'issue.no_tag'}`,
+      }),
+    toggleTagCheckbox: (name: string) => byRole('checkbox', { name }),
+  };
+
+  const ui = {
+    ...selectors,
+    async addComment(content: string) {
+      await user.click(selectors.commentAddBtn.get());
+      await user.type(selectors.commentTextInput.get(), content);
+      await act(async () => {
+        await user.click(selectors.commentSaveBtn.get());
+      });
+    },
+    async updateComment(content: string) {
+      await user.click(selectors.commentEditBtn.get());
+      await user.type(selectors.commentTextInput.get(), content);
+      await act(async () => {
+        await user.keyboard(`{Control>}{${KeyboardKeys.Enter}}{/Control}`);
+      });
+    },
+    async deleteComment() {
+      await user.click(selectors.commentDeleteBtn.get());
+      await act(async () => {
+        await user.click(selectors.commentConfirmDeleteBtn.get());
+      });
+    },
+    async updateType(currentType: IssueType, newType: IssueType) {
+      await user.click(selectors.updateTypeBtn(currentType).get());
+      await act(async () => {
+        await user.click(selectors.setTypeBtn(newType).get());
+      });
+    },
+    async updateSeverity(currentSeverity: IssueSeverity, newSeverity: IssueSeverity) {
+      await user.click(selectors.updateSeverityBtn(currentSeverity).get());
+      await act(async () => {
+        await user.click(selectors.setSeverityBtn(newSeverity).get());
+      });
+    },
+    async updateStatus(currentStatus: IssueStatus, transition: IssueTransition) {
+      await user.click(selectors.updateStatusBtn(currentStatus).get());
+      await act(async () => {
+        await user.click(selectors.setStatusBtn(transition).get());
+      });
+    },
+    async updateAssignee(currentAssignee: string, newAssignee: string) {
+      await user.click(selectors.updateAssigneeBtn(currentAssignee).get());
+      await user.type(selectors.assigneeSearchInput.get(), newAssignee);
+      await act(async () => {
+        await user.click(selectors.setAssigneeBtn(new RegExp(newAssignee)).get());
+      });
+    },
+    async addTag(tag: string, currentTagList?: string[]) {
+      await user.click(selectors.updateTagsBtn(currentTagList).get());
+      await act(async () => {
+        await user.click(selectors.toggleTagCheckbox(tag).get());
+      });
+      await act(async () => {
+        await user.keyboard('{Escape}');
+      });
+    },
+    async showChangelog() {
+      await user.click(selectors.toggleChangelogBtn.get());
+    },
+    async showSimilarIssues() {
+      await user.click(selectors.toggleSimilarIssuesBtn.get());
+    },
+    async toggleCheckbox() {
+      await user.click(selectors.checkbox.get());
+    },
+    async clickIssueMessage() {
+      await user.click(selectors.issueMessageBtn.get());
+    },
+    async pressDismissShortcut() {
+      await act(async () => {
+        await user.keyboard(`{${KeyboardKeys.Escape}}`);
+      });
+    },
+    async pressTransitionShortcut() {
+      await act(async () => {
+        await user.keyboard(`{${KeyboardKeys.KeyF}}`);
+      });
+    },
+    async pressAssignShortcut() {
+      await act(async () => {
+        await user.keyboard(`{${KeyboardKeys.KeyA}}`);
+      });
+    },
+    async pressAssignToMeShortcut() {
+      await act(async () => {
+        await user.keyboard(`{${KeyboardKeys.KeyM}}`);
+      });
+    },
+    async pressSeverityShortcut() {
+      await act(async () => {
+        await user.keyboard(`{${KeyboardKeys.KeyI}}`);
+      });
+    },
+    async pressCommentShortcut() {
+      await act(async () => {
+        await user.keyboard(`{${KeyboardKeys.KeyC}}`);
+      });
+    },
+    async pressTagsShortcut() {
+      await act(async () => {
+        await user.keyboard(`{${KeyboardKeys.KeyT}}`);
+      });
+    },
+    async pressCheckShortcut() {
+      await act(async () => {
+        await user.keyboard(`{${KeyboardKeys.Space}}`);
+      });
+    },
+  };
+
+  return { ui, user };
+}
+
+function renderIssue(props: Partial<Omit<Issue['props'], 'onChange' | 'onPopupToggle'>> = {}) {
+  function Wrapper(wrapperProps: Omit<Issue['props'], 'onChange' | 'onPopupToggle'>) {
+    const [issue, setIssue] = React.useState(wrapperProps.issue);
+    const [openPopup, setOpenPopup] = React.useState<string | undefined>();
+    return (
+      <Issue
+        issue={issue}
+        openPopup={openPopup}
+        onChange={(newIssue) => {
+          setIssue({ ...issue, ...newIssue });
+        }}
+        onPopupToggle={(_key, popup, open) => {
+          setOpenPopup(open === false ? undefined : popup);
+        }}
+        {...omit(wrapperProps, 'issue')}
+      />
+    );
+  }
+
+  return renderApp('/', <Wrapper issue={mockIssue()} selected={false} {...props} />, {
+    currentUser: mockLoggedInUser({ login: 'leia', name: 'Organa' }),
+  });
+}
diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/IssueView-test.tsx b/server/sonar-web/src/main/js/components/issue/__tests__/IssueView-test.tsx
deleted file mode 100644 (file)
index a1e3b0f..0000000
+++ /dev/null
@@ -1,62 +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 { mockIssue } from '../../../helpers/testMocks';
-import IssueView from '../IssueView';
-
-it('should render issues correctly', () => {
-  expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should render hotspots correctly', () => {
-  expect(
-    shallowRender({ issue: mockIssue(false, { type: 'SECURITY_HOTSPOT' }) })
-  ).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<IssueView['props']> = {}) {
-  return shallow(
-    <IssueView
-      issue={mockIssue(false, {
-        comments: [
-          {
-            key: '1',
-            htmlText: 'My comment',
-            markdown: 'My comment',
-            updatable: false,
-            createdAt: '2017-07-05T09:33:29+0200',
-            author: 'admin',
-            authorLogin: 'admin',
-            authorName: 'Admin',
-            authorAvatar: 'admin-avatar',
-            authorActive: true,
-          },
-        ],
-      })}
-      onAssign={jest.fn()}
-      onChange={jest.fn()}
-      onClick={jest.fn()}
-      selected={true}
-      togglePopup={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap
deleted file mode 100644 (file)
index bb33e70..0000000
+++ /dev/null
@@ -1,224 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render hotspots correctly 1`] = `
-<div
-  aria-label="Reduce the number of conditional operators (4) used in the expression"
-  className="issue hotspot selected"
-  onClick={[Function]}
-  role="region"
->
-  <IssueTitleBar
-    issue={
-      {
-        "actions": [],
-        "component": "main.js",
-        "componentEnabled": true,
-        "componentLongName": "main.js",
-        "componentQualifier": "FIL",
-        "componentUuid": "foo1234",
-        "creationDate": "2017-03-01T09:36:01+0100",
-        "flows": [],
-        "flowsWithType": [],
-        "key": "AVsae-CQS-9G3txfbFN2",
-        "line": 25,
-        "message": "Reduce the number of conditional operators (4) used in the expression",
-        "project": "myproject",
-        "projectKey": "foo",
-        "projectName": "Foo",
-        "rule": "javascript:S1067",
-        "ruleName": "foo",
-        "scope": "MAIN",
-        "secondaryLocations": [],
-        "severity": "MAJOR",
-        "status": "OPEN",
-        "textRange": {
-          "endLine": 26,
-          "endOffset": 15,
-          "startLine": 25,
-          "startOffset": 0,
-        },
-        "transitions": [],
-        "type": "SECURITY_HOTSPOT",
-      }
-    }
-    onClick={[Function]}
-    togglePopup={[MockFunction]}
-  />
-  <IssueActionsBar
-    className="padded-left"
-    issue={
-      {
-        "actions": [],
-        "component": "main.js",
-        "componentEnabled": true,
-        "componentLongName": "main.js",
-        "componentQualifier": "FIL",
-        "componentUuid": "foo1234",
-        "creationDate": "2017-03-01T09:36:01+0100",
-        "flows": [],
-        "flowsWithType": [],
-        "key": "AVsae-CQS-9G3txfbFN2",
-        "line": 25,
-        "message": "Reduce the number of conditional operators (4) used in the expression",
-        "project": "myproject",
-        "projectKey": "foo",
-        "projectName": "Foo",
-        "rule": "javascript:S1067",
-        "ruleName": "foo",
-        "scope": "MAIN",
-        "secondaryLocations": [],
-        "severity": "MAJOR",
-        "status": "OPEN",
-        "textRange": {
-          "endLine": 26,
-          "endOffset": 15,
-          "startLine": 25,
-          "startOffset": 0,
-        },
-        "transitions": [],
-        "type": "SECURITY_HOTSPOT",
-      }
-    }
-    onAssign={[MockFunction]}
-    onChange={[MockFunction]}
-    togglePopup={[MockFunction]}
-  />
-</div>
-`;
-
-exports[`should render issues correctly 1`] = `
-<div
-  aria-label="Reduce the number of conditional operators (4) used in the expression"
-  className="issue selected"
-  onClick={[Function]}
-  role="region"
->
-  <IssueTitleBar
-    issue={
-      {
-        "actions": [],
-        "comments": [
-          {
-            "author": "admin",
-            "authorActive": true,
-            "authorAvatar": "admin-avatar",
-            "authorLogin": "admin",
-            "authorName": "Admin",
-            "createdAt": "2017-07-05T09:33:29+0200",
-            "htmlText": "My comment",
-            "key": "1",
-            "markdown": "My comment",
-            "updatable": false,
-          },
-        ],
-        "component": "main.js",
-        "componentEnabled": true,
-        "componentLongName": "main.js",
-        "componentQualifier": "FIL",
-        "componentUuid": "foo1234",
-        "creationDate": "2017-03-01T09:36:01+0100",
-        "flows": [],
-        "flowsWithType": [],
-        "key": "AVsae-CQS-9G3txfbFN2",
-        "line": 25,
-        "message": "Reduce the number of conditional operators (4) used in the expression",
-        "project": "myproject",
-        "projectKey": "foo",
-        "projectName": "Foo",
-        "rule": "javascript:S1067",
-        "ruleName": "foo",
-        "scope": "MAIN",
-        "secondaryLocations": [],
-        "severity": "MAJOR",
-        "status": "OPEN",
-        "textRange": {
-          "endLine": 26,
-          "endOffset": 15,
-          "startLine": 25,
-          "startOffset": 0,
-        },
-        "transitions": [],
-        "type": "BUG",
-      }
-    }
-    onClick={[Function]}
-    togglePopup={[MockFunction]}
-  />
-  <IssueActionsBar
-    className="padded-left"
-    issue={
-      {
-        "actions": [],
-        "comments": [
-          {
-            "author": "admin",
-            "authorActive": true,
-            "authorAvatar": "admin-avatar",
-            "authorLogin": "admin",
-            "authorName": "Admin",
-            "createdAt": "2017-07-05T09:33:29+0200",
-            "htmlText": "My comment",
-            "key": "1",
-            "markdown": "My comment",
-            "updatable": false,
-          },
-        ],
-        "component": "main.js",
-        "componentEnabled": true,
-        "componentLongName": "main.js",
-        "componentQualifier": "FIL",
-        "componentUuid": "foo1234",
-        "creationDate": "2017-03-01T09:36:01+0100",
-        "flows": [],
-        "flowsWithType": [],
-        "key": "AVsae-CQS-9G3txfbFN2",
-        "line": 25,
-        "message": "Reduce the number of conditional operators (4) used in the expression",
-        "project": "myproject",
-        "projectKey": "foo",
-        "projectName": "Foo",
-        "rule": "javascript:S1067",
-        "ruleName": "foo",
-        "scope": "MAIN",
-        "secondaryLocations": [],
-        "severity": "MAJOR",
-        "status": "OPEN",
-        "textRange": {
-          "endLine": 26,
-          "endOffset": 15,
-          "startLine": 25,
-          "startOffset": 0,
-        },
-        "transitions": [],
-        "type": "BUG",
-      }
-    }
-    onAssign={[MockFunction]}
-    onChange={[MockFunction]}
-    togglePopup={[MockFunction]}
-  />
-  <div
-    className="issue-comments"
-  >
-    <IssueCommentLine
-      comment={
-        {
-          "author": "admin",
-          "authorActive": true,
-          "authorAvatar": "admin-avatar",
-          "authorLogin": "admin",
-          "authorName": "Admin",
-          "createdAt": "2017-07-05T09:33:29+0200",
-          "htmlText": "My comment",
-          "key": "1",
-          "markdown": "My comment",
-          "updatable": false,
-        }
-      }
-      key="1"
-      onDelete={[Function]}
-      onEdit={[Function]}
-    />
-  </div>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/issue-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/issue-test.tsx.snap
deleted file mode 100644 (file)
index 655c1f6..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render issues correctly 1`] = `
-<IssueView
-  displayLocationsCount={true}
-  displayLocationsLink={false}
-  issue={
-    {
-      "actions": [],
-      "comments": [
-        {
-          "author": "admin",
-          "authorActive": true,
-          "authorAvatar": "admin-avatar",
-          "authorLogin": "admin",
-          "authorName": "Admin",
-          "createdAt": "2017-07-05T09:33:29+0200",
-          "htmlText": "My comment",
-          "key": "1",
-          "markdown": "My comment",
-          "updatable": false,
-        },
-      ],
-      "component": "main.js",
-      "componentEnabled": true,
-      "componentLongName": "main.js",
-      "componentQualifier": "FIL",
-      "componentUuid": "foo1234",
-      "creationDate": "2017-03-01T09:36:01+0100",
-      "flows": [],
-      "flowsWithType": [],
-      "key": "AVsae-CQS-9G3txfbFN2",
-      "line": 25,
-      "message": "Reduce the number of conditional operators (4) used in the expression",
-      "project": "myproject",
-      "projectKey": "foo",
-      "projectName": "Foo",
-      "rule": "javascript:S1067",
-      "ruleName": "foo",
-      "scope": "MAIN",
-      "secondaryLocations": [],
-      "severity": "MAJOR",
-      "status": "OPEN",
-      "textRange": {
-        "endLine": 26,
-        "endOffset": 15,
-        "startLine": 25,
-        "startOffset": 0,
-      },
-      "transitions": [],
-      "type": "BUG",
-    }
-  }
-  onAssign={[Function]}
-  onChange={[MockFunction]}
-  onCheck={[MockFunction]}
-  onClick={[MockFunction]}
-  onFilter={[MockFunction]}
-  selected={true}
-  togglePopup={[Function]}
-/>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/__tests__/issue-test.tsx b/server/sonar-web/src/main/js/components/issue/__tests__/issue-test.tsx
deleted file mode 100644 (file)
index d80be59..0000000
+++ /dev/null
@@ -1,113 +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 { KeyboardKeys } from '../../../helpers/keycodes';
-import { mockIssue } from '../../../helpers/testMocks';
-import { keydown } from '../../../helpers/testUtils';
-import Issue from '../Issue';
-
-it('should render issues correctly', () => {
-  expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should call the proper function with the proper props when pressing shortcuts (FAMICT)', () => {
-  const onPopupToggle = jest.fn();
-  const onCheck = jest.fn();
-  const issue = mockIssue(false, {
-    comments: [
-      {
-        key: '1',
-        htmlText: 'My comment',
-        markdown: 'My comment',
-        updatable: false,
-        createdAt: '2017-07-05T09:33:29+0200',
-        author: 'admin',
-        authorLogin: 'admin',
-        authorName: 'Admin',
-        authorAvatar: 'admin-avatar',
-        authorActive: true,
-      },
-    ],
-    actions: ['assign'],
-  });
-
-  shallowRender({ onPopupToggle, issue, onCheck });
-  keydown({ key: KeyboardKeys.KeyF, metaKey: true });
-  expect(onPopupToggle).not.toHaveBeenCalledWith(issue.key, 'transition', undefined);
-
-  keydown({ key: KeyboardKeys.KeyF });
-  expect(onPopupToggle).toHaveBeenCalledWith(issue.key, 'transition', undefined);
-
-  keydown({ key: KeyboardKeys.KeyA });
-  expect(onPopupToggle).toHaveBeenCalledWith(issue.key, 'assign', undefined);
-  keydown({ key: KeyboardKeys.Escape });
-
-  keydown({ key: KeyboardKeys.KeyM });
-  expect(onPopupToggle).toHaveBeenCalledWith(issue.key, 'assign', false);
-
-  keydown({ key: KeyboardKeys.KeyI });
-  expect(onPopupToggle).toHaveBeenCalledWith(issue.key, 'set-severity', undefined);
-
-  keydown({ key: KeyboardKeys.KeyC, metaKey: true });
-  expect(onPopupToggle).not.toHaveBeenCalledWith(issue.key, 'comment', undefined);
-
-  keydown({ key: KeyboardKeys.KeyC });
-  expect(onPopupToggle).toHaveBeenCalledWith(issue.key, 'comment', undefined);
-  keydown({ key: KeyboardKeys.Escape });
-
-  keydown({ key: KeyboardKeys.KeyT });
-  expect(onPopupToggle).toHaveBeenCalledWith(issue.key, 'edit-tags', undefined);
-
-  keydown({ key: KeyboardKeys.Space });
-  expect(onCheck).toHaveBeenCalledWith(issue.key);
-});
-
-function shallowRender(props: Partial<Issue['props']> = {}) {
-  return shallow<Issue>(
-    <Issue
-      displayLocationsCount={true}
-      displayLocationsLink={false}
-      issue={mockIssue(false, {
-        comments: [
-          {
-            key: '1',
-            htmlText: 'My comment',
-            markdown: 'My comment',
-            updatable: false,
-            createdAt: '2017-07-05T09:33:29+0200',
-            author: 'admin',
-            authorLogin: 'admin',
-            authorName: 'Admin',
-            authorAvatar: 'admin-avatar',
-            authorActive: true,
-          },
-        ],
-      })}
-      onChange={jest.fn()}
-      onCheck={jest.fn()}
-      onClick={jest.fn()}
-      onFilter={jest.fn()}
-      onPopupToggle={jest.fn()}
-      selected={true}
-      {...props}
-    />
-  );
-}
index 3856c5489ddecbb32424792d3e003c8451fa9f5d..1153e95b6c891b78e37769be4b5c8f0518fe6dda 100644 (file)
 import classNames from 'classnames';
 import * as React from 'react';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { IssueResolution, IssueResponse, IssueType as IssueTypeEnum } from '../../../types/issues';
+import {
+  IssueActions,
+  IssueResolution,
+  IssueResponse,
+  IssueType as IssueTypeEnum,
+} from '../../../types/issues';
 import { Issue, RawQuery } from '../../../types/types';
 import { updateIssue } from '../actions';
 import IssueAssign from './IssueAssign';
@@ -90,13 +95,12 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> {
 
   render() {
     const { issue, className, showCommentsInPopup } = this.props;
-    const canAssign = issue.actions.includes('assign');
-    const canComment = issue.actions.includes('comment');
-    const canSetSeverity = issue.actions.includes('set_severity');
-    const canSetType = issue.actions.includes('set_type');
-    const canSetTags = issue.actions.includes('set_tags');
-    const hasTransitions = issue.transitions && issue.transitions.length > 0;
-    const isSecurityHotspot = issue.type === IssueTypeEnum.SecurityHotspot;
+    const canAssign = issue.actions.includes(IssueActions.Assign);
+    const canComment = issue.actions.includes(IssueActions.Comment);
+    const canSetSeverity = issue.actions.includes(IssueActions.SetSeverity);
+    const canSetType = issue.actions.includes(IssueActions.SetType);
+    const canSetTags = issue.actions.includes(IssueActions.SetTags);
+    const hasTransitions = issue.transitions.length > 0;
 
     return (
       <div className={classNames(className, 'issue-actions')}>
@@ -110,17 +114,15 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> {
               togglePopup={this.props.togglePopup}
             />
           </div>
-          {!isSecurityHotspot && (
-            <div className="issue-meta">
-              <IssueSeverity
-                canSetSeverity={canSetSeverity}
-                isOpen={this.props.currentPopup === 'set-severity' && canSetSeverity}
-                issue={issue}
-                setIssueProperty={this.setIssueProperty}
-                togglePopup={this.props.togglePopup}
-              />
-            </div>
-          )}
+          <div className="issue-meta">
+            <IssueSeverity
+              canSetSeverity={canSetSeverity}
+              isOpen={this.props.currentPopup === 'set-severity' && canSetSeverity}
+              issue={issue}
+              setIssueProperty={this.setIssueProperty}
+              togglePopup={this.props.togglePopup}
+            />
+          </div>
           <div className="issue-meta">
             <IssueTransition
               hasTransitions={hasTransitions}
@@ -139,7 +141,7 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> {
               togglePopup={this.props.togglePopup}
             />
           </div>
-          {!isSecurityHotspot && issue.effort && (
+          {issue.effort && (
             <div className="issue-meta">
               <span className="issue-meta-label">
                 {translateWithParameters('issue.x_effort', issue.effort)}
index 474292dd00c3e306a2247e1f10ab33f475460841..0807942f0415151e4fd4a834068f1e98e87f37ae 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import { injectIntl, WrappedComponentProps } from 'react-intl';
 import { ButtonLink } from '../../../components/controls/buttons';
 import Toggler from '../../../components/controls/Toggler';
 import DropdownIcon from '../../../components/icons/DropdownIcon';
+import { translateWithParameters } from '../../../helpers/l10n';
 import { Issue } from '../../../types/types';
+import { formatterOption } from '../../intl/DateFormatter';
 import DateFromNow from '../../intl/DateFromNow';
 import ChangelogPopup from '../popups/ChangelogPopup';
 
-interface Props {
+export interface IssueChangelogProps extends WrappedComponentProps {
   isOpen: boolean;
   issue: Pick<Issue, 'author' | 'creationDate' | 'key'>;
   creationDate: string;
   togglePopup: (popup: string, show?: boolean) => void;
 }
 
-export default class IssueChangelog extends React.PureComponent<Props> {
-  toggleChangelog = (open?: boolean) => {
-    this.props.togglePopup('changelog', open);
-  };
-
-  handleClick = () => {
-    this.toggleChangelog();
-  };
-
-  handleClose = () => {
-    this.toggleChangelog(false);
-  };
-
-  render() {
-    return (
-      <div className="dropdown">
-        <Toggler
-          onRequestClose={this.handleClose}
-          open={this.props.isOpen}
-          overlay={<ChangelogPopup issue={this.props.issue} />}
+function IssueChangelog(props: IssueChangelogProps) {
+  const {
+    isOpen,
+    issue,
+    creationDate,
+    intl: { formatDate },
+  } = props;
+  return (
+    <div className="dropdown">
+      <Toggler
+        onRequestClose={() => {
+          props.togglePopup('changelog', false);
+        }}
+        open={isOpen}
+        overlay={<ChangelogPopup issue={issue} />}
+      >
+        <ButtonLink
+          aria-expanded={isOpen}
+          aria-label={translateWithParameters(
+            'issue.changelog.found_on_x_show_more',
+            formatDate(creationDate, formatterOption)
+          )}
+          className="issue-action issue-action-with-options js-issue-show-changelog"
+          onClick={() => {
+            props.togglePopup('changelog');
+          }}
         >
-          <ButtonLink
-            aria-expanded={this.props.isOpen}
-            className="issue-action issue-action-with-options js-issue-show-changelog"
-            onClick={this.handleClick}
-          >
-            <span className="issue-meta-label">
-              <DateFromNow date={this.props.creationDate} />
-            </span>
-            <DropdownIcon className="little-spacer-left" />
-          </ButtonLink>
-        </Toggler>
-      </div>
-    );
-  }
+          <span className="issue-meta-label">
+            <DateFromNow date={creationDate} />
+          </span>
+          <DropdownIcon className="little-spacer-left" />
+        </ButtonLink>
+      </Toggler>
+    </div>
+  );
 }
+
+export default injectIntl(IssueChangelog);
index 476af1ec357ba9245adba99d8da631d5673ca43b..fdb0c7a6d2d0c1e19a2728d5dda06611115644ef 100644 (file)
@@ -22,18 +22,18 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { formatMeasure } from '../../../helpers/measures';
 import { IssueChangelogDiff as TypeIssueChangelogDiff } from '../../../types/types';
 
-interface Props {
+export interface IssueChangelogDiffProps {
   diff: TypeIssueChangelogDiff;
 }
 
-export default function IssueChangelogDiff({ diff }: Props) {
+export default function IssueChangelogDiff({ diff }: IssueChangelogDiffProps) {
   if (diff.key === 'file') {
     return (
       <p>
         {translateWithParameters(
           'issue.change.file_move',
-          diff.oldValue || '',
-          diff.newValue || ''
+          diff.oldValue ?? '',
+          diff.newValue ?? ''
         )}
       </p>
     );
@@ -42,8 +42,8 @@ export default function IssueChangelogDiff({ diff }: Props) {
       <p>
         {translateWithParameters(
           'issue.change.from_branch',
-          diff.oldValue || '',
-          diff.newValue || ''
+          diff.oldValue ?? '',
+          diff.newValue ?? ''
         )}
       </p>
     );
@@ -53,13 +53,13 @@ export default function IssueChangelogDiff({ diff }: Props) {
       <p>
         {translateWithParameters(
           'issue.change.from_non_branch',
-          diff.oldValue || '',
-          diff.newValue || ''
+          diff.oldValue ?? '',
+          diff.newValue ?? ''
         )}
       </p>
     );
   } else if (diff.key === 'line') {
-    return <p>{translateWithParameters('issue.changelog.line_removed_X', diff.oldValue || '')}</p>;
+    return <p>{translateWithParameters('issue.changelog.line_removed_X', diff.oldValue ?? '')}</p>;
   }
 
   let message;
index e60f161c599974e17c0a13435834a000057b7707..08ec3edc3cf3c02da069185750e243c7c43e9546 100644 (file)
@@ -85,7 +85,7 @@ export default class IssueCommentLine extends React.PureComponent<Props, State>
         ? translateWithParameters('user.x_deleted', author)
         : author;
     return (
-      <div className="issue-comment">
+      <li className="issue-comment">
         <div className="issue-comment-author" title={displayName}>
           <Avatar
             className="little-spacer-right"
@@ -145,7 +145,7 @@ export default class IssueCommentLine extends React.PureComponent<Props, State>
             </div>
           )}
         </div>
-      </div>
+      </li>
     );
   }
 }
index a4ab9133b34c47baf77e08dc575f175c13062fd8..5bf6636996f1fe88e8dada4d8238219bdd3f296b 100644 (file)
@@ -37,7 +37,7 @@ export default function IssueMessageTags(props: IssueMessageTagsProps) {
   const { engine, quickFixAvailable, ruleStatus } = props;
 
   const { externalRulesRepoNames } = React.useContext(WorkspaceContext);
-  const ruleEngine = (engine && externalRulesRepoNames && externalRulesRepoNames[engine]) || engine;
+  const ruleEngine = (engine && externalRulesRepoNames[engine]) || engine;
 
   return (
     <>
@@ -64,7 +64,7 @@ export default function IssueMessageTags(props: IssueMessageTagsProps) {
           <SonarLintIcon
             className="it__issues-sonarlint-quick-fix spacer-right"
             size={15}
-            label="sonar-lint-icon"
+            description={translate('issue.quick_fix_available_with_sonarlint_no_link')}
           />
         </Tooltip>
       )}
index 881e8b349244e9c2f14b233d8f01865c7915d23a..4df5ca5a8e71ad17c687c8d0326ab3f7fb8807b4 100644 (file)
@@ -26,6 +26,8 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { formatMeasure } from '../../../helpers/measures';
 import { getComponentIssuesUrl } from '../../../helpers/urls';
 import { BranchLike } from '../../../types/branch-like';
+import { IssueType } from '../../../types/issues';
+import { MetricType } from '../../../types/metrics';
 import { Issue } from '../../../types/types';
 import LocationIndex from '../../common/LocationIndex';
 import IssueChangelog from './IssueChangelog';
@@ -57,7 +59,7 @@ export default function IssueTitleBar(props: IssueTitleBarProps) {
     <Tooltip
       overlay={translateWithParameters(
         'issue.this_issue_involves_x_code_locations',
-        formatMeasure(locationsCount, 'INT')
+        formatMeasure(locationsCount, MetricType.Integer)
       )}
     >
       <LocationIndex>{locationsCount}</LocationIndex>
@@ -70,7 +72,7 @@ export default function IssueTitleBar(props: IssueTitleBarProps) {
     ...getBranchLikeQuery(props.branchLike),
     issues: issue.key,
     open: issue.key,
-    types: issue.type === 'SECURITY_HOTSPOT' ? issue.type : undefined,
+    types: issue.type === IssueType.SecurityHotspot ? issue.type : undefined,
   });
 
   return (
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueView.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueView.tsx
new file mode 100644 (file)
index 0000000..ccc675f
--- /dev/null
@@ -0,0 +1,155 @@
+/*
+ * 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 classNames from 'classnames';
+import * as React from 'react';
+import { deleteIssueComment, editIssueComment } from '../../../api/issues';
+import { translate, translateWithParameters } from '../../../helpers/l10n';
+import { BranchLike } from '../../../types/branch-like';
+import { Issue } from '../../../types/types';
+import Checkbox from '../../controls/Checkbox';
+import { updateIssue } from '../actions';
+import IssueActionsBar from './IssueActionsBar';
+import IssueCommentLine from './IssueCommentLine';
+import IssueTitleBar from './IssueTitleBar';
+
+interface Props {
+  branchLike?: BranchLike;
+  checked?: boolean;
+  currentPopup?: string;
+  displayWhyIsThisAnIssue?: boolean;
+  displayLocationsCount?: boolean;
+  displayLocationsLink?: boolean;
+  issue: Issue;
+  onAssign: (login: string) => void;
+  onChange: (issue: Issue) => void;
+  onCheck?: (issue: string) => void;
+  onClick?: (issueKey: string) => void;
+  onFilter?: (property: string, issue: Issue) => void;
+  selected: boolean;
+  togglePopup: (popup: string, show: boolean | void) => void;
+}
+
+export default class IssueView extends React.PureComponent<Props> {
+  handleCheck = () => {
+    if (this.props.onCheck) {
+      this.props.onCheck(this.props.issue.key);
+    }
+  };
+
+  handleBoxClick = (event: React.MouseEvent<HTMLDivElement>) => {
+    if (!isClickable(event.target as HTMLElement) && this.props.onClick) {
+      event.preventDefault();
+      this.handleDetailClick();
+    }
+  };
+
+  handleDetailClick = () => {
+    if (this.props.onClick) {
+      this.props.onClick(this.props.issue.key);
+    }
+  };
+
+  editComment = (comment: string, text: string) => {
+    updateIssue(this.props.onChange, editIssueComment({ comment, text }));
+  };
+
+  deleteComment = (comment: string) => {
+    updateIssue(this.props.onChange, deleteIssueComment({ comment }));
+  };
+
+  render() {
+    const {
+      issue,
+      branchLike,
+      checked,
+      currentPopup,
+      displayWhyIsThisAnIssue,
+      displayLocationsLink,
+      displayLocationsCount,
+    } = this.props;
+
+    const hasCheckbox = this.props.onCheck != null;
+
+    const issueClass = classNames('issue', {
+      'no-click': this.props.onClick === undefined,
+      'issue-with-checkbox': hasCheckbox,
+      selected: this.props.selected,
+    });
+
+    return (
+      <div
+        className={issueClass}
+        onClick={this.handleBoxClick}
+        role="region"
+        aria-label={issue.message}
+      >
+        {hasCheckbox && (
+          <Checkbox
+            checked={checked ?? false}
+            className="issue-checkbox-container"
+            onCheck={this.handleCheck}
+            label={translateWithParameters('issues.action_select.label', issue.message)}
+            title={translate('issues.action_select')}
+          />
+        )}
+        <IssueTitleBar
+          branchLike={branchLike}
+          onClick={this.handleDetailClick}
+          currentPopup={currentPopup}
+          displayLocationsCount={displayLocationsCount}
+          displayLocationsLink={displayLocationsLink}
+          displayWhyIsThisAnIssue={displayWhyIsThisAnIssue}
+          issue={issue}
+          onFilter={this.props.onFilter}
+          togglePopup={this.props.togglePopup}
+        />
+        <IssueActionsBar
+          className="padded-left"
+          currentPopup={currentPopup}
+          issue={issue}
+          onAssign={this.props.onAssign}
+          onChange={this.props.onChange}
+          togglePopup={this.props.togglePopup}
+        />
+        {issue.comments && issue.comments.length > 0 && (
+          <ul className="issue-comments" data-testid="issue-comments">
+            {issue.comments.map((comment) => (
+              <IssueCommentLine
+                comment={comment}
+                key={comment.key}
+                onDelete={this.deleteComment}
+                onEdit={this.editComment}
+              />
+            ))}
+          </ul>
+        )}
+      </div>
+    );
+  }
+}
+
+function isClickable(node: HTMLElement | undefined | null): boolean {
+  if (!node) {
+    return false;
+  }
+  const clickableTags = ['A', 'BUTTON', 'INPUT', 'TEXTAREA'];
+  const tagName = (node.tagName || '').toUpperCase();
+  return clickableTags.includes(tagName) || isClickable(node.parentElement);
+}
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueActionsBar-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueActionsBar-test.tsx
deleted file mode 100644 (file)
index 191d565..0000000
+++ /dev/null
@@ -1,124 +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 { mockIssue } from '../../../../helpers/testMocks';
-import { Issue } from '../../../../types/types';
-import IssueActionsBar from '../IssueActionsBar';
-
-jest.mock('../../actions', () => ({ updateIssue: jest.fn() }));
-
-it('should render issue correctly', () => {
-  const element = shallow(
-    <IssueActionsBar
-      issue={mockIssue()}
-      onAssign={jest.fn()}
-      onChange={jest.fn()}
-      togglePopup={jest.fn()}
-    />
-  );
-  expect(element).toMatchSnapshot();
-});
-
-it('should render security hotspot correctly', () => {
-  const element = shallow(
-    <IssueActionsBar
-      issue={mockIssue(false, { type: 'SECURITY_HOTSPOT' })}
-      onAssign={jest.fn()}
-      onChange={jest.fn()}
-      togglePopup={jest.fn()}
-    />
-  );
-  expect(element).toMatchSnapshot();
-});
-
-it('should render commentable correctly', () => {
-  const element = shallow(
-    <IssueActionsBar
-      issue={mockIssue(false, { actions: ['comment'] })}
-      onAssign={jest.fn()}
-      onChange={jest.fn()}
-      togglePopup={jest.fn()}
-    />
-  );
-  expect(element).toMatchSnapshot();
-});
-
-it('should render effort correctly', () => {
-  const element = shallow(
-    <IssueActionsBar
-      issue={mockIssue(false, { effort: 'great' })}
-      onAssign={jest.fn()}
-      onChange={jest.fn()}
-      togglePopup={jest.fn()}
-    />
-  );
-  expect(element).toMatchSnapshot();
-});
-
-describe('callback', () => {
-  const issue: Issue = mockIssue();
-  const onChangeMock = jest.fn();
-  const togglePopupMock = jest.fn();
-
-  const element = shallow<IssueActionsBar>(
-    <IssueActionsBar
-      issue={issue}
-      onAssign={jest.fn()}
-      onChange={onChangeMock}
-      togglePopup={togglePopupMock}
-    />
-  );
-
-  beforeEach(() => {
-    jest.clearAllMocks();
-  });
-
-  it('handleTransition should call onChange', () => {
-    const instance = element.instance();
-    instance.handleTransition(issue);
-    expect(onChangeMock).toHaveBeenCalledTimes(1);
-    expect(togglePopupMock).toHaveBeenCalledTimes(0);
-  });
-
-  it('setIssueProperty should call togglePopup', () => {
-    const instance = element.instance();
-
-    const apiCallMock = jest.fn();
-
-    instance.setIssueProperty('author', 'popup', apiCallMock, 'Jay');
-    expect(togglePopupMock).toHaveBeenCalledTimes(1);
-    expect(apiCallMock).toHaveBeenCalledTimes(1);
-  });
-
-  it('toggleComment should call togglePopup and update state', () => {
-    const instance = element.instance();
-
-    expect(element.state('commentAutoTriggered')).toBe(false);
-    expect(element.state('commentPlaceholder')).toBe('');
-
-    instance.toggleComment(false, 'hold my place', true);
-
-    expect(element.state('commentAutoTriggered')).toBe(true);
-    expect(element.state('commentPlaceholder')).toBe('hold my place');
-
-    expect(togglePopupMock).toHaveBeenCalledTimes(1);
-  });
-});
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueAssign-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueAssign-test.tsx
deleted file mode 100644 (file)
index bb077ad..0000000
+++ /dev/null
@@ -1,66 +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 { mockIssue } from '../../../../helpers/testMocks';
-import { click } from '../../../../helpers/testUtils';
-import IssueAssign from '../IssueAssign';
-
-const issue = mockIssue(false, {
-  assignee: 'john',
-  assigneeAvatar: 'gravatarhash',
-  assigneeName: 'John Doe',
-});
-
-it('should render without the action when the correct rights are missing', () => {
-  expect(shallowRender({ canAssign: false })).toMatchSnapshot();
-});
-
-it('should render with the action', () => {
-  expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should render a fallback assignee display if assignee info are not available', () => {
-  expect(
-    shallowRender({ issue: mockIssue(false, { assignee: undefined, assigneeName: undefined }) })
-  ).toMatchSnapshot();
-});
-
-it('should open the popup when the button is clicked', () => {
-  const togglePopup = jest.fn();
-  const element = shallowRender({ togglePopup });
-  click(element.find('ButtonLink'));
-  expect(togglePopup.mock.calls).toMatchSnapshot();
-  element.setProps({ isOpen: true });
-  expect(element).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<IssueAssign['props']> = {}) {
-  return shallow<IssueAssign>(
-    <IssueAssign
-      canAssign={true}
-      isOpen={false}
-      issue={issue}
-      onAssign={jest.fn()}
-      togglePopup={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelog-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelog-test.tsx
deleted file mode 100644 (file)
index 40f4c3e..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 { click } from '../../../../helpers/testUtils';
-import IssueChangelog from '../IssueChangelog';
-
-const issue = {
-  key: 'issuekey',
-  author: 'john.david.dalton@gmail.com',
-  creationDate: '2017-03-01T09:36:01+0100',
-};
-
-it('should render correctly', () => {
-  const element = shallowRender();
-  expect(element).toMatchSnapshot();
-});
-
-it('should open the popup when the button is clicked', () => {
-  const togglePopup = jest.fn();
-  const element = shallowRender({ togglePopup });
-  click(element.find('ButtonLink'));
-  expect(togglePopup.mock.calls).toMatchSnapshot();
-  element.setProps({ isOpen: true });
-  expect(element).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<IssueChangelog['props']> = {}) {
-  return shallow(
-    <IssueChangelog
-      creationDate="2017-03-01T09:36:01+0100"
-      isOpen={false}
-      issue={issue}
-      togglePopup={jest.fn()}
-      {...props}
-    />
-  );
-}
index e159697ab45df7e14c6aa44df6a8f61305ed644a..179ecf35bf556d8b440577b8c78064567b43a34e 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 { shallow } from 'enzyme';
+import { screen } from '@testing-library/react';
 import * as React from 'react';
-import { IssueChangelogDiff as TypeIssueChangelogDiff } from '../../../../types/types';
-import IssueChangelogDiff from '../IssueChangelogDiff';
+import { mockIssueChangelogDiff } from '../../../../helpers/mocks/issues';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import IssueChangelogDiff, { IssueChangelogDiffProps } from '../IssueChangelogDiff';
 
-it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot();
-});
+jest.mock('../../../../helpers/measures', () => ({
+  formatMeasure: jest
+    .fn()
+    .mockImplementation((value: string, type: string) => `formatted.${value}.as.${type}`),
+}));
 
-it('should render correctly file diff', () => {
-  expect(
-    shallowRender({ diff: { key: 'file', oldValue: 'foo/bar.js', newValue: 'bar/baz.js' } })
-  ).toMatchSnapshot();
-});
+it.each([
+  ['file', 'issue.change.file_move.oldValue.newValue', undefined],
+  ['from_branch', 'issue.change.from_branch.oldValue.newValue', undefined],
+  ['line', 'issue.changelog.line_removed_X.oldValue', undefined],
+  [
+    'effort',
+    'issue.changelog.changed_to.issue.changelog.field.effort.formatted.12.as.WORK_DUR',
+    { newValue: '12', oldValue: undefined },
+  ],
+  [
+    'effort',
+    'issue.changelog.removed.issue.changelog.field.effort (issue.changelog.was.formatted.14.as.WORK_DUR)',
+    { newValue: undefined, oldValue: '14' },
+  ],
+  [
+    'effort',
+    'issue.changelog.removed.issue.changelog.field.effort',
+    { newValue: undefined, oldValue: undefined },
+  ],
+  [
+    'assign',
+    'issue.changelog.changed_to.issue.changelog.field.assign.newValue (issue.changelog.was.oldValue)',
+    undefined,
+  ],
+  ['from_short_branch', 'issue.change.from_non_branch.oldValue.newValue', undefined],
 
-it('should render correctly branch diff', () => {
-  expect(
-    shallowRender({
-      diff: {
-        // Legacy key
-        key: 'from_long_branch',
-        oldValue: 'foo',
-        newValue: 'bar',
-      },
-    })
-  ).toMatchSnapshot();
+  // This should be deprecated. Can this still happen?
+  ['from_long_branch', 'issue.change.from_branch.oldValue.newValue', undefined],
+])(
+  'should render correctly for "%s" diff types',
+  (key, expected, diff?: Partial<IssueChangelogDiffProps['diff']>) => {
+    renderIssueChangelogDiff({
+      diff: mockIssueChangelogDiff({ key, newValue: 'newValue', oldValue: 'oldValue', ...diff }),
+    });
+    expect(screen.getByText(expected)).toBeInTheDocument();
+  }
+);
 
-  expect(
-    shallowRender({
-      diff: {
-        // Legacy key
-        key: 'from_short_branch',
-        oldValue: 'foo',
-        newValue: 'bar',
-      },
-    })
-  ).toMatchSnapshot();
-
-  expect(
-    shallowRender({
-      diff: {
-        key: 'from_branch',
-        oldValue: 'foo',
-        newValue: 'bar',
-      },
-    })
-  ).toMatchSnapshot();
-});
-
-it('should render correctly line diff', () => {
-  expect(shallowRender({ diff: { key: 'line', oldValue: '80' } })).toMatchSnapshot();
-});
-
-it('should render correctly effort diff', () => {
-  expect(shallowRender({ diff: { key: 'effort', newValue: '12' } })).toMatchSnapshot();
-  expect(
-    shallowRender({ diff: { key: 'effort', newValue: '12', oldValue: '10' } })
-  ).toMatchSnapshot();
-  expect(shallowRender({ diff: { key: 'effort', oldValue: '10' } })).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<{ diff: TypeIssueChangelogDiff }> = {}) {
-  return shallow(<IssueChangelogDiff diff={{ key: 'foo' }} {...props} />);
+function renderIssueChangelogDiff(props: Partial<IssueChangelogDiffProps> = {}) {
+  return renderComponent(<IssueChangelogDiff diff={mockIssueChangelogDiff()} {...props} />);
 }
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentLine-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentLine-test.tsx
deleted file mode 100644 (file)
index 98a3340..0000000
+++ /dev/null
@@ -1,68 +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 { IssueComment } from '../../../../types/types';
-import IssueCommentLine from '../IssueCommentLine';
-
-const comment: IssueComment = {
-  author: 'john.doe',
-  authorActive: true,
-  authorAvatar: 'gravatarhash',
-  authorName: 'John Doe',
-  createdAt: '2017-03-01T09:36:01+0100',
-  htmlText: '<b>test</b>',
-  key: 'comment-key',
-  markdown: '*test*',
-  updatable: true,
-};
-
-it('should render correctly a comment that is updatable', () => {
-  expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should render correctly a comment that is not updatable', () => {
-  expect(shallowRender({ comment: { ...comment, updatable: false } })).toMatchSnapshot();
-});
-
-it('should open the right popups when the buttons are clicked', () => {
-  const wrapper = shallowRender();
-  click(wrapper.find('.js-issue-comment-edit'));
-  expect(wrapper.state()).toMatchSnapshot();
-  click(wrapper.find('.js-issue-comment-delete'));
-  expect(wrapper.state()).toMatchSnapshot();
-  wrapper.update();
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should render correctly a comment with a deleted author', () => {
-  expect(
-    shallowRender({
-      comment: { ...comment, authorActive: false, authorName: undefined },
-    }).find('.issue-comment-author')
-  ).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<IssueCommentLine['props']> = {}) {
-  return shallow(
-    <IssueCommentLine comment={comment} onDelete={jest.fn()} onEdit={jest.fn()} {...props} />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx
deleted file mode 100644 (file)
index 8cea8e5..0000000
+++ /dev/null
@@ -1,66 +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 { mockBranch } from '../../../../helpers/mocks/branch-like';
-import { mockIssue } from '../../../../helpers/testMocks';
-import { RuleStatus } from '../../../../types/rules';
-import IssueMessage, { IssueMessageProps } from '../IssueMessage';
-
-jest.mock('react', () => {
-  return {
-    ...jest.requireActual('react'),
-    useContext: jest
-      .fn()
-      .mockImplementation(() => ({ externalRulesRepoNames: {}, openRule: jest.fn() })),
-  };
-});
-
-it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot('default');
-  expect(shallowRender({ issue: mockIssue(false, { externalRuleEngine: 'js' }) })).toMatchSnapshot(
-    'with engine info'
-  );
-  expect(shallowRender({ issue: mockIssue(false, { quickFixAvailable: true }) })).toMatchSnapshot(
-    'with quick fix'
-  );
-  expect(
-    shallowRender({ issue: mockIssue(false, { ruleStatus: RuleStatus.Deprecated }) })
-  ).toMatchSnapshot('is deprecated rule');
-  expect(
-    shallowRender({ issue: mockIssue(false, { ruleStatus: RuleStatus.Removed }) })
-  ).toMatchSnapshot('is removed rule');
-  expect(shallowRender({ displayWhyIsThisAnIssue: false })).toMatchSnapshot(
-    'hide why is it an issue'
-  );
-});
-
-function shallowRender(props: Partial<IssueMessageProps> = {}) {
-  return shallow<IssueMessageProps>(
-    <IssueMessage
-      issue={mockIssue(false, {
-        message: 'Reduce the number of conditional operators (4) used in the expression',
-      })}
-      displayWhyIsThisAnIssue={true}
-      branchLike={mockBranch()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueSeverity-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueSeverity-test.tsx
deleted file mode 100644 (file)
index f175342..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 { click } from '../../../../helpers/testUtils';
-import IssueSeverity from '../IssueSeverity';
-
-const issue = { severity: 'BLOCKER' };
-
-it('should render without the action when the correct rights are missing', () => {
-  expect(shallowRender({ canSetSeverity: false })).toMatchSnapshot();
-});
-
-it('should render with the action', () => {
-  expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should open the popup when the button is clicked', () => {
-  const togglePopup = jest.fn();
-  const element = shallowRender({ togglePopup });
-  click(element.find('ButtonLink'));
-  expect(togglePopup.mock.calls).toMatchSnapshot();
-  element.setProps({ isOpen: true });
-  expect(element).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<IssueSeverity['props']> = {}) {
-  return shallow(
-    <IssueSeverity
-      canSetSeverity={true}
-      isOpen={false}
-      issue={issue}
-      setIssueProperty={jest.fn()}
-      togglePopup={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.tsx
deleted file mode 100644 (file)
index 0a433da..0000000
+++ /dev/null
@@ -1,50 +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 { mockBranch } from '../../../../helpers/mocks/branch-like';
-import { mockIssue } from '../../../../helpers/testMocks';
-import IssueTitleBar, { IssueTitleBarProps } from '../IssueTitleBar';
-
-it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot('default');
-  expect(shallowRender({ onFilter: jest.fn() })).toMatchSnapshot('with filter');
-  expect(shallowRender({ displayLocationsCount: true, issue: mockIssue(true) })).toMatchSnapshot(
-    'with multi locations'
-  );
-  expect(
-    shallowRender({
-      branchLike: mockBranch(),
-      displayLocationsCount: true,
-      displayLocationsLink: true,
-      issue: mockIssue(true),
-    })
-  ).toMatchSnapshot('with multi locations and link');
-});
-
-function shallowRender(props: Partial<IssueTitleBarProps> = {}) {
-  return shallow<IssueTitleBarProps>(
-    <IssueTitleBar
-      issue={mockIssue(false, { externalRuleEngine: 'foo' })}
-      togglePopup={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTransition-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTransition-test.tsx
deleted file mode 100644 (file)
index e765a08..0000000
+++ /dev/null
@@ -1,51 +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 IssueTransition from '../IssueTransition';
-
-const issue: IssueTransition['props']['issue'] = {
-  key: 'foo1234',
-  transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
-  status: 'OPEN',
-  type: 'BUG',
-};
-
-it('should render without the action when there is no transitions', () => {
-  expect(
-    shallowRender({
-      hasTransitions: false,
-      issue: { key: 'foo1234', transitions: [], status: 'CLOSED', type: 'BUG' },
-    })
-  ).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<IssueTransition['props']> = {}) {
-  return shallow(
-    <IssueTransition
-      hasTransitions={true}
-      isOpen={false}
-      issue={issue}
-      onChange={jest.fn()}
-      togglePopup={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.tsx b/server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.tsx
deleted file mode 100644 (file)
index b5d0a5f..0000000
+++ /dev/null
@@ -1,42 +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 { Issue } from '../../../../types/types';
-import IssueType from '../IssueType';
-
-const issue: Pick<Issue, 'type'> = { type: 'BUG' };
-
-it('should render without the action when the correct rights are missing', () => {
-  expect(shallowRender({ canSetType: false })).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<IssueType['props']> = {}) {
-  return shallow(
-    <IssueType
-      canSetType={true}
-      isOpen={false}
-      issue={issue}
-      setIssueProperty={jest.fn()}
-      togglePopup={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap
deleted file mode 100644 (file)
index 0a5febf..0000000
+++ /dev/null
@@ -1,910 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render commentable correctly 1`] = `
-<div
-  className="issue-actions"
->
-  <div
-    className="issue-meta-list"
-  >
-    <div
-      className="issue-meta"
-    >
-      <IssueType
-        canSetType={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [
-              "comment",
-            ],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        setIssueProperty={[Function]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <div
-      className="issue-meta"
-    >
-      <IssueSeverity
-        canSetSeverity={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [
-              "comment",
-            ],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        setIssueProperty={[Function]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <div
-      className="issue-meta"
-    >
-      <IssueTransition
-        hasTransitions={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [
-              "comment",
-            ],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        onChange={[Function]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <div
-      className="issue-meta"
-    >
-      <IssueAssign
-        canAssign={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [
-              "comment",
-            ],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        onAssign={[MockFunction]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <IssueCommentAction
-      canComment={true}
-      commentAutoTriggered={false}
-      commentPlaceholder=""
-      issueKey="AVsae-CQS-9G3txfbFN2"
-      onChange={[MockFunction]}
-      toggleComment={[Function]}
-    />
-  </div>
-  <div
-    className="list-inline"
-  >
-    <div
-      className="issue-meta js-issue-tags"
-    >
-      <IssueTags
-        canSetTags={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [
-              "comment",
-            ],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        onChange={[MockFunction]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-  </div>
-</div>
-`;
-
-exports[`should render effort correctly 1`] = `
-<div
-  className="issue-actions"
->
-  <div
-    className="issue-meta-list"
-  >
-    <div
-      className="issue-meta"
-    >
-      <IssueType
-        canSetType={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "effort": "great",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        setIssueProperty={[Function]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <div
-      className="issue-meta"
-    >
-      <IssueSeverity
-        canSetSeverity={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "effort": "great",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        setIssueProperty={[Function]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <div
-      className="issue-meta"
-    >
-      <IssueTransition
-        hasTransitions={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "effort": "great",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        onChange={[Function]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <div
-      className="issue-meta"
-    >
-      <IssueAssign
-        canAssign={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "effort": "great",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        onAssign={[MockFunction]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <div
-      className="issue-meta"
-    >
-      <span
-        className="issue-meta-label"
-      >
-        issue.x_effort.great
-      </span>
-    </div>
-  </div>
-  <div
-    className="list-inline"
-  >
-    <div
-      className="issue-meta js-issue-tags"
-    >
-      <IssueTags
-        canSetTags={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "effort": "great",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        onChange={[MockFunction]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-  </div>
-</div>
-`;
-
-exports[`should render issue correctly 1`] = `
-<div
-  className="issue-actions"
->
-  <div
-    className="issue-meta-list"
-  >
-    <div
-      className="issue-meta"
-    >
-      <IssueType
-        canSetType={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        setIssueProperty={[Function]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <div
-      className="issue-meta"
-    >
-      <IssueSeverity
-        canSetSeverity={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        setIssueProperty={[Function]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <div
-      className="issue-meta"
-    >
-      <IssueTransition
-        hasTransitions={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        onChange={[Function]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <div
-      className="issue-meta"
-    >
-      <IssueAssign
-        canAssign={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        onAssign={[MockFunction]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-  </div>
-  <div
-    className="list-inline"
-  >
-    <div
-      className="issue-meta js-issue-tags"
-    >
-      <IssueTags
-        canSetTags={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "BUG",
-          }
-        }
-        onChange={[MockFunction]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-  </div>
-</div>
-`;
-
-exports[`should render security hotspot correctly 1`] = `
-<div
-  className="issue-actions"
->
-  <div
-    className="issue-meta-list"
-  >
-    <div
-      className="issue-meta"
-    >
-      <IssueType
-        canSetType={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "SECURITY_HOTSPOT",
-          }
-        }
-        setIssueProperty={[Function]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <div
-      className="issue-meta"
-    >
-      <IssueTransition
-        hasTransitions={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "SECURITY_HOTSPOT",
-          }
-        }
-        onChange={[Function]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-    <div
-      className="issue-meta"
-    >
-      <IssueAssign
-        canAssign={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "SECURITY_HOTSPOT",
-          }
-        }
-        onAssign={[MockFunction]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-  </div>
-  <div
-    className="list-inline"
-  >
-    <div
-      className="issue-meta js-issue-tags"
-    >
-      <IssueTags
-        canSetTags={false}
-        isOpen={false}
-        issue={
-          {
-            "actions": [],
-            "component": "main.js",
-            "componentEnabled": true,
-            "componentLongName": "main.js",
-            "componentQualifier": "FIL",
-            "componentUuid": "foo1234",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "flows": [],
-            "flowsWithType": [],
-            "key": "AVsae-CQS-9G3txfbFN2",
-            "line": 25,
-            "message": "Reduce the number of conditional operators (4) used in the expression",
-            "project": "myproject",
-            "projectKey": "foo",
-            "projectName": "Foo",
-            "rule": "javascript:S1067",
-            "ruleName": "foo",
-            "scope": "MAIN",
-            "secondaryLocations": [],
-            "severity": "MAJOR",
-            "status": "OPEN",
-            "textRange": {
-              "endLine": 26,
-              "endOffset": 15,
-              "startLine": 25,
-              "startOffset": 0,
-            },
-            "transitions": [],
-            "type": "SECURITY_HOTSPOT",
-          }
-        }
-        onChange={[MockFunction]}
-        togglePopup={[MockFunction]}
-      />
-    </div>
-  </div>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap
deleted file mode 100644 (file)
index 5a25cdc..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should open the popup when the button is clicked 1`] = `
-[
-  [
-    "assign",
-    undefined,
-  ],
-]
-`;
-
-exports[`should open the popup when the button is clicked 2`] = `
-<div
-  className="dropdown"
->
-  <Toggler
-    closeOnEscape={true}
-    onRequestClose={[Function]}
-    open={true}
-    overlay={
-      <withCurrentUserContext(SetAssigneePopup)
-        onSelect={[MockFunction]}
-      />
-    }
-  >
-    <ButtonLink
-      aria-expanded={true}
-      aria-label="issue.assign.assigned_to_x_click_to_change.John Doe"
-      className="issue-action issue-action-with-options js-issue-assign"
-      onClick={[Function]}
-    >
-      <span
-        className="text-top"
-      >
-        <withAppStateContext(Avatar)
-          className="little-spacer-right"
-          hash="gravatarhash"
-          name=""
-          size={16}
-        />
-      </span>
-      <span
-        className="issue-meta-label"
-        title="John Doe"
-      >
-        John Doe
-      </span>
-      <DropdownIcon
-        className="little-spacer-left"
-      />
-    </ButtonLink>
-  </Toggler>
-</div>
-`;
-
-exports[`should render a fallback assignee display if assignee info are not available 1`] = `
-<div
-  className="dropdown"
->
-  <Toggler
-    closeOnEscape={true}
-    onRequestClose={[Function]}
-    open={false}
-    overlay={
-      <withCurrentUserContext(SetAssigneePopup)
-        onSelect={[MockFunction]}
-      />
-    }
-  >
-    <ButtonLink
-      aria-expanded={false}
-      aria-label="issue.assign.unassigned_click_to_assign"
-      className="issue-action issue-action-with-options js-issue-assign"
-      onClick={[Function]}
-    >
-      <span
-        className="issue-meta-label"
-      >
-        unassigned
-      </span>
-      <DropdownIcon
-        className="little-spacer-left"
-      />
-    </ButtonLink>
-  </Toggler>
-</div>
-`;
-
-exports[`should render with the action 1`] = `
-<div
-  className="dropdown"
->
-  <Toggler
-    closeOnEscape={true}
-    onRequestClose={[Function]}
-    open={false}
-    overlay={
-      <withCurrentUserContext(SetAssigneePopup)
-        onSelect={[MockFunction]}
-      />
-    }
-  >
-    <ButtonLink
-      aria-expanded={false}
-      aria-label="issue.assign.assigned_to_x_click_to_change.John Doe"
-      className="issue-action issue-action-with-options js-issue-assign"
-      onClick={[Function]}
-    >
-      <span
-        className="text-top"
-      >
-        <withAppStateContext(Avatar)
-          className="little-spacer-right"
-          hash="gravatarhash"
-          name=""
-          size={16}
-        />
-      </span>
-      <span
-        className="issue-meta-label"
-        title="John Doe"
-      >
-        John Doe
-      </span>
-      <DropdownIcon
-        className="little-spacer-left"
-      />
-    </ButtonLink>
-  </Toggler>
-</div>
-`;
-
-exports[`should render without the action when the correct rights are missing 1`] = `
-<Fragment>
-  <span
-    className="text-top"
-  >
-    <withAppStateContext(Avatar)
-      className="little-spacer-right"
-      hash="gravatarhash"
-      name=""
-      size={16}
-    />
-  </span>
-  <span
-    className="issue-meta-label"
-    title="John Doe"
-  >
-    John Doe
-  </span>
-</Fragment>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueChangelog-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueChangelog-test.tsx.snap
deleted file mode 100644 (file)
index 3cb8bc7..0000000
+++ /dev/null
@@ -1,88 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should open the popup when the button is clicked 1`] = `
-[
-  [
-    "changelog",
-    undefined,
-  ],
-]
-`;
-
-exports[`should open the popup when the button is clicked 2`] = `
-<div
-  className="dropdown"
->
-  <Toggler
-    onRequestClose={[Function]}
-    open={true}
-    overlay={
-      <ChangelogPopup
-        issue={
-          {
-            "author": "john.david.dalton@gmail.com",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "key": "issuekey",
-          }
-        }
-      />
-    }
-  >
-    <ButtonLink
-      aria-expanded={true}
-      className="issue-action issue-action-with-options js-issue-show-changelog"
-      onClick={[Function]}
-    >
-      <span
-        className="issue-meta-label"
-      >
-        <DateFromNow
-          date="2017-03-01T09:36:01+0100"
-        />
-      </span>
-      <DropdownIcon
-        className="little-spacer-left"
-      />
-    </ButtonLink>
-  </Toggler>
-</div>
-`;
-
-exports[`should render correctly 1`] = `
-<div
-  className="dropdown"
->
-  <Toggler
-    onRequestClose={[Function]}
-    open={false}
-    overlay={
-      <ChangelogPopup
-        issue={
-          {
-            "author": "john.david.dalton@gmail.com",
-            "creationDate": "2017-03-01T09:36:01+0100",
-            "key": "issuekey",
-          }
-        }
-      />
-    }
-  >
-    <ButtonLink
-      aria-expanded={false}
-      className="issue-action issue-action-with-options js-issue-show-changelog"
-      onClick={[Function]}
-    >
-      <span
-        className="issue-meta-label"
-      >
-        <DateFromNow
-          date="2017-03-01T09:36:01+0100"
-        />
-      </span>
-      <DropdownIcon
-        className="little-spacer-left"
-      />
-    </ButtonLink>
-  </Toggler>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueChangelogDiff-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueChangelogDiff-test.tsx.snap
deleted file mode 100644 (file)
index 1e953fa..0000000
+++ /dev/null
@@ -1,55 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<p>
-  issue.changelog.removed.issue.changelog.field.foo
-</p>
-`;
-
-exports[`should render correctly branch diff 1`] = `
-<p>
-  issue.change.from_branch.foo.bar
-</p>
-`;
-
-exports[`should render correctly branch diff 2`] = `
-<p>
-  issue.change.from_non_branch.foo.bar
-</p>
-`;
-
-exports[`should render correctly branch diff 3`] = `
-<p>
-  issue.change.from_branch.foo.bar
-</p>
-`;
-
-exports[`should render correctly effort diff 1`] = `
-<p>
-  issue.changelog.changed_to.issue.changelog.field.effort.work_duration.x_minutes.12
-</p>
-`;
-
-exports[`should render correctly effort diff 2`] = `
-<p>
-  issue.changelog.changed_to.issue.changelog.field.effort.work_duration.x_minutes.12 (issue.changelog.was.work_duration.x_minutes.10)
-</p>
-`;
-
-exports[`should render correctly effort diff 3`] = `
-<p>
-  issue.changelog.removed.issue.changelog.field.effort (issue.changelog.was.work_duration.x_minutes.10)
-</p>
-`;
-
-exports[`should render correctly file diff 1`] = `
-<p>
-  issue.change.file_move.foo/bar.js.bar/baz.js
-</p>
-`;
-
-exports[`should render correctly line diff 1`] = `
-<p>
-  issue.changelog.line_removed_X.80
-</p>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentLine-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentLine-test.tsx.snap
deleted file mode 100644 (file)
index 563f86a..0000000
+++ /dev/null
@@ -1,266 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should open the right popups when the buttons are clicked 1`] = `
-{
-  "openPopup": "edit",
-}
-`;
-
-exports[`should open the right popups when the buttons are clicked 2`] = `
-{
-  "openPopup": "delete",
-}
-`;
-
-exports[`should open the right popups when the buttons are clicked 3`] = `
-<div
-  className="issue-comment"
->
-  <div
-    className="issue-comment-author"
-    title="John Doe"
-  >
-    <withAppStateContext(Avatar)
-      className="little-spacer-right"
-      hash="gravatarhash"
-      name="John Doe"
-      size={16}
-    />
-    John Doe
-  </div>
-  <div
-    className="issue-comment-text markdown"
-    dangerouslySetInnerHTML={
-      {
-        "__html": "<b>test</b>",
-      }
-    }
-  />
-  <div
-    className="issue-comment-age"
-  >
-    <span
-      className="a11y-hidden"
-    >
-      issue.comment.posted_on
-    </span>
-    <DateFromNow
-      date="2017-03-01T09:36:01+0100"
-    />
-  </div>
-  <div
-    className="issue-comment-actions"
-  >
-    <div
-      className="dropdown"
-    >
-      <Toggler
-        closeOnClickOutside={false}
-        onRequestClose={[Function]}
-        open={false}
-        overlay={
-          <CommentPopup
-            comment={
-              {
-                "author": "john.doe",
-                "authorActive": true,
-                "authorAvatar": "gravatarhash",
-                "authorName": "John Doe",
-                "createdAt": "2017-03-01T09:36:01+0100",
-                "htmlText": "<b>test</b>",
-                "key": "comment-key",
-                "markdown": "*test*",
-                "updatable": true,
-              }
-            }
-            onComment={[Function]}
-            placeholder=""
-            placement="bottom-right"
-            toggleComment={[Function]}
-          />
-        }
-      >
-        <EditButton
-          aria-label="issue.comment.edit"
-          className="js-issue-comment-edit button-small"
-          onClick={[Function]}
-        />
-      </Toggler>
-    </div>
-    <div
-      className="dropdown"
-    >
-      <Toggler
-        onRequestClose={[Function]}
-        open={true}
-        overlay={
-          <CommentDeletePopup
-            onDelete={[Function]}
-          />
-        }
-      >
-        <DeleteButton
-          aria-label="issue.comment.delete"
-          className="js-issue-comment-delete button-small"
-          onClick={[Function]}
-        />
-      </Toggler>
-    </div>
-  </div>
-</div>
-`;
-
-exports[`should render correctly a comment that is not updatable 1`] = `
-<div
-  className="issue-comment"
->
-  <div
-    className="issue-comment-author"
-    title="John Doe"
-  >
-    <withAppStateContext(Avatar)
-      className="little-spacer-right"
-      hash="gravatarhash"
-      name="John Doe"
-      size={16}
-    />
-    John Doe
-  </div>
-  <div
-    className="issue-comment-text markdown"
-    dangerouslySetInnerHTML={
-      {
-        "__html": "<b>test</b>",
-      }
-    }
-  />
-  <div
-    className="issue-comment-age"
-  >
-    <span
-      className="a11y-hidden"
-    >
-      issue.comment.posted_on
-    </span>
-    <DateFromNow
-      date="2017-03-01T09:36:01+0100"
-    />
-  </div>
-  <div
-    className="issue-comment-actions"
-  />
-</div>
-`;
-
-exports[`should render correctly a comment that is updatable 1`] = `
-<div
-  className="issue-comment"
->
-  <div
-    className="issue-comment-author"
-    title="John Doe"
-  >
-    <withAppStateContext(Avatar)
-      className="little-spacer-right"
-      hash="gravatarhash"
-      name="John Doe"
-      size={16}
-    />
-    John Doe
-  </div>
-  <div
-    className="issue-comment-text markdown"
-    dangerouslySetInnerHTML={
-      {
-        "__html": "<b>test</b>",
-      }
-    }
-  />
-  <div
-    className="issue-comment-age"
-  >
-    <span
-      className="a11y-hidden"
-    >
-      issue.comment.posted_on
-    </span>
-    <DateFromNow
-      date="2017-03-01T09:36:01+0100"
-    />
-  </div>
-  <div
-    className="issue-comment-actions"
-  >
-    <div
-      className="dropdown"
-    >
-      <Toggler
-        closeOnClickOutside={false}
-        onRequestClose={[Function]}
-        open={false}
-        overlay={
-          <CommentPopup
-            comment={
-              {
-                "author": "john.doe",
-                "authorActive": true,
-                "authorAvatar": "gravatarhash",
-                "authorName": "John Doe",
-                "createdAt": "2017-03-01T09:36:01+0100",
-                "htmlText": "<b>test</b>",
-                "key": "comment-key",
-                "markdown": "*test*",
-                "updatable": true,
-              }
-            }
-            onComment={[Function]}
-            placeholder=""
-            placement="bottom-right"
-            toggleComment={[Function]}
-          />
-        }
-      >
-        <EditButton
-          aria-label="issue.comment.edit"
-          className="js-issue-comment-edit button-small"
-          onClick={[Function]}
-        />
-      </Toggler>
-    </div>
-    <div
-      className="dropdown"
-    >
-      <Toggler
-        onRequestClose={[Function]}
-        open={false}
-        overlay={
-          <CommentDeletePopup
-            onDelete={[Function]}
-          />
-        }
-      >
-        <DeleteButton
-          aria-label="issue.comment.delete"
-          className="js-issue-comment-delete button-small"
-          onClick={[Function]}
-        />
-      </Toggler>
-    </div>
-  </div>
-</div>
-`;
-
-exports[`should render correctly a comment with a deleted author 1`] = `
-<div
-  className="issue-comment-author"
-  title="user.x_deleted.john.doe"
->
-  <withAppStateContext(Avatar)
-    className="little-spacer-right"
-    hash="gravatarhash"
-    name="john.doe"
-    size={16}
-  />
-  user.x_deleted.john.doe
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap
deleted file mode 100644 (file)
index 22c41aa..0000000
+++ /dev/null
@@ -1,181 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: default 1`] = `
-<Fragment>
-  <div
-    className="display-inline-flex-center issue-message break-word"
-  >
-    <span
-      className="spacer-right"
-    >
-      <IssueMessageHighlighting
-        message="Reduce the number of conditional operators (4) used in the expression"
-      />
-    </span>
-    <IssueMessageTags />
-  </div>
-  <ForwardRef(Link)
-    aria-label="issue.why_this_issue.long"
-    className="spacer-right"
-    target="_blank"
-    to={
-      {
-        "hash": "",
-        "pathname": "/project/issues",
-        "search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
-      }
-    }
-  >
-    issue.why_this_issue
-  </ForwardRef(Link)>
-</Fragment>
-`;
-
-exports[`should render correctly: hide why is it an issue 1`] = `
-<Fragment>
-  <div
-    className="display-inline-flex-center issue-message break-word"
-  >
-    <span
-      className="spacer-right"
-    >
-      <IssueMessageHighlighting
-        message="Reduce the number of conditional operators (4) used in the expression"
-      />
-    </span>
-    <IssueMessageTags />
-  </div>
-</Fragment>
-`;
-
-exports[`should render correctly: is deprecated rule 1`] = `
-<Fragment>
-  <div
-    className="display-inline-flex-center issue-message break-word"
-  >
-    <span
-      className="spacer-right"
-    >
-      <IssueMessageHighlighting
-        message="Reduce the number of conditional operators (4) used in the expression"
-      />
-    </span>
-    <IssueMessageTags
-      ruleStatus="DEPRECATED"
-    />
-  </div>
-  <ForwardRef(Link)
-    aria-label="issue.why_this_issue.long"
-    className="spacer-right"
-    target="_blank"
-    to={
-      {
-        "hash": "",
-        "pathname": "/project/issues",
-        "search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
-      }
-    }
-  >
-    issue.why_this_issue
-  </ForwardRef(Link)>
-</Fragment>
-`;
-
-exports[`should render correctly: is removed rule 1`] = `
-<Fragment>
-  <div
-    className="display-inline-flex-center issue-message break-word"
-  >
-    <span
-      className="spacer-right"
-    >
-      <IssueMessageHighlighting
-        message="Reduce the number of conditional operators (4) used in the expression"
-      />
-    </span>
-    <IssueMessageTags
-      ruleStatus="REMOVED"
-    />
-  </div>
-  <ForwardRef(Link)
-    aria-label="issue.why_this_issue.long"
-    className="spacer-right"
-    target="_blank"
-    to={
-      {
-        "hash": "",
-        "pathname": "/project/issues",
-        "search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
-      }
-    }
-  >
-    issue.why_this_issue
-  </ForwardRef(Link)>
-</Fragment>
-`;
-
-exports[`should render correctly: with engine info 1`] = `
-<Fragment>
-  <div
-    className="display-inline-flex-center issue-message break-word"
-  >
-    <span
-      className="spacer-right"
-    >
-      <IssueMessageHighlighting
-        message="Reduce the number of conditional operators (4) used in the expression"
-      />
-    </span>
-    <IssueMessageTags
-      engine="js"
-    />
-  </div>
-  <ForwardRef(Link)
-    aria-label="issue.why_this_issue.long"
-    className="spacer-right"
-    target="_blank"
-    to={
-      {
-        "hash": "",
-        "pathname": "/project/issues",
-        "search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
-      }
-    }
-  >
-    issue.why_this_issue
-  </ForwardRef(Link)>
-</Fragment>
-`;
-
-exports[`should render correctly: with quick fix 1`] = `
-<Fragment>
-  <div
-    className="display-inline-flex-center issue-message break-word"
-  >
-    <span
-      className="spacer-right"
-    >
-      <IssueMessageHighlighting
-        message="Reduce the number of conditional operators (4) used in the expression"
-      />
-    </span>
-    <IssueMessageTags
-      quickFixAvailable={true}
-    />
-  </div>
-  <ForwardRef(Link)
-    aria-label="issue.why_this_issue.long"
-    className="spacer-right"
-    target="_blank"
-    to={
-      {
-        "hash": "",
-        "pathname": "/project/issues",
-        "search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
-      }
-    }
-  >
-    issue.why_this_issue
-  </ForwardRef(Link)>
-</Fragment>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueSeverity-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueSeverity-test.tsx.snap
deleted file mode 100644 (file)
index c2d685b..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should open the popup when the button is clicked 1`] = `
-[
-  [
-    "set-severity",
-    undefined,
-  ],
-]
-`;
-
-exports[`should open the popup when the button is clicked 2`] = `
-<div
-  className="dropdown"
->
-  <Toggler
-    onRequestClose={[Function]}
-    open={true}
-    overlay={
-      <SetSeverityPopup
-        issue={
-          {
-            "severity": "BLOCKER",
-          }
-        }
-        onSelect={[Function]}
-      />
-    }
-  >
-    <ButtonLink
-      aria-expanded={true}
-      aria-label="issue.severity.severity_x_click_to_change.severity.BLOCKER"
-      className="issue-action issue-action-with-options js-issue-set-severity"
-      onClick={[Function]}
-    >
-      <SeverityHelper
-        className="issue-meta-label"
-        severity="BLOCKER"
-      />
-      <DropdownIcon
-        className="little-spacer-left"
-      />
-    </ButtonLink>
-  </Toggler>
-</div>
-`;
-
-exports[`should render with the action 1`] = `
-<div
-  className="dropdown"
->
-  <Toggler
-    onRequestClose={[Function]}
-    open={false}
-    overlay={
-      <SetSeverityPopup
-        issue={
-          {
-            "severity": "BLOCKER",
-          }
-        }
-        onSelect={[Function]}
-      />
-    }
-  >
-    <ButtonLink
-      aria-expanded={false}
-      aria-label="issue.severity.severity_x_click_to_change.severity.BLOCKER"
-      className="issue-action issue-action-with-options js-issue-set-severity"
-      onClick={[Function]}
-    >
-      <SeverityHelper
-        className="issue-meta-label"
-        severity="BLOCKER"
-      />
-      <DropdownIcon
-        className="little-spacer-left"
-      />
-    </ButtonLink>
-  </Toggler>
-</div>
-`;
-
-exports[`should render without the action when the correct rights are missing 1`] = `
-<SeverityHelper
-  className="issue-meta-label"
-  severity="BLOCKER"
-/>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap
deleted file mode 100644 (file)
index 75e6385..0000000
+++ /dev/null
@@ -1,849 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: default 1`] = `
-<div
-  className="issue-row"
->
-  <IssueMessage
-    issue={
-      {
-        "actions": [],
-        "component": "main.js",
-        "componentEnabled": true,
-        "componentLongName": "main.js",
-        "componentQualifier": "FIL",
-        "componentUuid": "foo1234",
-        "creationDate": "2017-03-01T09:36:01+0100",
-        "externalRuleEngine": "foo",
-        "flows": [],
-        "flowsWithType": [],
-        "key": "AVsae-CQS-9G3txfbFN2",
-        "line": 25,
-        "message": "Reduce the number of conditional operators (4) used in the expression",
-        "project": "myproject",
-        "projectKey": "foo",
-        "projectName": "Foo",
-        "rule": "javascript:S1067",
-        "ruleName": "foo",
-        "scope": "MAIN",
-        "secondaryLocations": [],
-        "severity": "MAJOR",
-        "status": "OPEN",
-        "textRange": {
-          "endLine": 26,
-          "endOffset": 15,
-          "startLine": 25,
-          "startOffset": 0,
-        },
-        "transitions": [],
-        "type": "BUG",
-      }
-    }
-  />
-  <div
-    className="issue-row-meta"
-  >
-    <div
-      className="issue-meta-list"
-    >
-      <div
-        className="issue-meta"
-      >
-        <IssueChangelog
-          creationDate="2017-03-01T09:36:01+0100"
-          isOpen={false}
-          issue={
-            {
-              "actions": [],
-              "component": "main.js",
-              "componentEnabled": true,
-              "componentLongName": "main.js",
-              "componentQualifier": "FIL",
-              "componentUuid": "foo1234",
-              "creationDate": "2017-03-01T09:36:01+0100",
-              "externalRuleEngine": "foo",
-              "flows": [],
-              "flowsWithType": [],
-              "key": "AVsae-CQS-9G3txfbFN2",
-              "line": 25,
-              "message": "Reduce the number of conditional operators (4) used in the expression",
-              "project": "myproject",
-              "projectKey": "foo",
-              "projectName": "Foo",
-              "rule": "javascript:S1067",
-              "ruleName": "foo",
-              "scope": "MAIN",
-              "secondaryLocations": [],
-              "severity": "MAJOR",
-              "status": "OPEN",
-              "textRange": {
-                "endLine": 26,
-                "endOffset": 15,
-                "startLine": 25,
-                "startOffset": 0,
-              },
-              "transitions": [],
-              "type": "BUG",
-            }
-          }
-          togglePopup={[MockFunction]}
-        />
-      </div>
-      <div
-        className="issue-meta"
-      >
-        <span
-          className="issue-meta-label"
-          title="line_number"
-        >
-          L
-          26
-        </span>
-      </div>
-      <div
-        className="issue-meta"
-      >
-        <ForwardRef(Link)
-          className="js-issue-permalink link-no-underline"
-          target="_blank"
-          title="permalink"
-          to={
-            {
-              "hash": "",
-              "pathname": "/project/issues",
-              "search": "?issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
-            }
-          }
-        >
-          <LinkIcon />
-        </ForwardRef(Link)>
-      </div>
-    </div>
-  </div>
-</div>
-`;
-
-exports[`should render correctly: with filter 1`] = `
-<div
-  className="issue-row"
->
-  <IssueMessage
-    issue={
-      {
-        "actions": [],
-        "component": "main.js",
-        "componentEnabled": true,
-        "componentLongName": "main.js",
-        "componentQualifier": "FIL",
-        "componentUuid": "foo1234",
-        "creationDate": "2017-03-01T09:36:01+0100",
-        "externalRuleEngine": "foo",
-        "flows": [],
-        "flowsWithType": [],
-        "key": "AVsae-CQS-9G3txfbFN2",
-        "line": 25,
-        "message": "Reduce the number of conditional operators (4) used in the expression",
-        "project": "myproject",
-        "projectKey": "foo",
-        "projectName": "Foo",
-        "rule": "javascript:S1067",
-        "ruleName": "foo",
-        "scope": "MAIN",
-        "secondaryLocations": [],
-        "severity": "MAJOR",
-        "status": "OPEN",
-        "textRange": {
-          "endLine": 26,
-          "endOffset": 15,
-          "startLine": 25,
-          "startOffset": 0,
-        },
-        "transitions": [],
-        "type": "BUG",
-      }
-    }
-  />
-  <div
-    className="issue-row-meta"
-  >
-    <div
-      className="issue-meta-list"
-    >
-      <div
-        className="issue-meta"
-      >
-        <IssueChangelog
-          creationDate="2017-03-01T09:36:01+0100"
-          isOpen={false}
-          issue={
-            {
-              "actions": [],
-              "component": "main.js",
-              "componentEnabled": true,
-              "componentLongName": "main.js",
-              "componentQualifier": "FIL",
-              "componentUuid": "foo1234",
-              "creationDate": "2017-03-01T09:36:01+0100",
-              "externalRuleEngine": "foo",
-              "flows": [],
-              "flowsWithType": [],
-              "key": "AVsae-CQS-9G3txfbFN2",
-              "line": 25,
-              "message": "Reduce the number of conditional operators (4) used in the expression",
-              "project": "myproject",
-              "projectKey": "foo",
-              "projectName": "Foo",
-              "rule": "javascript:S1067",
-              "ruleName": "foo",
-              "scope": "MAIN",
-              "secondaryLocations": [],
-              "severity": "MAJOR",
-              "status": "OPEN",
-              "textRange": {
-                "endLine": 26,
-                "endOffset": 15,
-                "startLine": 25,
-                "startOffset": 0,
-              },
-              "transitions": [],
-              "type": "BUG",
-            }
-          }
-          togglePopup={[MockFunction]}
-        />
-      </div>
-      <div
-        className="issue-meta"
-      >
-        <span
-          className="issue-meta-label"
-          title="line_number"
-        >
-          L
-          26
-        </span>
-      </div>
-      <div
-        className="issue-meta"
-      >
-        <ForwardRef(Link)
-          className="js-issue-permalink link-no-underline"
-          target="_blank"
-          title="permalink"
-          to={
-            {
-              "hash": "",
-              "pathname": "/project/issues",
-              "search": "?issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
-            }
-          }
-        >
-          <LinkIcon />
-        </ForwardRef(Link)>
-      </div>
-      <div
-        className="issue-meta"
-      >
-        <SimilarIssuesFilter
-          isOpen={false}
-          issue={
-            {
-              "actions": [],
-              "component": "main.js",
-              "componentEnabled": true,
-              "componentLongName": "main.js",
-              "componentQualifier": "FIL",
-              "componentUuid": "foo1234",
-              "creationDate": "2017-03-01T09:36:01+0100",
-              "externalRuleEngine": "foo",
-              "flows": [],
-              "flowsWithType": [],
-              "key": "AVsae-CQS-9G3txfbFN2",
-              "line": 25,
-              "message": "Reduce the number of conditional operators (4) used in the expression",
-              "project": "myproject",
-              "projectKey": "foo",
-              "projectName": "Foo",
-              "rule": "javascript:S1067",
-              "ruleName": "foo",
-              "scope": "MAIN",
-              "secondaryLocations": [],
-              "severity": "MAJOR",
-              "status": "OPEN",
-              "textRange": {
-                "endLine": 26,
-                "endOffset": 15,
-                "startLine": 25,
-                "startOffset": 0,
-              },
-              "transitions": [],
-              "type": "BUG",
-            }
-          }
-          onFilter={[MockFunction]}
-          togglePopup={[MockFunction]}
-        />
-      </div>
-    </div>
-  </div>
-</div>
-`;
-
-exports[`should render correctly: with multi locations 1`] = `
-<div
-  className="issue-row"
->
-  <IssueMessage
-    issue={
-      {
-        "actions": [],
-        "component": "main.js",
-        "componentEnabled": true,
-        "componentLongName": "main.js",
-        "componentQualifier": "FIL",
-        "componentUuid": "foo1234",
-        "creationDate": "2017-03-01T09:36:01+0100",
-        "flows": [
-          [
-            {
-              "component": "main.js",
-              "textRange": {
-                "endLine": 2,
-                "endOffset": 2,
-                "startLine": 1,
-                "startOffset": 1,
-              },
-            },
-            {
-              "component": "main.js",
-              "textRange": {
-                "endLine": 2,
-                "endOffset": 2,
-                "startLine": 1,
-                "startOffset": 1,
-              },
-            },
-            {
-              "component": "main.js",
-              "textRange": {
-                "endLine": 2,
-                "endOffset": 2,
-                "startLine": 1,
-                "startOffset": 1,
-              },
-            },
-          ],
-          [
-            {
-              "component": "main.js",
-              "textRange": {
-                "endLine": 2,
-                "endOffset": 2,
-                "startLine": 1,
-                "startOffset": 1,
-              },
-            },
-            {
-              "component": "main.js",
-              "textRange": {
-                "endLine": 2,
-                "endOffset": 2,
-                "startLine": 1,
-                "startOffset": 1,
-              },
-            },
-          ],
-        ],
-        "flowsWithType": [],
-        "key": "AVsae-CQS-9G3txfbFN2",
-        "line": 25,
-        "message": "Reduce the number of conditional operators (4) used in the expression",
-        "project": "myproject",
-        "projectKey": "foo",
-        "projectName": "Foo",
-        "rule": "javascript:S1067",
-        "ruleName": "foo",
-        "scope": "MAIN",
-        "secondaryLocations": [
-          {
-            "component": "main.js",
-            "textRange": {
-              "endLine": 2,
-              "endOffset": 2,
-              "startLine": 1,
-              "startOffset": 1,
-            },
-          },
-          {
-            "component": "main.js",
-            "textRange": {
-              "endLine": 2,
-              "endOffset": 2,
-              "startLine": 1,
-              "startOffset": 1,
-            },
-          },
-        ],
-        "severity": "MAJOR",
-        "status": "OPEN",
-        "textRange": {
-          "endLine": 26,
-          "endOffset": 15,
-          "startLine": 25,
-          "startOffset": 0,
-        },
-        "transitions": [],
-        "type": "BUG",
-      }
-    }
-  />
-  <div
-    className="issue-row-meta"
-  >
-    <div
-      className="issue-meta-list"
-    >
-      <div
-        className="issue-meta"
-      >
-        <IssueChangelog
-          creationDate="2017-03-01T09:36:01+0100"
-          isOpen={false}
-          issue={
-            {
-              "actions": [],
-              "component": "main.js",
-              "componentEnabled": true,
-              "componentLongName": "main.js",
-              "componentQualifier": "FIL",
-              "componentUuid": "foo1234",
-              "creationDate": "2017-03-01T09:36:01+0100",
-              "flows": [
-                [
-                  {
-                    "component": "main.js",
-                    "textRange": {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                  {
-                    "component": "main.js",
-                    "textRange": {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                  {
-                    "component": "main.js",
-                    "textRange": {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                ],
-                [
-                  {
-                    "component": "main.js",
-                    "textRange": {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                  {
-                    "component": "main.js",
-                    "textRange": {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                ],
-              ],
-              "flowsWithType": [],
-              "key": "AVsae-CQS-9G3txfbFN2",
-              "line": 25,
-              "message": "Reduce the number of conditional operators (4) used in the expression",
-              "project": "myproject",
-              "projectKey": "foo",
-              "projectName": "Foo",
-              "rule": "javascript:S1067",
-              "ruleName": "foo",
-              "scope": "MAIN",
-              "secondaryLocations": [
-                {
-                  "component": "main.js",
-                  "textRange": {
-                    "endLine": 2,
-                    "endOffset": 2,
-                    "startLine": 1,
-                    "startOffset": 1,
-                  },
-                },
-                {
-                  "component": "main.js",
-                  "textRange": {
-                    "endLine": 2,
-                    "endOffset": 2,
-                    "startLine": 1,
-                    "startOffset": 1,
-                  },
-                },
-              ],
-              "severity": "MAJOR",
-              "status": "OPEN",
-              "textRange": {
-                "endLine": 26,
-                "endOffset": 15,
-                "startLine": 25,
-                "startOffset": 0,
-              },
-              "transitions": [],
-              "type": "BUG",
-            }
-          }
-          togglePopup={[MockFunction]}
-        />
-      </div>
-      <div
-        className="issue-meta"
-      >
-        <span
-          className="issue-meta-label"
-          title="line_number"
-        >
-          L
-          26
-        </span>
-      </div>
-      <div
-        className="issue-meta"
-      >
-        <Tooltip
-          overlay="issue.this_issue_involves_x_code_locations.7"
-        >
-          <LocationIndex>
-            7
-          </LocationIndex>
-        </Tooltip>
-      </div>
-      <div
-        className="issue-meta"
-      >
-        <ForwardRef(Link)
-          className="js-issue-permalink link-no-underline"
-          target="_blank"
-          title="permalink"
-          to={
-            {
-              "hash": "",
-              "pathname": "/project/issues",
-              "search": "?issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
-            }
-          }
-        >
-          <LinkIcon />
-        </ForwardRef(Link)>
-      </div>
-    </div>
-  </div>
-</div>
-`;
-
-exports[`should render correctly: with multi locations and link 1`] = `
-<div
-  className="issue-row"
->
-  <IssueMessage
-    branchLike={
-      {
-        "analysisDate": "2018-01-01",
-        "excludedFromPurge": true,
-        "isMain": false,
-        "name": "branch-6.7",
-      }
-    }
-    issue={
-      {
-        "actions": [],
-        "component": "main.js",
-        "componentEnabled": true,
-        "componentLongName": "main.js",
-        "componentQualifier": "FIL",
-        "componentUuid": "foo1234",
-        "creationDate": "2017-03-01T09:36:01+0100",
-        "flows": [
-          [
-            {
-              "component": "main.js",
-              "textRange": {
-                "endLine": 2,
-                "endOffset": 2,
-                "startLine": 1,
-                "startOffset": 1,
-              },
-            },
-            {
-              "component": "main.js",
-              "textRange": {
-                "endLine": 2,
-                "endOffset": 2,
-                "startLine": 1,
-                "startOffset": 1,
-              },
-            },
-            {
-              "component": "main.js",
-              "textRange": {
-                "endLine": 2,
-                "endOffset": 2,
-                "startLine": 1,
-                "startOffset": 1,
-              },
-            },
-          ],
-          [
-            {
-              "component": "main.js",
-              "textRange": {
-                "endLine": 2,
-                "endOffset": 2,
-                "startLine": 1,
-                "startOffset": 1,
-              },
-            },
-            {
-              "component": "main.js",
-              "textRange": {
-                "endLine": 2,
-                "endOffset": 2,
-                "startLine": 1,
-                "startOffset": 1,
-              },
-            },
-          ],
-        ],
-        "flowsWithType": [],
-        "key": "AVsae-CQS-9G3txfbFN2",
-        "line": 25,
-        "message": "Reduce the number of conditional operators (4) used in the expression",
-        "project": "myproject",
-        "projectKey": "foo",
-        "projectName": "Foo",
-        "rule": "javascript:S1067",
-        "ruleName": "foo",
-        "scope": "MAIN",
-        "secondaryLocations": [
-          {
-            "component": "main.js",
-            "textRange": {
-              "endLine": 2,
-              "endOffset": 2,
-              "startLine": 1,
-              "startOffset": 1,
-            },
-          },
-          {
-            "component": "main.js",
-            "textRange": {
-              "endLine": 2,
-              "endOffset": 2,
-              "startLine": 1,
-              "startOffset": 1,
-            },
-          },
-        ],
-        "severity": "MAJOR",
-        "status": "OPEN",
-        "textRange": {
-          "endLine": 26,
-          "endOffset": 15,
-          "startLine": 25,
-          "startOffset": 0,
-        },
-        "transitions": [],
-        "type": "BUG",
-      }
-    }
-  />
-  <div
-    className="issue-row-meta"
-  >
-    <div
-      className="issue-meta-list"
-    >
-      <div
-        className="issue-meta"
-      >
-        <IssueChangelog
-          creationDate="2017-03-01T09:36:01+0100"
-          isOpen={false}
-          issue={
-            {
-              "actions": [],
-              "component": "main.js",
-              "componentEnabled": true,
-              "componentLongName": "main.js",
-              "componentQualifier": "FIL",
-              "componentUuid": "foo1234",
-              "creationDate": "2017-03-01T09:36:01+0100",
-              "flows": [
-                [
-                  {
-                    "component": "main.js",
-                    "textRange": {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                  {
-                    "component": "main.js",
-                    "textRange": {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                  {
-                    "component": "main.js",
-                    "textRange": {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                ],
-                [
-                  {
-                    "component": "main.js",
-                    "textRange": {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                  {
-                    "component": "main.js",
-                    "textRange": {
-                      "endLine": 2,
-                      "endOffset": 2,
-                      "startLine": 1,
-                      "startOffset": 1,
-                    },
-                  },
-                ],
-              ],
-              "flowsWithType": [],
-              "key": "AVsae-CQS-9G3txfbFN2",
-              "line": 25,
-              "message": "Reduce the number of conditional operators (4) used in the expression",
-              "project": "myproject",
-              "projectKey": "foo",
-              "projectName": "Foo",
-              "rule": "javascript:S1067",
-              "ruleName": "foo",
-              "scope": "MAIN",
-              "secondaryLocations": [
-                {
-                  "component": "main.js",
-                  "textRange": {
-                    "endLine": 2,
-                    "endOffset": 2,
-                    "startLine": 1,
-                    "startOffset": 1,
-                  },
-                },
-                {
-                  "component": "main.js",
-                  "textRange": {
-                    "endLine": 2,
-                    "endOffset": 2,
-                    "startLine": 1,
-                    "startOffset": 1,
-                  },
-                },
-              ],
-              "severity": "MAJOR",
-              "status": "OPEN",
-              "textRange": {
-                "endLine": 26,
-                "endOffset": 15,
-                "startLine": 25,
-                "startOffset": 0,
-              },
-              "transitions": [],
-              "type": "BUG",
-            }
-          }
-          togglePopup={[MockFunction]}
-        />
-      </div>
-      <div
-        className="issue-meta"
-      >
-        <span
-          className="issue-meta-label"
-          title="line_number"
-        >
-          L
-          26
-        </span>
-      </div>
-      <div
-        className="issue-meta"
-      >
-        <ForwardRef(Link)
-          target="_blank"
-          to={
-            {
-              "hash": "",
-              "pathname": "/project/issues",
-              "search": "?branch=branch-6.7&issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
-            }
-          }
-        >
-          <Tooltip
-            overlay="issue.this_issue_involves_x_code_locations.7"
-          >
-            <LocationIndex>
-              7
-            </LocationIndex>
-          </Tooltip>
-        </ForwardRef(Link)>
-      </div>
-      <div
-        className="issue-meta"
-      >
-        <ForwardRef(Link)
-          className="js-issue-permalink link-no-underline"
-          target="_blank"
-          title="permalink"
-          to={
-            {
-              "hash": "",
-              "pathname": "/project/issues",
-              "search": "?branch=branch-6.7&issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
-            }
-          }
-        >
-          <LinkIcon />
-        </ForwardRef(Link)>
-      </div>
-    </div>
-  </div>
-</div>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTransition-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTransition-test.tsx.snap
deleted file mode 100644 (file)
index e4eb999..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render without the action when there is no transitions 1`] = `
-<StatusHelper
-  className="issue-meta-label"
-  status="CLOSED"
-/>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueType-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueType-test.tsx.snap
deleted file mode 100644 (file)
index 1078e10..0000000
+++ /dev/null
@@ -1,11 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render without the action when the correct rights are missing 1`] = `
-<span>
-  <IssueTypeIcon
-    className="little-spacer-right"
-    query="BUG"
-  />
-  issue.type.BUG
-</span>
-`;
index 40b047101095fbd52972cb719fa0b860ad8e2417..3cd9bcbb419c904b23ae48ad51f13b8c9713a8aa 100644 (file)
@@ -85,7 +85,7 @@ export default class ChangelogPopup extends React.PureComponent<Props, State> {
                       <DateTimeFormatter date={item.creationDate} />
                     </td>
                     <td className="text-left text-top">
-                      <p>
+                      <div>
                         {userName && (
                           <>
                             <Avatar
@@ -104,7 +104,7 @@ export default class ChangelogPopup extends React.PureComponent<Props, State> {
                             'issue.changelog.webhook_source',
                             item.webhookSource
                           )}
-                      </p>
+                      </div>
                       {item.diffs.map((diff) => (
                         <IssueChangelogDiff diff={diff} key={diff.key} />
                       ))}
index da7b0aa6482834b8844556e9a48fbe6b10e4c1fa..58bcee83f15cdd579a654291239937c7c2db3d1f 100644 (file)
@@ -44,9 +44,9 @@ export default function CommentForm(props: CommentFormProps) {
           style={{ resize: 'vertical' }}
           placeholder={placeholder}
           aria-label={translate('issue.comment.enter_comment')}
-          onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) =>
-            setEditComment(event.target.value)
-          }
+          onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => {
+            setEditComment(event.target.value);
+          }}
           onKeyDown={(event: React.KeyboardEvent) => {
             if (event.nativeEvent.key === KeyboardKeys.Enter && (event.metaKey || event.ctrlKey)) {
               props.onSaveComment(editComment);
index e42615b3dd2b40341fbcf3e69f69b108e7946991..ce5b4f2736047f24fd1985cb037eece6a956a5f1 100644 (file)
@@ -24,6 +24,7 @@ import QualifierIcon from '../../../components/icons/QualifierIcon';
 import TagsIcon from '../../../components/icons/TagsIcon';
 import { translate, translateWithParameters } from '../../../helpers/l10n';
 import { fileFromPath, limitComponentName } from '../../../helpers/path';
+import { ComponentQualifier } from '../../../types/component';
 import { Issue } from '../../../types/types';
 import SelectList from '../../common/SelectList';
 import SelectListItem from '../../common/SelectListItem';
@@ -31,112 +32,107 @@ import SeverityHelper from '../../shared/SeverityHelper';
 import StatusHelper from '../../shared/StatusHelper';
 import Avatar from '../../ui/Avatar';
 
-interface Props {
+interface SimilarIssuesPopupProps {
   issue: Issue;
   onFilter: (property: string, issue: Issue) => void;
 }
 
-export default class SimilarIssuesPopup extends React.PureComponent<Props> {
-  handleSelect = (property: string) => {
-    this.props.onFilter(property, this.props.issue);
-  };
-
-  render() {
-    const { issue } = this.props;
-
-    const items = [
-      'type',
-      'severity',
-      'status',
-      'resolution',
-      'assignee',
-      'rule',
-      ...(issue.tags || []).map((tag) => `tag###${tag}`),
-      'project',
-      'file',
-    ].filter((item) => item) as string[];
-
-    const assignee = issue.assigneeName || issue.assignee;
-
-    return (
-      <DropdownOverlay noPadding={true}>
-        <header className="menu-search">
-          <h6>{translate('issue.filter_similar_issues')}</h6>
-        </header>
-
-        <SelectList
-          className="issues-similar-issues-menu"
-          currentItem={items[0]}
-          items={items}
-          onSelect={this.handleSelect}
-        >
-          <SelectListItem className="display-flex-center" item="type">
-            <IssueTypeIcon className="little-spacer-right" query={issue.type} />
-            {translate('issue.type', issue.type)}
+export default function SimilarIssuesPopup(props: SimilarIssuesPopupProps) {
+  const { issue } = props;
+
+  const items = [
+    'type',
+    'severity',
+    'status',
+    'resolution',
+    'assignee',
+    'rule',
+    ...(issue.tags ?? []).map((tag) => `tag###${tag}`),
+    'project',
+    'file',
+  ].filter((item) => item) as string[];
+
+  const assignee = issue.assigneeName ?? issue.assignee;
+
+  return (
+    <DropdownOverlay noPadding={true}>
+      <div className="menu-search">
+        <h6>{translate('issue.filter_similar_issues')}</h6>
+      </div>
+
+      <SelectList
+        className="issues-similar-issues-menu"
+        currentItem={items[0]}
+        items={items}
+        onSelect={(property: string) => {
+          props.onFilter(property, issue);
+        }}
+      >
+        <SelectListItem className="display-flex-center" item="type">
+          <IssueTypeIcon className="little-spacer-right" query={issue.type} />
+          {translate('issue.type', issue.type)}
+        </SelectListItem>
+
+        <SelectListItem item="severity">
+          <SeverityHelper className="display-flex-center" severity={issue.severity} />
+        </SelectListItem>
+
+        <SelectListItem item="status">
+          <StatusHelper
+            className="display-flex-center"
+            resolution={undefined}
+            status={issue.status}
+          />
+        </SelectListItem>
+
+        <SelectListItem item="resolution">
+          {issue.resolution != null
+            ? translate('issue.resolution', issue.resolution)
+            : translate('unresolved')}
+        </SelectListItem>
+
+        <SelectListItem item="assignee">
+          {assignee ? (
+            <span>
+              {translate('assigned_to')}
+              <Avatar
+                className="little-spacer-left little-spacer-right"
+                hash={issue.assigneeAvatar}
+                name={assignee}
+                size={16}
+              />
+              {issue.assigneeActive === false
+                ? translateWithParameters('user.x_deleted', assignee)
+                : assignee}
+            </span>
+          ) : (
+            translate('unassigned')
+          )}
+        </SelectListItem>
+
+        <li className="divider" />
+
+        <SelectListItem item="rule">{limitComponentName(issue.ruleName)}</SelectListItem>
+
+        {issue.tags?.map((tag) => (
+          <SelectListItem item={`tag###${tag}`} key={`tag###${tag}`}>
+            <TagsIcon className="little-spacer-right text-middle" />
+            <span className="text-middle">{tag}</span>
           </SelectListItem>
-
-          <SelectListItem item="severity">
-            <SeverityHelper className="display-flex-center" severity={issue.severity} />
-          </SelectListItem>
-
-          <SelectListItem item="status">
-            <StatusHelper
-              className="display-flex-center"
-              resolution={undefined}
-              status={issue.status}
-            />
-          </SelectListItem>
-
-          <SelectListItem item="resolution">
-            {issue.resolution != null
-              ? translate('issue.resolution', issue.resolution)
-              : translate('unresolved')}
-          </SelectListItem>
-
-          <SelectListItem item="assignee">
-            {assignee ? (
-              <span>
-                {translate('assigned_to')}
-                <Avatar
-                  className="little-spacer-left little-spacer-right"
-                  hash={issue.assigneeAvatar}
-                  name={assignee}
-                  size={16}
-                />
-                {issue.assigneeActive === false
-                  ? translateWithParameters('user.x_deleted', assignee)
-                  : assignee}
-              </span>
-            ) : (
-              translate('unassigned')
-            )}
-          </SelectListItem>
-
-          <li className="divider" />
-
-          <SelectListItem item="rule">{limitComponentName(issue.ruleName)}</SelectListItem>
-
-          {issue.tags != null &&
-            issue.tags.map((tag) => (
-              <SelectListItem item={`tag###${tag}`} key={`tag###${tag}`}>
-                <TagsIcon className="little-spacer-right text-middle" />
-                <span className="text-middle">{tag}</span>
-              </SelectListItem>
-            ))}
-
-          <li className="divider" />
-
-          <SelectListItem item="project">
-            <QualifierIcon className="little-spacer-right" qualifier="TRK" />
-            {issue.projectName}
-          </SelectListItem>
-
-          <SelectListItem item="file">
-            <QualifierIcon className="little-spacer-right" qualifier={issue.componentQualifier} />
-            {fileFromPath(issue.componentLongName)}
-          </SelectListItem>
-        </SelectList>
-      </DropdownOverlay>
-    );
-  }
+        ))}
+
+        <li className="divider" />
+
+        <SelectListItem item="project">
+          <QualifierIcon className="little-spacer-right" qualifier={ComponentQualifier.Project} />
+          {issue.projectName}
+        </SelectListItem>
+
+        <SelectListItem item="file">
+          <QualifierIcon className="little-spacer-right" qualifier={issue.componentQualifier} />
+          {fileFromPath(issue.componentLongName)}
+        </SelectListItem>
+      </SelectList>
+    </DropdownOverlay>
+  );
 }
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/ChangelogPopup-test.tsx b/server/sonar-web/src/main/js/components/issue/popups/__tests__/ChangelogPopup-test.tsx
deleted file mode 100644 (file)
index fc2923c..0000000
+++ /dev/null
@@ -1,132 +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 { getIssueChangelog } from '../../../../api/issues';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
-import ChangelogPopup from '../ChangelogPopup';
-
-jest.mock('../../../../api/issues', () => ({
-  getIssueChangelog: jest.fn().mockResolvedValue({
-    changelog: [
-      {
-        creationDate: '2017-03-01T09:36:01+0100',
-        user: 'john.doe',
-        isUserActive: true,
-        userName: 'John Doe',
-        avatar: 'gravatarhash',
-        diffs: [{ key: 'severity', newValue: 'MINOR', oldValue: 'CRITICAL' }],
-      },
-    ],
-  }),
-}));
-
-beforeEach(() => {
-  jest.clearAllMocks();
-});
-
-it('should render the changelog popup correctly', async () => {
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  expect(getIssueChangelog).toHaveBeenCalledWith('issuekey');
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should render the changelog popup when we have a deleted user', async () => {
-  (getIssueChangelog as jest.Mock).mockResolvedValueOnce({
-    changelog: [
-      {
-        creationDate: '2017-03-01T09:36:01+0100',
-        user: 'john.doe',
-        isUserActive: false,
-        diffs: [{ key: 'severity', newValue: 'MINOR', oldValue: 'CRITICAL' }],
-      },
-    ],
-  });
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should render the changelog popup when change was triggered by a webhook with external user', async () => {
-  (getIssueChangelog as jest.Mock).mockResolvedValueOnce({
-    changelog: [
-      {
-        creationDate: '2017-03-01T09:36:01+0100',
-        user: null,
-        isUserActive: false,
-        diffs: [{ key: 'severity', newValue: 'MINOR', oldValue: 'CRITICAL' }],
-        webhookSource: 'GitHub',
-        externalUser: 'toto@github.com',
-      },
-    ],
-  });
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should render the changelog popup when change was triggered by a webhook without user', async () => {
-  (getIssueChangelog as jest.Mock).mockResolvedValueOnce({
-    changelog: [
-      {
-        creationDate: '2017-03-01T09:36:01+0100',
-        user: null,
-        isUserActive: false,
-        diffs: [{ key: 'severity', newValue: 'MINOR', oldValue: 'CRITICAL' }],
-        webhookSource: 'GitHub',
-      },
-    ],
-  });
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-});
-
-it('should render the changelog popup with SQ user when both SQ and external user are presents', async () => {
-  (getIssueChangelog as jest.Mock).mockResolvedValueOnce({
-    changelog: [
-      {
-        creationDate: '2017-03-01T09:36:01+0100',
-        user: 'toto@sonarqube.com',
-        isUserActive: false,
-        diffs: [{ key: 'severity', newValue: 'MINOR', oldValue: 'CRITICAL' }],
-        webhookSource: 'GitHub',
-        externalUser: 'toto@github.com',
-      },
-    ],
-  });
-  const wrapper = shallowRender();
-  await waitAndUpdate(wrapper);
-  expect(wrapper).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<ChangelogPopup['props']> = {}) {
-  return shallow(
-    <ChangelogPopup
-      issue={{
-        key: 'issuekey',
-        author: 'john.david.dalton@gmail.com',
-        creationDate: '2017-03-01T09:36:01+0100',
-      }}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentDeletePopup-test.tsx b/server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentDeletePopup-test.tsx
deleted file mode 100644 (file)
index 99d02c2..0000000
+++ /dev/null
@@ -1,31 +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 CommentDeletePopup from '../CommentDeletePopup';
-
-it('should render the comment delete popup correctly', () => {
-  const onDelete = jest.fn();
-  const element = shallow(<CommentDeletePopup onDelete={onDelete} />);
-  expect(element).toMatchSnapshot();
-  click(element.find('Button'));
-  expect(onDelete.mock.calls.length).toBe(1);
-});
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentPopup-test.tsx b/server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentPopup-test.tsx
deleted file mode 100644 (file)
index 669cdfc..0000000
+++ /dev/null
@@ -1,50 +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 { screen } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-import * as React from 'react';
-import { renderComponent } from '../../../../helpers/testReactTestingUtils';
-import CommentPopup, { CommentPopupProps } from '../CommentPopup';
-
-it('should trigger comment change', async () => {
-  const user = userEvent.setup();
-  const onComment = jest.fn();
-  const toggleComment = jest.fn();
-  shallowRender({ onComment, toggleComment });
-
-  expect(await screen.findByRole('textbox')).toHaveFocus();
-  await user.keyboard('test');
-  await user.keyboard('{Control>}{Enter}{/Control}');
-  expect(onComment).toHaveBeenCalledWith('test');
-
-  await user.click(screen.getByRole('button', { name: 'issue.comment.add_comment.cancel' }));
-  expect(toggleComment).toHaveBeenCalledWith(false);
-});
-
-function shallowRender(overrides: Partial<CommentPopupProps> = {}) {
-  return renderComponent(
-    <CommentPopup
-      onComment={jest.fn()}
-      placeholder="placeholder test"
-      toggleComment={jest.fn()}
-      {...overrides}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetAssigneePopup-test.tsx b/server/sonar-web/src/main/js/components/issue/popups/__tests__/SetAssigneePopup-test.tsx
deleted file mode 100644 (file)
index d225be2..0000000
+++ /dev/null
@@ -1,52 +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 { searchUsers } from '../../../../api/users';
-import { mockLoggedInUser, mockUser } from '../../../../helpers/testMocks';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
-import { SetAssigneePopup } from '../SetAssigneePopup';
-
-jest.mock('../../../../api/users', () => {
-  const { mockUser } = jest.requireActual('../../../../helpers/testMocks');
-  return { searchUsers: jest.fn().mockResolvedValue({ users: [mockUser()] }) };
-});
-
-beforeEach(() => {
-  jest.clearAllMocks();
-});
-
-it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should allow to search for a user on SQ', async () => {
-  const wrapper = shallowRender();
-  wrapper.find('SearchBox').prop<Function>('onChange')('o');
-  await waitAndUpdate(wrapper);
-  expect(searchUsers).toHaveBeenCalledWith({ q: 'o', ps: 10 });
-  expect(wrapper.state('users')).toEqual([mockUser()]);
-});
-
-function shallowRender(props: Partial<SetAssigneePopup['props']> = {}) {
-  return shallow(
-    <SetAssigneePopup currentUser={mockLoggedInUser()} onSelect={jest.fn()} {...props} />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/SimilarIssuesPopup-test.tsx b/server/sonar-web/src/main/js/components/issue/popups/__tests__/SimilarIssuesPopup-test.tsx
deleted file mode 100644 (file)
index e3d89c9..0000000
+++ /dev/null
@@ -1,59 +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 { mockIssue } from '../../../../helpers/testMocks';
-import SimilarIssuesPopup from '../SimilarIssuesPopup';
-
-it('should render correctly', () => {
-  expect(shallowRender()).toMatchSnapshot();
-});
-
-it('should render correctly when assigned', () => {
-  expect(
-    shallowRender({
-      issue: mockIssue(false, { assignee: 'luke', assigneeName: 'Luke Skywalker' }),
-    }).find('SelectListItem[item="assignee"]')
-  ).toMatchSnapshot();
-
-  expect(
-    shallowRender({ issue: mockIssue(false, { assignee: 'luke', assigneeActive: false }) }).find(
-      'SelectListItem[item="assignee"]'
-    )
-  ).toMatchSnapshot();
-});
-
-it('should filter properly', () => {
-  const issue = mockIssue();
-  const onFilter = jest.fn();
-  const wrapper = shallowRender({ issue, onFilter });
-  wrapper.find('SelectList').prop<Function>('onSelect')('assignee');
-  expect(onFilter).toHaveBeenCalledWith('assignee', issue);
-});
-
-function shallowRender(props: Partial<SimilarIssuesPopup['props']> = {}) {
-  return shallow(
-    <SimilarIssuesPopup
-      issue={mockIssue(false, { tags: ['test-tag'] })}
-      onFilter={jest.fn()}
-      {...props}
-    />
-  );
-}
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/ChangelogPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/ChangelogPopup-test.tsx.snap
deleted file mode 100644 (file)
index e294878..0000000
+++ /dev/null
@@ -1,319 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render the changelog popup correctly 1`] = `
-<DropdownOverlay
-  placement="bottom-right"
->
-  <div
-    className="menu is-container issue-changelog"
-  >
-    <table
-      className="spaced"
-    >
-      <tbody>
-        <tr>
-          <td
-            className="thin text-left text-top nowrap"
-          >
-            <DateTimeFormatter
-              date="2017-03-01T09:36:01+0100"
-            />
-          </td>
-          <td
-            className="text-left text-top"
-          >
-            created_by john.david.dalton@gmail.com
-          </td>
-        </tr>
-        <tr
-          key="0"
-        >
-          <td
-            className="thin text-left text-top nowrap"
-          >
-            <DateTimeFormatter
-              date="2017-03-01T09:36:01+0100"
-            />
-          </td>
-          <td
-            className="text-left text-top"
-          >
-            <p>
-              <withAppStateContext(Avatar)
-                className="little-spacer-right"
-                hash="gravatarhash"
-                name="John Doe"
-                size={16}
-              />
-              John Doe
-            </p>
-            <IssueChangelogDiff
-              diff={
-                {
-                  "key": "severity",
-                  "newValue": "MINOR",
-                  "oldValue": "CRITICAL",
-                }
-              }
-              key="severity"
-            />
-          </td>
-        </tr>
-      </tbody>
-    </table>
-  </div>
-</DropdownOverlay>
-`;
-
-exports[`should render the changelog popup when change was triggered by a webhook with external user 1`] = `
-<DropdownOverlay
-  placement="bottom-right"
->
-  <div
-    className="menu is-container issue-changelog"
-  >
-    <table
-      className="spaced"
-    >
-      <tbody>
-        <tr>
-          <td
-            className="thin text-left text-top nowrap"
-          >
-            <DateTimeFormatter
-              date="2017-03-01T09:36:01+0100"
-            />
-          </td>
-          <td
-            className="text-left text-top"
-          >
-            created_by john.david.dalton@gmail.com
-          </td>
-        </tr>
-        <tr
-          key="0"
-        >
-          <td
-            className="thin text-left text-top nowrap"
-          >
-            <DateTimeFormatter
-              date="2017-03-01T09:36:01+0100"
-            />
-          </td>
-          <td
-            className="text-left text-top"
-          >
-            <p>
-              <withAppStateContext(Avatar)
-                className="little-spacer-right"
-                name="toto@github.com"
-                size={16}
-              />
-              toto@github.com
-              issue.changelog.webhook_source.GitHub
-            </p>
-            <IssueChangelogDiff
-              diff={
-                {
-                  "key": "severity",
-                  "newValue": "MINOR",
-                  "oldValue": "CRITICAL",
-                }
-              }
-              key="severity"
-            />
-          </td>
-        </tr>
-      </tbody>
-    </table>
-  </div>
-</DropdownOverlay>
-`;
-
-exports[`should render the changelog popup when change was triggered by a webhook without user 1`] = `
-<DropdownOverlay
-  placement="bottom-right"
->
-  <div
-    className="menu is-container issue-changelog"
-  >
-    <table
-      className="spaced"
-    >
-      <tbody>
-        <tr>
-          <td
-            className="thin text-left text-top nowrap"
-          >
-            <DateTimeFormatter
-              date="2017-03-01T09:36:01+0100"
-            />
-          </td>
-          <td
-            className="text-left text-top"
-          >
-            created_by john.david.dalton@gmail.com
-          </td>
-        </tr>
-        <tr
-          key="0"
-        >
-          <td
-            className="thin text-left text-top nowrap"
-          >
-            <DateTimeFormatter
-              date="2017-03-01T09:36:01+0100"
-            />
-          </td>
-          <td
-            className="text-left text-top"
-          >
-            <p>
-              issue.changelog.webhook_source.GitHub
-            </p>
-            <IssueChangelogDiff
-              diff={
-                {
-                  "key": "severity",
-                  "newValue": "MINOR",
-                  "oldValue": "CRITICAL",
-                }
-              }
-              key="severity"
-            />
-          </td>
-        </tr>
-      </tbody>
-    </table>
-  </div>
-</DropdownOverlay>
-`;
-
-exports[`should render the changelog popup when we have a deleted user 1`] = `
-<DropdownOverlay
-  placement="bottom-right"
->
-  <div
-    className="menu is-container issue-changelog"
-  >
-    <table
-      className="spaced"
-    >
-      <tbody>
-        <tr>
-          <td
-            className="thin text-left text-top nowrap"
-          >
-            <DateTimeFormatter
-              date="2017-03-01T09:36:01+0100"
-            />
-          </td>
-          <td
-            className="text-left text-top"
-          >
-            created_by john.david.dalton@gmail.com
-          </td>
-        </tr>
-        <tr
-          key="0"
-        >
-          <td
-            className="thin text-left text-top nowrap"
-          >
-            <DateTimeFormatter
-              date="2017-03-01T09:36:01+0100"
-            />
-          </td>
-          <td
-            className="text-left text-top"
-          >
-            <p>
-              <withAppStateContext(Avatar)
-                className="little-spacer-right"
-                name="john.doe"
-                size={16}
-              />
-              user.x_deleted.john.doe
-            </p>
-            <IssueChangelogDiff
-              diff={
-                {
-                  "key": "severity",
-                  "newValue": "MINOR",
-                  "oldValue": "CRITICAL",
-                }
-              }
-              key="severity"
-            />
-          </td>
-        </tr>
-      </tbody>
-    </table>
-  </div>
-</DropdownOverlay>
-`;
-
-exports[`should render the changelog popup with SQ user when both SQ and external user are presents 1`] = `
-<DropdownOverlay
-  placement="bottom-right"
->
-  <div
-    className="menu is-container issue-changelog"
-  >
-    <table
-      className="spaced"
-    >
-      <tbody>
-        <tr>
-          <td
-            className="thin text-left text-top nowrap"
-          >
-            <DateTimeFormatter
-              date="2017-03-01T09:36:01+0100"
-            />
-          </td>
-          <td
-            className="text-left text-top"
-          >
-            created_by john.david.dalton@gmail.com
-          </td>
-        </tr>
-        <tr
-          key="0"
-        >
-          <td
-            className="thin text-left text-top nowrap"
-          >
-            <DateTimeFormatter
-              date="2017-03-01T09:36:01+0100"
-            />
-          </td>
-          <td
-            className="text-left text-top"
-          >
-            <p>
-              <withAppStateContext(Avatar)
-                className="little-spacer-right"
-                name="toto@sonarqube.com"
-                size={16}
-              />
-              toto@sonarqube.com
-              issue.changelog.webhook_source.GitHub
-            </p>
-            <IssueChangelogDiff
-              diff={
-                {
-                  "key": "severity",
-                  "newValue": "MINOR",
-                  "oldValue": "CRITICAL",
-                }
-              }
-              key="severity"
-            />
-          </td>
-        </tr>
-      </tbody>
-    </table>
-  </div>
-</DropdownOverlay>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/CommentDeletePopup-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/CommentDeletePopup-test.tsx.snap
deleted file mode 100644 (file)
index 955955a..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render the comment delete popup correctly 1`] = `
-<DropdownOverlay
-  placement="bottom-right"
->
-  <div
-    className="menu is-container"
-  >
-    <div
-      className="spacer-bottom"
-    >
-      issue.comment.delete_confirm_message
-    </div>
-    <Button
-      className="button-red"
-      onClick={[MockFunction]}
-    >
-      delete
-    </Button>
-  </div>
-</DropdownOverlay>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetAssigneePopup-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetAssigneePopup-test.tsx.snap
deleted file mode 100644 (file)
index ae5a826..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<DropdownOverlay
-  noPadding={true}
->
-  <div
-    className="multi-select"
-  >
-    <div
-      className="menu-search"
-    >
-      <SearchBox
-        autoFocus={true}
-        className="little-spacer-top"
-        minLength={2}
-        onChange={[Function]}
-        placeholder="search.search_for_users"
-        value=""
-      />
-    </div>
-    <SelectList
-      currentItem="luke"
-      items={
-        [
-          "luke",
-          "",
-        ]
-      }
-      onSelect={[MockFunction]}
-    >
-      <SelectListItem
-        item="luke"
-        key="luke"
-      >
-        <withAppStateContext(Avatar)
-          className="spacer-right"
-          name="Skywalker"
-          size={16}
-        />
-        <span
-          className="text-middle"
-          style={
-            {
-              "marginLeft": 24,
-            }
-          }
-        >
-          Skywalker
-        </span>
-      </SelectListItem>
-      <SelectListItem
-        item=""
-        key=""
-      >
-        <span
-          className="text-middle"
-          style={
-            {
-              "marginLeft": undefined,
-            }
-          }
-        >
-          unassigned
-        </span>
-      </SelectListItem>
-    </SelectList>
-  </div>
-</DropdownOverlay>
-`;
diff --git a/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SimilarIssuesPopup-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SimilarIssuesPopup-test.tsx.snap
deleted file mode 100644 (file)
index 67756ef..0000000
+++ /dev/null
@@ -1,144 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<DropdownOverlay
-  noPadding={true}
->
-  <header
-    className="menu-search"
-  >
-    <h6>
-      issue.filter_similar_issues
-    </h6>
-  </header>
-  <SelectList
-    className="issues-similar-issues-menu"
-    currentItem="type"
-    items={
-      [
-        "type",
-        "severity",
-        "status",
-        "resolution",
-        "assignee",
-        "rule",
-        "tag###test-tag",
-        "project",
-        "file",
-      ]
-    }
-    onSelect={[Function]}
-  >
-    <SelectListItem
-      className="display-flex-center"
-      item="type"
-    >
-      <IssueTypeIcon
-        className="little-spacer-right"
-        query="BUG"
-      />
-      issue.type.BUG
-    </SelectListItem>
-    <SelectListItem
-      item="severity"
-    >
-      <SeverityHelper
-        className="display-flex-center"
-        severity="MAJOR"
-      />
-    </SelectListItem>
-    <SelectListItem
-      item="status"
-    >
-      <StatusHelper
-        className="display-flex-center"
-        status="OPEN"
-      />
-    </SelectListItem>
-    <SelectListItem
-      item="resolution"
-    >
-      unresolved
-    </SelectListItem>
-    <SelectListItem
-      item="assignee"
-    >
-      unassigned
-    </SelectListItem>
-    <li
-      className="divider"
-    />
-    <SelectListItem
-      item="rule"
-    >
-      foo
-    </SelectListItem>
-    <SelectListItem
-      item="tag###test-tag"
-      key="tag###test-tag"
-    >
-      <TagsIcon
-        className="little-spacer-right text-middle"
-      />
-      <span
-        className="text-middle"
-      >
-        test-tag
-      </span>
-    </SelectListItem>
-    <li
-      className="divider"
-    />
-    <SelectListItem
-      item="project"
-    >
-      <QualifierIcon
-        className="little-spacer-right"
-        qualifier="TRK"
-      />
-      Foo
-    </SelectListItem>
-    <SelectListItem
-      item="file"
-    >
-      <QualifierIcon
-        className="little-spacer-right"
-        qualifier="FIL"
-      />
-      main.js
-    </SelectListItem>
-  </SelectList>
-</DropdownOverlay>
-`;
-
-exports[`should render correctly when assigned 1`] = `
-<SelectListItem
-  item="assignee"
->
-  <span>
-    assigned_to
-    <withAppStateContext(Avatar)
-      className="little-spacer-left little-spacer-right"
-      name="Luke Skywalker"
-      size={16}
-    />
-    Luke Skywalker
-  </span>
-</SelectListItem>
-`;
-
-exports[`should render correctly when assigned 2`] = `
-<SelectListItem
-  item="assignee"
->
-  <span>
-    assigned_to
-    <withAppStateContext(Avatar)
-      className="little-spacer-left little-spacer-right"
-      name="luke"
-      size={16}
-    />
-    user.x_deleted.luke
-  </span>
-</SelectListItem>
-`;
index 2c7957083cd0f28d9bd29575264c2c13724f69e9..a28b90793e28a1ac0977f436f54a7143a789fffb 100644 (file)
 import { colors } from '../app/theme';
 import { AlmKeys } from '../types/alm-settings';
 import { ComponentQualifier } from '../types/component';
-import { IssueScope, IssueType } from '../types/issues';
+import { IssueScope, IssueSeverity, IssueType } from '../types/issues';
 import { RuleType } from '../types/types';
 
-export const SEVERITIES = ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO'];
+export const SEVERITIES = Object.values(IssueSeverity);
 export const STATUSES = ['OPEN', 'REOPENED', 'CONFIRMED', 'RESOLVED', 'CLOSED'];
 export const ISSUE_TYPES: IssueType[] = [
   IssueType.Bug,
index e77d65a40383dad91b328764fea8a471289c8c69..7eb14844271181ad7e161ddae8ee4f3112b726e9 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 { uniqueId } from 'lodash';
 import { Query } from '../../apps/issues/utils';
 import { ReferencedRule } from '../../types/issues';
-import { IssueChangelog } from '../../types/types';
+import { IssueChangelog, IssueChangelogDiff, IssueComment } from '../../types/types';
 
 export function mockReferencedRule(overrides: Partial<ReferencedRule> = {}): ReferencedRule {
   return {
@@ -35,13 +36,32 @@ export function mockIssueChangelog(overrides: Partial<IssueChangelog> = {}): Iss
     isUserActive: true,
     user: 'luke.skywalker',
     userName: 'Luke Skywalker',
-    diffs: [
-      {
-        key: 'assign',
-        newValue: 'darth.vader',
-        oldValue: 'luke.skywalker',
-      },
-    ],
+    diffs: [mockIssueChangelogDiff()],
+    ...overrides,
+  };
+}
+
+export function mockIssueChangelogDiff(
+  overrides: Partial<IssueChangelogDiff> = {}
+): IssueChangelogDiff {
+  return {
+    key: 'assign',
+    newValue: 'darth.vader',
+    oldValue: 'luke.skywalker',
+    ...overrides,
+  };
+}
+
+export function mockIssueComment(overrides: Partial<IssueComment> = {}): IssueComment {
+  return {
+    author: 'luke.skywalker',
+    authorLogin: 'luke.skywalker',
+    authorName: 'Luke Skywalker',
+    createdAt: '2018-10-01',
+    htmlText: 'This is a <strong>comment</strong>, <code>bud</code>',
+    key: uniqueId(),
+    markdown: 'This is a *comment*, `bud`',
+    updatable: false,
     ...overrides,
   };
 }
index b2824755591cc7753aa37f230e8d5024d93a76b3..40521eaeac6a5876ca53b13b5700d0eb90f603d6 100644 (file)
@@ -295,6 +295,7 @@ export function mockRawIssue(withLocations = false, overrides: Partial<RawIssue>
     status: IssueStatus.Open,
     textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 },
     type: IssueType.CodeSmell,
+    transitions: [],
     scope: IssueScope.Main,
     ...overrides,
   };
index c5dea846e5691efb107bc8758557747c58f4a938..3e96f1cf3c75e8cf8cbd1b2fc72b890c68fe331a 100644 (file)
@@ -29,12 +29,13 @@ export enum IssueType {
   SecurityHotspot = 'SECURITY_HOTSPOT',
 }
 
+// Keep this enum in the correct order (most severe to least severe).
 export enum IssueSeverity {
   Blocker = 'BLOCKER',
-  Minor = 'MINOR',
   Critical = 'CRITICAL',
-  Info = 'INFO',
   Major = 'MAJOR',
+  Minor = 'MINOR',
+  Info = 'INFO',
 }
 
 export enum IssueScope {
@@ -58,6 +59,23 @@ export enum IssueStatus {
   Closed = 'CLOSED',
 }
 
+export enum IssueActions {
+  SetType = 'set_type',
+  SetTags = 'set_tags',
+  SetSeverity = 'set_severity',
+  Comment = 'comment',
+  Assign = 'assign',
+}
+
+export enum IssueTransition {
+  Confirm = 'confirm',
+  UnConfirm = 'unconfirm',
+  Resolve = 'resolve',
+  FalsePositive = 'falsepositive',
+  WontFix = 'wontfix',
+  Reopen = 'reopen',
+}
+
 interface Comment {
   createdAt: string;
   htmlText: string;
@@ -87,11 +105,11 @@ export interface RawFlowLocation {
 
 export interface RawIssue {
   actions: string[];
-  transitions?: string[];
+  transitions: string[];
   tags?: string[];
   assignee?: string;
   author?: string;
-  comments?: Array<Comment>;
+  comments?: Comment[];
   creationDate: string;
   component: string;
   flows?: Array<{
@@ -120,7 +138,7 @@ export interface IssueResponse {
   components?: Array<{ key: string; name: string }>;
   issue: RawIssue;
   rules?: Array<{}>;
-  users?: Array<UserBase>;
+  users?: UserBase[];
 }
 
 export interface RawIssuesResponse {
@@ -131,7 +149,7 @@ export interface RawIssuesResponse {
   languages: ReferencedLanguage[];
   paging: Paging;
   rules?: Array<{}>;
-  users?: Array<UserBase>;
+  users?: UserBase[];
 }
 
 export interface FetchIssuesPromise {
index 3b8586ad21bde89f9cbe43175060ef38ca857db4..e40fb501af7cef420bc6a757c4c0f755e95125b1 100644 (file)
@@ -846,6 +846,7 @@ issue.assign.unassigned_click_to_assign=Unassigned, click to assign issue
 issue.assign.formlink=Assign
 issue.assign.to_me=to me
 issue.quick_fix_available_with_sonarlint=Quick fix available in {link}
+issue.quick_fix_available_with_sonarlint_no_link=Quick fix available in SonarLint
 issue.comment.add_comment=Add Comment
 issue.comment.add_comment.cancel=Cancel adding comment
 issue.comment.enter_comment=Enter Comment
@@ -969,6 +970,7 @@ issues.not_all_issue_show_why=You do not have access to all projects in this por
 # ISSUE CHANGELOG
 #
 #------------------------------------------------------------------------------
+issue.changelog.found_on_x_show_more=Found on {0}; click to see changelog
 issue.changelog.changed_to={0} changed to {1}
 issue.changelog.was=was {0}
 issue.changelog.webhook_source= (change triggered by a {0} webhook)