From: Kevin Silva Date: Mon, 20 Feb 2023 14:08:48 +0000 (+0100) Subject: SONAR-18458 React Testing Library for Security Hotspot - As any user X-Git-Tag: 10.0.0.68432~211 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=5af56eda84b62f4602578d195207cd108da0487a;p=sonarqube.git SONAR-18458 React Testing Library for Security Hotspot - As any user --- diff --git a/server/sonar-web/src/main/js/api/mocks/SecurityHotspotServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/SecurityHotspotServiceMock.ts index 8149070f1d0..d4729ef47b9 100644 --- a/server/sonar-web/src/main/js/api/mocks/SecurityHotspotServiceMock.ts +++ b/server/sonar-web/src/main/js/api/mocks/SecurityHotspotServiceMock.ts @@ -17,10 +17,11 @@ * 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, pick, range, times } from 'lodash'; -import { mockHotspot, mockRawHotspot } from '../../helpers/mocks/security-hotspots'; +import { cloneDeep, range, times } from 'lodash'; +import { mockHotspot, mockRawHotspot, mockStandards } from '../../helpers/mocks/security-hotspots'; import { mockSourceLine } from '../../helpers/mocks/sources'; -import { mockRuleDetails } from '../../helpers/testMocks'; +import { getStandards } from '../../helpers/security-standard'; +import { mockPaging, mockRuleDetails, mockUser } from '../../helpers/testMocks'; import { BranchParameters } from '../../types/branch-like'; import { Hotspot, @@ -34,28 +35,31 @@ import { getRuleDetails } from '../rules'; import { assignSecurityHotspot, getSecurityHotspotDetails, + getSecurityHotspotList, getSecurityHotspots, + setSecurityHotspotStatus, } from '../security-hotspots'; +import { searchUsers } from '../users'; const NUMBER_OF_LINES = 20; const MAX_END_RANGE = 10; export default class SecurityHotspotServiceMock { hotspots: Hotspot[] = []; - rawHotspotKey: string[] = []; nextAssignee: string | undefined; constructor() { this.reset(); - (getMeasures as jest.Mock).mockImplementation(this.handleGetMeasures); - (getSecurityHotspots as jest.Mock).mockImplementation(this.handleGetSecurityHotspots); - (getSecurityHotspotDetails as jest.Mock).mockImplementation( - this.handleGetSecurityHotspotDetails - ); - (assignSecurityHotspot as jest.Mock).mockImplementation(this.handleAssignSecurityHotspot); - (getRuleDetails as jest.Mock).mockResolvedValue({ rule: mockRuleDetails() }); - (getSources as jest.Mock).mockResolvedValue( + jest.mocked(getMeasures).mockImplementation(this.handleGetMeasures); + jest.mocked(getSecurityHotspots).mockImplementation(this.handleGetSecurityHotspots); + jest.mocked(getSecurityHotspotDetails).mockImplementation(this.handleGetSecurityHotspotDetails); + jest.mocked(getSecurityHotspotList).mockImplementation(this.handleGetSecurityHotspotList); + jest.mocked(assignSecurityHotspot).mockImplementation(this.handleAssignSecurityHotspot); + jest.mocked(setSecurityHotspotStatus).mockImplementation(this.handleSetSecurityHotspotStatus); + jest.mocked(searchUsers).mockImplementation(this.handleSearchUsers); + jest.mocked(getRuleDetails).mockResolvedValue({ rule: mockRuleDetails() }); + jest.mocked(getSources).mockResolvedValue( times(NUMBER_OF_LINES, (n) => mockSourceLine({ line: n, @@ -63,6 +67,7 @@ export default class SecurityHotspotServiceMock { }) ) ); + jest.mocked(getStandards).mockImplementation(this.handleGetStandards); } handleGetSources = (data: { key: string; from?: number; to?: number } & BranchParameters) => { @@ -71,6 +76,47 @@ export default class SecurityHotspotServiceMock { ); }; + handleGetStandards = () => { + return Promise.resolve(mockStandards()); + }; + + handleSetSecurityHotspotStatus = () => { + return Promise.resolve(); + }; + + handleSearchUsers = () => { + return this.reply({ + users: [ + mockUser({ name: 'User John' }), + mockUser({ name: 'User Doe' }), + mockUser({ name: 'User Foo' }), + ], + paging: mockPaging(), + }); + }; + + handleGetSecurityHotspotList = () => { + return this.reply({ + paging: mockPaging(), + hotspots: [mockRawHotspot({ assignee: 'John Doe' })], + components: [ + { + key: 'guillaume-peoch-sonarsource_benflix_AYGpXq2bd8qy4i0eO9ed:index.php', + qualifier: 'FIL', + name: 'index.php', + longName: 'index.php', + path: 'index.php', + }, + { + key: 'guillaume-peoch-sonarsource_benflix_AYGpXq2bd8qy4i0eO9ed', + qualifier: 'TRK', + name: 'benflix', + longName: 'benflix', + }, + ], + }); + }; + handleGetSecurityHotspots = ( data: { projectKey: string; @@ -83,8 +129,8 @@ export default class SecurityHotspotServiceMock { } & BranchParameters ) => { return this.reply({ - paging: { pageIndex: 1, pageSize: data.ps, total: this.hotspots.length }, - hotspots: this.hotspots.map((hotspot) => pick(hotspot, this.rawHotspotKey)), + paging: mockPaging({ pageIndex: 1, pageSize: data.ps, total: this.hotspots.length }), + hotspots: this.mockRawHotspots(data.onlyMine), components: [ { key: 'guillaume-peoch-sonarsource_benflix_AYGpXq2bd8qy4i0eO9ed:index.php', @@ -103,6 +149,16 @@ export default class SecurityHotspotServiceMock { }); }; + mockRawHotspots = (onlyMine: boolean | undefined) => { + if (onlyMine) { + return []; + } + return [ + mockRawHotspot({ assignee: 'John Doe', key: 'test-1' }), + mockRawHotspot({ assignee: 'John Doe', key: 'test-2' }), + ]; + }; + handleGetSecurityHotspotDetails = (securityHotspotKey: string) => { const hotspot = this.hotspots.find((h) => h.key === securityHotspotKey); @@ -121,25 +177,26 @@ export default class SecurityHotspotServiceMock { this.nextAssignee = undefined; } + hotspot.canChangeStatus = true; + return this.reply(hotspot); }; handleGetMeasures = () => { return this.reply([ { - component: { - key: 'guillaume-peoch-sonarsource_benflix_AYGpXq2bd8qy4i0eO9ed', - name: 'benflix', - qualifier: 'TRK', - measures: [{ metric: 'security_hotspots_reviewed', value: '0.0', bestValue: false }], - }, + key: 'guillaume-peoch-sonarsource_benflix_AYGpXq2bd8qy4i0eO9ed', + name: 'benflix', + qualifier: 'TRK', + metric: 'security_hotspots_reviewed', + measures: [{ metric: 'security_hotspots_reviewed', value: '0.0', bestValue: false }], }, ]); }; handleAssignSecurityHotspot = (_: string, data: HotspotAssignRequest) => { this.nextAssignee = data.assignee; - return this.reply({}); + return Promise.resolve(); }; reply(response: T): Promise { @@ -147,10 +204,13 @@ export default class SecurityHotspotServiceMock { } reset = () => { - this.rawHotspotKey = Object.keys(mockRawHotspot()); this.hotspots = [ - mockHotspot({ key: '1', status: HotspotStatus.TO_REVIEW }), - mockHotspot({ key: '2', status: HotspotStatus.TO_REVIEW }), + mockHotspot({ key: 'test-1', status: HotspotStatus.TO_REVIEW }), + mockHotspot({ + key: 'test-2', + status: HotspotStatus.TO_REVIEW, + message: "'2' is a magic number.", + }), ]; }; } diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx index a9ba25fb5cb..719c8aecaec 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-it.tsx @@ -21,8 +21,12 @@ import { screen, within } from '@testing-library/react'; 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 SecurityHotspotServiceMock from '../../../api/mocks/SecurityHotspotServiceMock'; +import { getSecurityHotspots, setSecurityHotspotStatus } from '../../../api/security-hotspots'; +import { searchUsers } from '../../../api/users'; +import { mockMainBranch } from '../../../helpers/mocks/branch-like'; import { mockComponent } from '../../../helpers/mocks/component'; import { mockLoggedInUser } from '../../../helpers/testMocks'; import { renderAppWithComponentContext } from '../../../helpers/testReactTestingUtils'; @@ -32,14 +36,30 @@ jest.mock('../../../api/measures'); jest.mock('../../../api/security-hotspots'); jest.mock('../../../api/rules'); jest.mock('../../../api/components'); +jest.mock('../../../helpers/security-standard'); +jest.mock('../../../api/users'); const ui = { + inputAssignee: byRole('searchbox', { name: 'hotspots.assignee.select_user' }), selectStatusButton: byRole('button', { name: 'hotspots.status.select_status', }), editAssigneeButton: byRole('button', { name: 'hotspots.assignee.change_user', }), + filterAssigneeToMe: byRole('button', { + name: 'hotspot.filters.assignee.assigned_to_me', + }), + filterSeeAll: byRole('button', { name: 'hotspot.filters.assignee.all' }), + filterByStatus: byRole('combobox', { name: 'hotspot.filters.status' }), + filterByPeriod: byRole('combobox', { name: 'hotspot.filters.period' }), + noHotspotForFilter: byText('hotspots.no_hotspots_for_filters.title'), + selectStatus: byRole('button', { name: 'hotspots.status.select_status' }), + toReviewStatus: byText('hotspots.status_option.TO_REVIEW'), + changeStatus: byRole('button', { name: 'hotspots.status.change_status' }), + 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' }), activeAssignee: byTestId('assignee-name'), successGlobalMessage: byRole('status'), currentUserSelectionItem: byText('foo'), @@ -56,19 +76,105 @@ afterEach(() => { handler.reset(); }); -it('should self-assign hotspot', async () => { +it('should be able to self-assign a hotspot', async () => { const user = userEvent.setup(); renderSecurityHotspotsApp(); expect(await ui.activeAssignee.find()).toHaveTextContent('John Doe'); - await user.click(await ui.editAssigneeButton.find()); + await user.click(ui.editAssigneeButton.get()); await user.click(ui.currentUserSelectionItem.get()); expect(ui.successGlobalMessage.get()).toHaveTextContent(`hotspots.assign.success.foo`); expect(ui.activeAssignee.get()).toHaveTextContent('foo'); }); +it('should be able to search for a user on the assignee', async () => { + const user = userEvent.setup(); + renderSecurityHotspotsApp(); + + await user.click(await ui.editAssigneeButton.find()); + await user.click(ui.inputAssignee.get()); + + await user.keyboard('User'); + + expect(searchUsers).toHaveBeenLastCalledWith({ q: 'User' }); + await user.keyboard('{ArrowDown}{Enter}'); + expect(ui.successGlobalMessage.get()).toHaveTextContent(`hotspots.assign.success.User John`); +}); + +it('should be able to filter the hotspot list', async () => { + const user = userEvent.setup(); + renderSecurityHotspotsApp(); + + expect(await ui.hotpostListTitle.find()).toBeInTheDocument(); + + await user.click(ui.filterAssigneeToMe.get()); + expect(ui.noHotspotForFilter.get()).toBeInTheDocument(); + await selectEvent.select(ui.filterByStatus.get(), ['hotspot.filters.status.to_review']); + + expect(getSecurityHotspots).toHaveBeenLastCalledWith({ + inNewCodePeriod: false, + onlyMine: true, + p: 1, + projectKey: 'guillaume-peoch-sonarsource_benflix_AYGpXq2bd8qy4i0eO9ed', + ps: 500, + resolution: undefined, + status: 'TO_REVIEW', + }); + + await selectEvent.select(ui.filterByPeriod.get(), ['hotspot.filters.period.since_leak_period']); + + expect(getSecurityHotspots).toHaveBeenLastCalledWith({ + inNewCodePeriod: true, + onlyMine: true, + p: 1, + projectKey: 'guillaume-peoch-sonarsource_benflix_AYGpXq2bd8qy4i0eO9ed', + ps: 500, + resolution: undefined, + status: 'TO_REVIEW', + }); + + await user.click(ui.filterSeeAll.get()); + + expect(ui.hotpostListTitle.get()).toBeInTheDocument(); +}); + +it('should be able to navigate the hotspot list with keyboard', async () => { + const user = userEvent.setup(); + renderSecurityHotspotsApp(); + + await user.keyboard('{ArrowDown}'); + expect(await ui.hotspotTitle(/'2' is a magic number./).find()).toBeInTheDocument(); + await user.keyboard('{ArrowUp}'); + expect(await ui.hotspotTitle(/'3' is a magic number./).find()).toBeInTheDocument(); +}); + +it('should be able to change the status of a hotspot', async () => { + const user = userEvent.setup(); + const comment = 'COMMENT-TEXT'; + + renderSecurityHotspotsApp(); + + expect(await ui.selectStatus.find()).toBeInTheDocument(); + + await user.click(ui.selectStatus.get()); + await user.click(ui.toReviewStatus.get()); + + await user.click(screen.getByRole('textbox', { name: 'hotspots.status.add_comment' })); + await user.keyboard(comment); + + await user.click(ui.changeStatus.get()); + + expect(setSecurityHotspotStatus).toHaveBeenLastCalledWith('test-1', { + comment: 'COMMENT-TEXT', + resolution: undefined, + status: 'TO_REVIEW', + }); + + expect(ui.hotspotStatus.get()).toBeInTheDocument(); +}); + it('should remember the comment when toggling change status panel for the same security hotspot', async () => { const user = userEvent.setup(); renderSecurityHotspotsApp(); @@ -86,7 +192,7 @@ it('should remember the comment when toggling change status panel for the same s // Check panel is closed expect(ui.panel.query()).not.toBeInTheDocument(); - await user.click(await ui.selectStatusButton.find()); + await user.click(ui.selectStatusButton.get()); expect(await screen.findByText(comment)).toBeInTheDocument(); }); @@ -103,7 +209,7 @@ function renderSecurityHotspotsApp(navigateTo?: string) { }), }, { - branchLikes: [], + branchLike: mockMainBranch(), onBranchesChange: jest.fn(), onComponentChange: jest.fn(), component: mockComponent({ diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx deleted file mode 100644 index 7b0fbfa3c84..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsApp-test.tsx +++ /dev/null @@ -1,474 +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 { getMeasures } from '../../../api/measures'; -import { getSecurityHotspotList, getSecurityHotspots } from '../../../api/security-hotspots'; -import { KeyboardKeys } from '../../../helpers/keycodes'; -import { mockBranch, mockPullRequest } from '../../../helpers/mocks/branch-like'; -import { mockComponent } from '../../../helpers/mocks/component'; -import { mockRawHotspot, mockStandards } from '../../../helpers/mocks/security-hotspots'; -import { getStandards } from '../../../helpers/security-standard'; -import { - mockCurrentUser, - mockFlowLocation, - mockLocation, - mockLoggedInUser, -} from '../../../helpers/testMocks'; -import { mockEvent, waitAndUpdate } from '../../../helpers/testUtils'; -import { SecurityStandard } from '../../../types/security'; -import { - HotspotResolution, - HotspotStatus, - HotspotStatusFilter, -} from '../../../types/security-hotspots'; -import { SecurityHotspotsApp } from '../SecurityHotspotsApp'; -import SecurityHotspotsAppRenderer from '../SecurityHotspotsAppRenderer'; - -beforeEach(() => { - jest.clearAllMocks(); -}); - -jest.mock('../../../api/measures', () => ({ - getMeasures: jest.fn().mockResolvedValue([]), -})); - -jest.mock('../../../api/security-hotspots', () => ({ - getSecurityHotspots: jest.fn().mockResolvedValue({ hotspots: [], paging: { total: 0 } }), - getSecurityHotspotList: jest.fn().mockResolvedValue({ hotspots: [], rules: [] }), -})); - -jest.mock('../../../helpers/security-standard', () => ({ - getStandards: jest.fn().mockResolvedValue({ sonarsourceSecurity: { cat1: { title: 'cat 1' } } }), -})); - -jest.mock('../../../helpers/scrolling', () => ({ - scrollToElement: jest.fn(), -})); - -const branch = mockBranch(); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should load data correctly', async () => { - const hotspots = [mockRawHotspot()]; - (getSecurityHotspots as jest.Mock).mockResolvedValue({ - hotspots, - paging: { - total: 1, - }, - }); - (getMeasures as jest.Mock).mockResolvedValue([{ value: '86.6' }]); - - const wrapper = shallowRender(); - - expect(wrapper.state().loading).toBe(true); - expect(wrapper.state().loadingMeasure).toBe(true); - - expect(getStandards).toHaveBeenCalled(); - expect(getSecurityHotspots).toHaveBeenCalledWith( - expect.objectContaining({ - branch: branch.name, - }) - ); - expect(getMeasures).toHaveBeenCalledWith( - expect.objectContaining({ - branch: branch.name, - }) - ); - - await waitAndUpdate(wrapper); - - expect(wrapper.state().loading).toBe(false); - expect(wrapper.state().hotspots).toEqual(hotspots); - expect(wrapper.state().selectedHotspot).toBe(hotspots[0]); - expect(wrapper.state().standards).toEqual({ - sonarsourceSecurity: { - cat1: { title: 'cat 1' }, - }, - }); - expect(wrapper.state().loadingMeasure).toBe(false); - expect(wrapper.state().hotspotsReviewedMeasure).toBe('86.6'); -}); - -it('should handle category request', () => { - (getStandards as jest.Mock).mockResolvedValue(mockStandards()); - (getMeasures as jest.Mock).mockResolvedValue([{ value: '86.6' }]); - - shallowRender({ - location: mockLocation({ query: { [SecurityStandard.OWASP_TOP10]: 'a1' } }), - }); - - expect(getSecurityHotspots).toHaveBeenCalledWith( - expect.objectContaining({ [SecurityStandard.OWASP_TOP10]: 'a1' }) - ); -}); - -it('should handle cwe request', () => { - (getStandards as jest.Mock).mockResolvedValue(mockStandards()); - (getMeasures as jest.Mock).mockResolvedValue([{ value: '86.6' }]); - - shallowRender({ - location: mockLocation({ query: { [SecurityStandard.CWE]: '1004' } }), - }); - - expect(getSecurityHotspots).toHaveBeenCalledWith( - expect.objectContaining({ [SecurityStandard.CWE]: '1004' }) - ); -}); - -it('should handle file request', () => { - (getStandards as jest.Mock).mockResolvedValue(mockStandards()); - (getMeasures as jest.Mock).mockResolvedValue([{ value: '86.6' }]); - - const filepath = 'src/path/to/file.java'; - - shallowRender({ - location: mockLocation({ query: { files: filepath } }), - }); - - expect(getSecurityHotspots).toHaveBeenCalledWith(expect.objectContaining({ files: filepath })); -}); - -it('should load data correctly when hotspot key list is forced', async () => { - const hotspots = [ - mockRawHotspot({ key: 'test1' }), - mockRawHotspot({ key: 'test2' }), - mockRawHotspot({ key: 'test3' }), - ]; - const hotspotKeys = hotspots.map((h) => h.key); - (getSecurityHotspotList as jest.Mock).mockResolvedValueOnce({ - hotspots, - }); - - const location = mockLocation({ query: { hotspots: hotspotKeys.join() } }); - const wrapper = shallowRender({ - location, - }); - - await waitAndUpdate(wrapper); - expect(getSecurityHotspotList).toHaveBeenCalledWith(hotspotKeys, { - projectKey: 'my-project', - branch: 'branch-6.7', - }); - expect(wrapper.state().hotspotKeys).toEqual(hotspotKeys); - expect(wrapper.find(SecurityHotspotsAppRenderer).props().isStaticListOfHotspots).toBe(true); - - // Reset - (getSecurityHotspots as jest.Mock).mockClear(); - (getSecurityHotspotList as jest.Mock).mockClear(); - - // Simulate a new location - wrapper.setProps({ - location: { ...location, query: { ...location.query, hotspots: undefined } }, - }); - await waitAndUpdate(wrapper); - expect(wrapper.state().hotspotKeys).toBeUndefined(); - expect(getSecurityHotspotList).not.toHaveBeenCalled(); - expect(getSecurityHotspots).toHaveBeenCalled(); -}); - -it('should set "leakperiod" filter according to context (branchlike & location query)', () => { - expect(shallowRender().state().filters.inNewCodePeriod).toBe(false); - expect(shallowRender({ branchLike: mockPullRequest() }).state().filters.inNewCodePeriod).toBe( - true - ); - expect( - shallowRender({ location: mockLocation({ query: { inNewCodePeriod: 'true' } }) }).state() - .filters.inNewCodePeriod - ).toBe(true); -}); - -it('should set "assigned to me" filter according to context (logged in & explicit location query)', () => { - const wrapper = shallowRender(); - expect(wrapper.state().filters.assignedToMe).toBe(false); - - wrapper.setProps({ location: mockLocation({ query: { assignedToMe: 'true' } }) }); - expect(wrapper.state().filters.assignedToMe).toBe(false); - - expect(shallowRender({ currentUser: mockLoggedInUser() }).state().filters.assignedToMe).toBe( - false - ); - expect( - shallowRender({ - location: mockLocation({ query: { assignedToMe: 'true' } }), - currentUser: mockLoggedInUser(), - }).state().filters.assignedToMe - ).toBe(true); -}); - -it('should handle loading more', async () => { - const hotspots = [mockRawHotspot({ key: '1' }), mockRawHotspot({ key: '2' })]; - const hotspots2 = [mockRawHotspot({ key: '3' }), mockRawHotspot({ key: '4' })]; - (getSecurityHotspots as jest.Mock) - .mockResolvedValueOnce({ - hotspots, - paging: { total: 5 }, - }) - .mockResolvedValueOnce({ - hotspots: hotspots2, - paging: { total: 5 }, - }); - - const wrapper = shallowRender(); - - await waitAndUpdate(wrapper); - - wrapper.instance().handleLoadMore(); - - expect(wrapper.state().loadingMore).toBe(true); - expect(getSecurityHotspots).toHaveBeenCalledTimes(2); - - await waitAndUpdate(wrapper); - - expect(wrapper.state().loadingMore).toBe(false); - expect(wrapper.state().hotspotsPageIndex).toBe(2); - expect(wrapper.state().hotspotsTotal).toBe(5); - expect(wrapper.state().hotspots).toHaveLength(4); -}); - -it('should handle hotspot update', async () => { - const key = 'hotspotKey'; - const hotspots = [mockRawHotspot(), mockRawHotspot({ key })]; - const fetchBranchStatusMock = jest.fn(); - const branchLike = mockPullRequest(); - const componentKey = 'test'; - - (getSecurityHotspots as jest.Mock).mockResolvedValueOnce({ - hotspots, - paging: { pageIndex: 1, total: 1252 }, - }); - - let wrapper = shallowRender(); - await waitAndUpdate(wrapper); - wrapper.setState({ hotspotsPageIndex: 2 }); - - jest.clearAllMocks(); - (getSecurityHotspots as jest.Mock) - .mockResolvedValueOnce({ - hotspots: [mockRawHotspot()], - paging: { pageIndex: 1, total: 1251 }, - }) - .mockResolvedValueOnce({ - hotspots: [mockRawHotspot()], - paging: { pageIndex: 2, total: 1251 }, - }); - - const selectedHotspotIndex = wrapper - .state() - .hotspots.findIndex((h) => h.key === wrapper.state().selectedHotspot?.key); - - await wrapper.find(SecurityHotspotsAppRenderer).props().onUpdateHotspot(key); - - expect(getSecurityHotspots).toHaveBeenCalledTimes(2); - - expect(wrapper.state().hotspots).toHaveLength(2); - expect(wrapper.state().hotspotsPageIndex).toBe(2); - expect(wrapper.state().hotspotsTotal).toBe(1251); - expect( - wrapper.state().hotspots.findIndex((h) => h.key === wrapper.state().selectedHotspot?.key) - ).toBe(selectedHotspotIndex); - - expect(getMeasures).toHaveBeenCalled(); - - (getSecurityHotspots as jest.Mock).mockResolvedValueOnce({ - hotspots, - paging: { pageIndex: 1, total: 1252 }, - }); - - wrapper = shallowRender({ - branchLike, - fetchBranchStatus: fetchBranchStatusMock, - component: mockComponent({ key: componentKey }), - }); - await wrapper.find(SecurityHotspotsAppRenderer).props().onUpdateHotspot(key); - expect(fetchBranchStatusMock).toHaveBeenCalledWith(branchLike, componentKey); -}); - -it('should handle status filter change', async () => { - const hotspots = [mockRawHotspot({ key: 'key1' })]; - const hotspots2 = [mockRawHotspot({ key: 'key2' })]; - (getSecurityHotspots as jest.Mock) - .mockResolvedValueOnce({ hotspots, paging: { total: 1 } }) - .mockResolvedValueOnce({ hotspots: hotspots2, paging: { total: 1 } }) - .mockResolvedValueOnce({ hotspots: [], paging: { total: 0 } }); - - const wrapper = shallowRender(); - - expect(getSecurityHotspots).toHaveBeenCalledWith( - expect.objectContaining({ status: HotspotStatus.TO_REVIEW, resolution: undefined }) - ); - - await waitAndUpdate(wrapper); - - expect(getMeasures).toHaveBeenCalledTimes(1); - - // Set filter to SAFE: - wrapper.instance().handleChangeFilters({ status: HotspotStatusFilter.SAFE }); - expect(getMeasures).toHaveBeenCalledTimes(1); - - expect(getSecurityHotspots).toHaveBeenCalledWith( - expect.objectContaining({ status: HotspotStatus.REVIEWED, resolution: HotspotResolution.SAFE }) - ); - - await waitAndUpdate(wrapper); - - expect(wrapper.state().hotspots[0]).toBe(hotspots2[0]); - - // Set filter to FIXED (use the other method to check this one): - wrapper.instance().handleChangeStatusFilter(HotspotStatusFilter.FIXED); - - expect(getSecurityHotspots).toHaveBeenCalledWith( - expect.objectContaining({ status: HotspotStatus.REVIEWED, resolution: HotspotResolution.FIXED }) - ); - - await waitAndUpdate(wrapper); - - expect(wrapper.state().hotspots).toHaveLength(0); -}); - -it('should handle leakPeriod filter change', async () => { - const hotspots = [mockRawHotspot({ key: 'key1' })]; - const hotspots2 = [mockRawHotspot({ key: 'key2' })]; - (getSecurityHotspots as jest.Mock) - .mockResolvedValueOnce({ hotspots, paging: { total: 1 } }) - .mockResolvedValueOnce({ hotspots: hotspots2, paging: { total: 1 } }) - .mockResolvedValueOnce({ hotspots: [], paging: { total: 0 } }); - - const wrapper = shallowRender(); - - expect(getSecurityHotspots).toHaveBeenCalledWith( - expect.objectContaining({ status: HotspotStatus.TO_REVIEW, resolution: undefined }) - ); - - await waitAndUpdate(wrapper); - - expect(getMeasures).toHaveBeenCalledTimes(1); - - wrapper.instance().handleChangeFilters({ inNewCodePeriod: true }); - - expect(getMeasures).toHaveBeenCalledTimes(2); - expect(getSecurityHotspots).toHaveBeenCalledWith( - expect.objectContaining({ inNewCodePeriod: true }) - ); -}); - -it('should handle hotspot click', () => { - const wrapper = shallowRender(); - const selectedHotspot = mockRawHotspot(); - wrapper.instance().handleHotspotClick(selectedHotspot); - - expect(wrapper.instance().state.selectedHotspotLocationIndex).toBeUndefined(); - expect(wrapper.instance().state.selectedHotspot).toEqual(selectedHotspot); -}); - -it('should handle secondary location click', () => { - const wrapper = shallowRender(); - wrapper.instance().handleLocationClick(0); - expect(wrapper.instance().state.selectedHotspotLocationIndex).toEqual(0); - - wrapper.instance().handleLocationClick(1); - expect(wrapper.instance().state.selectedHotspotLocationIndex).toEqual(1); - - wrapper.instance().handleLocationClick(1); - expect(wrapper.instance().state.selectedHotspotLocationIndex).toBeUndefined(); - - wrapper.setState({ selectedHotspotLocationIndex: 2 }); - wrapper.instance().handleLocationClick(); - expect(wrapper.instance().state.selectedHotspotLocationIndex).toBeUndefined(); -}); - -describe('keyboard navigation', () => { - const hotspots = [ - mockRawHotspot({ key: 'k1' }), - mockRawHotspot({ key: 'k2' }), - mockRawHotspot({ key: 'k3' }), - ]; - const flowsData = { - flows: [{ locations: [mockFlowLocation(), mockFlowLocation(), mockFlowLocation()] }], - }; - const hotspotsForLocation = mockRawHotspot(flowsData); - - (getSecurityHotspots as jest.Mock).mockResolvedValueOnce({ hotspots, paging: { total: 3 } }); - - const wrapper = shallowRender(); - - it.each([ - ['selecting next', 0, 1, 1], - ['selecting previous', 1, -1, 0], - ['selecting previous, non-existent', 0, -1, 0], - ['selecting next, non-existent', 2, 1, 2], - ['jumping down', 0, 18, 2], - ['jumping up', 2, -18, 0], - ['none selected', 4, -2, 4], - ])('should work when %s', (_, start, shift, expected) => { - wrapper.setState({ selectedHotspot: hotspots[start] }); - wrapper.instance().selectNeighboringHotspot(shift); - - expect(wrapper.state().selectedHotspot).toBe(hotspots[expected]); - }); - - it.each([ - ['selecting next locations when nothing is selected', undefined, 0], - ['selecting next locations', 0, 1], - ['selecting next locations, non-existent', 2, undefined], - ])('should work when %s', (_, start, expected) => { - wrapper.setState({ selectedHotspotLocationIndex: start, selectedHotspot: hotspotsForLocation }); - wrapper.instance().handleKeyDown(mockEvent({ altKey: true, key: KeyboardKeys.DownArrow })); - - expect(wrapper.state().selectedHotspotLocationIndex).toBe(expected); - }); - - it.each([ - ['selecting previous locations when nothing is selected', undefined, undefined], - ['selecting previous locations', 1, 0], - ['selecting previous locations, non-existent', 0, undefined], - ])('should work when %s', (_, start, expected) => { - wrapper.setState({ selectedHotspotLocationIndex: start, selectedHotspot: hotspotsForLocation }); - wrapper.instance().handleKeyDown(mockEvent({ altKey: true, key: KeyboardKeys.UpArrow })); - - expect(wrapper.state().selectedHotspotLocationIndex).toBe(expected); - }); - - it('should not change location index when locations are empty', () => { - wrapper.setState({ selectedHotspotLocationIndex: undefined, selectedHotspot: hotspots[0] }); - - wrapper.instance().handleKeyDown(mockEvent({ altKey: true, key: KeyboardKeys.UpArrow })); - expect(wrapper.state().selectedHotspotLocationIndex).toBeUndefined(); - - wrapper.instance().handleKeyDown(mockEvent({ altKey: true, key: KeyboardKeys.DownArrow })); - expect(wrapper.state().selectedHotspotLocationIndex).toBeUndefined(); - }); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx deleted file mode 100644 index 1e0594d62cb..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/SecurityHotspotsAppRenderer-test.tsx +++ /dev/null @@ -1,154 +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 React from 'react'; -import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; -import { mockComponent } from '../../../helpers/mocks/component'; -import { mockRawHotspot, mockStandards } from '../../../helpers/mocks/security-hotspots'; -import { scrollToElement } from '../../../helpers/scrolling'; -import { SecurityStandard } from '../../../types/security'; -import { HotspotStatusFilter } from '../../../types/security-hotspots'; -import SecurityHotspotsAppRenderer, { - SecurityHotspotsAppRendererProps, -} from '../SecurityHotspotsAppRenderer'; - -jest.mock('../../../helpers/scrolling', () => ({ - scrollToElement: jest.fn(), -})); - -jest.mock('../../../components/common/ScreenPositionHelper'); - -beforeEach(() => { - jest.clearAllMocks(); -}); - -jest.mock('react', () => { - return { - ...jest.requireActual('react'), - useRef: jest.fn(), - useEffect: jest.fn(), - }; -}); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); - expect( - shallowRender({ - filters: { - assignedToMe: true, - inNewCodePeriod: false, - status: HotspotStatusFilter.TO_REVIEW, - }, - }) - ).toMatchSnapshot('no hotspots with filters'); - expect(shallowRender({ loading: true })).toMatchSnapshot('loading'); -}); - -it('should render correctly with hotspots', () => { - const hotspots = [mockRawHotspot({ key: 'h1' }), mockRawHotspot({ key: 'h2' })]; - expect(shallowRender({ hotspots, hotspotsTotal: 2 })).toMatchSnapshot(); - expect( - shallowRender({ hotspots, hotspotsTotal: 3, selectedHotspot: mockRawHotspot({ key: 'h2' }) }) - .find(ScreenPositionHelper) - .dive() - ).toMatchSnapshot(); -}); - -it('should render correctly when filtered by category or cwe', () => { - const hotspots = [mockRawHotspot({ key: 'h1' }), mockRawHotspot({ key: 'h2' })]; - - expect( - shallowRender({ filterByCWE: '327', hotspots, hotspotsTotal: 2, selectedHotspot: hotspots[0] }) - .find(ScreenPositionHelper) - .dive() - ).toMatchSnapshot('cwe'); - expect( - shallowRender({ - filterByCategory: { category: 'a1', standard: SecurityStandard.OWASP_TOP10 }, - hotspots, - hotspotsTotal: 2, - selectedHotspot: hotspots[0], - }) - .find(ScreenPositionHelper) - .dive() - ).toMatchSnapshot('category'); -}); - -describe('side effect', () => { - const fakeElement = document.createElement('span'); - const fakeParent = document.createElement('div'); - - beforeEach(() => { - (React.useEffect as jest.Mock).mockImplementationOnce((f) => f()); - jest.spyOn(document, 'querySelector').mockImplementationOnce(() => fakeElement); - (React.useRef as jest.Mock).mockImplementationOnce(() => ({ current: fakeParent })); - }); - - it('should trigger scrolling', () => { - shallowRender({ selectedHotspot: mockRawHotspot() }); - - expect(scrollToElement).toHaveBeenCalledWith( - fakeElement, - expect.objectContaining({ parent: fakeParent }) - ); - }); - - it('should not trigger scrolling if no selected hotspot', () => { - shallowRender(); - expect(scrollToElement).not.toHaveBeenCalled(); - }); - - it('should not trigger scrolling if no parent', () => { - const mockUseRef = React.useRef as jest.Mock; - mockUseRef.mockReset(); - mockUseRef.mockImplementationOnce(() => ({ current: null })); - shallowRender({ selectedHotspot: mockRawHotspot() }); - expect(scrollToElement).not.toHaveBeenCalled(); - }); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap deleted file mode 100644 index 6c80b807c45..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsApp-test.tsx.snap +++ /dev/null @@ -1,68 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` - -`; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap deleted file mode 100644 index fa4d05d80fa..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/__tests__/__snapshots__/SecurityHotspotsAppRenderer-test.tsx.snap +++ /dev/null @@ -1,617 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
- - - - - -
-`; - -exports[`should render correctly when filtered by category or cwe: category 1`] = ` -
-
- -
-
-`; - -exports[`should render correctly when filtered by category or cwe: cwe 1`] = ` -
-
- -
-
-`; - -exports[`should render correctly with hotspots 1`] = ` -
- - - - - -
-`; - -exports[`should render correctly with hotspots 2`] = ` -
-
- -
-
-`; - -exports[`should render correctly: loading 1`] = ` -
- - - - -
-
- -
-
-
-`; - -exports[`should render correctly: no hotspots with filters 1`] = ` -
- - - - - -
-`; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx index ce2e7940a0a..07dacf5f64e 100644 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx +++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotHeader.tsx @@ -37,9 +37,9 @@ export function HotspotHeader(props: HotspotHeaderProps) { return (
-
+

-

+
{rule.name} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/EmptyHotspotsPage-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/EmptyHotspotsPage-test.tsx deleted file mode 100644 index 38fffee404e..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/EmptyHotspotsPage-test.tsx +++ /dev/null @@ -1,40 +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 EmptyHotspotsPage, { EmptyHotspotsPageProps } from '../EmptyHotspotsPage'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); - expect(shallowRender({ filtered: true })).toMatchSnapshot('filtered'); - expect(shallowRender({ isStaticListOfHotspots: true })).toMatchSnapshot('keys'); - expect(shallowRender({ filterByFile: true })).toMatchSnapshot('file'); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/FilterBar-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/FilterBar-test.tsx deleted file mode 100644 index 7318feaa0cd..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/FilterBar-test.tsx +++ /dev/null @@ -1,99 +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 ButtonToggle from '../../../../components/controls/ButtonToggle'; -import Select from '../../../../components/controls/Select'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; -import { ComponentQualifier } from '../../../../types/component'; -import { HotspotStatusFilter } from '../../../../types/security-hotspots'; -import { AssigneeFilterOption, FilterBar, FilterBarProps } from '../FilterBar'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('anonymous'); - expect(shallowRender({ currentUser: mockLoggedInUser() })).toMatchSnapshot('logged-in'); - expect(shallowRender({ onBranch: false })).toMatchSnapshot('on Pull request'); - expect(shallowRender({ hotspotsReviewedMeasure: '23.30' })).toMatchSnapshot( - 'with hotspots reviewed measure' - ); - expect( - shallowRender({ - currentUser: mockLoggedInUser(), - component: mockComponent({ qualifier: ComponentQualifier.Application }), - }) - ).toMatchSnapshot('non-project'); -}); - -it('should render correctly when the list of hotspot is static', () => { - const wrapper = shallowRender({ - isStaticListOfHotspots: true, - }); - expect(wrapper).toMatchSnapshot(); -}); - -it('should trigger onChange for status', () => { - const onChangeFilters = jest.fn(); - const wrapper = shallowRender({ onChangeFilters }); - - const { onChange } = wrapper.find(Select).at(0).props(); - - onChange({ value: HotspotStatusFilter.SAFE }); - expect(onChangeFilters).toHaveBeenCalledWith({ status: HotspotStatusFilter.SAFE }); -}); - -it('should trigger onChange for self-assigned toggle', () => { - const onChangeFilters = jest.fn(); - const wrapper = shallowRender({ currentUser: mockLoggedInUser(), onChangeFilters }); - - const { onCheck } = wrapper.find(ButtonToggle).props(); - - onCheck(AssigneeFilterOption.ALL); - expect(onChangeFilters).toHaveBeenCalledWith({ assignedToMe: false }); -}); - -it('should trigger onChange for leak period', () => { - const onChangeFilters = jest.fn(); - const wrapper = shallowRender({ onChangeFilters }); - - const { onChange } = wrapper.find(Select).at(1).props(); - - onChange({ value: true }); - expect(onChangeFilters).toHaveBeenCalledWith({ inNewCodePeriod: true }); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCategory-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCategory-test.tsx deleted file mode 100644 index 3517dd1c106..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCategory-test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockRawHotspot } from '../../../../helpers/mocks/security-hotspots'; -import HotspotCategory, { HotspotCategoryProps } from '../HotspotCategory'; - -it('should render correctly', () => { - expect(shallowRender().type()).toBeNull(); -}); - -it('should render correctly with hotspots', () => { - const securityCategory = 'command-injection'; - const hotspots = [ - mockRawHotspot({ key: 'h1', securityCategory }), - mockRawHotspot({ key: 'h2', securityCategory }), - ]; - expect(shallowRender({ hotspots })).toMatchSnapshot(); - expect(shallowRender({ hotspots, expanded: false })).toMatchSnapshot('collapsed'); - expect( - shallowRender({ categoryKey: securityCategory, hotspots, selectedHotspot: hotspots[0] }) - ).toMatchSnapshot('contains selected'); - expect(shallowRender({ hotspots, isLastAndIncomplete: true })).toMatchSnapshot( - 'lastAndIncomplete' - ); -}); - -it('should handle collapse and expand', () => { - const onToggleExpand = jest.fn(); - - const categoryKey = 'xss-injection'; - - const wrapper = shallowRender({ - categoryKey, - expanded: true, - hotspots: [mockRawHotspot()], - onToggleExpand, - }); - - wrapper.find('.hotspot-category-header').simulate('click'); - - expect(onToggleExpand).toHaveBeenCalledWith(categoryKey, false); - - wrapper.setProps({ expanded: false }); - wrapper.find('.hotspot-category-header').simulate('click'); - - expect(onToggleExpand).toHaveBeenCalledWith(categoryKey, true); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCommentPopup-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCommentPopup-test.tsx deleted file mode 100644 index 4e81f39822b..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotCommentPopup-test.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { Button, ResetButtonLink } from '../../../../components/controls/buttons'; -import HotspotCommentPopup, { HotspotCommentPopupProps } from '../HotspotCommentPopup'; - -it('should render correclty', () => { - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot(); -}); - -it('should trigger update comment', () => { - const props = { - onCommentEditSubmit: jest.fn(), - }; - const wrapper = shallowRender(props); - wrapper.find('textarea').simulate('change', { target: { value: 'foo' } }); - wrapper.find(Button).simulate('click'); - - expect(props.onCommentEditSubmit).toHaveBeenCalledWith('foo'); -}); - -it('should trigger cancel update comment', () => { - const props = { - onCancelEdit: jest.fn(), - }; - const wrapper = shallowRender(props); - wrapper.find(ResetButtonLink).simulate('click'); - - expect(props.onCancelEdit).toHaveBeenCalledTimes(1); -}); - -function shallowRender(props?: Partial) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotHeader-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotHeader-test.tsx deleted file mode 100644 index a1295913719..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotHeader-test.tsx +++ /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 React from 'react'; -import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; -import { HotspotStatusOption } from '../../../../types/security-hotspots'; -import { HotspotHeader, HotspotHeaderProps } from '../HotspotHeader'; -import Status from '../status/Status'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('correctly propagates the status change', () => { - const onUpdateHotspot = jest.fn(); - const wrapper = shallowRender({ onUpdateHotspot }); - - wrapper.find(Status).props().onStatusChange(HotspotStatusOption.FIXED); - - expect(onUpdateHotspot).toHaveBeenCalledWith(true, HotspotStatusOption.FIXED); -}); - -function shallowRender(props: Partial = {}) { - return shallow(); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx deleted file mode 100644 index ef075366be2..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotList-test.tsx +++ /dev/null @@ -1,127 +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 { mockRawHotspot } from '../../../../helpers/mocks/security-hotspots'; -import { addSideBarClass, removeSideBarClass } from '../../../../helpers/pages'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import { HotspotStatusFilter, RiskExposure } from '../../../../types/security-hotspots'; -import HotspotList from '../HotspotList'; - -jest.mock('../../../../helpers/pages', () => ({ - addSideBarClass: jest.fn(), - removeSideBarClass: jest.fn(), -})); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); - expect(shallowRender({ loadingMore: true })).toMatchSnapshot(); -}); - -it('should add/remove sidebar classes', async () => { - const wrapper = shallowRender(); - - await waitAndUpdate(wrapper); - - expect(addSideBarClass).toHaveBeenCalled(); - - wrapper.unmount(); - - expect(removeSideBarClass).toHaveBeenCalled(); -}); - -it('should render correctly when the list of hotspot is static', () => { - expect(shallowRender({ isStaticListOfHotspots: true })).toMatchSnapshot(); -}); - -const hotspots = [ - mockRawHotspot({ key: 'h1', securityCategory: 'cat2' }), - mockRawHotspot({ key: 'h2', securityCategory: 'cat1' }), - mockRawHotspot({ - key: 'h3', - securityCategory: 'cat1', - vulnerabilityProbability: RiskExposure.MEDIUM, - }), - mockRawHotspot({ - key: 'h4', - securityCategory: 'cat1', - vulnerabilityProbability: RiskExposure.MEDIUM, - }), - mockRawHotspot({ - key: 'h5', - securityCategory: 'cat2', - vulnerabilityProbability: RiskExposure.MEDIUM, - }), -]; - -it('should render correctly with hotspots', () => { - expect(shallowRender({ hotspots, hotspotsTotal: hotspots.length })).toMatchSnapshot( - 'no pagination' - ); - expect(shallowRender({ hotspots, hotspotsTotal: 7 })).toMatchSnapshot('pagination'); -}); - -it('should update expanded categories correctly', () => { - const wrapper = shallowRender({ hotspots, selectedHotspot: hotspots[0] }); - - expect(wrapper.state().expandedCategories).toEqual({ cat2: true }); - - wrapper.setProps({ selectedHotspot: hotspots[1] }); - - expect(wrapper.state().expandedCategories).toEqual({ cat1: true, cat2: true }); -}); - -it('should update grouped hotspots when the list changes', () => { - const wrapper = shallowRender({ hotspots, selectedHotspot: hotspots[0] }); - - wrapper.setProps({ hotspots: [mockRawHotspot()] }); - - expect(wrapper.state().groupedHotspots).toHaveLength(1); - expect(wrapper.state().groupedHotspots[0].categories).toHaveLength(1); - expect(wrapper.state().groupedHotspots[0].categories[0].hotspots).toHaveLength(1); -}); - -it('should expand the categories for which the location is selected', () => { - const wrapper = shallowRender({ hotspots, selectedHotspot: hotspots[0] }); - - wrapper.setState({ expandedCategories: { cat1: true, cat2: false } }); - - wrapper.setProps({ selectedHotspotLocation: 1 }); - - expect(wrapper.state().expandedCategories).toEqual({ cat1: true, cat2: true }); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotListItem-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotListItem-test.tsx deleted file mode 100644 index e81e47a4dc4..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotListItem-test.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { mockRawHotspot } from '../../../../helpers/mocks/security-hotspots'; -import HotspotListItem, { HotspotListItemProps } from '../HotspotListItem'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); - expect(shallowRender({ selected: true })).toMatchSnapshot(); -}); - -it('should handle click', () => { - const hotspot = mockRawHotspot({ key: 'hotspotKey' }); - const onClick = jest.fn(); - const wrapper = shallowRender({ hotspot, onClick }); - - wrapper.simulate('click'); - - expect(onClick).toHaveBeenCalledWith(hotspot); -}); - -it('should handle click on the title', () => { - const hotspot = mockRawHotspot({ key: 'hotspotKey' }); - const onLocationClick = jest.fn(); - const wrapper = shallowRender({ hotspot, onLocationClick, selected: true }); - - wrapper.find('div.cursor-pointer').simulate('click'); - - expect(onLocationClick).toHaveBeenCalledWith(); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotOpenInIdeButton-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotOpenInIdeButton-test.tsx deleted file mode 100644 index f8b02ea5189..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotOpenInIdeButton-test.tsx +++ /dev/null @@ -1,82 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import * as React from 'react'; -import { Button } from '../../../../components/controls/buttons'; -import * as sonarlint from '../../../../helpers/sonarlint'; -import HotspotOpenInIdeButton from '../HotspotOpenInIdeButton'; - -jest.mock('../../../../helpers/sonarlint'); - -describe('HotspotOpenInIdeButton', () => { - beforeEach(jest.resetAllMocks); - - it('should render correctly', async () => { - const projectKey = 'my-project:key'; - const hotspotKey = 'AXWsgE9RpggAQesHYfwm'; - const port = 42001; - - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - - (sonarlint.probeSonarLintServers as jest.Mock).mockResolvedValue([ - { port, ideName: 'BlueJ IDE', description: 'Hello World' }, - ]); - (sonarlint.openHotspot as jest.Mock).mockResolvedValue(null); - - wrapper.find(Button).simulate('click'); - - await new Promise(setImmediate); - expect(sonarlint.openHotspot).toHaveBeenCalledWith(port, projectKey, hotspotKey); - }); - - it('should gracefully handle zero IDE detected', async () => { - const wrapper = shallow(); - (sonarlint.probeSonarLintServers as jest.Mock).mockResolvedValue([]); - wrapper.find(Button).simulate('click'); - - await new Promise(setImmediate); - expect(sonarlint.openHotspot).not.toHaveBeenCalled(); - }); - - it('should handle several IDE', async () => { - const projectKey = 'my-project:key'; - const hotspotKey = 'AXWsgE9RpggAQesHYfwm'; - const port1 = 42000; - const port2 = 42001; - - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - - (sonarlint.probeSonarLintServers as jest.Mock).mockResolvedValue([ - { port: port1, ideName: 'BlueJ IDE', description: 'Hello World' }, - { port: port2, ideName: 'Arduino IDE', description: 'Blink' }, - ]); - - wrapper.find(Button).simulate('click'); - - await new Promise(setImmediate); - expect(wrapper).toMatchSnapshot('dropdown open'); - }); -}); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotOpenInIdeOverlay-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotOpenInIdeOverlay-test.tsx deleted file mode 100644 index 4208234a040..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotOpenInIdeOverlay-test.tsx +++ /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 { HotspotOpenInIdeOverlay } from '../HotspotOpenInIdeOverlay'; - -it('should render nothing with fewer than 2 IDE', () => { - const onIdeSelected = jest.fn(); - expect( - shallow().type() - ).toBeNull(); - expect( - shallow( - - ).type() - ).toBeNull(); -}); - -it('should render menu and select the right IDE', () => { - const onIdeSelected = jest.fn(); - const ide1 = { port: 0, ideName: 'Polop', description: 'Plouf' }; - const ide2 = { port: 1, ideName: 'Foo', description: '' }; - const wrapper = shallow( - - ); - expect(wrapper).toMatchSnapshot(); - - wrapper.find('a').last().simulate('click'); - expect(onIdeSelected).toHaveBeenCalledWith(ide2); -}); diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotPrimaryLocationBox-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotPrimaryLocationBox-test.tsx deleted file mode 100644 index 17739630d51..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotPrimaryLocationBox-test.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -import { shallow } from 'enzyme'; -import React from 'react'; -import { ButtonLink } from '../../../../components/controls/buttons'; -import { mockHotspot, mockHotspotRule } from '../../../../helpers/mocks/security-hotspots'; -import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; -import { RiskExposure } from '../../../../types/security-hotspots'; -import { - HotspotPrimaryLocationBox, - HotspotPrimaryLocationBoxProps, -} from '../HotspotPrimaryLocationBox'; - -jest.mock('react', () => { - return { - ...jest.requireActual('react'), - useRef: jest.fn(), - useEffect: jest.fn(), - }; -}); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('User logged in'); - expect(shallowRender({ currentUser: mockCurrentUser() })).toMatchSnapshot('User not logged in '); -}); - -it.each([[RiskExposure.HIGH], [RiskExposure.MEDIUM], [RiskExposure.LOW]])( - 'should indicate risk exposure: %s', - (vulnerabilityProbability) => { - const wrapper = shallowRender({ - hotspot: mockHotspot({ rule: mockHotspotRule({ vulnerabilityProbability }) }), - }); - - expect(wrapper.hasClass(`hotspot-risk-exposure-${vulnerabilityProbability}`)).toBe(true); - } -); - -it('should handle click', () => { - const onCommentClick = jest.fn(); - const wrapper = shallowRender({ onCommentClick }); - - wrapper.find(ButtonLink).simulate('click'); - - expect(onCommentClick).toHaveBeenCalled(); -}); - -it('should scroll on load if no secondary locations selected', () => { - const node = document.createElement('div'); - (React.useRef as jest.Mock).mockImplementationOnce(() => ({ current: node })); - (React.useEffect as jest.Mock).mockImplementationOnce((f) => f()); - - const scroll = jest.fn(); - shallowRender({ scroll }); - - expect(scroll).toHaveBeenCalled(); -}); - -it('should not scroll on load if a secondary location is selected', () => { - const node = document.createElement('div'); - (React.useRef as jest.Mock).mockImplementationOnce(() => ({ current: node })); - (React.useEffect as jest.Mock).mockImplementationOnce((f) => f()); - - const scroll = jest.fn(); - shallowRender({ scroll, secondaryLocationSelected: true }); - - expect(scroll).not.toHaveBeenCalled(); -}); - -it('should not scroll on load if node is not defined', () => { - (React.useRef as jest.Mock).mockImplementationOnce(() => ({ current: undefined })); - (React.useEffect as jest.Mock).mockImplementationOnce((f) => f()); - - const scroll = jest.fn(); - shallowRender({ scroll }); - - expect(scroll).not.toHaveBeenCalled(); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx deleted file mode 100644 index 791de003a18..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistory-test.tsx +++ /dev/null @@ -1,152 +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 React from 'react'; -import { Button, EditButton } from '../../../../components/controls/buttons'; -import Dropdown, { DropdownOverlay } from '../../../../components/controls/Dropdown'; -import Toggler from '../../../../components/controls/Toggler'; -import { mockIssueChangelog } from '../../../../helpers/mocks/issues'; -import { mockHotspot, mockHotspotComment } from '../../../../helpers/mocks/security-hotspots'; -import { mockUser } from '../../../../helpers/testMocks'; -import HotspotCommentPopup from '../HotspotCommentPopup'; -import HotspotReviewHistory, { HotspotReviewHistoryProps } from '../HotspotReviewHistory'; - -jest.mock('react', () => { - return { - ...jest.requireActual('react'), - useState: jest.fn().mockImplementation(() => ['', jest.fn()]), - }; -}); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('default'); - expect(shallowRender({ showFullHistory: true })).toMatchSnapshot('show full list'); - expect(shallowRender({ showFullHistory: true }).find(Toggler).props().overlay).toMatchSnapshot( - 'edit comment overlay' - ); - expect(shallowRender({ showFullHistory: true }).find(Dropdown).props().overlay).toMatchSnapshot( - 'delete comment overlay' - ); -}); - -it('should correctly handle comment updating', () => { - return new Promise((resolve, reject) => { - const setEditedCommentKey = jest.fn(); - (React.useState as jest.Mock).mockImplementationOnce(() => ['', setEditedCommentKey]); - - const onEditComment = jest.fn(); - const wrapper = shallowRender({ onEditComment, showFullHistory: true }); - - // Closing the Toggler sets the edited key back to an empty string. - wrapper.find(Toggler).at(0).props().onRequestClose(); - expect(setEditedCommentKey).toHaveBeenCalledWith(''); - - const editOnClick = wrapper.find(EditButton).at(0).props().onClick; - if (!editOnClick) { - reject(); - return; - } - - // Clicking on the EditButton correctly flags the comment for editing. - editOnClick(); - expect(setEditedCommentKey).toHaveBeenLastCalledWith('comment-1'); - - // Cancelling an edit sets the edited key back to an empty string - const dropdownOverlay = shallow( - wrapper.find(Toggler).at(0).props().overlay as React.ReactElement - ); - dropdownOverlay.find(HotspotCommentPopup).props().onCancelEdit(); - expect(setEditedCommentKey).toHaveBeenLastCalledWith(''); - - // Updating the comment sets the edited key back to an empty string, and calls the - // prop to update the comment value. - dropdownOverlay.find(HotspotCommentPopup).props().onCommentEditSubmit('comment'); - expect(onEditComment).toHaveBeenLastCalledWith('comment-1', 'comment'); - expect(setEditedCommentKey).toHaveBeenLastCalledWith(''); - expect(setEditedCommentKey).toHaveBeenCalledTimes(4); - - resolve(); - }); -}); - -it('should correctly handle comment deleting', () => { - return new Promise((resolve, reject) => { - const setEditedCommentKey = jest.fn(); - (React.useState as jest.Mock).mockImplementationOnce(() => ['', setEditedCommentKey]); - - const onDeleteComment = jest.fn(); - const wrapper = shallowRender({ onDeleteComment, showFullHistory: true }); - - // Opening the deletion Dropdown sets the edited key back to an empty string. - const dropdownOnOpen = wrapper.find(Dropdown).at(0).props().onOpen; - if (!dropdownOnOpen) { - reject(); - return; - } - dropdownOnOpen(); - expect(setEditedCommentKey).toHaveBeenLastCalledWith(''); - - // Confirming deletion calls the prop to delete the comment. - const dropdownOverlay = shallow( - wrapper.find(Dropdown).at(0).props().overlay as React.ReactElement - ); - const deleteButtonOnClick = dropdownOverlay.find(Button).props().onClick; - if (!deleteButtonOnClick) { - reject(); - return; - } - - deleteButtonOnClick(); - expect(onDeleteComment).toHaveBeenCalledWith('comment-1'); - - resolve(); - }); -}); - -function shallowRender(props?: Partial) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx deleted file mode 100644 index 22abda84f2f..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotReviewHistoryAndComments-test.tsx +++ /dev/null @@ -1,121 +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 { - commentSecurityHotspot, - deleteSecurityHotspotComment, - editSecurityHotspotComment, -} from '../../../../api/security-hotspots'; -import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; -import { mockCurrentUser } from '../../../../helpers/testMocks'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import { isLoggedIn } from '../../../../types/users'; -import HotspotReviewHistory from '../HotspotReviewHistory'; -import HotspotReviewHistoryAndComments from '../HotspotReviewHistoryAndComments'; - -jest.mock('../../../../api/security-hotspots', () => ({ - commentSecurityHotspot: jest.fn().mockResolvedValue({}), - deleteSecurityHotspotComment: jest.fn().mockResolvedValue({}), - editSecurityHotspotComment: jest.fn().mockResolvedValue({}), -})); - -jest.mock('../../../../types/users', () => ({ isLoggedIn: jest.fn(() => true) })); - -it('should render correctly', () => { - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot(); -}); - -it('should render correctly without user', () => { - (isLoggedIn as any as jest.Mock).mockReturnValueOnce(false); - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot(); -}); - -it('should submit comment', async () => { - const mockApi = commentSecurityHotspot as jest.Mock; - const hotspot = mockHotspot(); - const wrapper = shallowRender({ hotspot }); - mockApi.mockClear(); - wrapper.instance().setState({ comment: 'Comment' }); - - wrapper.find('#hotspot-comment-box-submit').simulate('click'); - await waitAndUpdate(wrapper); - - expect(mockApi).toHaveBeenCalledWith(hotspot.key, 'Comment'); - expect(wrapper.state().comment).toBe(''); - expect(wrapper.instance().props.onCommentUpdate).toHaveBeenCalledTimes(1); -}); - -it('should change comment', () => { - const wrapper = shallowRender(); - wrapper.instance().setState({ comment: 'Comment' }); - wrapper.find('textarea').simulate('change', { target: { value: 'Foo' } }); - - expect(wrapper.state().comment).toBe('Foo'); -}); - -it('should reset on change hotspot', () => { - const wrapper = shallowRender(); - wrapper.setState({ comment: 'NOP' }); - wrapper.setProps({ hotspot: mockHotspot({ key: 'other-hotspot' }) }); - - expect(wrapper.state().comment).toBe(''); -}); - -it('should delete comment', async () => { - const wrapper = shallowRender(); - - wrapper.find(HotspotReviewHistory).simulate('deleteComment', 'me1'); - await waitAndUpdate(wrapper); - - expect(deleteSecurityHotspotComment).toHaveBeenCalledWith('me1'); - expect(wrapper.instance().props.onCommentUpdate).toHaveBeenCalledTimes(1); -}); - -it('should edit comment', async () => { - const wrapper = shallowRender(); - - wrapper.find(HotspotReviewHistory).simulate('editComment', 'me1', 'new'); - await waitAndUpdate(wrapper); - - expect(editSecurityHotspotComment).toHaveBeenCalledWith('me1', 'new'); - expect(wrapper.instance().props.onCommentUpdate).toHaveBeenCalledTimes(1); -}); - -it('should correctly toggle the show full history state', () => { - const wrapper = shallowRender(); - expect(wrapper.state().showFullHistory).toBe(false); - wrapper.find(HotspotReviewHistory).props().onShowFullHistory(); - expect(wrapper.state().showFullHistory).toBe(true); -}); - -function shallowRender(props?: Partial) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx deleted file mode 100644 index 7ed9e70744d..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSimpleList-test.tsx +++ /dev/null @@ -1,88 +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 { mockRawHotspot } from '../../../../helpers/mocks/security-hotspots'; -import { addSideBarClass, removeSideBarClass } from '../../../../helpers/pages'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import { SecurityStandard } from '../../../../types/security'; -import HotspotSimpleList, { HotspotSimpleListProps } from '../HotspotSimpleList'; - -jest.mock('../../../../helpers/pages', () => ({ - addSideBarClass: jest.fn(), - removeSideBarClass: jest.fn(), -})); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('filter by category'); - expect(shallowRender({ filterByCategory: undefined, filterByCWE: '327' })).toMatchSnapshot( - 'filter by cwe' - ); - expect(shallowRender({ filterByCWE: '327' })).toMatchSnapshot('filter by both'); - expect(shallowRender({ filterByFile: 'src/apps/something/main.ts' })).toMatchSnapshot( - 'filter by file' - ); -}); - -it('should add/remove sidebar classes', async () => { - const wrapper = shallowRender(); - - await waitAndUpdate(wrapper); - - expect(addSideBarClass).toHaveBeenCalled(); - - wrapper.unmount(); - - expect(removeSideBarClass).toHaveBeenCalled(); -}); - -function shallowRender(props: Partial = {}) { - const hotspots = [mockRawHotspot({ key: 'h1' }), mockRawHotspot({ key: 'h2' })]; - - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx deleted file mode 100644 index ccc25e4f5e2..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx +++ /dev/null @@ -1,221 +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 { range } from 'lodash'; -import * as React from 'react'; -import { getSources } from '../../../../api/components'; -import { mockBranch } from '../../../../helpers/mocks/branch-like'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockHotspot, mockHotspotComponent } from '../../../../helpers/mocks/security-hotspots'; -import { mockSourceLine } from '../../../../helpers/mocks/sources'; -import { mockFlowLocation } from '../../../../helpers/testMocks'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import { ComponentQualifier } from '../../../../types/component'; -import HotspotSnippetContainer from '../HotspotSnippetContainer'; -import HotspotSnippetContainerRenderer from '../HotspotSnippetContainerRenderer'; - -jest.mock('../../../../api/components', () => ({ - getSources: jest.fn().mockResolvedValue([]), -})); - -jest.mock('../../../../helpers/scrolling', () => ({ - scrollToElement: jest.fn(), -})); - -beforeEach(() => jest.clearAllMocks()); - -const branch = mockBranch(); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); -}); - -it('should load sources on mount', async () => { - (getSources as jest.Mock).mockResolvedValueOnce( - range(1, 25).map((line) => mockSourceLine({ line })) - ); - - const hotspot = mockHotspot({ - project: mockHotspotComponent({ branch: branch.name, qualifier: ComponentQualifier.Project }), - textRange: { startLine: 10, endLine: 11, startOffset: 0, endOffset: 12 }, - flows: [ - { - locations: [ - mockFlowLocation({ - textRange: { startLine: 8, endLine: 8, startOffset: 0, endOffset: 1 }, - }), - mockFlowLocation({ - textRange: { startLine: 13, endLine: 13, startOffset: 0, endOffset: 1 }, - }), - ], - }, - ], - }); - - const wrapper = shallowRender({ hotspot }); - - await waitAndUpdate(wrapper); - - expect(getSources).toHaveBeenCalledWith( - expect.objectContaining({ - key: hotspot.component.key, - branch: branch.name, - from: 1, - to: 24, - }) - ); - expect(wrapper.state().lastLine).toBeUndefined(); - expect(wrapper.state().sourceLines).toHaveLength(23); -}); - -it('should handle load sources failure', async () => { - (getSources as jest.Mock).mockRejectedValueOnce(null); - - const wrapper = shallowRender(); - - await waitAndUpdate(wrapper); - - expect(getSources).toHaveBeenCalled(); - expect(wrapper.state().loading).toBe(false); - expect(wrapper.state().lastLine).toBeUndefined(); - expect(wrapper.state().sourceLines).toHaveLength(0); -}); - -it('should not load sources on mount when the hotspot is not associated to any loc', async () => { - const hotspot = mockHotspot({ - line: undefined, - textRange: undefined, - }); - - const wrapper = shallowRender({ hotspot }); - - await waitAndUpdate(wrapper); - - expect(getSources).not.toHaveBeenCalled(); - expect(wrapper.state().lastLine).toBeUndefined(); - expect(wrapper.state().sourceLines).toHaveLength(0); -}); - -it('should handle end-of-file on mount', async () => { - (getSources as jest.Mock).mockResolvedValueOnce( - range(5, 15).map((line) => mockSourceLine({ line })) - ); - - const hotspot = mockHotspot({ - textRange: { startLine: 10, endLine: 11, startOffset: 0, endOffset: 12 }, - }); - - const wrapper = shallowRender({ hotspot }); - - await waitAndUpdate(wrapper); - - expect(getSources).toHaveBeenCalled(); - expect(wrapper.state().lastLine).toBe(14); - expect(wrapper.state().sourceLines).toHaveLength(10); -}); - -describe('Expansion', () => { - beforeEach(() => { - (getSources as jest.Mock).mockResolvedValueOnce( - range(10, 32).map((line) => mockSourceLine({ line })) - ); - }); - - const hotspot = mockHotspot({ - project: mockHotspotComponent({ branch: branch.name, qualifier: ComponentQualifier.Project }), - textRange: { startLine: 20, endLine: 21, startOffset: 0, endOffset: 12 }, - }); - - it('up should work', async () => { - (getSources as jest.Mock).mockResolvedValueOnce( - range(1, 10).map((line) => mockSourceLine({ line })) - ); - - const wrapper = shallowRender({ hotspot }); - await waitAndUpdate(wrapper); - - wrapper.find(HotspotSnippetContainerRenderer).props().onExpandBlock('up'); - - await waitAndUpdate(wrapper); - - expect(getSources).toHaveBeenCalledWith( - expect.objectContaining({ - branch: branch.name, - }) - ); - expect(wrapper.state().sourceLines).toHaveLength(31); - }); - - it('down should work', async () => { - (getSources as jest.Mock).mockResolvedValueOnce( - // lastLine + expand + extra for EOF check + range end is excluded - // 31 + 50 + 1 + 1 - range(32, 83).map((line) => mockSourceLine({ line })) - ); - - const wrapper = shallowRender({ hotspot }); - await waitAndUpdate(wrapper); - - wrapper.find(HotspotSnippetContainerRenderer).props().onExpandBlock('down'); - - await waitAndUpdate(wrapper); - - expect(wrapper.state().lastLine).toBeUndefined(); - expect(wrapper.state().sourceLines).toHaveLength(72); - }); - - it('down should work and handle EOF', async () => { - (getSources as jest.Mock).mockResolvedValueOnce( - // lastLine + expand + extra for EOF check + range end is excluded - 1 to trigger end-of-file - // 26 + 50 + 1 + 1 - 1 - range(27, 77).map((line) => mockSourceLine({ line })) - ); - - const wrapper = shallowRender({ hotspot }); - await waitAndUpdate(wrapper); - - wrapper.find(HotspotSnippetContainerRenderer).props().onExpandBlock('down'); - - await waitAndUpdate(wrapper); - - expect(wrapper.state().lastLine).toBe(76); - expect(wrapper.state().sourceLines).toHaveLength(72); - }); -}); - -it('should handle symbol click', () => { - const wrapper = shallowRender(); - const symbols = ['symbol']; - wrapper.find(HotspotSnippetContainerRenderer).props().onSymbolClick(symbols); - expect(wrapper.state().highlightedSymbols).toBe(symbols); -}); - -function shallowRender(props?: Partial) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx deleted file mode 100644 index cd01b0ca4c1..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx +++ /dev/null @@ -1,177 +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 React, { RefObject } from 'react'; -import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; -import { mockSourceLine, mockSourceViewerFile } from '../../../../helpers/mocks/sources'; -import { scrollToElement } from '../../../../helpers/scrolling'; -import SnippetViewer from '../../../issues/crossComponentSourceViewer/SnippetViewer'; -import HotspotSnippetContainerRenderer, { - animateExpansion, - getScrollHandler, - HotspotSnippetContainerRendererProps, -} from '../HotspotSnippetContainerRenderer'; - -jest.mock('../../../../helpers/scrolling', () => ({ - scrollToElement: jest.fn(), -})); - -beforeEach(() => { - jest.spyOn(React, 'useMemo').mockImplementationOnce((f) => f()); -}); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot(); - expect(shallowRender({ sourceLines: [mockSourceLine()] })).toMatchSnapshot('with sourcelines'); -}); - -it('should render a HotspotPrimaryLocationBox', () => { - const wrapper = shallowRender({ - hotspot: mockHotspot({ line: 42 }), - sourceLines: [mockSourceLine()], - }); - - const { renderAdditionalChildInLine } = wrapper.find(SnippetViewer).props(); - - expect(renderAdditionalChildInLine!(mockSourceLine({ line: 10 }))).toBeUndefined(); - expect(renderAdditionalChildInLine!(mockSourceLine({ line: 42 }))).not.toBeUndefined(); -}); - -it('should render correctly when secondary location is selected', () => { - const wrapper = shallowRender({ - selectedHotspotLocation: 1, - }); - expect(wrapper).toMatchSnapshot('with selected hotspot location'); -}); - -describe('scrolling', () => { - beforeAll(() => { - jest.useFakeTimers(); - }); - - afterAll(() => { - jest.useRealTimers(); - }); - - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should scroll to element if parent is defined', () => { - const ref: RefObject = { - current: document.createElement('div'), - }; - - const scrollHandler = getScrollHandler(ref); - - const targetElement = document.createElement('div'); - - scrollHandler(targetElement); - jest.runAllTimers(); - - expect(scrollToElement).toHaveBeenCalled(); - }); - - it('should not scroll if parent is undefined', () => { - const ref: RefObject = { - current: null, - }; - - const scrollHandler = getScrollHandler(ref); - - const targetElement = document.createElement('div'); - - scrollHandler(targetElement); - jest.runAllTimers(); - - expect(scrollToElement).not.toHaveBeenCalled(); - }); -}); - -describe('expand', () => { - it('should work as expected', async () => { - jest.useFakeTimers(); - const onExpandBlock = jest.fn().mockResolvedValue({}); - - const scrollableNode = document.createElement('div'); - scrollableNode.scrollTo = jest.fn(); - const ref: RefObject = { - current: scrollableNode, - }; - - jest.spyOn(React, 'useRef').mockReturnValue(ref); - - const snippet = document.createElement('div'); - const table = document.createElement('table'); - snippet.appendChild(table); - scrollableNode.querySelector = jest.fn().mockReturnValue(snippet); - - jest - .spyOn(table, 'getBoundingClientRect') - .mockReturnValueOnce({ height: 42 } as DOMRect) - .mockReturnValueOnce({ height: 99 } as DOMRect) - .mockReturnValueOnce({ height: 99 } as DOMRect) - .mockReturnValueOnce({ height: 112 } as DOMRect); - - await animateExpansion(ref, onExpandBlock, 'up'); - expect(onExpandBlock).toHaveBeenCalledWith('up'); - - expect(snippet).toHaveStyle({ maxHeight: '42px' }); - expect(table).toHaveStyle({ marginTop: '-57px' }); - - jest.advanceTimersByTime(100); - - expect(snippet).toHaveStyle({ maxHeight: '99px' }); - expect(table).toHaveStyle({ marginTop: '0px' }); - - expect(scrollableNode.scrollTo).not.toHaveBeenCalled(); - - jest.runAllTimers(); - - await animateExpansion(ref, onExpandBlock, 'down'); - expect(onExpandBlock).toHaveBeenCalledWith('down'); - expect(snippet).toHaveStyle({ maxHeight: '112px' }); - - jest.useRealTimers(); - }); -}); - -function shallowRender(props?: Partial) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetHeader-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetHeader-test.tsx deleted file mode 100644 index a1a77d6c72d..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetHeader-test.tsx +++ /dev/null @@ -1,48 +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 React from 'react'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; -import { mockCurrentUser, mockLoggedInUser } from '../../../../helpers/testMocks'; -import { ComponentQualifier } from '../../../../types/component'; -import { HotspotSnippetHeader, HotspotSnippetHeaderProps } from '../HotspotSnippetHeader'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('user not logged in'); - expect(shallowRender({ currentUser: mockLoggedInUser() })).toMatchSnapshot('user logged in'); - expect( - shallowRender({ - currentUser: mockLoggedInUser(), - component: mockComponent({ qualifier: ComponentQualifier.Application }), - }) - ).toMatchSnapshot('user logged in with project Name'); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx deleted file mode 100644 index 9ce369d647c..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewer-test.tsx +++ /dev/null @@ -1,188 +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 { clone } from 'lodash'; -import * as React from 'react'; -import { getRuleDetails } from '../../../../api/rules'; -import { getSecurityHotspotDetails } from '../../../../api/security-hotspots'; -import { mockComponent } from '../../../../helpers/mocks/component'; -import { scrollToElement } from '../../../../helpers/scrolling'; -import { mockRuleDetails } from '../../../../helpers/testMocks'; -import { waitAndUpdate } from '../../../../helpers/testUtils'; -import { HotspotStatusOption } from '../../../../types/security-hotspots'; -import { RuleDescriptionSections } from '../../../coding-rules/rule'; -import HotspotViewer from '../HotspotViewer'; -import HotspotViewerRenderer from '../HotspotViewerRenderer'; - -const hotspotKey = 'hotspot-key'; - -jest.mock('../../../../api/security-hotspots', () => ({ - getSecurityHotspotDetails: jest - .fn() - .mockResolvedValue({ id: `I am a detailled hotspot`, rule: {} }), -})); - -jest.mock('../../../../api/rules', () => ({ - getRuleDetails: jest.fn().mockResolvedValue({ rule: { descriptionSections: [] } }), -})); - -jest.mock('../../../../helpers/scrolling', () => ({ - scrollToElement: jest.fn(), -})); - -it('should render correctly', async () => { - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot(); - - await waitAndUpdate(wrapper); - - expect(wrapper).toMatchSnapshot(); - expect(getSecurityHotspotDetails).toHaveBeenCalledWith(hotspotKey); - - const newHotspotKey = `new-${hotspotKey}`; - wrapper.setProps({ hotspotKey: newHotspotKey }); - - await waitAndUpdate(wrapper); - expect(getSecurityHotspotDetails).toHaveBeenCalledWith(newHotspotKey); -}); - -it('should render fetch rule details', async () => { - (getRuleDetails as jest.Mock).mockResolvedValueOnce({ - rule: mockRuleDetails({ - descriptionSections: [ - { - key: RuleDescriptionSections.ASSESS_THE_PROBLEM, - content: 'assess', - }, - { - key: RuleDescriptionSections.ROOT_CAUSE, - content: 'cause', - }, - { - key: RuleDescriptionSections.HOW_TO_FIX, - content: 'how', - }, - ], - }), - }); - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - - expect(wrapper.state().ruleDescriptionSections).toStrictEqual([ - { - key: RuleDescriptionSections.ASSESS_THE_PROBLEM, - content: 'assess', - }, - { - key: RuleDescriptionSections.ROOT_CAUSE, - content: 'cause', - }, - { - key: RuleDescriptionSections.HOW_TO_FIX, - content: 'how', - }, - ]); -}); - -it('should refresh hotspot list on status update', () => { - const onUpdateHotspot = jest.fn(); - const wrapper = shallowRender({ onUpdateHotspot }); - wrapper.find(HotspotViewerRenderer).props().onUpdateHotspot(true); - expect(onUpdateHotspot).toHaveBeenCalled(); -}); - -it('should store last status selected when updating a hotspot status', () => { - const wrapper = shallowRender(); - - expect(wrapper.state().lastStatusChangedTo).toBeUndefined(); - wrapper.find(HotspotViewerRenderer).props().onUpdateHotspot(true, HotspotStatusOption.FIXED); - expect(wrapper.state().lastStatusChangedTo).toBe(HotspotStatusOption.FIXED); -}); - -it('should correctly propagate a request to switch the status filter', () => { - const onSwitchStatusFilter = jest.fn(); - const wrapper = shallowRender({ onSwitchStatusFilter }); - - wrapper.instance().handleSwitchFilterToStatusOfUpdatedHotspot(); - expect(onSwitchStatusFilter).not.toHaveBeenCalled(); - - wrapper.setState({ lastStatusChangedTo: HotspotStatusOption.FIXED }); - wrapper.instance().handleSwitchFilterToStatusOfUpdatedHotspot(); - expect(onSwitchStatusFilter).toHaveBeenCalledWith(HotspotStatusOption.FIXED); -}); - -it('should correctly close the success modal', () => { - const wrapper = shallowRender(); - wrapper.setState({ showStatusUpdateSuccessModal: true }); - wrapper.instance().handleCloseStatusUpdateSuccessModal(); - expect(wrapper.state().showStatusUpdateSuccessModal).toBe(false); -}); - -it('should NOT refresh hotspot list on assignee/comment updates', () => { - const onUpdateHotspot = jest.fn(); - const wrapper = shallowRender({ onUpdateHotspot }); - wrapper.find(HotspotViewerRenderer).props().onUpdateHotspot(); - expect(onUpdateHotspot).not.toHaveBeenCalled(); -}); - -it('should scroll to comment form', () => { - const wrapper = shallowRender(); - const mockTextRef = { - current: { focus: jest.fn() }, - } as any as React.RefObject; - wrapper.instance().commentTextRef = mockTextRef; - - wrapper.find(HotspotViewerRenderer).simulate('showCommentForm'); - - expect(mockTextRef.current?.focus).toHaveBeenCalled(); - expect(scrollToElement).toHaveBeenCalledWith(mockTextRef.current, expect.anything()); -}); - -it('should reset loading even on fetch error', async () => { - const mockGetHostpot = getSecurityHotspotDetails as jest.Mock; - mockGetHostpot.mockRejectedValueOnce({}); - - const wrapper = shallowRender(); - await waitAndUpdate(wrapper); - - expect(wrapper.state().loading).toBe(false); -}); - -it('should keep state on unmoint', () => { - const wrapper = shallowRender(); - wrapper.instance().componentWillUnmount(); - const prevState = clone(wrapper.state()); - - wrapper.find(HotspotViewerRenderer).simulate('updateHotspot'); - expect(wrapper.state()).toStrictEqual(prevState); -}); - -function shallowRender(props?: Partial) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx deleted file mode 100644 index 53008469164..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerRenderer-test.tsx +++ /dev/null @@ -1,71 +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 { mockComponent } from '../../../../helpers/mocks/component'; -import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; -import { mockCurrentUser, mockUser } from '../../../../helpers/testMocks'; -import { HotspotStatusOption } from '../../../../types/security-hotspots'; -import { HotspotViewerRenderer, HotspotViewerRendererProps } from '../HotspotViewerRenderer'; - -jest.mock('../../../../helpers/users', () => ({ isLoggedIn: jest.fn(() => true) })); - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('default'); - expect(shallowRender({ showStatusUpdateSuccessModal: true })).toMatchSnapshot( - 'show success modal' - ); - expect(shallowRender({ hotspot: undefined })).toMatchSnapshot('no hotspot'); - expect(shallowRender({ hotspot: mockHotspot({ assignee: undefined }) })).toMatchSnapshot( - 'unassigned' - ); - expect( - shallowRender({ hotspot: mockHotspot({ assigneeUser: mockUser({ active: false }) }) }) - ).toMatchSnapshot('deleted assignee'); - expect( - shallowRender({ - hotspot: mockHotspot({ - assigneeUser: mockUser({ name: undefined, login: 'assignee_login' }), - }), - }) - ).toMatchSnapshot('assignee without name'); - expect(shallowRender()).toMatchSnapshot('anonymous user'); -}); - -function shallowRender(props?: Partial) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx deleted file mode 100644 index d5648c16dd4..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotViewerTabs-test.tsx +++ /dev/null @@ -1,210 +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 { mount, shallow } from 'enzyme'; -import * as React from 'react'; -import BoxedTabs, { BoxedTabsProps } from '../../../../components/controls/BoxedTabs'; -import { KeyboardKeys } from '../../../../helpers/keycodes'; -import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; -import { mockUser } from '../../../../helpers/testMocks'; -import { mockEvent } from '../../../../helpers/testUtils'; -import { RuleDescriptionSections } from '../../../coding-rules/rule'; -import HotspotViewerTabs, { TabKeys } from '../HotspotViewerTabs'; - -const originalAddEventListener = window.addEventListener; -const originalRemoveEventListener = window.removeEventListener; - -beforeEach(() => { - Object.defineProperty(window, 'addEventListener', { - value: jest.fn(), - }); - Object.defineProperty(window, 'removeEventListener', { - value: jest.fn(), - }); -}); - -afterEach(() => { - Object.defineProperty(window, 'addEventListener', { - value: originalAddEventListener, - }); - Object.defineProperty(window, 'removeEventListener', { - value: originalRemoveEventListener, - }); -}); - -it('should render correctly', () => { - const wrapper = shallowRender(); - expect(wrapper).toMatchSnapshot('risk'); - - const onSelect: (tab: TabKeys) => void = wrapper.find(BoxedTabs).prop('onSelect'); - - onSelect(TabKeys.VulnerabilityDescription); - expect(wrapper).toMatchSnapshot('vulnerability'); - - onSelect(TabKeys.FixRecommendation); - expect(wrapper).toMatchSnapshot('fix'); - - expect( - shallowRender({ - hotspot: mockHotspot({ - creationDate: undefined, - }), - ruleDescriptionSections: undefined, - }) - .find>(BoxedTabs) - .props().tabs - ).toHaveLength(1); - - expect( - shallowRender({ - hotspot: mockHotspot({ - comment: [ - { - createdAt: '2019-01-01', - htmlText: 'test', - key: 'comment-key', - login: 'me', - markdown: '*test*', - updatable: false, - user: mockUser(), - }, - ], - }), - }) - ).toMatchSnapshot('with comments or changelog element'); -}); - -it('should filter empty tab', () => { - const count = shallowRender({ - hotspot: mockHotspot(), - }).state().tabs.length; - - expect( - shallowRender({ - ruleDescriptionSections: [ - { - key: RuleDescriptionSections.ROOT_CAUSE, - content: 'cause', - }, - { - key: RuleDescriptionSections.HOW_TO_FIX, - content: 'how', - }, - ], - }).state().tabs.length - ).toBe(count - 1); -}); - -it('should select first tab on hotspot update', () => { - const wrapper = shallowRender(); - const onSelect: (tab: TabKeys) => void = wrapper.find(BoxedTabs).prop('onSelect'); - - onSelect(TabKeys.VulnerabilityDescription); - expect(wrapper.state().currentTab.key).toBe(TabKeys.VulnerabilityDescription); - wrapper.setProps({ hotspot: mockHotspot({ key: 'new_key' }) }); - expect(wrapper.state().currentTab.key).toBe(TabKeys.Code); -}); - -it('should select first tab when hotspot location is selected and is not undefined', () => { - const wrapper = shallowRender(); - const onSelect: (tab: TabKeys) => void = wrapper.find(BoxedTabs).prop('onSelect'); - - onSelect(TabKeys.VulnerabilityDescription); - expect(wrapper.state().currentTab.key).toBe(TabKeys.VulnerabilityDescription); - - wrapper.setProps({ selectedHotspotLocation: 1 }); - expect(wrapper.state().currentTab.key).toBe(TabKeys.Code); - - onSelect(TabKeys.VulnerabilityDescription); - expect(wrapper.state().currentTab.key).toBe(TabKeys.VulnerabilityDescription); - - wrapper.setProps({ selectedHotspotLocation: undefined }); - expect(wrapper.state().currentTab.key).toBe(TabKeys.VulnerabilityDescription); -}); - -describe('keyboard navigation', () => { - const tabList = [ - TabKeys.Code, - TabKeys.RiskDescription, - TabKeys.VulnerabilityDescription, - TabKeys.FixRecommendation, - ]; - const wrapper = shallowRender(); - - it.each([ - ['selecting next', 0, KeyboardKeys.RightArrow, 1], - ['selecting previous', 1, KeyboardKeys.LeftArrow, 0], - ['selecting previous, non-existent', 0, KeyboardKeys.LeftArrow, 0], - ['selecting next, non-existent', 3, KeyboardKeys.RightArrow, 3], - ])('should work when %s', (_, start, key, expected) => { - wrapper.setState({ currentTab: wrapper.state().tabs[start] }); - wrapper.instance().handleKeyboardNavigation(mockEvent({ key })); - - expect(wrapper.state().currentTab.key).toBe(tabList[expected]); - }); -}); - -it("shouldn't navigate when ctrl or command are pressed with up and down", () => { - const wrapper = mount( - CodeTabContent
} hotspot={mockHotspot()} /> - ); - - wrapper.setState({ currentTab: wrapper.state().tabs[0] }); - wrapper - .instance() - .handleKeyboardNavigation(mockEvent({ key: KeyboardKeys.LeftArrow, metaKey: true })); - - expect(wrapper.state().currentTab.key).toBe(TabKeys.Code); -}); - -it('should navigate when up and down key are pressed', () => { - const wrapper = mount( - CodeTabContent
} hotspot={mockHotspot()} /> - ); - - expect(window.addEventListener).toHaveBeenCalled(); - - wrapper.unmount(); - - expect(window.removeEventListener).toHaveBeenCalled(); -}); - -function shallowRender(props?: Partial) { - return shallow( - CodeTabContent
} - hotspot={mockHotspot()} - ruleDescriptionSections={[ - { - key: RuleDescriptionSections.ASSESS_THE_PROBLEM, - content: 'assess', - }, - { - key: RuleDescriptionSections.ROOT_CAUSE, - content: 'cause', - }, - { - key: RuleDescriptionSections.HOW_TO_FIX, - content: 'how', - }, - ]} - {...props} - /> - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/StatusUpdateSuccessModal-test.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/StatusUpdateSuccessModal-test.tsx deleted file mode 100644 index 41f9085202d..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/StatusUpdateSuccessModal-test.tsx +++ /dev/null @@ -1,44 +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 { HotspotStatusOption } from '../../../../types/security-hotspots'; -import StatusUpdateSuccessModal, { - StatusUpdateSuccessModalProps, -} from '../StatusUpdateSuccessModal'; - -it('should render correctly', () => { - expect(shallowRender()).toMatchSnapshot('default'); - expect(shallowRender({ lastStatusChangedTo: HotspotStatusOption.TO_REVIEW })).toMatchSnapshot( - 'opening hotspots again' - ); - expect(shallowRender({ lastStatusChangedTo: undefined }).type()).toBeNull(); -}); - -function shallowRender(props: Partial = {}) { - return shallow( - - ); -} diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/EmptyHotspotsPage-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/EmptyHotspotsPage-test.tsx.snap deleted file mode 100644 index 0a01522c9a7..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/EmptyHotspotsPage-test.tsx.snap +++ /dev/null @@ -1,105 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly 1`] = ` -
- hotspots.page -

- hotspots.no_hotspots.title -

-
- hotspots.no_hotspots.description -
- - hotspots.learn_more - -
-`; - -exports[`should render correctly: file 1`] = ` -
- hotspots.page -

- hotspots.no_hotspots_for_file.title -

-
- hotspots.no_hotspots_for_file.description -
- - hotspots.learn_more - -
-`; - -exports[`should render correctly: filtered 1`] = ` -
- hotspots.page -

- hotspots.no_hotspots_for_filters.title -

-
- hotspots.no_hotspots_for_filters.description -
-
-`; - -exports[`should render correctly: keys 1`] = ` -
- hotspots.page -

- hotspots.no_hotspots_for_keys.title -

-
- hotspots.no_hotspots_for_keys.description -
-
-`; diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/FilterBar-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/FilterBar-test.tsx.snap deleted file mode 100644 index 91edea01d57..00000000000 --- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/FilterBar-test.tsx.snap +++ /dev/null @@ -1,589 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`should render correctly when the list of hotspot is static 1`] = ` -
-
-
- - hotspot.filters.show_all - -
-
-
-`; - -exports[`should render correctly: anonymous 1`] = ` -
-
-
-
-
-

- hotspot.filters.title -

- - - status - - - -
-
- - metric.security_hotspots_reviewed.name - - - - - -
-
-
-
-
-`; - -exports[`should render correctly: logged-in 1`] = ` -
-
-
-
-
-

- hotspot.filters.title -

- - - - - - status - - - -
-
- - metric.security_hotspots_reviewed.name - - - - - -
-
-
-
-
-`; - -exports[`should render correctly: non-project 1`] = ` -
-
-
-
-
-

- hotspot.filters.title -

- - - - - - status - - - -
-
-
-
-
-`; - -exports[`should render correctly: on Pull request 1`] = ` -
-
-
-
-
-

- hotspot.filters.title -

- - - status - - - -