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