3 * Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
21 import { range } from 'lodash';
22 import * as React from 'react';
23 import { getSources } from '../../../../api/components';
24 import { mockBranch } from '../../../../helpers/mocks/branch-like';
25 import { mockComponent } from '../../../../helpers/mocks/component';
26 import { mockHotspot, mockHotspotComponent } from '../../../../helpers/mocks/security-hotspots';
27 import { mockSourceLine } from '../../../../helpers/mocks/sources';
28 import { mockFlowLocation } from '../../../../helpers/testMocks';
29 import { waitAndUpdate } from '../../../../helpers/testUtils';
30 import { ComponentQualifier } from '../../../../types/component';
31 import HotspotSnippetContainer from '../HotspotSnippetContainer';
32 import HotspotSnippetContainerRenderer from '../HotspotSnippetContainerRenderer';
34 jest.mock('../../../../api/components', () => ({
35 getSources: jest.fn().mockResolvedValue([]),
38 jest.mock('../../../../helpers/scrolling', () => ({
39 scrollToElement: jest.fn(),
42 beforeEach(() => jest.clearAllMocks());
44 const branch = mockBranch();
46 it('should render correctly', () => {
47 expect(shallowRender()).toMatchSnapshot();
50 it('should load sources on mount', async () => {
51 (getSources as jest.Mock).mockResolvedValueOnce(
52 range(1, 25).map((line) => mockSourceLine({ line }))
55 const hotspot = mockHotspot({
56 project: mockHotspotComponent({ branch: branch.name, qualifier: ComponentQualifier.Project }),
57 textRange: { startLine: 10, endLine: 11, startOffset: 0, endOffset: 12 },
62 textRange: { startLine: 8, endLine: 8, startOffset: 0, endOffset: 1 },
65 textRange: { startLine: 13, endLine: 13, startOffset: 0, endOffset: 1 },
72 const wrapper = shallowRender({ hotspot });
74 await waitAndUpdate(wrapper);
76 expect(getSources).toHaveBeenCalledWith(
77 expect.objectContaining({
78 key: hotspot.component.key,
84 expect(wrapper.state().lastLine).toBeUndefined();
85 expect(wrapper.state().sourceLines).toHaveLength(23);
88 it('should handle load sources failure', async () => {
89 (getSources as jest.Mock).mockRejectedValueOnce(null);
91 const wrapper = shallowRender();
93 await waitAndUpdate(wrapper);
95 expect(getSources).toHaveBeenCalled();
96 expect(wrapper.state().loading).toBe(false);
97 expect(wrapper.state().lastLine).toBeUndefined();
98 expect(wrapper.state().sourceLines).toHaveLength(0);
101 it('should not load sources on mount when the hotspot is not associated to any loc', async () => {
102 const hotspot = mockHotspot({
104 textRange: undefined,
107 const wrapper = shallowRender({ hotspot });
109 await waitAndUpdate(wrapper);
111 expect(getSources).not.toHaveBeenCalled();
112 expect(wrapper.state().lastLine).toBeUndefined();
113 expect(wrapper.state().sourceLines).toHaveLength(0);
116 it('should handle end-of-file on mount', async () => {
117 (getSources as jest.Mock).mockResolvedValueOnce(
118 range(5, 15).map((line) => mockSourceLine({ line }))
121 const hotspot = mockHotspot({
122 textRange: { startLine: 10, endLine: 11, startOffset: 0, endOffset: 12 },
125 const wrapper = shallowRender({ hotspot });
127 await waitAndUpdate(wrapper);
129 expect(getSources).toHaveBeenCalled();
130 expect(wrapper.state().lastLine).toBe(14);
131 expect(wrapper.state().sourceLines).toHaveLength(10);
134 describe('Expansion', () => {
136 (getSources as jest.Mock).mockResolvedValueOnce(
137 range(10, 32).map((line) => mockSourceLine({ line }))
141 const hotspot = mockHotspot({
142 project: mockHotspotComponent({ branch: branch.name, qualifier: ComponentQualifier.Project }),
143 textRange: { startLine: 20, endLine: 21, startOffset: 0, endOffset: 12 },
146 it('up should work', async () => {
147 (getSources as jest.Mock).mockResolvedValueOnce(
148 range(1, 10).map((line) => mockSourceLine({ line }))
151 const wrapper = shallowRender({ hotspot });
152 await waitAndUpdate(wrapper);
154 wrapper.find(HotspotSnippetContainerRenderer).props().onExpandBlock('up');
156 await waitAndUpdate(wrapper);
158 expect(getSources).toHaveBeenCalledWith(
159 expect.objectContaining({
163 expect(wrapper.state().sourceLines).toHaveLength(31);
166 it('down should work', async () => {
167 (getSources as jest.Mock).mockResolvedValueOnce(
168 // lastLine + expand + extra for EOF check + range end is excluded
170 range(32, 83).map((line) => mockSourceLine({ line }))
173 const wrapper = shallowRender({ hotspot });
174 await waitAndUpdate(wrapper);
176 wrapper.find(HotspotSnippetContainerRenderer).props().onExpandBlock('down');
178 await waitAndUpdate(wrapper);
180 expect(wrapper.state().lastLine).toBeUndefined();
181 expect(wrapper.state().sourceLines).toHaveLength(72);
184 it('down should work and handle EOF', async () => {
185 (getSources as jest.Mock).mockResolvedValueOnce(
186 // lastLine + expand + extra for EOF check + range end is excluded - 1 to trigger end-of-file
187 // 26 + 50 + 1 + 1 - 1
188 range(27, 77).map((line) => mockSourceLine({ line }))
191 const wrapper = shallowRender({ hotspot });
192 await waitAndUpdate(wrapper);
194 wrapper.find(HotspotSnippetContainerRenderer).props().onExpandBlock('down');
196 await waitAndUpdate(wrapper);
198 expect(wrapper.state().lastLine).toBe(76);
199 expect(wrapper.state().sourceLines).toHaveLength(72);
203 it('should handle symbol click', () => {
204 const wrapper = shallowRender();
205 const symbols = ['symbol'];
206 wrapper.find(HotspotSnippetContainerRenderer).props().onSymbolClick(symbols);
207 expect(wrapper.state().highlightedSymbols).toBe(symbols);
210 function shallowRender(props?: Partial<HotspotSnippetContainer['props']>) {
211 return shallow<HotspotSnippetContainer>(
212 <HotspotSnippetContainer
214 component={mockComponent()}
215 hotspot={mockHotspot()}
216 onCommentButtonClick={jest.fn()}
217 onLocationSelect={jest.fn()}