3 * Copyright (C) 2009-2022 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 { getSources } from '../../../../api/components';
24 import Issue from '../../../../components/issue/Issue';
25 import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
29 mockSnippetsByComponent,
32 } from '../../../../helpers/testMocks';
33 import { waitAndUpdate } from '../../../../helpers/testUtils';
34 import { SnippetGroup } from '../../../../types/types';
35 import ComponentSourceSnippetGroupViewer from '../ComponentSourceSnippetGroupViewer';
36 import SnippetViewer from '../SnippetViewer';
38 jest.mock('../../../../api/components', () => ({
39 getSources: jest.fn().mockResolvedValue([])
46 it('should render correctly', () => {
47 expect(shallowRender()).toMatchSnapshot();
50 it('should render correctly with secondary locations', () => {
51 // issue with secondary locations but no flows
52 const issue = mockIssue(true, {
54 textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 }
57 const snippetGroup: SnippetGroup = {
60 component: issue.component,
61 textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
64 component: issue.component,
65 textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
68 ...mockSnippetsByComponent(issue.component, [
74 const wrapper = shallowRender({ issue, snippetGroup });
75 expect(wrapper.state('snippets')).toHaveLength(3);
76 expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 2, end: 16 });
77 expect(wrapper.state('snippets')[1]).toEqual({ index: 1, start: 29, end: 39 });
78 expect(wrapper.state('snippets')[2]).toEqual({ index: 2, start: 69, end: 79 });
81 it('should render correctly with flows', () => {
82 // issue with flows but no secondary locations
83 const issue = mockIssue(true, {
84 secondaryLocations: [],
85 textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 }
88 const snippetGroup: SnippetGroup = {
91 component: issue.component,
92 textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
95 component: issue.component,
96 textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
99 ...mockSnippetsByComponent(issue.component, [
105 const wrapper = shallowRender({ issue, snippetGroup });
106 expect(wrapper.state('snippets')).toHaveLength(2);
107 expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 29, end: 39 });
108 expect(wrapper.state('snippets')[1]).toEqual({ index: 1, start: 69, end: 79 });
110 // Check that locationsByLine is defined when isLastOccurenceOfPrimaryComponent
115 .props().locationsByLine
118 // If not, it should be an empty object:
119 const snippets = shallowRender({
120 isLastOccurenceOfPrimaryComponent: false,
123 }).find(SnippetViewer);
125 expect(snippets.at(0).props().locationsByLine).toEqual({});
126 expect(snippets.at(1).props().locationsByLine).toEqual({});
129 it('should render file-level issue correctly', () => {
130 // issue with secondary locations and no primary location
131 const issue = mockIssue(true, {
136 const wrapper = shallowRender({
141 component: issue.component,
142 textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
145 ...mockSnippetsByComponent(issue.component, range(29, 39))
149 expect(wrapper.find(Issue).exists()).toBe(true);
152 it('should expand block', async () => {
153 (getSources as jest.Mock).mockResolvedValueOnce(
154 Object.values(mockSnippetsByComponent('a', range(6, 59)).sources)
156 const issue = mockIssue(true, {
157 textRange: { startLine: 74, endLine: 74, startOffset: 5, endOffset: 10 }
159 const snippetGroup: SnippetGroup = {
163 textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
167 textRange: { startLine: 107, endLine: 107, startOffset: 0, endOffset: 0 }
170 ...mockSnippetsByComponent('a', [...range(69, 83), ...range(102, 112)])
173 const wrapper = shallowRender({ issue, snippetGroup });
175 wrapper.instance().expandBlock(0, 'up');
176 await waitAndUpdate(wrapper);
178 expect(getSources).toHaveBeenCalledWith({ from: 9, key: 'a', to: 68 });
179 expect(wrapper.state('snippets')).toHaveLength(2);
180 expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 19, end: 83 });
181 expect(Object.keys(wrapper.state('additionalLines'))).toHaveLength(53);
184 it('should expand full component', async () => {
185 (getSources as jest.Mock).mockResolvedValueOnce(
186 Object.values(mockSnippetsByComponent('a', times(14)).sources)
188 const snippetGroup: SnippetGroup = {
192 textRange: { startLine: 3, endLine: 3, startOffset: 0, endOffset: 0 }
196 textRange: { startLine: 12, endLine: 12, startOffset: 0, endOffset: 0 }
199 ...mockSnippetsByComponent('a', [1, 2, 3, 4, 5, 10, 11, 12, 13, 14])
202 const wrapper = shallowRender({ snippetGroup });
204 wrapper.instance().expandComponent();
205 await waitAndUpdate(wrapper);
207 expect(getSources).toHaveBeenCalledWith({ key: 'a' });
208 expect(wrapper.state('snippets')).toHaveLength(1);
209 expect(wrapper.state('snippets')[0]).toEqual({ index: -1, start: 0, end: 13 });
212 it('should get the right branch when expanding', async () => {
213 (getSources as jest.Mock).mockResolvedValueOnce(
215 mockSnippetsByComponent('a', [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]).sources
218 const snippetGroup: SnippetGroup = {
219 locations: [mockFlowLocation()],
220 ...mockSnippetsByComponent('a', [1, 2, 3, 4, 5, 6, 7])
223 const wrapper = shallowRender({
224 branchLike: mockBranch({ name: 'asdf' }),
228 wrapper.instance().expandBlock(0, 'down');
229 await waitAndUpdate(wrapper);
231 expect(getSources).toHaveBeenCalledWith({ branch: 'asdf', from: 8, key: 'a', to: 67 });
234 it('should handle correctly open/close issue', () => {
235 const wrapper = shallowRender();
236 const sourceLine = mockSourceLine();
237 expect(wrapper.state('openIssuesByLine')).toEqual({});
238 wrapper.instance().handleOpenIssues(sourceLine);
239 expect(wrapper.state('openIssuesByLine')).toEqual({ [sourceLine.line]: true });
240 wrapper.instance().handleCloseIssues(sourceLine);
241 expect(wrapper.state('openIssuesByLine')).toEqual({ [sourceLine.line]: false });
244 it('should handle symbol highlighting', () => {
245 const wrapper = shallowRender();
246 expect(wrapper.state('highlightedSymbols')).toEqual([]);
247 wrapper.instance().handleSymbolClick(['foo']);
248 expect(wrapper.state('highlightedSymbols')).toEqual(['foo']);
249 wrapper.instance().handleSymbolClick(['foo']);
250 expect(wrapper.state('highlightedSymbols')).toEqual([]);
253 it('should correctly handle lines actions', () => {
254 const snippetGroup: SnippetGroup = {
258 textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
262 textRange: { startLine: 54, endLine: 54, startOffset: 0, endOffset: 0 }
265 ...mockSnippetsByComponent('a', [32, 33, 34, 35, 36, 52, 53, 54, 55, 56])
267 const loadDuplications = jest.fn();
268 const renderDuplicationPopup = jest.fn();
270 const wrapper = shallowRender({
272 renderDuplicationPopup,
276 const line = mockSourceLine();
278 .find('SnippetViewer')
280 .prop<Function>('loadDuplications')(line);
281 expect(loadDuplications).toHaveBeenCalledWith('a', line);
284 .find('SnippetViewer')
286 .prop<Function>('renderDuplicationPopup')(1, 13);
287 expect(renderDuplicationPopup).toHaveBeenCalledWith(
288 mockSourceViewerFile({ key: 'a', path: 'a' }),
294 it('should render correctly line with issue', () => {
295 const issue = mockIssue(false, {
296 textRange: { endLine: 1, startLine: 1, endOffset: 1, startOffset: 0 }
298 const wrapper = shallowRender({
300 issuesByLine: { '1': [issue] }
302 wrapper.instance().setState({ openIssuesByLine: { '1': true } });
303 const wrapperLine = shallow(wrapper.instance().renderIssuesList(mockSourceLine({ line: 1 })));
304 expect(wrapperLine).toMatchSnapshot();
307 describe('getNodes', () => {
308 const snippetGroup: SnippetGroup = {
309 component: mockSourceViewerFile(),
313 const wrapper = mount<ComponentSourceSnippetGroupViewer>(
314 <ComponentSourceSnippetGroupViewer
315 branchLike={mockMainBranch()}
316 highlightedLocationMessage={{ index: 0, text: '' }}
317 isLastOccurenceOfPrimaryComponent={true}
320 lastSnippetGroup={false}
321 loadDuplications={jest.fn()}
323 onIssueChange={jest.fn()}
324 onIssuePopupToggle={jest.fn()}
325 onLocationSelect={jest.fn()}
326 renderDuplicationPopup={jest.fn()}
328 snippetGroup={snippetGroup}
332 it('should return undefined if any node is missing', async () => {
333 await waitAndUpdate(wrapper);
334 const rootNode = wrapper.instance().rootNodeRef;
335 mockDom(rootNode.current!);
336 expect(wrapper.instance().getNodes(0)).toBeUndefined();
337 expect(wrapper.instance().getNodes(1)).toBeUndefined();
338 expect(wrapper.instance().getNodes(2)).toBeUndefined();
341 it('should return elements if dom is correct', async () => {
342 await waitAndUpdate(wrapper);
343 const rootNode = wrapper.instance().rootNodeRef;
344 mockDom(rootNode.current!);
345 expect(wrapper.instance().getNodes(3)).not.toBeUndefined();
348 it('should enable cleaning the dom', async () => {
349 await waitAndUpdate(wrapper);
350 const rootNode = wrapper.instance().rootNodeRef;
351 mockDom(rootNode.current!);
353 wrapper.instance().cleanDom(3);
354 const nodes = wrapper.instance().getNodes(3);
355 expect(nodes!.wrapper.style.maxHeight).toBe('');
356 expect(nodes!.table.style.marginTop).toBe('');
360 describe('getHeight', () => {
362 jest.useFakeTimers();
366 jest.runOnlyPendingTimers();
367 jest.useRealTimers();
370 const snippetGroup: SnippetGroup = {
371 component: mockSourceViewerFile(),
375 const wrapper = mount<ComponentSourceSnippetGroupViewer>(
376 <ComponentSourceSnippetGroupViewer
377 branchLike={mockMainBranch()}
378 highlightedLocationMessage={{ index: 0, text: '' }}
379 isLastOccurenceOfPrimaryComponent={true}
382 lastSnippetGroup={false}
383 loadDuplications={jest.fn()}
385 onIssueChange={jest.fn()}
386 onIssuePopupToggle={jest.fn()}
387 onLocationSelect={jest.fn()}
388 renderDuplicationPopup={jest.fn()}
390 snippetGroup={snippetGroup}
394 it('should set maxHeight to current height', async () => {
395 await waitAndUpdate(wrapper);
397 const nodes = mockDomForSizes(wrapper, { wrapperHeight: 42, tableHeight: 68 });
398 wrapper.instance().setMaxHeight(0);
400 expect(nodes.wrapper.getAttribute('style')).toBe('max-height: 88px;');
401 expect(nodes.table.getAttribute('style')).toBeNull();
404 it('should set margin and then maxHeight for a nice upwards animation', async () => {
405 await waitAndUpdate(wrapper);
407 const nodes = mockDomForSizes(wrapper, { wrapperHeight: 42, tableHeight: 68 });
408 wrapper.instance().setMaxHeight(0, undefined, true);
410 expect(nodes.wrapper.getAttribute('style')).toBeNull();
411 expect(nodes.table.getAttribute('style')).toBe('transition: none; margin-top: -26px;');
415 expect(nodes.wrapper.getAttribute('style')).toBe('max-height: 88px;');
416 expect(nodes.table.getAttribute('style')).toBe('margin-top: 0px;');
420 function shallowRender(props: Partial<ComponentSourceSnippetGroupViewer['props']> = {}) {
421 const snippetGroup: SnippetGroup = {
422 component: mockSourceViewerFile(),
426 return shallow<ComponentSourceSnippetGroupViewer>(
427 <ComponentSourceSnippetGroupViewer
428 branchLike={mockMainBranch()}
429 highlightedLocationMessage={{ index: 0, text: '' }}
430 isLastOccurenceOfPrimaryComponent={true}
433 lastSnippetGroup={false}
434 loadDuplications={jest.fn()}
436 onIssueChange={jest.fn()}
437 onIssuePopupToggle={jest.fn()}
438 onLocationSelect={jest.fn()}
439 renderDuplicationPopup={jest.fn()}
441 snippetGroup={snippetGroup}
447 function mockDom(refNode: HTMLDivElement) {
448 refNode.querySelector = jest.fn(query => {
449 const index = query.split('-').pop();
455 return mount(<div />).getDOMNode();
459 <div className="snippet" />
465 <div className="snippet">
476 function mockDomForSizes(
477 componentWrapper: ReactWrapper<{}, {}, ComponentSourceSnippetGroupViewer>,
478 { wrapperHeight = 0, tableHeight = 0 }
480 const wrapper = mount(<div className="snippet" />).getDOMNode();
481 wrapper.getBoundingClientRect = jest.fn().mockReturnValue({ height: wrapperHeight });
482 const table = mount(<div />).getDOMNode();
483 table.getBoundingClientRect = jest.fn().mockReturnValue({ height: tableHeight });
484 componentWrapper.instance().getNodes = jest.fn().mockReturnValue({
488 return { wrapper, table };