123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492 |
- /*
- * SonarQube
- * Copyright (C) 2009-2022 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
- import { mount, ReactWrapper, shallow } from 'enzyme';
- import { range, times } from 'lodash';
- import * as React from 'react';
- import { getSources } from '../../../../api/components';
- import Issue from '../../../../components/issue/Issue';
- import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
- import {
- mockSnippetsByComponent,
- mockSourceLine,
- mockSourceViewerFile
- } from '../../../../helpers/mocks/sources';
- import { mockFlowLocation, mockIssue } from '../../../../helpers/testMocks';
- import { waitAndUpdate } from '../../../../helpers/testUtils';
- import { SnippetGroup } from '../../../../types/types';
- import ComponentSourceSnippetGroupViewer from '../ComponentSourceSnippetGroupViewer';
- import SnippetViewer from '../SnippetViewer';
-
- jest.mock('../../../../api/components', () => ({
- getSources: jest.fn().mockResolvedValue([])
- }));
-
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot();
- });
-
- it('should render correctly with secondary locations', () => {
- // issue with secondary locations but no flows
- const issue = mockIssue(true, {
- component: 'project:main.js',
- flows: [],
- textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 }
- });
-
- const snippetGroup: SnippetGroup = {
- locations: [
- mockFlowLocation({
- component: issue.component,
- textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
- }),
- mockFlowLocation({
- component: issue.component,
- textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
- })
- ],
- ...mockSnippetsByComponent('main.js', 'project', [
- ...range(2, 17),
- ...range(29, 39),
- ...range(69, 79)
- ])
- };
- const wrapper = shallowRender({ issue, snippetGroup });
- expect(wrapper.state('snippets')).toHaveLength(3);
- expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 2, end: 16 });
- expect(wrapper.state('snippets')[1]).toEqual({ index: 1, start: 29, end: 39 });
- expect(wrapper.state('snippets')[2]).toEqual({ index: 2, start: 69, end: 79 });
- });
-
- it('should render correctly with flows', () => {
- // issue with flows but no secondary locations
- const issue = mockIssue(true, {
- component: 'project:main.js',
- secondaryLocations: [],
- textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 }
- });
-
- const snippetGroup: SnippetGroup = {
- locations: [
- mockFlowLocation({
- component: issue.component,
- textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
- }),
- mockFlowLocation({
- component: issue.component,
- textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
- })
- ],
- ...mockSnippetsByComponent('main.js', 'project', [
- ...range(2, 17),
- ...range(29, 39),
- ...range(69, 79)
- ])
- };
- const wrapper = shallowRender({ issue, snippetGroup });
- expect(wrapper.state('snippets')).toHaveLength(2);
- expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 29, end: 39 });
- expect(wrapper.state('snippets')[1]).toEqual({ index: 1, start: 69, end: 79 });
-
- // Check that locationsByLine is defined when isLastOccurenceOfPrimaryComponent
- expect(
- wrapper
- .find(SnippetViewer)
- .at(0)
- .props().locationsByLine
- ).not.toEqual({});
-
- // If not, it should be an empty object:
- const snippets = shallowRender({
- isLastOccurenceOfPrimaryComponent: false,
- issue,
- snippetGroup
- }).find(SnippetViewer);
-
- expect(snippets.at(0).props().locationsByLine).toEqual({});
- expect(snippets.at(1).props().locationsByLine).toEqual({});
- });
-
- it('should render file-level issue correctly', () => {
- // issue with secondary locations and no primary location
- const issue = mockIssue(true, {
- component: 'project:main.js',
- flows: [],
- textRange: undefined
- });
-
- const wrapper = shallowRender({
- issue,
- snippetGroup: {
- locations: [
- mockFlowLocation({
- component: issue.component,
- textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
- })
- ],
- ...mockSnippetsByComponent('main.js', 'project', range(29, 39))
- }
- });
-
- expect(wrapper.find(Issue).exists()).toBe(true);
- });
-
- it('should expand block', async () => {
- (getSources as jest.Mock).mockResolvedValueOnce(
- Object.values(mockSnippetsByComponent('a', 'project', range(6, 59)).sources)
- );
- const issue = mockIssue(true, {
- textRange: { startLine: 74, endLine: 74, startOffset: 5, endOffset: 10 }
- });
- const snippetGroup: SnippetGroup = {
- locations: [
- mockFlowLocation({
- component: 'a',
- textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
- }),
- mockFlowLocation({
- component: 'a',
- textRange: { startLine: 107, endLine: 107, startOffset: 0, endOffset: 0 }
- })
- ],
- ...mockSnippetsByComponent('a', 'project', [...range(69, 83), ...range(102, 112)])
- };
-
- const wrapper = shallowRender({ issue, snippetGroup });
-
- wrapper.instance().expandBlock(0, 'up');
- await waitAndUpdate(wrapper);
-
- expect(getSources).toHaveBeenCalledWith({ from: 9, key: 'project:a', to: 68 });
- expect(wrapper.state('snippets')).toHaveLength(2);
- expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 19, end: 83 });
- expect(Object.keys(wrapper.state('additionalLines'))).toHaveLength(53);
- });
-
- it('should expand full component', async () => {
- (getSources as jest.Mock).mockResolvedValueOnce(
- Object.values(mockSnippetsByComponent('a', 'project', times(14)).sources)
- );
- const snippetGroup: SnippetGroup = {
- locations: [
- mockFlowLocation({
- component: 'a',
- textRange: { startLine: 3, endLine: 3, startOffset: 0, endOffset: 0 }
- }),
- mockFlowLocation({
- component: 'a',
- textRange: { startLine: 12, endLine: 12, startOffset: 0, endOffset: 0 }
- })
- ],
- ...mockSnippetsByComponent('a', 'project', [1, 2, 3, 4, 5, 10, 11, 12, 13, 14])
- };
-
- const wrapper = shallowRender({ snippetGroup });
-
- wrapper.instance().expandComponent();
- await waitAndUpdate(wrapper);
-
- expect(getSources).toHaveBeenCalledWith({ key: 'project:a' });
- expect(wrapper.state('snippets')).toHaveLength(1);
- expect(wrapper.state('snippets')[0]).toEqual({ index: -1, start: 0, end: 13 });
- });
-
- it('should get the right branch when expanding', async () => {
- (getSources as jest.Mock).mockResolvedValueOnce(
- Object.values(
- mockSnippetsByComponent('a', 'project', [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17])
- .sources
- )
- );
- const snippetGroup: SnippetGroup = {
- locations: [mockFlowLocation()],
- ...mockSnippetsByComponent('a', 'project', [1, 2, 3, 4, 5, 6, 7])
- };
-
- const wrapper = shallowRender({
- branchLike: mockBranch({ name: 'asdf' }),
- snippetGroup
- });
-
- wrapper.instance().expandBlock(0, 'down');
- await waitAndUpdate(wrapper);
-
- expect(getSources).toHaveBeenCalledWith({ branch: 'asdf', from: 8, key: 'project:a', to: 67 });
- });
-
- it('should handle correctly open/close issue', () => {
- const wrapper = shallowRender();
- const sourceLine = mockSourceLine();
- expect(wrapper.state('openIssuesByLine')).toEqual({});
- wrapper.instance().handleOpenIssues(sourceLine);
- expect(wrapper.state('openIssuesByLine')).toEqual({ [sourceLine.line]: true });
- wrapper.instance().handleCloseIssues(sourceLine);
- expect(wrapper.state('openIssuesByLine')).toEqual({ [sourceLine.line]: false });
- });
-
- it('should handle symbol highlighting', () => {
- const wrapper = shallowRender();
- expect(wrapper.state('highlightedSymbols')).toEqual([]);
- wrapper.instance().handleSymbolClick(['foo']);
- expect(wrapper.state('highlightedSymbols')).toEqual(['foo']);
- wrapper.instance().handleSymbolClick(['foo']);
- expect(wrapper.state('highlightedSymbols')).toEqual([]);
- });
-
- it('should correctly handle lines actions', () => {
- const snippetGroup: SnippetGroup = {
- locations: [
- mockFlowLocation({
- component: 'my-project:foo/bar.ts',
- textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
- }),
- mockFlowLocation({
- component: 'my-project:foo/bar.ts',
- textRange: { startLine: 54, endLine: 54, startOffset: 0, endOffset: 0 }
- })
- ],
- ...mockSnippetsByComponent('foo/bar.ts', 'my-project', [32, 33, 34, 35, 36, 52, 53, 54, 55, 56])
- };
- const loadDuplications = jest.fn();
- const renderDuplicationPopup = jest.fn();
-
- const wrapper = shallowRender({
- loadDuplications,
- renderDuplicationPopup,
- snippetGroup
- });
-
- const line = mockSourceLine();
- wrapper
- .find('SnippetViewer')
- .first()
- .prop<Function>('loadDuplications')(line);
- expect(loadDuplications).toHaveBeenCalledWith('my-project:foo/bar.ts', line);
-
- wrapper
- .find('SnippetViewer')
- .first()
- .prop<Function>('renderDuplicationPopup')(1, 13);
- expect(renderDuplicationPopup).toHaveBeenCalledWith(
- mockSourceViewerFile('foo/bar.ts', 'my-project'),
- 1,
- 13
- );
- });
-
- it('should render correctly line with issue', () => {
- const issue = mockIssue(false, {
- textRange: { endLine: 1, startLine: 1, endOffset: 1, startOffset: 0 }
- });
- const wrapper = shallowRender({
- issue,
- issuesByLine: { '1': [issue] }
- });
- wrapper.instance().setState({ openIssuesByLine: { '1': true } });
- const wrapperLine = shallow(wrapper.instance().renderIssuesList(mockSourceLine({ line: 1 })));
- expect(wrapperLine).toMatchSnapshot();
- });
-
- describe('getNodes', () => {
- const snippetGroup: SnippetGroup = {
- component: mockSourceViewerFile(),
- locations: [],
- sources: []
- };
- const wrapper = mount<ComponentSourceSnippetGroupViewer>(
- <ComponentSourceSnippetGroupViewer
- branchLike={mockMainBranch()}
- highlightedLocationMessage={{ index: 0, text: '' }}
- isLastOccurenceOfPrimaryComponent={true}
- issue={mockIssue()}
- issuesByLine={{}}
- lastSnippetGroup={false}
- loadDuplications={jest.fn()}
- locations={[]}
- onIssueChange={jest.fn()}
- onIssuePopupToggle={jest.fn()}
- onLocationSelect={jest.fn()}
- renderDuplicationPopup={jest.fn()}
- scroll={jest.fn()}
- snippetGroup={snippetGroup}
- />
- );
-
- it('should return undefined if any node is missing', async () => {
- await waitAndUpdate(wrapper);
- const rootNode = wrapper.instance().rootNodeRef;
- mockDom(rootNode.current!);
- expect(wrapper.instance().getNodes(0)).toBeUndefined();
- expect(wrapper.instance().getNodes(1)).toBeUndefined();
- expect(wrapper.instance().getNodes(2)).toBeUndefined();
- });
-
- it('should return elements if dom is correct', async () => {
- await waitAndUpdate(wrapper);
- const rootNode = wrapper.instance().rootNodeRef;
- mockDom(rootNode.current!);
- expect(wrapper.instance().getNodes(3)).not.toBeUndefined();
- });
-
- it('should enable cleaning the dom', async () => {
- await waitAndUpdate(wrapper);
- const rootNode = wrapper.instance().rootNodeRef;
- mockDom(rootNode.current!);
-
- wrapper.instance().cleanDom(3);
- const nodes = wrapper.instance().getNodes(3);
- expect(nodes!.wrapper.style.maxHeight).toBe('');
- expect(nodes!.table.style.marginTop).toBe('');
- });
- });
-
- describe('getHeight', () => {
- beforeAll(() => {
- jest.useFakeTimers();
- });
-
- afterAll(() => {
- jest.runOnlyPendingTimers();
- jest.useRealTimers();
- });
-
- const snippetGroup: SnippetGroup = {
- component: mockSourceViewerFile(),
- locations: [],
- sources: []
- };
- const wrapper = mount<ComponentSourceSnippetGroupViewer>(
- <ComponentSourceSnippetGroupViewer
- branchLike={mockMainBranch()}
- highlightedLocationMessage={{ index: 0, text: '' }}
- isLastOccurenceOfPrimaryComponent={true}
- issue={mockIssue()}
- issuesByLine={{}}
- lastSnippetGroup={false}
- loadDuplications={jest.fn()}
- locations={[]}
- onIssueChange={jest.fn()}
- onIssuePopupToggle={jest.fn()}
- onLocationSelect={jest.fn()}
- renderDuplicationPopup={jest.fn()}
- scroll={jest.fn()}
- snippetGroup={snippetGroup}
- />
- );
-
- it('should set maxHeight to current height', async () => {
- await waitAndUpdate(wrapper);
-
- const nodes = mockDomForSizes(wrapper, { wrapperHeight: 42, tableHeight: 68 });
- wrapper.instance().setMaxHeight(0);
-
- expect(nodes.wrapper.getAttribute('style')).toBe('max-height: 88px;');
- expect(nodes.table.getAttribute('style')).toBeNull();
- });
-
- it('should set margin and then maxHeight for a nice upwards animation', async () => {
- await waitAndUpdate(wrapper);
-
- const nodes = mockDomForSizes(wrapper, { wrapperHeight: 42, tableHeight: 68 });
- wrapper.instance().setMaxHeight(0, undefined, true);
-
- expect(nodes.wrapper.getAttribute('style')).toBeNull();
- expect(nodes.table.getAttribute('style')).toBe('transition: none; margin-top: -26px;');
-
- jest.runAllTimers();
-
- expect(nodes.wrapper.getAttribute('style')).toBe('max-height: 88px;');
- expect(nodes.table.getAttribute('style')).toBe('margin-top: 0px;');
- });
- });
-
- function shallowRender(props: Partial<ComponentSourceSnippetGroupViewer['props']> = {}) {
- const snippetGroup: SnippetGroup = {
- component: mockSourceViewerFile(),
- locations: [],
- sources: []
- };
- return shallow<ComponentSourceSnippetGroupViewer>(
- <ComponentSourceSnippetGroupViewer
- branchLike={mockMainBranch()}
- highlightedLocationMessage={{ index: 0, text: '' }}
- isLastOccurenceOfPrimaryComponent={true}
- issue={mockIssue()}
- issuesByLine={{}}
- lastSnippetGroup={false}
- loadDuplications={jest.fn()}
- locations={[]}
- onIssueChange={jest.fn()}
- onIssuePopupToggle={jest.fn()}
- onLocationSelect={jest.fn()}
- renderDuplicationPopup={jest.fn()}
- scroll={jest.fn()}
- snippetGroup={snippetGroup}
- {...props}
- />
- );
- }
-
- function mockDom(refNode: HTMLDivElement) {
- refNode.querySelector = jest.fn(query => {
- const index = query.split('-').pop();
-
- switch (index) {
- case '0':
- return null;
- case '1':
- return mount(<div />).getDOMNode();
- case '2':
- return mount(
- <div>
- <div className="snippet" />
- </div>
- ).getDOMNode();
- case '3':
- return mount(
- <div>
- <div className="snippet">
- <div />
- </div>
- </div>
- ).getDOMNode();
- default:
- return null;
- }
- });
- }
-
- function mockDomForSizes(
- componentWrapper: ReactWrapper<{}, {}, ComponentSourceSnippetGroupViewer>,
- { wrapperHeight = 0, tableHeight = 0 }
- ) {
- const wrapper = mount(<div className="snippet" />).getDOMNode();
- wrapper.getBoundingClientRect = jest.fn().mockReturnValue({ height: wrapperHeight });
- const table = mount(<div />).getDOMNode();
- table.getBoundingClientRect = jest.fn().mockReturnValue({ height: tableHeight });
- componentWrapper.instance().getNodes = jest.fn().mockReturnValue({
- wrapper,
- table
- });
- return { wrapper, table };
- }
|