]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18464 React Testing Library for Security Hotspot - As a user without edit permi...
authorKevin Silva <kevin.silva@sonarsource.com>
Wed, 22 Feb 2023 09:55:59 +0000 (10:55 +0100)
committersonartech <sonartech@sonarsource.com>
Thu, 23 Feb 2023 20:03:01 +0000 (20:03 +0000)
server/sonar-web/src/main/js/api/mocks/SecurityHotspotServiceMock.ts
server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotReviewHistory.tsx

index d4729ef47b9d6485cad0d70e753d93287519857a..d906c95bd4d5fe31044ac1667fc910f3d5c40de7 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, range, times } from 'lodash';
-import { mockHotspot, mockRawHotspot, mockStandards } from '../../helpers/mocks/security-hotspots';
+import { cloneDeep, times } from 'lodash';
+import {
+  mockHotspot,
+  mockHotspotComment,
+  mockRawHotspot,
+  mockStandards,
+} from '../../helpers/mocks/security-hotspots';
 import { mockSourceLine } from '../../helpers/mocks/sources';
 import { getStandards } from '../../helpers/security-standard';
 import { mockPaging, mockRuleDetails, mockUser } from '../../helpers/testMocks';
@@ -26,6 +31,7 @@ import { BranchParameters } from '../../types/branch-like';
 import {
   Hotspot,
   HotspotAssignRequest,
+  HotspotComment,
   HotspotResolution,
   HotspotStatus,
 } from '../../types/security-hotspots';
@@ -34,6 +40,9 @@ import { getMeasures } from '../measures';
 import { getRuleDetails } from '../rules';
 import {
   assignSecurityHotspot,
+  commentSecurityHotspot,
+  deleteSecurityHotspotComment,
+  editSecurityHotspotComment,
   getSecurityHotspotDetails,
   getSecurityHotspotList,
   getSecurityHotspots,
@@ -42,11 +51,12 @@ import {
 import { searchUsers } from '../users';
 
 const NUMBER_OF_LINES = 20;
-const MAX_END_RANGE = 10;
 
 export default class SecurityHotspotServiceMock {
   hotspots: Hotspot[] = [];
   nextAssignee: string | undefined;
+  canChangeStatus: boolean = true;
+  hotspotsComments: HotspotComment[] = [];
 
   constructor() {
     this.reset();
@@ -67,13 +77,40 @@ export default class SecurityHotspotServiceMock {
         })
       )
     );
+    jest.mocked(commentSecurityHotspot).mockImplementation(this.handleCommentSecurityHotspot);
+    jest
+      .mocked(deleteSecurityHotspotComment)
+      .mockImplementation(this.handleDeleteSecurityHotspotComment);
+    jest
+      .mocked(editSecurityHotspotComment)
+      .mockImplementation(this.handleEditSecurityHotspotComment);
     jest.mocked(getStandards).mockImplementation(this.handleGetStandards);
   }
 
-  handleGetSources = (data: { key: string; from?: number; to?: number } & BranchParameters) => {
-    return this.reply(
-      range(data.from || 1, data.to || MAX_END_RANGE).map((line) => mockSourceLine({ line }))
-    );
+  handleCommentSecurityHotspot = () => {
+    this.hotspotsComments = [
+      mockHotspotComment({
+        htmlText: 'This is a comment from john doe',
+        markdown: 'This is a comment from john doe',
+        updatable: true,
+      }),
+    ];
+    return Promise.resolve();
+  };
+
+  handleDeleteSecurityHotspotComment = () => {
+    this.hotspotsComments = [];
+    return Promise.resolve();
+  };
+
+  handleEditSecurityHotspotComment = () => {
+    const response = mockHotspotComment({
+      htmlText: 'This is a comment from john doe test',
+      markdown: 'This is a comment from john doe test',
+      updatable: true,
+    });
+    this.hotspotsComments = [response];
+    return Promise.resolve(response);
   };
 
   handleGetStandards = () => {
@@ -87,9 +124,9 @@ export default class SecurityHotspotServiceMock {
   handleSearchUsers = () => {
     return this.reply({
       users: [
-        mockUser({ name: 'User John' }),
-        mockUser({ name: 'User Doe' }),
-        mockUser({ name: 'User Foo' }),
+        mockUser({ name: 'User John', login: 'user.john' }),
+        mockUser({ name: 'User Doe', login: 'user.doe' }),
+        mockUser({ name: 'User Foo', login: 'user.foo' }),
       ],
       paging: mockPaging(),
     });
@@ -177,7 +214,8 @@ export default class SecurityHotspotServiceMock {
       this.nextAssignee = undefined;
     }
 
-    hotspot.canChangeStatus = true;
+    hotspot.canChangeStatus = this.canChangeStatus;
+    hotspot.comment = this.hotspotsComments;
 
     return this.reply(hotspot);
   };
@@ -199,6 +237,8 @@ export default class SecurityHotspotServiceMock {
     return Promise.resolve();
   };
 
+  setHotspotChangeStatusPermission = (value: boolean) => (this.canChangeStatus = value);
+
   reply<T>(response: T): Promise<T> {
     return Promise.resolve(cloneDeep(response));
   }
@@ -212,5 +252,6 @@ export default class SecurityHotspotServiceMock {
         message: "'2' is a magic number.",
       }),
     ];
+    this.canChangeStatus = true;
   };
 }
