3 * Copyright (C) 2009-2020 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 { 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';
29 mockSnippetsByComponent,
32 } from '../../../../helpers/testMocks';
33 import ComponentSourceSnippetGroupViewer from '../ComponentSourceSnippetGroupViewer';
35 jest.mock('../../../../api/components', () => ({
36 getSources: jest.fn().mockResolvedValue([])
43 it('should render correctly', () => {
44 expect(shallowRender()).toMatchSnapshot();
47 it('should render correctly with secondary locations', () => {
48 // issue with secondary locations but no flows
49 const issue = mockIssue(true, {
51 textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 }
54 const snippetGroup: T.SnippetGroup = {
57 component: issue.component,
58 textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
61 component: issue.component,
62 textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
65 ...mockSnippetsByComponent(issue.component, [
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 });
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 }
85 const snippetGroup: T.SnippetGroup = {
88 component: issue.component,
89 textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
92 component: issue.component,
93 textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
96 ...mockSnippetsByComponent(issue.component, [
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 });
108 it('should expand block', async () => {
109 (getSources as jest.Mock).mockResolvedValueOnce(
110 Object.values(mockSnippetsByComponent('a', range(6, 59)).sources)
112 const issue = mockIssue(true, {
113 textRange: { startLine: 74, endLine: 74, startOffset: 5, endOffset: 10 }
115 const snippetGroup: T.SnippetGroup = {
119 textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
123 textRange: { startLine: 107, endLine: 107, startOffset: 0, endOffset: 0 }
126 ...mockSnippetsByComponent('a', [...range(69, 83), ...range(102, 112)])
129 const wrapper = shallowRender({ issue, snippetGroup });
131 wrapper.instance().expandBlock(0, 'up');
132 await waitAndUpdate(wrapper);
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);
140 it('should expand full component', async () => {
141 (getSources as jest.Mock).mockResolvedValueOnce(
142 Object.values(mockSnippetsByComponent('a', times(14)).sources)
144 const snippetGroup: T.SnippetGroup = {
148 textRange: { startLine: 3, endLine: 3, startOffset: 0, endOffset: 0 }
152 textRange: { startLine: 12, endLine: 12, startOffset: 0, endOffset: 0 }
155 ...mockSnippetsByComponent('a', [1, 2, 3, 4, 5, 10, 11, 12, 13, 14])
158 const wrapper = shallowRender({ snippetGroup });
160 wrapper.instance().expandComponent();
161 await waitAndUpdate(wrapper);
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 });
168 it('should get the right branch when expanding', async () => {
169 (getSources as jest.Mock).mockResolvedValueOnce(
171 mockSnippetsByComponent('a', [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]).sources
174 const snippetGroup: T.SnippetGroup = {
175 locations: [mockFlowLocation()],
176 ...mockSnippetsByComponent('a', [1, 2, 3, 4, 5, 6, 7])
179 const wrapper = shallowRender({
180 branchLike: mockBranch({ name: 'asdf' }),
184 wrapper.instance().expandBlock(0, 'down');
185 await waitAndUpdate(wrapper);
187 expect(getSources).toHaveBeenCalledWith({ branch: 'asdf', from: 8, key: 'a', to: 67 });
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 });
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([]);
209 it('should correctly handle lines actions', () => {
210 const snippetGroup: T.SnippetGroup = {
214 textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
218 textRange: { startLine: 54, endLine: 54, startOffset: 0, endOffset: 0 }
221 ...mockSnippetsByComponent('a', [32, 33, 34, 35, 36, 52, 53, 54, 55, 56])
223 const loadDuplications = jest.fn();
224 const onLinePopupToggle = jest.fn();
225 const renderDuplicationPopup = jest.fn();
227 const wrapper = shallowRender({
230 renderDuplicationPopup,
234 const line = mockSourceLine();
236 .find('SnippetViewer')
238 .prop<Function>('loadDuplications')(line);
239 expect(loadDuplications).toHaveBeenCalledWith('a', line);
242 .find('SnippetViewer')
244 .prop<Function>('handleLinePopupToggle')({ line: 13, name: 'foo' });
245 expect(onLinePopupToggle).toHaveBeenCalledWith({ component: 'a', line: 13, name: 'foo' });
248 .find('SnippetViewer')
250 .prop<Function>('renderDuplicationPopup')(1, 13);
251 expect(renderDuplicationPopup).toHaveBeenCalledWith(
252 mockSourceViewerFile({ key: 'a', path: 'a' }),
258 describe('getNodes', () => {
259 const snippetGroup: T.SnippetGroup = {
260 component: mockSourceViewerFile(),
264 const wrapper = mount<ComponentSourceSnippetGroupViewer>(
265 <ComponentSourceSnippetGroupViewer
266 branchLike={mockMainBranch()}
267 duplications={undefined}
268 duplicationsByLine={undefined}
269 highlightedLocationMessage={{ index: 0, text: '' }}
272 lastSnippetGroup={false}
273 linePopup={undefined}
274 loadDuplications={jest.fn()}
276 onIssueChange={jest.fn()}
277 onIssuePopupToggle={jest.fn()}
278 onLinePopupToggle={jest.fn()}
279 onLocationSelect={jest.fn()}
280 renderDuplicationPopup={jest.fn()}
282 snippetGroup={snippetGroup}
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();
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();
302 it('should enable cleaning the dom', async () => {
303 await waitAndUpdate(wrapper);
304 const rootNode = wrapper.instance().rootNodeRef;
305 mockDom(rootNode.current!);
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('');
314 describe('getHeight', () => {
315 jest.useFakeTimers();
317 const snippetGroup: T.SnippetGroup = {
318 component: mockSourceViewerFile(),
322 const wrapper = mount<ComponentSourceSnippetGroupViewer>(
323 <ComponentSourceSnippetGroupViewer
324 branchLike={mockMainBranch()}
325 duplications={undefined}
326 duplicationsByLine={undefined}
327 highlightedLocationMessage={{ index: 0, text: '' }}
330 lastSnippetGroup={false}
331 linePopup={undefined}
332 loadDuplications={jest.fn()}
334 onIssueChange={jest.fn()}
335 onIssuePopupToggle={jest.fn()}
336 onLinePopupToggle={jest.fn()}
337 onLocationSelect={jest.fn()}
338 renderDuplicationPopup={jest.fn()}
340 snippetGroup={snippetGroup}
344 it('should set maxHeight to current height', async () => {
345 await waitAndUpdate(wrapper);
347 const nodes = mockDomForSizes(wrapper, { wrapperHeight: 42, tableHeight: 68 });
348 wrapper.instance().setMaxHeight(0);
350 expect(nodes.wrapper.getAttribute('style')).toBe('max-height: 88px;');
351 expect(nodes.table.getAttribute('style')).toBeNull();
354 it('should set margin and then maxHeight for a nice upwards animation', async () => {
355 await waitAndUpdate(wrapper);
357 const nodes = mockDomForSizes(wrapper, { wrapperHeight: 42, tableHeight: 68 });
358 wrapper.instance().setMaxHeight(0, undefined, true);
360 expect(nodes.wrapper.getAttribute('style')).toBeNull();
361 expect(nodes.table.getAttribute('style')).toBe('transition: none; margin-top: -26px;');
365 expect(nodes.wrapper.getAttribute('style')).toBe('max-height: 88px;');
366 expect(nodes.table.getAttribute('style')).toBe('margin-top: 0px;');
370 function shallowRender(props: Partial<ComponentSourceSnippetGroupViewer['props']> = {}) {
371 const snippetGroup: T.SnippetGroup = {
372 component: mockSourceViewerFile(),
376 return shallow<ComponentSourceSnippetGroupViewer>(
377 <ComponentSourceSnippetGroupViewer
378 branchLike={mockMainBranch()}
379 duplications={undefined}
380 duplicationsByLine={undefined}
381 highlightedLocationMessage={{ index: 0, text: '' }}
384 lastSnippetGroup={false}
385 linePopup={undefined}
386 loadDuplications={jest.fn()}
388 onIssueChange={jest.fn()}
389 onIssuePopupToggle={jest.fn()}
390 onLinePopupToggle={jest.fn()}
391 onLocationSelect={jest.fn()}
392 renderDuplicationPopup={jest.fn()}
394 snippetGroup={snippetGroup}
400 function mockDom(refNode: HTMLDivElement) {
401 refNode.querySelector = jest.fn(query => {
402 const index = query.split('-').pop();
408 return mount(<div />).getDOMNode();
412 <div className="snippet" />
418 <div className="snippet">
429 function mockDomForSizes(
430 componentWrapper: ReactWrapper<{}, {}, ComponentSourceSnippetGroupViewer>,
431 { wrapperHeight = 0, tableHeight = 0 }
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({
441 return { wrapper, table };