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