index 719c8aecaeccf43b4dbd63bb679dd812085eb1b9..a7ba5c392ef07d7dd893297b21cd642597ba8a86 100644 (file)
@@ -22,7 +22,7 @@ import userEvent from '@testing-library/user-event';
 import React from 'react';
 import { Route } from 'react-router-dom';
 import selectEvent from 'react-select-event';
-import { byRole, byTestId, byText } from 'testing-library-selector';
+import { byDisplayValue, byRole, byTestId, byText } from 'testing-library-selector';
 import SecurityHotspotServiceMock from '../../../api/mocks/SecurityHotspotServiceMock';
 import { getSecurityHotspots, setSecurityHotspotStatus } from '../../../api/security-hotspots';
 import { searchUsers } from '../../../api/users';
@@ -60,6 +60,11 @@ const ui = {
   hotspotTitle: (name: string | RegExp) => byRole('heading', { name }),
   hotspotStatus: byRole('heading', { name: 'status: hotspots.status_option.FIXED' }),
   hotpostListTitle: byRole('heading', { name: 'hotspots.list_title.TO_REVIEW.2' }),
+  hotspotCommentBox: byRole('textbox', { name: 'hotspots.comment.field' }),
+  commentSubmitButton: byRole('button', { name: 'hotspots.comment.submit' }),
+  commentEditButton: byRole('button', { name: 'issue.comment.edit' }),
+  commentDeleteButton: byRole('button', { name: 'issue.comment.delete' }),
+  textboxWithText: (value: string) => byDisplayValue(value),
   activeAssignee: byTestId('assignee-name'),
   successGlobalMessage: byRole('status'),
   currentUserSelectionItem: byText('foo'),
@@ -175,6 +180,12 @@ it('should be able to change the status of a hotspot', async () => {
   expect(ui.hotspotStatus.get()).toBeInTheDocument();
 });
 
+it('should not be able to change the status if does not have edit permissions', async () => {
+  handler.setHotspotChangeStatusPermission(false);
+  renderSecurityHotspotsApp();
+  expect(await ui.selectStatus.find()).toBeDisabled();
+});
+
 it('should remember the comment when toggling change status panel for the same security hotspot', async () => {
   const user = userEvent.setup();
   renderSecurityHotspotsApp();
@@ -197,6 +208,40 @@ it('should remember the comment when toggling change status panel for the same s
   expect(await screen.findByText(comment)).toBeInTheDocument();
 });
 
+it('should be able to add, edit and remove own comments', async () => {
+  const uiComment = {
+    saveButton: byRole('button', { name: 'save' }),
+    deleteButton: byRole('button', { name: 'delete' }),
+  };
+  const user = userEvent.setup();
+  const comment = 'This is a comment from john doe';
+  renderSecurityHotspotsApp();
+
+  const commentSection = await ui.hotspotCommentBox.find();
+  const submitButton = ui.commentSubmitButton.get();
+
+  // Add a new comment
+  await user.click(commentSection);
+  await user.keyboard(comment);
+  await user.click(submitButton);
+
+  expect(await screen.findByText(comment)).toBeInTheDocument();
+
+  // Edit the comment
+  await user.click(ui.commentEditButton.get());
+  await user.click(ui.textboxWithText(comment).get());
+  await user.keyboard(' test');
+  await user.click(uiComment.saveButton.get());
+
+  expect(await byText(`${comment} test`).find()).toBeInTheDocument();
+
+  // Delete the comment
+  await user.click(ui.commentDeleteButton.get());
+  await user.click(uiComment.deleteButton.get());
+
+  expect(screen.queryByText(`${comment} test`)).not.toBeInTheDocument();
+});
+
 function renderSecurityHotspotsApp(navigateTo?: string) {
   renderAppWithComponentContext(
     'security_hotspots',
index 3dad3b014b6c0805be572ceb142f067cc29d9d59..df68802a9e662055830317f928444b14488a8712 100644 (file)
@@ -130,6 +130,7 @@ export default function HotspotReviewHistory(props: HotspotReviewHistoryProps) {
                           }
                         >
                           <EditButton
+                            title="issue.comment.edit"
                             className="button-small"
                             onClick={() => setEditedCommentKey(key)}
                           />
@@ -150,7 +151,7 @@ export default function HotspotReviewHistory(props: HotspotReviewHistoryProps) {
                         }
                         overlayPlacement={PopupPlacement.BottomRight}
                       >
-                        <DeleteButton className="button-small" />
+                        <DeleteButton title="issue.comment.delete" className="button-small" />
                       </Dropdown>
                     </div>
                   )}