3 * Copyright (C) 2009-2023 SonarSource SA
4 * mailto:info AT sonarsource DOT com
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 3 of the License, or (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20 import { shallow } from 'enzyme';
21 import { range, times } from 'lodash';
22 import * as React from 'react';
23 import { FormattedMessage } from 'react-intl';
24 import { getSources } from '../../../../api/components';
25 import IssueMessageBox from '../../../../components/issue/IssueMessageBox';
26 import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
28 mockSnippetsByComponent,
31 } from '../../../../helpers/mocks/sources';
32 import { mockFlowLocation, mockIssue } from '../../../../helpers/testMocks';
33 import { waitAndUpdate } from '../../../../helpers/testUtils';
34 import { ComponentQualifier } from '../../../../types/component';
35 import { IssueStatus } from '../../../../types/issues';
36 import { SnippetGroup } from '../../../../types/types';
37 import ComponentSourceSnippetGroupViewer from '../ComponentSourceSnippetGroupViewer';
38 import SnippetViewer from '../SnippetViewer';
40 jest.mock('../../../../api/components', () => ({
41 getSources: jest.fn().mockResolvedValue([]),
48 it('should render correctly', () => {
49 expect(shallowRender()).toMatchSnapshot();
52 it('should render correctly with secondary locations', () => {
53 // issue with secondary locations but no flows
54 const issue = mockIssue(true, {
55 component: 'project:main.js',
57 textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 },
60 const snippetGroup: SnippetGroup = {
63 component: issue.component,
64 textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 },
67 component: issue.component,
68 textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 },
71 ...mockSnippetsByComponent('main.js', 'project', [
77 const wrapper = shallowRender({ issue, snippetGroup });
78 expect(wrapper.state('snippets')).toHaveLength(3);
79 expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 2, end: 16 });
80 expect(wrapper.state('snippets')[1]).toEqual({ index: 2, start: 29, end: 39 });
81 expect(wrapper.state('snippets')[2]).toEqual({ index: 3, start: 69, end: 79 });
84 it('should render correctly with flows', () => {
85 // issue with flows but no secondary locations
86 const issue = mockIssue(true, {
87 component: 'project:main.js',
88 secondaryLocations: [],
89 textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 },
92 const snippetGroup: SnippetGroup = {
95 component: issue.component,
96 textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 },
99 component: issue.component,
100 textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 },
103 ...mockSnippetsByComponent('main.js', 'project', [
109 const wrapper = shallowRender({ issue, snippetGroup });
110 expect(wrapper.state('snippets')).toHaveLength(3);
111 expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 2, end: 16 });
112 expect(wrapper.state('snippets')[1]).toEqual({ index: 1, start: 29, end: 39 });
113 expect(wrapper.state('snippets')[2]).toEqual({ index: 2, start: 69, end: 79 });
115 // Check that locationsByLine is defined when isLastOccurenceOfPrimaryComponent
116 expect(wrapper.find(SnippetViewer).at(0).props().locationsByLine).not.toEqual({});
118 // If not, it should be an empty object:
119 const snippets = shallowRender({
120 isLastOccurenceOfPrimaryComponent: false,
123 }).find(SnippetViewer);
125 expect(snippets.at(0).props().locationsByLine).toEqual({});
126 expect(snippets.at(1).props().locationsByLine).toEqual({});
129 it('should render file-level issue correctly', () => {
130 // issue with secondary locations and no primary location
131 const issue = mockIssue(true, {
132 component: 'project:main.js',
134 textRange: undefined,
137 const wrapper = shallowRender({
142 component: issue.component,
143 textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 },
146 ...mockSnippetsByComponent('main.js', 'project', range(29, 39)),
150 expect(wrapper.find('ContextConsumer').dive().find(IssueMessageBox).exists()).toBe(true);
154 ['file-level', ComponentQualifier.File, true, 'issue.closed.file_level'],
155 ['file-level', ComponentQualifier.File, false, 'issue.closed.project_level'],
156 ['project-level', ComponentQualifier.Project, false, 'issue.closed.project_level'],
158 'should render a closed %s issue correctly',
159 async (_level, componentQualifier, componentEnabled, expectedLabel) => {
160 // issue with secondary locations and no primary location
161 const issue = mockIssue(true, {
162 component: 'project:main.js',
166 textRange: undefined,
167 status: IssueStatus.Closed,
170 const wrapper = shallowRender({
174 ...mockSnippetsByComponent('main.js', 'project', range(1, 10)),
178 await waitAndUpdate(wrapper);
180 expect(wrapper.find<FormattedMessage>(FormattedMessage).prop('id')).toEqual(expectedLabel);
181 expect(wrapper.find('ContextConsumer').exists()).toBe(false);
185 it('should expand block', async () => {
186 (getSources as jest.Mock).mockResolvedValueOnce(
187 Object.values(mockSnippetsByComponent('a', 'project', range(6, 59)).sources)
189 const issue = mockIssue(true, {
190 textRange: { startLine: 74, endLine: 74, startOffset: 5, endOffset: 10 },
192 const snippetGroup: SnippetGroup = {
196 textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 },
200 textRange: { startLine: 107, endLine: 107, startOffset: 0, endOffset: 0 },
203 ...mockSnippetsByComponent('a', 'project', [...range(69, 83), ...range(102, 112)]),
206 const wrapper = shallowRender({ issue, snippetGroup });
208 wrapper.instance().expandBlock(0, 'up');
209 await waitAndUpdate(wrapper);
211 expect(getSources).toHaveBeenCalledWith({ from: 9, key: 'project:a', to: 68 });
212 expect(wrapper.state('snippets')).toHaveLength(2);
213 expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 19, end: 83 });
214 expect(Object.keys(wrapper.state('additionalLines'))).toHaveLength(53);
217 it('should expand full component', async () => {
218 (getSources as jest.Mock).mockResolvedValueOnce(
219 Object.values(mockSnippetsByComponent('a', 'project', times(14)).sources)
221 const snippetGroup: SnippetGroup = {
225 textRange: { startLine: 3, endLine: 3, startOffset: 0, endOffset: 0 },
229 textRange: { startLine: 12, endLine: 12, startOffset: 0, endOffset: 0 },
232 ...mockSnippetsByComponent('a', 'project', [1, 2, 3, 4, 5, 10, 11, 12, 13, 14]),
235 const wrapper = shallowRender({ snippetGroup });
237 wrapper.instance().expandComponent();
238 await waitAndUpdate(wrapper);
240 expect(getSources).toHaveBeenCalledWith({ key: 'project:a' });
241 expect(wrapper.state('snippets')).toHaveLength(1);
242 expect(wrapper.state('snippets')[0]).toEqual({ index: -1, start: 0, end: 13 });
245 it('should get the right branch when expanding', async () => {
246 (getSources as jest.Mock).mockResolvedValueOnce(
247 Object.values(mockSnippetsByComponent('a', 'project', range(8, 67)).sources)
249 const snippetGroup: SnippetGroup = {
250 locations: [mockFlowLocation()],
251 ...mockSnippetsByComponent('a', 'project', [1, 2, 3, 4, 5, 6, 7]),
254 const wrapper = shallowRender({
255 branchLike: mockBranch({ name: 'asdf' }),
259 wrapper.instance().expandBlock(0, 'down');
260 await waitAndUpdate(wrapper);
262 expect(getSources).toHaveBeenCalledWith({ branch: 'asdf', from: 8, key: 'project:a', to: 67 });
265 it('should handle symbol highlighting', () => {
266 const wrapper = shallowRender();
267 expect(wrapper.state('highlightedSymbols')).toEqual([]);
268 wrapper.instance().handleSymbolClick(['foo']);
269 expect(wrapper.state('highlightedSymbols')).toEqual(['foo']);
270 wrapper.instance().handleSymbolClick(['foo']);
271 expect(wrapper.state('highlightedSymbols')).toEqual([]);
274 it('should correctly handle lines actions', () => {
275 const snippetGroup: SnippetGroup = {
278 component: 'my-project:foo/bar.ts',
279 textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 },
282 component: 'my-project:foo/bar.ts',
283 textRange: { startLine: 54, endLine: 54, startOffset: 0, endOffset: 0 },
286 ...mockSnippetsByComponent(
289 [32, 33, 34, 35, 36, 52, 53, 54, 55, 56]
292 const loadDuplications = jest.fn();
293 const renderDuplicationPopup = jest.fn();
295 const wrapper = shallowRender({
297 renderDuplicationPopup,
301 const line = mockSourceLine();
302 wrapper.find('SnippetViewer').first().prop<Function>('loadDuplications')(line);
303 expect(loadDuplications).toHaveBeenCalledWith('my-project:foo/bar.ts', line);
305 wrapper.find('SnippetViewer').first().prop<Function>('renderDuplicationPopup')(1, 13);
306 expect(renderDuplicationPopup).toHaveBeenCalledWith(
307 mockSourceViewerFile('foo/bar.ts', 'my-project'),
313 function shallowRender(props: Partial<ComponentSourceSnippetGroupViewer['props']> = {}) {
314 const snippetGroup: SnippetGroup = {
315 component: mockSourceViewerFile(),
319 return shallow<ComponentSourceSnippetGroupViewer>(
320 <ComponentSourceSnippetGroupViewer
321 branchLike={mockMainBranch()}
322 highlightedLocationMessage={{ index: 0, text: '' }}
323 isLastOccurenceOfPrimaryComponent={true}
326 lastSnippetGroup={false}
327 loadDuplications={jest.fn()}
329 onIssueSelect={jest.fn()}
330 onLocationSelect={jest.fn()}
331 renderDuplicationPopup={jest.fn()}
332 snippetGroup={snippetGroup}