]> source.dussan.org Git - sonarqube.git/blob
9c97893afd89f15c88387f84a8b7c75a449cd396
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2023 SonarSource SA
4  * mailto:info AT sonarsource DOT com
5  *
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.
10  *
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.
15  *
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.
19  */
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';
27 import {
28   mockSnippetsByComponent,
29   mockSourceLine,
30   mockSourceViewerFile,
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';
39
40 jest.mock('../../../../api/components', () => ({
41   getSources: jest.fn().mockResolvedValue([]),
42 }));
43
44 beforeEach(() => {
45   jest.clearAllMocks();
46 });
47
48 it('should render correctly', () => {
49   expect(shallowRender()).toMatchSnapshot();
50 });
51
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',
56     flows: [],
57     textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 },
58   });
59
60   const snippetGroup: SnippetGroup = {
61     locations: [
62       mockFlowLocation({
63         component: issue.component,
64         textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 },
65       }),
66       mockFlowLocation({
67         component: issue.component,
68         textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 },
69       }),
70     ],
71     ...mockSnippetsByComponent('main.js', 'project', [
72       ...range(2, 17),
73       ...range(29, 39),
74       ...range(69, 79),
75     ]),
76   };
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 });
82 });
83
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 },
90   });
91
92   const snippetGroup: SnippetGroup = {
93     locations: [
94       mockFlowLocation({
95         component: issue.component,
96         textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 },
97       }),
98       mockFlowLocation({
99         component: issue.component,
100         textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 },
101       }),
102     ],
103     ...mockSnippetsByComponent('main.js', 'project', [
104       ...range(2, 17),
105       ...range(29, 39),
106       ...range(69, 79),
107     ]),
108   };
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 });
114
115   // Check that locationsByLine is defined when isLastOccurenceOfPrimaryComponent
116   expect(wrapper.find(SnippetViewer).at(0).props().locationsByLine).not.toEqual({});
117
118   // If not, it should be an empty object:
119   const snippets = shallowRender({
120     isLastOccurenceOfPrimaryComponent: false,
121     issue,
122     snippetGroup,
123   }).find(SnippetViewer);
124
125   expect(snippets.at(0).props().locationsByLine).toEqual({});
126   expect(snippets.at(1).props().locationsByLine).toEqual({});
127 });
128
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',
133     flows: [],
134     textRange: undefined,
135   });
136
137   const wrapper = shallowRender({
138     issue,
139     snippetGroup: {
140       locations: [
141         mockFlowLocation({
142           component: issue.component,
143           textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 },
144         }),
145       ],
146       ...mockSnippetsByComponent('main.js', 'project', range(29, 39)),
147     },
148   });
149
150   expect(wrapper.find('ContextConsumer').dive().find(IssueMessageBox).exists()).toBe(true);
151 });
152
153 it.each([
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'],
157 ])(
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',
163       componentQualifier,
164       componentEnabled,
165       flows: [],
166       textRange: undefined,
167       status: IssueStatus.Closed,
168     });
169
170     const wrapper = shallowRender({
171       issue,
172       snippetGroup: {
173         locations: [],
174         ...mockSnippetsByComponent('main.js', 'project', range(1, 10)),
175       },
176     });
177
178     await waitAndUpdate(wrapper);
179
180     expect(wrapper.find<FormattedMessage>(FormattedMessage).prop('id')).toEqual(expectedLabel);
181     expect(wrapper.find('ContextConsumer').exists()).toBe(false);
182   }
183 );
184
185 it('should expand block', async () => {
186   (getSources as jest.Mock).mockResolvedValueOnce(
187     Object.values(mockSnippetsByComponent('a', 'project', range(6, 59)).sources)
188   );
189   const issue = mockIssue(true, {
190     textRange: { startLine: 74, endLine: 74, startOffset: 5, endOffset: 10 },
191   });
192   const snippetGroup: SnippetGroup = {
193     locations: [
194       mockFlowLocation({
195         component: 'a',
196         textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 },
197       }),
198       mockFlowLocation({
199         component: 'a',
200         textRange: { startLine: 107, endLine: 107, startOffset: 0, endOffset: 0 },
201       }),
202     ],
203     ...mockSnippetsByComponent('a', 'project', [...range(69, 83), ...range(102, 112)]),
204   };
205
206   const wrapper = shallowRender({ issue, snippetGroup });
207
208   wrapper.instance().expandBlock(0, 'up');
209   await waitAndUpdate(wrapper);
210
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);
215 });
216
217 it('should expand full component', async () => {
218   (getSources as jest.Mock).mockResolvedValueOnce(
219     Object.values(mockSnippetsByComponent('a', 'project', times(14)).sources)
220   );
221   const snippetGroup: SnippetGroup = {
222     locations: [
223       mockFlowLocation({
224         component: 'a',
225         textRange: { startLine: 3, endLine: 3, startOffset: 0, endOffset: 0 },
226       }),
227       mockFlowLocation({
228         component: 'a',
229         textRange: { startLine: 12, endLine: 12, startOffset: 0, endOffset: 0 },
230       }),
231     ],
232     ...mockSnippetsByComponent('a', 'project', [1, 2, 3, 4, 5, 10, 11, 12, 13, 14]),
233   };
234
235   const wrapper = shallowRender({ snippetGroup });
236
237   wrapper.instance().expandComponent();
238   await waitAndUpdate(wrapper);
239
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 });
243 });
244
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)
248   );
249   const snippetGroup: SnippetGroup = {
250     locations: [mockFlowLocation()],
251     ...mockSnippetsByComponent('a', 'project', [1, 2, 3, 4, 5, 6, 7]),
252   };
253
254   const wrapper = shallowRender({
255     branchLike: mockBranch({ name: 'asdf' }),
256     snippetGroup,
257   });
258
259   wrapper.instance().expandBlock(0, 'down');
260   await waitAndUpdate(wrapper);
261
262   expect(getSources).toHaveBeenCalledWith({ branch: 'asdf', from: 8, key: 'project:a', to: 67 });
263 });
264
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([]);
272 });
273
274 it('should correctly handle lines actions', () => {
275   const snippetGroup: SnippetGroup = {
276     locations: [
277       mockFlowLocation({
278         component: 'my-project:foo/bar.ts',
279         textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 },
280       }),
281       mockFlowLocation({
282         component: 'my-project:foo/bar.ts',
283         textRange: { startLine: 54, endLine: 54, startOffset: 0, endOffset: 0 },
284       }),
285     ],
286     ...mockSnippetsByComponent(
287       'foo/bar.ts',
288       'my-project',
289       [32, 33, 34, 35, 36, 52, 53, 54, 55, 56]
290     ),
291   };
292   const loadDuplications = jest.fn();
293   const renderDuplicationPopup = jest.fn();
294
295   const wrapper = shallowRender({
296     loadDuplications,
297     renderDuplicationPopup,
298     snippetGroup,
299   });
300
301   const line = mockSourceLine();
302   wrapper.find('SnippetViewer').first().prop<Function>('loadDuplications')(line);
303   expect(loadDuplications).toHaveBeenCalledWith('my-project:foo/bar.ts', line);
304
305   wrapper.find('SnippetViewer').first().prop<Function>('renderDuplicationPopup')(1, 13);
306   expect(renderDuplicationPopup).toHaveBeenCalledWith(
307     mockSourceViewerFile('foo/bar.ts', 'my-project'),
308     1,
309     13
310   );
311 });
312
313 function shallowRender(props: Partial<ComponentSourceSnippetGroupViewer['props']> = {}) {
314   const snippetGroup: SnippetGroup = {
315     component: mockSourceViewerFile(),
316     locations: [],
317     sources: [],
318   };
319   return shallow<ComponentSourceSnippetGroupViewer>(
320     <ComponentSourceSnippetGroupViewer
321       branchLike={mockMainBranch()}
322       highlightedLocationMessage={{ index: 0, text: '' }}
323       isLastOccurenceOfPrimaryComponent={true}
324       issue={mockIssue()}
325       issuesByLine={{}}
326       lastSnippetGroup={false}
327       loadDuplications={jest.fn()}
328       locations={[]}
329       onIssueSelect={jest.fn()}
330       onLocationSelect={jest.fn()}
331       renderDuplicationPopup={jest.fn()}
332       snippetGroup={snippetGroup}
333       {...props}
334     />
335   );
336 }