]> source.dussan.org Git - sonarqube.git/blob
f68a22f601b0f185353ee881952471f950f4bb28
[sonarqube.git] /
1 /*
2  * SonarQube
3  * Copyright (C) 2009-2020 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 { mount, ReactWrapper, shallow } from 'enzyme';
21 import { range, times } from 'lodash';
22 import * as React from 'react';
23 import { waitAndUpdate } from 'sonar-ui-common/helpers/testUtils';
24 import { getSources } from '../../../../api/components';
25 import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
26 import {
27   mockFlowLocation,
28   mockIssue,
29   mockSnippetsByComponent,
30   mockSourceLine,
31   mockSourceViewerFile
32 } from '../../../../helpers/testMocks';
33 import ComponentSourceSnippetGroupViewer from '../ComponentSourceSnippetGroupViewer';
34
35 jest.mock('../../../../api/components', () => ({
36   getSources: jest.fn().mockResolvedValue([])
37 }));
38
39 beforeEach(() => {
40   jest.clearAllMocks();
41 });
42
43 it('should render correctly', () => {
44   expect(shallowRender()).toMatchSnapshot();
45 });
46
47 it('should render correctly with secondary locations', () => {
48   // issue with secondary locations but no flows
49   const issue = mockIssue(true, {
50     flows: [],
51     textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 }
52   });
53
54   const snippetGroup: T.SnippetGroup = {
55     locations: [
56       mockFlowLocation({
57         component: issue.component,
58         textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
59       }),
60       mockFlowLocation({
61         component: issue.component,
62         textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
63       })
64     ],
65     ...mockSnippetsByComponent(issue.component, [
66       ...range(2, 17),
67       ...range(29, 39),
68       ...range(69, 79)
69     ])
70   };
71   const wrapper = shallowRender({ issue, snippetGroup });
72   expect(wrapper.state('snippets')).toHaveLength(3);
73   expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 2, end: 16 });
74   expect(wrapper.state('snippets')[1]).toEqual({ index: 1, start: 29, end: 39 });
75   expect(wrapper.state('snippets')[2]).toEqual({ index: 2, start: 69, end: 79 });
76 });
77
78 it('should render correctly with flows', () => {
79   // issue with flows but no secondary locations
80   const issue = mockIssue(true, {
81     secondaryLocations: [],
82     textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 }
83   });
84
85   const snippetGroup: T.SnippetGroup = {
86     locations: [
87       mockFlowLocation({
88         component: issue.component,
89         textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
90       }),
91       mockFlowLocation({
92         component: issue.component,
93         textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
94       })
95     ],
96     ...mockSnippetsByComponent(issue.component, [
97       ...range(2, 17),
98       ...range(29, 39),
99       ...range(69, 79)
100     ])
101   };
102   const wrapper = shallowRender({ issue, snippetGroup });
103   expect(wrapper.state('snippets')).toHaveLength(2);
104   expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 29, end: 39 });
105   expect(wrapper.state('snippets')[1]).toEqual({ index: 1, start: 69, end: 79 });
106 });
107
108 it('should expand block', async () => {
109   (getSources as jest.Mock).mockResolvedValueOnce(
110     Object.values(mockSnippetsByComponent('a', range(6, 59)).sources)
111   );
112   const issue = mockIssue(true, {
113     textRange: { startLine: 74, endLine: 74, startOffset: 5, endOffset: 10 }
114   });
115   const snippetGroup: T.SnippetGroup = {
116     locations: [
117       mockFlowLocation({
118         component: 'a',
119         textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
120       }),
121       mockFlowLocation({
122         component: 'a',
123         textRange: { startLine: 107, endLine: 107, startOffset: 0, endOffset: 0 }
124       })
125     ],
126     ...mockSnippetsByComponent('a', [...range(69, 83), ...range(102, 112)])
127   };
128
129   const wrapper = shallowRender({ issue, snippetGroup });
130
131   wrapper.instance().expandBlock(0, 'up');
132   await waitAndUpdate(wrapper);
133
134   expect(getSources).toHaveBeenCalledWith({ from: 9, key: 'a', to: 68 });
135   expect(wrapper.state('snippets')).toHaveLength(2);
136   expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 19, end: 83 });
137   expect(Object.keys(wrapper.state('additionalLines'))).toHaveLength(53);
138 });
139
140 it('should expand full component', async () => {
141   (getSources as jest.Mock).mockResolvedValueOnce(
142     Object.values(mockSnippetsByComponent('a', times(14)).sources)
143   );
144   const snippetGroup: T.SnippetGroup = {
145     locations: [
146       mockFlowLocation({
147         component: 'a',
148         textRange: { startLine: 3, endLine: 3, startOffset: 0, endOffset: 0 }
149       }),
150       mockFlowLocation({
151         component: 'a',
152         textRange: { startLine: 12, endLine: 12, startOffset: 0, endOffset: 0 }
153       })
154     ],
155     ...mockSnippetsByComponent('a', [1, 2, 3, 4, 5, 10, 11, 12, 13, 14])
156   };
157
158   const wrapper = shallowRender({ snippetGroup });
159
160   wrapper.instance().expandComponent();
161   await waitAndUpdate(wrapper);
162
163   expect(getSources).toHaveBeenCalledWith({ key: 'a' });
164   expect(wrapper.state('snippets')).toHaveLength(1);
165   expect(wrapper.state('snippets')[0]).toEqual({ index: -1, start: 0, end: 13 });
166 });
167
168 it('should get the right branch when expanding', async () => {
169   (getSources as jest.Mock).mockResolvedValueOnce(
170     Object.values(
171       mockSnippetsByComponent('a', [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]).sources
172     )
173   );
174   const snippetGroup: T.SnippetGroup = {
175     locations: [mockFlowLocation()],
176     ...mockSnippetsByComponent('a', [1, 2, 3, 4, 5, 6, 7])
177   };
178
179   const wrapper = shallowRender({
180     branchLike: mockBranch({ name: 'asdf' }),
181     snippetGroup
182   });
183
184   wrapper.instance().expandBlock(0, 'down');
185   await waitAndUpdate(wrapper);
186
187   expect(getSources).toHaveBeenCalledWith({ branch: 'asdf', from: 8, key: 'a', to: 67 });
188 });
189
190 it('should handle correctly open/close issue', () => {
191   const wrapper = shallowRender();
192   const sourceLine = mockSourceLine();
193   expect(wrapper.state('openIssuesByLine')).toEqual({});
194   wrapper.instance().handleOpenIssues(sourceLine);
195   expect(wrapper.state('openIssuesByLine')).toEqual({ [sourceLine.line]: true });
196   wrapper.instance().handleCloseIssues(sourceLine);
197   expect(wrapper.state('openIssuesByLine')).toEqual({ [sourceLine.line]: false });
198 });
199
200 it('should handle symbol highlighting', () => {
201   const wrapper = shallowRender();
202   expect(wrapper.state('highlightedSymbols')).toEqual([]);
203   wrapper.instance().handleSymbolClick(['foo']);
204   expect(wrapper.state('highlightedSymbols')).toEqual(['foo']);
205   wrapper.instance().handleSymbolClick(['foo']);
206   expect(wrapper.state('highlightedSymbols')).toEqual([]);
207 });
208
209 it('should correctly handle lines actions', () => {
210   const snippetGroup: T.SnippetGroup = {
211     locations: [
212       mockFlowLocation({
213         component: 'a',
214         textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
215       }),
216       mockFlowLocation({
217         component: 'a',
218         textRange: { startLine: 54, endLine: 54, startOffset: 0, endOffset: 0 }
219       })
220     ],
221     ...mockSnippetsByComponent('a', [32, 33, 34, 35, 36, 52, 53, 54, 55, 56])
222   };
223   const loadDuplications = jest.fn();
224   const onLinePopupToggle = jest.fn();
225   const renderDuplicationPopup = jest.fn();
226
227   const wrapper = shallowRender({
228     loadDuplications,
229     onLinePopupToggle,
230     renderDuplicationPopup,
231     snippetGroup
232   });
233
234   const line = mockSourceLine();
235   wrapper
236     .find('SnippetViewer')
237     .first()
238     .prop<Function>('loadDuplications')(line);
239   expect(loadDuplications).toHaveBeenCalledWith('a', line);
240
241   wrapper
242     .find('SnippetViewer')
243     .first()
244     .prop<Function>('handleLinePopupToggle')({ line: 13, name: 'foo' });
245   expect(onLinePopupToggle).toHaveBeenCalledWith({ component: 'a', line: 13, name: 'foo' });
246
247   wrapper
248     .find('SnippetViewer')
249     .first()
250     .prop<Function>('renderDuplicationPopup')(1, 13);
251   expect(renderDuplicationPopup).toHaveBeenCalledWith(
252     mockSourceViewerFile({ key: 'a', path: 'a' }),
253     1,
254     13
255   );
256 });
257
258 describe('getNodes', () => {
259   const snippetGroup: T.SnippetGroup = {
260     component: mockSourceViewerFile(),
261     locations: [],
262     sources: []
263   };
264   const wrapper = mount<ComponentSourceSnippetGroupViewer>(
265     <ComponentSourceSnippetGroupViewer
266       branchLike={mockMainBranch()}
267       duplications={undefined}
268       duplicationsByLine={undefined}
269       highlightedLocationMessage={{ index: 0, text: '' }}
270       issue={mockIssue()}
271       issuesByLine={{}}
272       lastSnippetGroup={false}
273       linePopup={undefined}
274       loadDuplications={jest.fn()}
275       locations={[]}
276       onIssueChange={jest.fn()}
277       onIssuePopupToggle={jest.fn()}
278       onLinePopupToggle={jest.fn()}
279       onLocationSelect={jest.fn()}
280       renderDuplicationPopup={jest.fn()}
281       scroll={jest.fn()}
282       snippetGroup={snippetGroup}
283     />
284   );
285
286   it('should return undefined if any node is missing', async () => {
287     await waitAndUpdate(wrapper);
288     const rootNode = wrapper.instance().rootNodeRef;
289     mockDom(rootNode.current!);
290     expect(wrapper.instance().getNodes(0)).toBeUndefined();
291     expect(wrapper.instance().getNodes(1)).toBeUndefined();
292     expect(wrapper.instance().getNodes(2)).toBeUndefined();
293   });
294
295   it('should return elements if dom is correct', async () => {
296     await waitAndUpdate(wrapper);
297     const rootNode = wrapper.instance().rootNodeRef;
298     mockDom(rootNode.current!);
299     expect(wrapper.instance().getNodes(3)).not.toBeUndefined();
300   });
301
302   it('should enable cleaning the dom', async () => {
303     await waitAndUpdate(wrapper);
304     const rootNode = wrapper.instance().rootNodeRef;
305     mockDom(rootNode.current!);
306
307     wrapper.instance().cleanDom(3);
308     const nodes = wrapper.instance().getNodes(3);
309     expect(nodes!.wrapper.style.maxHeight).toBe('');
310     expect(nodes!.table.style.marginTop).toBe('');
311   });
312 });
313
314 describe('getHeight', () => {
315   jest.useFakeTimers();
316
317   const snippetGroup: T.SnippetGroup = {
318     component: mockSourceViewerFile(),
319     locations: [],
320     sources: []
321   };
322   const wrapper = mount<ComponentSourceSnippetGroupViewer>(
323     <ComponentSourceSnippetGroupViewer
324       branchLike={mockMainBranch()}
325       duplications={undefined}
326       duplicationsByLine={undefined}
327       highlightedLocationMessage={{ index: 0, text: '' }}
328       issue={mockIssue()}
329       issuesByLine={{}}
330       lastSnippetGroup={false}
331       linePopup={undefined}
332       loadDuplications={jest.fn()}
333       locations={[]}
334       onIssueChange={jest.fn()}
335       onIssuePopupToggle={jest.fn()}
336       onLinePopupToggle={jest.fn()}
337       onLocationSelect={jest.fn()}
338       renderDuplicationPopup={jest.fn()}
339       scroll={jest.fn()}
340       snippetGroup={snippetGroup}
341     />
342   );
343
344   it('should set maxHeight to current height', async () => {
345     await waitAndUpdate(wrapper);
346
347     const nodes = mockDomForSizes(wrapper, { wrapperHeight: 42, tableHeight: 68 });
348     wrapper.instance().setMaxHeight(0);
349
350     expect(nodes.wrapper.getAttribute('style')).toBe('max-height: 88px;');
351     expect(nodes.table.getAttribute('style')).toBeNull();
352   });
353
354   it('should set margin and then maxHeight for a nice upwards animation', async () => {
355     await waitAndUpdate(wrapper);
356
357     const nodes = mockDomForSizes(wrapper, { wrapperHeight: 42, tableHeight: 68 });
358     wrapper.instance().setMaxHeight(0, undefined, true);
359
360     expect(nodes.wrapper.getAttribute('style')).toBeNull();
361     expect(nodes.table.getAttribute('style')).toBe('transition: none; margin-top: -26px;');
362
363     jest.runAllTimers();
364
365     expect(nodes.wrapper.getAttribute('style')).toBe('max-height: 88px;');
366     expect(nodes.table.getAttribute('style')).toBe('margin-top: 0px;');
367   });
368 });
369
370 function shallowRender(props: Partial<ComponentSourceSnippetGroupViewer['props']> = {}) {
371   const snippetGroup: T.SnippetGroup = {
372     component: mockSourceViewerFile(),
373     locations: [],
374     sources: []
375   };
376   return shallow<ComponentSourceSnippetGroupViewer>(
377     <ComponentSourceSnippetGroupViewer
378       branchLike={mockMainBranch()}
379       duplications={undefined}
380       duplicationsByLine={undefined}
381       highlightedLocationMessage={{ index: 0, text: '' }}
382       issue={mockIssue()}
383       issuesByLine={{}}
384       lastSnippetGroup={false}
385       linePopup={undefined}
386       loadDuplications={jest.fn()}
387       locations={[]}
388       onIssueChange={jest.fn()}
389       onIssuePopupToggle={jest.fn()}
390       onLinePopupToggle={jest.fn()}
391       onLocationSelect={jest.fn()}
392       renderDuplicationPopup={jest.fn()}
393       scroll={jest.fn()}
394       snippetGroup={snippetGroup}
395       {...props}
396     />
397   );
398 }
399
400 function mockDom(refNode: HTMLDivElement) {
401   refNode.querySelector = jest.fn(query => {
402     const index = query.split('-').pop();
403
404     switch (index) {
405       case '0':
406         return null;
407       case '1':
408         return mount(<div />).getDOMNode();
409       case '2':
410         return mount(
411           <div>
412             <div className="snippet" />
413           </div>
414         ).getDOMNode();
415       case '3':
416         return mount(
417           <div>
418             <div className="snippet">
419               <div />
420             </div>
421           </div>
422         ).getDOMNode();
423       default:
424         return null;
425     }
426   });
427 }
428
429 function mockDomForSizes(
430   componentWrapper: ReactWrapper<{}, {}, ComponentSourceSnippetGroupViewer>,
431   { wrapperHeight = 0, tableHeight = 0 }
432 ) {
433   const wrapper = mount(<div className="snippet" />).getDOMNode();
434   wrapper.getBoundingClientRect = jest.fn().mockReturnValue({ height: wrapperHeight });
435   const table = mount(<div />).getDOMNode();
436   table.getBoundingClientRect = jest.fn().mockReturnValue({ height: tableHeight });
437   componentWrapper.instance().getNodes = jest.fn().mockReturnValue({
438     wrapper,
439     table
440   });
441   return { wrapper, table };
442 }