Bladeren bron

SONAR-16303 Add frontend IT for advance rule

tags/9.5.0.56709
Mathieu Suen 2 jaren geleden
bovenliggende
commit
aaf931d953
28 gewijzigde bestanden met toevoegingen van 475 en 157 verwijderingen
  1. 190
    11
      server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
  2. 1
    1
      server/sonar-web/src/main/js/api/mocks/SourceViewerServiceMock.ts
  3. 4
    4
      server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetails-test.tsx.snap
  4. 47
    2
      server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
  5. 3
    2
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx
  6. 23
    20
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx
  7. 3
    4
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/CrossComponentSourceViewerWrapper-test.tsx
  8. 3
    2
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx
  9. 6
    2
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/ComponentSourceSnippetGroupViewer-test.tsx.snap
  10. 25
    9
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap
  11. 21
    11
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/utils-test.ts
  12. 2
    1
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx
  13. 1
    1
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx
  14. 6
    2
      server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap
  15. 2
    1
      server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerBase-test.tsx
  16. 2
    1
      server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerCode-test.tsx
  17. 2
    2
      server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerHeader-test.tsx
  18. 11
    4
      server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerHeaderSlim-test.tsx
  19. 6
    2
      server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap
  20. 14
    14
      server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap
  21. 2
    1
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx
  22. 1
    1
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx
  23. 2
    1
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssueList-test.tsx
  24. 2
    1
      server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/issueLocations-test.ts
  25. 78
    0
      server/sonar-web/src/main/js/helpers/mocks/sources.ts
  26. 12
    57
      server/sonar-web/src/main/js/helpers/testMocks.ts
  27. 3
    0
      server/sonar-web/src/main/js/types/issues.ts
  28. 3
    0
      server/sonar-web/src/main/js/types/types.ts

+ 190
- 11
server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts Bestand weergeven

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import { cloneDeep } from 'lodash';
import { cloneDeep, keyBy, range, times } from 'lodash';
import {
mockSnippetsByComponent,
mockSourceLine,
mockSourceViewerFile
} from '../../helpers/mocks/sources';
import { RequestData } from '../../helpers/request'; import { RequestData } from '../../helpers/request';
import { getStandards } from '../../helpers/security-standard'; import { getStandards } from '../../helpers/security-standard';
import { mockPaging } from '../../helpers/testMocks';
import { RawFacet, RawIssuesResponse, ReferencedComponent } from '../../types/issues';
import { mockPaging, mockRawIssue, mockRuleDetails } from '../../helpers/testMocks';
import { BranchParameters } from '../../types/branch-like';
import { RawFacet, RawIssue, RawIssuesResponse, ReferencedComponent } from '../../types/issues';
import { Standards } from '../../types/security'; import { Standards } from '../../types/security';
import { searchIssues } from '../issues';
import {
Dict,
RuleActivation,
RuleDescriptionSections,
RuleDetails,
SnippetsByComponent,
SourceViewerFile
} from '../../types/types';
import { getComponentForSourceViewer, getSources } from '../components';
import { getIssueFlowSnippets, searchIssues } from '../issues';
import { getRuleDetails } from '../rules';


function mockReferenceComponent(override?: Partial<ReferencedComponent>) { function mockReferenceComponent(override?: Partial<ReferencedComponent>) {
return { return {
}; };
} }


interface IssueData {
issue: RawIssue;
snippets: Dict<SnippetsByComponent>;
}

export default class IssuesServiceMock { export default class IssuesServiceMock {
isAdmin = false; isAdmin = false;
standards?: Standards; standards?: Standards;
sourceViewerFiles: SourceViewerFile[];
list: IssueData[];


constructor() { constructor() {
(searchIssues as jest.Mock).mockImplementation(this.listHandler);
}

reset() {
this.setIsAdmin(false);
this.sourceViewerFiles = [
mockSourceViewerFile('file.foo', 'project'),
mockSourceViewerFile('file.bar', 'project')
];
this.list = [
{
issue: mockRawIssue(false, {
key: 'issue0',
component: 'project:file.foo',
message: 'Issue on file',
rule: 'simpleRuleId',
textRange: undefined,
line: undefined
}),
snippets: {}
},
{
issue: mockRawIssue(false, {
key: 'issue1',
component: 'project:file.foo',
message: 'Fix this',
rule: 'simpleRuleId',
textRange: {
startLine: 10,
endLine: 10,
startOffset: 0,
endOffset: 2
},
flows: [
{
locations: [
{
component: 'project:file.foo',
textRange: {
startLine: 1,
endLine: 1,
startOffset: 0,
endOffset: 1
}
}
]
},
{
locations: [
{
component: 'project:file.bar',
textRange: {
startLine: 20,
endLine: 20,
startOffset: 0,
endOffset: 1
}
}
]
}
]
}),
snippets: keyBy(
[
mockSnippetsByComponent(
'file.foo',
'project',
times(40, i => i + 1)
),
mockSnippetsByComponent(
'file.bar',
'project',
times(40, i => i + 1)
)
],
'component.key'
)
},
{
issue: mockRawIssue(false, {
key: 'issue2',
component: 'project:file.bar',
message: 'Fix that',
rule: 'advancedRuleId',
textRange: {
startLine: 25,
endLine: 25,
startOffset: 0,
endOffset: 1
}
}),
snippets: keyBy(
[
mockSnippetsByComponent(
'file.bar',
'project',
times(40, i => i + 20)
)
],
'component.key'
)
}
];
(searchIssues as jest.Mock).mockImplementation(this.handleSearchIssues);
(getRuleDetails as jest.Mock).mockImplementation(this.handleGetRuleDetails);
(getIssueFlowSnippets as jest.Mock).mockImplementation(this.handleGetIssueFlowSnippets);
(getSources as jest.Mock).mockImplementation(this.handleGetSources);
(getComponentForSourceViewer as jest.Mock).mockImplementation(
this.handleGetComponentForSourceViewer
);
} }


async getStandards(): Promise<Standards> { async getStandards(): Promise<Standards> {
this.isAdmin = isAdmin; this.isAdmin = isAdmin;
} }


listHandler = (query: RequestData): Promise<RawIssuesResponse> => {
handleGetSources = (data: { key: string; from?: number; to?: number } & BranchParameters) => {
return this.reply(range(data.from || 1, data.to || 10).map(line => mockSourceLine({ line })));
};

handleGetComponentForSourceViewer = (data: { component: string } & BranchParameters) => {
const file = this.sourceViewerFiles.find(f => f.key === data.component);
if (file === undefined) {
return Promise.reject({
errors: [{ msg: `No source file has been found for id ${data.component}` }]
});
}

return this.reply(file);
};

handleGetIssueFlowSnippets = (issueKey: string): Promise<Dict<SnippetsByComponent>> => {
const issue = this.list.find(i => i.issue.key === issueKey);
if (issue === undefined) {
return Promise.reject({ errors: [{ msg: `No issue has been found for id ${issueKey}` }] });
}
return this.reply(issue.snippets);
};

handleGetRuleDetails = (parameters: {
actives?: boolean;
key: string;
}): Promise<{ actives?: RuleActivation[]; rule: RuleDetails }> => {
if (parameters.key === 'advancedRuleId') {
return this.reply({
rule: mockRuleDetails({
key: parameters.key,
name: 'Advanced rule',
descriptionSections: [
{ key: RuleDescriptionSections.INTRODUCTION, content: '<h1>Into</h1>' },
{ key: RuleDescriptionSections.ROOT_CAUSE, content: '<h1>Because</h1>' },
{ key: RuleDescriptionSections.HOW_TO_FIX, content: '<h1>Fix with</h1>' },
{ key: RuleDescriptionSections.RESOURCES, content: '<h1>Link</h1>' }
]
})
});
}
return this.reply({
rule: mockRuleDetails({
key: parameters.key,
name: 'Simple rule',
htmlNote: '<h1>Note</h1>',
descriptionSections: [
{
key: RuleDescriptionSections.DEFAULT,
content: '<h1>Default</h1> Default description'
}
]
})
});
};

handleSearchIssues = (query: RequestData): Promise<RawIssuesResponse> => {
const facets = query.facets.split(',').map((name: string) => { const facets = query.facets.split(',').map((name: string) => {
if (name === 'owaspTop10-2021') { if (name === 'owaspTop10-2021') {
return this.owasp2021FacetList(); return this.owasp2021FacetList();
components: [mockReferenceComponent()], components: [mockReferenceComponent()],
effortTotal: 199629, effortTotal: 199629,
facets, facets,
issues: [],
issues: this.list.map(line => line.issue),
languages: [], languages: [],
paging: mockPaging() paging: mockPaging()
}); });

+ 1
- 1
server/sonar-web/src/main/js/api/mocks/SourceViewerServiceMock.ts Bestand weergeven

getDuplications, getDuplications,
getSources getSources
} from '../../api/components'; } from '../../api/components';
import { mockSourceLine } from '../../helpers/testMocks';
import { mockSourceLine } from '../../helpers/mocks/sources';
import { BranchParameters } from '../../types/branch-like'; import { BranchParameters } from '../../types/branch-like';
import { Dict } from '../../types/types'; import { Dict } from '../../types/types';



+ 4
- 4
server/sonar-web/src/main/js/apps/coding-rules/components/__tests__/__snapshots__/RuleDetails-test.tsx.snap Bestand weergeven

"defaultRemFnType": "CONSTANT_ISSUE", "defaultRemFnType": "CONSTANT_ISSUE",
"descriptionSections": Array [ "descriptionSections": Array [
Object { Object {
"content": "<b>Why<b/> Because",
"content": "<b>Why</b> Because",
"key": "root_cause", "key": "root_cause",
}, },
], ],
"defaultRemFnType": "CONSTANT_ISSUE", "defaultRemFnType": "CONSTANT_ISSUE",
"descriptionSections": Array [ "descriptionSections": Array [
Object { Object {
"content": "<b>Why<b/> Because",
"content": "<b>Why</b> Because",
"key": "root_cause", "key": "root_cause",
}, },
], ],
"defaultRemFnType": "CONSTANT_ISSUE", "defaultRemFnType": "CONSTANT_ISSUE",
"descriptionSections": Array [ "descriptionSections": Array [
Object { Object {
"content": "<b>Why<b/> Because",
"content": "<b>Why</b> Because",
"key": "root_cause", "key": "root_cause",
}, },
], ],
"defaultRemFnType": "CONSTANT_ISSUE", "defaultRemFnType": "CONSTANT_ISSUE",
"descriptionSections": Array [ "descriptionSections": Array [
Object { Object {
"content": "<b>Why<b/> Because",
"content": "<b>Why</b> Because",
"key": "root_cause", "key": "root_cause",
}, },
], ],

+ 47
- 2
server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx Bestand weergeven

import AppContainer from '../components/AppContainer'; import AppContainer from '../components/AppContainer';


jest.mock('../../../api/issues'); jest.mock('../../../api/issues');
jest.mock('../../../api/rules');
jest.mock('../../../api/components');


let handler: IssuesServiceMock; let handler: IssuesServiceMock;


beforeAll(() => {
beforeEach(() => {
window.scrollTo = jest.fn();
handler = new IssuesServiceMock(); handler = new IssuesServiceMock();
}); });


afterEach(() => handler.reset());
it('should open issue and navigate', async () => {
const user = userEvent.setup();
renderIssueApp();
expect(await screen.findByRole('region', { name: 'Fix that' })).toBeInTheDocument();
await user.click(screen.getByRole('region', { name: 'Fix that' }));
expect(screen.getByRole('heading', { level: 1, name: 'Fix that' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'advancedRuleId' })).toBeInTheDocument();

expect(screen.getByRole('button', { name: `issue.tabs.resources` })).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: `issue.tabs.resources` }));
expect(screen.getByRole('heading', { name: 'Link' })).toBeInTheDocument();

expect(screen.getByRole('button', { name: `issue.tabs.how` })).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: `issue.tabs.how` }));
expect(screen.getByRole('heading', { name: 'Fix with' })).toBeInTheDocument();

expect(screen.getByRole('button', { name: `issue.tabs.why` })).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: `issue.tabs.why` }));
expect(screen.getByRole('heading', { name: 'Because' })).toBeInTheDocument();

await user.keyboard('{ArrowUp}');

expect(screen.getByRole('heading', { level: 1, name: 'Fix this' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'simpleRuleId' })).toBeInTheDocument();

expect(screen.getByRole('button', { name: `issue.tabs.why` })).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: `issue.tabs.why` }));
expect(screen.getByRole('heading', { name: 'Default' })).toBeInTheDocument();

await user.keyboard('{ArrowUp}');

expect(screen.getByRole('heading', { level: 1, name: 'Issue on file' })).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'simpleRuleId' })).toBeInTheDocument();

expect(screen.getByRole('button', { name: `issue.tabs.code` })).toBeInTheDocument();
await user.click(screen.getByRole('button', { name: `issue.tabs.code` }));
expect(screen.getByRole('region', { name: 'Issue on file' })).toBeInTheDocument();
expect(
screen.getByRole('row', {
name: '2 source_viewer.tooltip.covered import java.util. ArrayList ;'
})
).toBeInTheDocument();
});


it('should support OWASP Top 10 version 2021', async () => { it('should support OWASP Top 10 version 2021', async () => {
const user = userEvent.setup(); const user = userEvent.setup();

+ 3
- 2
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/CrossComponentSourceViewerWrapper.tsx Bestand weergeven

import { getBranchLikeQuery } from '../../../helpers/branch-like'; import { getBranchLikeQuery } from '../../../helpers/branch-like';
import { throwGlobalError } from '../../../helpers/error'; import { throwGlobalError } from '../../../helpers/error';
import { translate } from '../../../helpers/l10n'; import { translate } from '../../../helpers/l10n';
import { HttpStatus } from '../../../helpers/request';
import { BranchLike } from '../../../types/branch-like'; import { BranchLike } from '../../../types/branch-like';
import { isFile } from '../../../types/component'; import { isFile } from '../../../types/component';
import { import {
} }
} catch (response) { } catch (response) {
const rsp = response as Response; const rsp = response as Response;
if (rsp.status !== 403) {
if (rsp.status !== HttpStatus.Forbidden) {
throwGlobalError(response); throwGlobalError(response);
} }
if (this.mounted) { if (this.mounted) {
this.setState({ loading: false, notAccessible: rsp.status === 403 });
this.setState({ loading: false, notAccessible: rsp.status === HttpStatus.Forbidden });
} }
} }
} }

+ 23
- 20
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/ComponentSourceSnippetGroupViewer-test.tsx Bestand weergeven

import Issue from '../../../../components/issue/Issue'; import Issue from '../../../../components/issue/Issue';
import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like'; import { mockBranch, mockMainBranch } from '../../../../helpers/mocks/branch-like';
import { import {
mockFlowLocation,
mockIssue,
mockSnippetsByComponent, mockSnippetsByComponent,
mockSourceLine, mockSourceLine,
mockSourceViewerFile mockSourceViewerFile
} from '../../../../helpers/testMocks';
} from '../../../../helpers/mocks/sources';
import { mockFlowLocation, mockIssue } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils'; import { waitAndUpdate } from '../../../../helpers/testUtils';
import { SnippetGroup } from '../../../../types/types'; import { SnippetGroup } from '../../../../types/types';
import ComponentSourceSnippetGroupViewer from '../ComponentSourceSnippetGroupViewer'; import ComponentSourceSnippetGroupViewer from '../ComponentSourceSnippetGroupViewer';
it('should render correctly with secondary locations', () => { it('should render correctly with secondary locations', () => {
// issue with secondary locations but no flows // issue with secondary locations but no flows
const issue = mockIssue(true, { const issue = mockIssue(true, {
component: 'project:main.js',
flows: [], flows: [],
textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 } textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 }
}); });
textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 } textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
}) })
], ],
...mockSnippetsByComponent(issue.component, [
...mockSnippetsByComponent('main.js', 'project', [
...range(2, 17), ...range(2, 17),
...range(29, 39), ...range(29, 39),
...range(69, 79) ...range(69, 79)
it('should render correctly with flows', () => { it('should render correctly with flows', () => {
// issue with flows but no secondary locations // issue with flows but no secondary locations
const issue = mockIssue(true, { const issue = mockIssue(true, {
component: 'project:main.js',
secondaryLocations: [], secondaryLocations: [],
textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 } textRange: { startLine: 7, endLine: 7, startOffset: 5, endOffset: 10 }
}); });
textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 } textRange: { startLine: 74, endLine: 74, startOffset: 0, endOffset: 0 }
}) })
], ],
...mockSnippetsByComponent(issue.component, [
...mockSnippetsByComponent('main.js', 'project', [
...range(2, 17), ...range(2, 17),
...range(29, 39), ...range(29, 39),
...range(69, 79) ...range(69, 79)
it('should render file-level issue correctly', () => { it('should render file-level issue correctly', () => {
// issue with secondary locations and no primary location // issue with secondary locations and no primary location
const issue = mockIssue(true, { const issue = mockIssue(true, {
component: 'project:main.js',
flows: [], flows: [],
textRange: undefined textRange: undefined
}); });
textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 } textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
}) })
], ],
...mockSnippetsByComponent(issue.component, range(29, 39))
...mockSnippetsByComponent('main.js', 'project', range(29, 39))
} }
}); });




it('should expand block', async () => { it('should expand block', async () => {
(getSources as jest.Mock).mockResolvedValueOnce( (getSources as jest.Mock).mockResolvedValueOnce(
Object.values(mockSnippetsByComponent('a', range(6, 59)).sources)
Object.values(mockSnippetsByComponent('a', 'project', range(6, 59)).sources)
); );
const issue = mockIssue(true, { const issue = mockIssue(true, {
textRange: { startLine: 74, endLine: 74, startOffset: 5, endOffset: 10 } textRange: { startLine: 74, endLine: 74, startOffset: 5, endOffset: 10 }
textRange: { startLine: 107, endLine: 107, startOffset: 0, endOffset: 0 } textRange: { startLine: 107, endLine: 107, startOffset: 0, endOffset: 0 }
}) })
], ],
...mockSnippetsByComponent('a', [...range(69, 83), ...range(102, 112)])
...mockSnippetsByComponent('a', 'project', [...range(69, 83), ...range(102, 112)])
}; };


const wrapper = shallowRender({ issue, snippetGroup }); const wrapper = shallowRender({ issue, snippetGroup });
wrapper.instance().expandBlock(0, 'up'); wrapper.instance().expandBlock(0, 'up');
await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);


expect(getSources).toHaveBeenCalledWith({ from: 9, key: 'a', to: 68 });
expect(getSources).toHaveBeenCalledWith({ from: 9, key: 'project:a', to: 68 });
expect(wrapper.state('snippets')).toHaveLength(2); expect(wrapper.state('snippets')).toHaveLength(2);
expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 19, end: 83 }); expect(wrapper.state('snippets')[0]).toEqual({ index: 0, start: 19, end: 83 });
expect(Object.keys(wrapper.state('additionalLines'))).toHaveLength(53); expect(Object.keys(wrapper.state('additionalLines'))).toHaveLength(53);


it('should expand full component', async () => { it('should expand full component', async () => {
(getSources as jest.Mock).mockResolvedValueOnce( (getSources as jest.Mock).mockResolvedValueOnce(
Object.values(mockSnippetsByComponent('a', times(14)).sources)
Object.values(mockSnippetsByComponent('a', 'project', times(14)).sources)
); );
const snippetGroup: SnippetGroup = { const snippetGroup: SnippetGroup = {
locations: [ locations: [
textRange: { startLine: 12, endLine: 12, startOffset: 0, endOffset: 0 } textRange: { startLine: 12, endLine: 12, startOffset: 0, endOffset: 0 }
}) })
], ],
...mockSnippetsByComponent('a', [1, 2, 3, 4, 5, 10, 11, 12, 13, 14])
...mockSnippetsByComponent('a', 'project', [1, 2, 3, 4, 5, 10, 11, 12, 13, 14])
}; };


const wrapper = shallowRender({ snippetGroup }); const wrapper = shallowRender({ snippetGroup });
wrapper.instance().expandComponent(); wrapper.instance().expandComponent();
await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);


expect(getSources).toHaveBeenCalledWith({ key: 'a' });
expect(getSources).toHaveBeenCalledWith({ key: 'project:a' });
expect(wrapper.state('snippets')).toHaveLength(1); expect(wrapper.state('snippets')).toHaveLength(1);
expect(wrapper.state('snippets')[0]).toEqual({ index: -1, start: 0, end: 13 }); expect(wrapper.state('snippets')[0]).toEqual({ index: -1, start: 0, end: 13 });
}); });
it('should get the right branch when expanding', async () => { it('should get the right branch when expanding', async () => {
(getSources as jest.Mock).mockResolvedValueOnce( (getSources as jest.Mock).mockResolvedValueOnce(
Object.values( Object.values(
mockSnippetsByComponent('a', [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]).sources
mockSnippetsByComponent('a', 'project', [5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17])
.sources
) )
); );
const snippetGroup: SnippetGroup = { const snippetGroup: SnippetGroup = {
locations: [mockFlowLocation()], locations: [mockFlowLocation()],
...mockSnippetsByComponent('a', [1, 2, 3, 4, 5, 6, 7])
...mockSnippetsByComponent('a', 'project', [1, 2, 3, 4, 5, 6, 7])
}; };


const wrapper = shallowRender({ const wrapper = shallowRender({
wrapper.instance().expandBlock(0, 'down'); wrapper.instance().expandBlock(0, 'down');
await waitAndUpdate(wrapper); await waitAndUpdate(wrapper);


expect(getSources).toHaveBeenCalledWith({ branch: 'asdf', from: 8, key: 'a', to: 67 });
expect(getSources).toHaveBeenCalledWith({ branch: 'asdf', from: 8, key: 'project:a', to: 67 });
}); });


it('should handle correctly open/close issue', () => { it('should handle correctly open/close issue', () => {
const snippetGroup: SnippetGroup = { const snippetGroup: SnippetGroup = {
locations: [ locations: [
mockFlowLocation({ mockFlowLocation({
component: 'a',
component: 'my-project:foo/bar.ts',
textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 } textRange: { startLine: 34, endLine: 34, startOffset: 0, endOffset: 0 }
}), }),
mockFlowLocation({ mockFlowLocation({
component: 'a',
component: 'my-project:foo/bar.ts',
textRange: { startLine: 54, endLine: 54, startOffset: 0, endOffset: 0 } textRange: { startLine: 54, endLine: 54, startOffset: 0, endOffset: 0 }
}) })
], ],
...mockSnippetsByComponent('a', [32, 33, 34, 35, 36, 52, 53, 54, 55, 56])
...mockSnippetsByComponent('foo/bar.ts', 'my-project', [32, 33, 34, 35, 36, 52, 53, 54, 55, 56])
}; };
const loadDuplications = jest.fn(); const loadDuplications = jest.fn();
const renderDuplicationPopup = jest.fn(); const renderDuplicationPopup = jest.fn();
.find('SnippetViewer') .find('SnippetViewer')
.first() .first()
.prop<Function>('loadDuplications')(line); .prop<Function>('loadDuplications')(line);
expect(loadDuplications).toHaveBeenCalledWith('a', line);
expect(loadDuplications).toHaveBeenCalledWith('my-project:foo/bar.ts', line);


wrapper wrapper
.find('SnippetViewer') .find('SnippetViewer')
.first() .first()
.prop<Function>('renderDuplicationPopup')(1, 13); .prop<Function>('renderDuplicationPopup')(1, 13);
expect(renderDuplicationPopup).toHaveBeenCalledWith( expect(renderDuplicationPopup).toHaveBeenCalledWith(
mockSourceViewerFile({ key: 'a', path: 'a' }),
mockSourceViewerFile('foo/bar.ts', 'my-project'),
1, 1,
13 13
); );

+ 3
- 4
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/CrossComponentSourceViewerWrapper-test.tsx Bestand weergeven

import { getDuplications } from '../../../../api/components'; import { getDuplications } from '../../../../api/components';
import { getIssueFlowSnippets } from '../../../../api/issues'; import { getIssueFlowSnippets } from '../../../../api/issues';
import { import {
mockFlowLocation,
mockIssue,
mockSnippetsByComponent, mockSnippetsByComponent,
mockSourceLine, mockSourceLine,
mockSourceViewerFile mockSourceViewerFile
} from '../../../../helpers/testMocks';
} from '../../../../helpers/mocks/sources';
import { mockFlowLocation, mockIssue } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils'; import { waitAndUpdate } from '../../../../helpers/testUtils';
import CrossComponentSourceViewerWrapper from '../CrossComponentSourceViewerWrapper'; import CrossComponentSourceViewerWrapper from '../CrossComponentSourceViewerWrapper';


jest.mock('../../../../api/issues', () => { jest.mock('../../../../api/issues', () => {
const { mockSnippetsByComponent } = jest.requireActual('../../../../helpers/testMocks');
const { mockSnippetsByComponent } = jest.requireActual('../../../../helpers/mocks/sources');
return { return {
getIssueFlowSnippets: jest.fn().mockResolvedValue({ 'main.js': mockSnippetsByComponent() }) getIssueFlowSnippets: jest.fn().mockResolvedValue({ 'main.js': mockSnippetsByComponent() })
}; };

+ 3
- 2
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/SnippetViewer-test.tsx Bestand weergeven

import { mount, shallow } from 'enzyme'; import { mount, shallow } from 'enzyme';
import { range } from 'lodash'; import { range } from 'lodash';
import * as React from 'react'; import * as React from 'react';
import { mockSourceLine, mockSourceViewerFile } from '../../../../helpers/mocks/sources';
import { scrollHorizontally } from '../../../../helpers/scrolling'; import { scrollHorizontally } from '../../../../helpers/scrolling';
import { mockIssue, mockSourceLine, mockSourceViewerFile } from '../../../../helpers/testMocks';
import { mockIssue } from '../../../../helpers/testMocks';
import SnippetViewer from '../SnippetViewer'; import SnippetViewer from '../SnippetViewer';


jest.mock('../../../../helpers/scrolling', () => ({ jest.mock('../../../../helpers/scrolling', () => ({
}); });


it('should render correctly when at the bottom of the file', () => { it('should render correctly when at the bottom of the file', () => {
const component = mockSourceViewerFile({ measures: { lines: '14' } });
const component = mockSourceViewerFile('foo/bar.ts', 'my-project', { measures: { lines: '14' } });
const snippet = range(10, 14).map(line => mockSourceLine({ line })); const snippet = range(10, 14).map(line => mockSourceLine({ line }));
const wrapper = shallowRender({ const wrapper = shallowRender({
component, component,

+ 6
- 2
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/ComponentSourceSnippetGroupViewer-test.tsx.snap Bestand weergeven

onExpand={[Function]} onExpand={[Function]}
sourceViewerFile={ sourceViewerFile={
Object { Object {
"key": "foo",
"canMarkAsFavorite": true,
"fav": false,
"key": "project:foo/bar.ts",
"longName": "foo/bar.ts",
"measures": Object { "measures": Object {
"coverage": "85.2", "coverage": "85.2",
"duplicationDensity": "1.0", "duplicationDensity": "1.0",
"issues": "12", "issues": "12",
"lines": "56", "lines": "56",
}, },
"name": "foo/bar.ts",
"path": "foo/bar.ts", "path": "foo/bar.ts",
"project": "my-project",
"project": "project",
"projectName": "MyProject", "projectName": "MyProject",
"q": "FIL", "q": "FIL",
"uuid": "foo-bar", "uuid": "foo-bar",

+ 25
- 9
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewerWrapper-test.tsx.snap Bestand weergeven

Object { Object {
"branchLike": undefined, "branchLike": undefined,
"file": Object { "file": Object {
"key": "main.js",
"canMarkAsFavorite": true,
"fav": false,
"key": "project:main.js",
"longName": "main.js",
"measures": Object { "measures": Object {
"coverage": "85.2", "coverage": "85.2",
"duplicationDensity": "1.0", "duplicationDensity": "1.0",
"issues": "12", "issues": "12",
"lines": "56", "lines": "56",
}, },
"name": "main.js",
"path": "main.js", "path": "main.js",
"project": "my-project",
"project": "project",
"projectName": "MyProject", "projectName": "MyProject",
"q": "FIL", "q": "FIL",
"uuid": "foo-bar", "uuid": "foo-bar",
> >
<ComponentSourceSnippetGroupViewer <ComponentSourceSnippetGroupViewer
duplicationsByLine={Object {}} duplicationsByLine={Object {}}
isLastOccurenceOfPrimaryComponent={true}
isLastOccurenceOfPrimaryComponent={false}
issue={ issue={
Object { Object {
"actions": Array [], "actions": Array [],
snippetGroup={ snippetGroup={
Object { Object {
"component": Object { "component": Object {
"key": "main.js",
"canMarkAsFavorite": true,
"fav": false,
"key": "project:main.js",
"longName": "main.js",
"measures": Object { "measures": Object {
"coverage": "85.2", "coverage": "85.2",
"duplicationDensity": "1.0", "duplicationDensity": "1.0",
"issues": "12", "issues": "12",
"lines": "56", "lines": "56",
}, },
"name": "main.js",
"path": "main.js", "path": "main.js",
"project": "my-project",
"project": "project",
"projectName": "MyProject", "projectName": "MyProject",
"q": "FIL", "q": "FIL",
"uuid": "foo-bar", "uuid": "foo-bar",
Object { Object {
"branchLike": undefined, "branchLike": undefined,
"file": Object { "file": Object {
"key": "main.js",
"canMarkAsFavorite": true,
"fav": false,
"key": "project:main.js",
"longName": "main.js",
"measures": Object { "measures": Object {
"coverage": "85.2", "coverage": "85.2",
"duplicationDensity": "1.0", "duplicationDensity": "1.0",
"issues": "12", "issues": "12",
"lines": "56", "lines": "56",
}, },
"name": "main.js",
"path": "main.js", "path": "main.js",
"project": "my-project",
"project": "project",
"projectName": "MyProject", "projectName": "MyProject",
"q": "FIL", "q": "FIL",
"uuid": "foo-bar", "uuid": "foo-bar",
snippetGroup={ snippetGroup={
Object { Object {
"component": Object { "component": Object {
"key": "main.js",
"canMarkAsFavorite": true,
"fav": false,
"key": "project:main.js",
"longName": "main.js",
"measures": Object { "measures": Object {
"coverage": "85.2", "coverage": "85.2",
"duplicationDensity": "1.0", "duplicationDensity": "1.0",
"issues": "12", "issues": "12",
"lines": "56", "lines": "56",
}, },
"name": "main.js",
"path": "main.js", "path": "main.js",
"project": "my-project",
"project": "project",
"projectName": "MyProject", "projectName": "MyProject",
"q": "FIL", "q": "FIL",
"uuid": "foo-bar", "uuid": "foo-bar",

+ 21
- 11
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/utils-test.ts Bestand weergeven

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import {
mockFlowLocation,
mockIssue,
mockSnippetsByComponent
} from '../../../../helpers/testMocks';
import { mockSnippetsByComponent } from '../../../../helpers/mocks/sources';
import { mockFlowLocation, mockIssue } from '../../../../helpers/testMocks';
import { createSnippets, expandSnippet, groupLocationsByComponent } from '../utils'; import { createSnippets, expandSnippet, groupLocationsByComponent } from '../utils';


describe('groupLocationsByComponent', () => { describe('groupLocationsByComponent', () => {
textRange: { startLine: 24, startOffset: 1, endLine: 24, endOffset: 2 } textRange: { startLine: 24, startOffset: 1, endLine: 24, endOffset: 2 }
}) })
], ],
{ 'main.js': mockSnippetsByComponent('main.js', [14, 15, 16, 17, 18, 22, 23, 24, 25, 26]) }
{
'main.js': mockSnippetsByComponent('main.js', 'project', [
14,
15,
16,
17,
18,
22,
23,
24,
25,
26
])
}
); );


expect(results).toHaveLength(1); expect(results).toHaveLength(1);
}) })
], ],
{ {
'A.js': mockSnippetsByComponent('A.js', [13, 14, 15, 16, 17, 18]),
'B.js': mockSnippetsByComponent('B.js', [14, 15, 16, 17, 18])
'A.js': mockSnippetsByComponent('A.js', 'project', [13, 14, 15, 16, 17, 18]),
'B.js': mockSnippetsByComponent('B.js', 'project', [14, 15, 16, 17, 18])
} }
); );


expect(results).toHaveLength(3); expect(results).toHaveLength(3);
expect(results[0].component.key).toBe('A.js');
expect(results[1].component.key).toBe('B.js');
expect(results[2].component.key).toBe('A.js');
expect(results[0].component.key).toBe('project:A.js');
expect(results[1].component.key).toBe('project:B.js');
expect(results[2].component.key).toBe('project:A.js');
expect(results[0].locations).toHaveLength(1); expect(results[0].locations).toHaveLength(1);
expect(results[1].locations).toHaveLength(1); expect(results[1].locations).toHaveLength(1);
expect(results[2].locations).toHaveLength(1); expect(results[2].locations).toHaveLength(1);

+ 2
- 1
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainer-test.tsx Bestand weergeven

import { mockBranch } from '../../../../helpers/mocks/branch-like'; import { mockBranch } from '../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../helpers/mocks/component'; import { mockComponent } from '../../../../helpers/mocks/component';
import { mockHotspot, mockHotspotComponent } from '../../../../helpers/mocks/security-hotspots'; import { mockHotspot, mockHotspotComponent } from '../../../../helpers/mocks/security-hotspots';
import { mockFlowLocation, mockSourceLine } from '../../../../helpers/testMocks';
import { mockSourceLine } from '../../../../helpers/mocks/sources';
import { mockFlowLocation } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils'; import { waitAndUpdate } from '../../../../helpers/testUtils';
import { ComponentQualifier } from '../../../../types/component'; import { ComponentQualifier } from '../../../../types/component';
import HotspotSnippetContainer from '../HotspotSnippetContainer'; import HotspotSnippetContainer from '../HotspotSnippetContainer';

+ 1
- 1
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/HotspotSnippetContainerRenderer-test.tsx Bestand weergeven

import { mockMainBranch } from '../../../../helpers/mocks/branch-like'; import { mockMainBranch } from '../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../helpers/mocks/component'; import { mockComponent } from '../../../../helpers/mocks/component';
import { mockHotspot } from '../../../../helpers/mocks/security-hotspots'; import { mockHotspot } from '../../../../helpers/mocks/security-hotspots';
import { mockSourceLine, mockSourceViewerFile } from '../../../../helpers/mocks/sources';
import { scrollToElement } from '../../../../helpers/scrolling'; import { scrollToElement } from '../../../../helpers/scrolling';
import { mockSourceLine, mockSourceViewerFile } from '../../../../helpers/testMocks';
import SnippetViewer from '../../../issues/crossComponentSourceViewer/SnippetViewer'; import SnippetViewer from '../../../issues/crossComponentSourceViewer/SnippetViewer';
import HotspotSnippetContainerRenderer, { import HotspotSnippetContainerRenderer, {
animateExpansion, animateExpansion,

+ 6
- 2
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotSnippetContainerRenderer-test.tsx.snap Bestand weergeven

<SnippetViewer <SnippetViewer
component={ component={
Object { Object {
"key": "foo",
"canMarkAsFavorite": true,
"fav": false,
"key": "project:foo/bar.ts",
"longName": "foo/bar.ts",
"measures": Object { "measures": Object {
"coverage": "85.2", "coverage": "85.2",
"duplicationDensity": "1.0", "duplicationDensity": "1.0",
"issues": "12", "issues": "12",
"lines": "56", "lines": "56",
}, },
"name": "foo/bar.ts",
"path": "foo/bar.ts", "path": "foo/bar.ts",
"project": "my-project",
"project": "project",
"projectName": "MyProject", "projectName": "MyProject",
"q": "FIL", "q": "FIL",
"uuid": "foo-bar", "uuid": "foo-bar",

+ 2
- 1
server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerBase-test.tsx Bestand weergeven

import * as React from 'react'; import * as React from 'react';
import { getComponentData, getComponentForSourceViewer, getSources } from '../../../api/components'; import { getComponentData, getComponentForSourceViewer, getSources } from '../../../api/components';
import { mockMainBranch } from '../../../helpers/mocks/branch-like'; import { mockMainBranch } from '../../../helpers/mocks/branch-like';
import { mockIssue, mockSourceLine, mockSourceViewerFile } from '../../../helpers/testMocks';
import { mockSourceLine, mockSourceViewerFile } from '../../../helpers/mocks/sources';
import { mockIssue } from '../../../helpers/testMocks';
import { waitAndUpdate } from '../../../helpers/testUtils'; import { waitAndUpdate } from '../../../helpers/testUtils';
import defaultLoadIssues from '../helpers/loadIssues'; import defaultLoadIssues from '../helpers/loadIssues';
import SourceViewerBase from '../SourceViewerBase'; import SourceViewerBase from '../SourceViewerBase';

+ 2
- 1
server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerCode-test.tsx Bestand weergeven

import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { mockBranch } from '../../../helpers/mocks/branch-like'; import { mockBranch } from '../../../helpers/mocks/branch-like';
import { mockIssue, mockSourceLine } from '../../../helpers/testMocks';
import { mockSourceLine } from '../../../helpers/mocks/sources';
import { mockIssue } from '../../../helpers/testMocks';
import { MetricKey } from '../../../types/metrics'; import { MetricKey } from '../../../types/metrics';
import Line from '../components/Line'; import Line from '../components/Line';
import SourceViewerCode from '../SourceViewerCode'; import SourceViewerCode from '../SourceViewerCode';

+ 2
- 2
server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerHeader-test.tsx Bestand weergeven

import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { mockMainBranch } from '../../../helpers/mocks/branch-like'; import { mockMainBranch } from '../../../helpers/mocks/branch-like';
import { mockSourceViewerFile } from '../../../helpers/testMocks';
import { mockSourceViewerFile } from '../../../helpers/mocks/sources';
import { ComponentQualifier } from '../../../types/component'; import { ComponentQualifier } from '../../../types/component';
import { MetricKey } from '../../../types/metrics'; import { MetricKey } from '../../../types/metrics';
import { Measure } from '../../../types/types'; import { Measure } from '../../../types/types';
expect( expect(
shallowRender({ shallowRender({
showMeasures: true, showMeasures: true,
sourceViewerFile: mockSourceViewerFile({
sourceViewerFile: mockSourceViewerFile('foo/bar.ts', 'my-project', {
q: ComponentQualifier.TestFile, q: ComponentQualifier.TestFile,
measures: { tests: '12' } measures: { tests: '12' }
}) })

+ 11
- 4
server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewerHeaderSlim-test.tsx Bestand weergeven

import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { mockMainBranch } from '../../../helpers/mocks/branch-like'; import { mockMainBranch } from '../../../helpers/mocks/branch-like';
import { mockSourceViewerFile } from '../../../helpers/testMocks';
import { mockSourceViewerFile } from '../../../helpers/mocks/sources';
import { ComponentQualifier } from '../../../types/component'; import { ComponentQualifier } from '../../../types/component';
import SourceViewerHeaderSlim, { Props } from '../SourceViewerHeaderSlim'; import SourceViewerHeaderSlim, { Props } from '../SourceViewerHeaderSlim';


expect(shallowRender({ linkToProject: false })).toMatchSnapshot('no link to project'); expect(shallowRender({ linkToProject: false })).toMatchSnapshot('no link to project');
expect(shallowRender({ displayProjectName: false })).toMatchSnapshot('no project name'); expect(shallowRender({ displayProjectName: false })).toMatchSnapshot('no project name');
expect( expect(
shallowRender({ sourceViewerFile: mockSourceViewerFile({ q: ComponentQualifier.Project }) })
shallowRender({
sourceViewerFile: mockSourceViewerFile('foo/bar.ts', 'my-project', {
q: ComponentQualifier.Project
})
})
).toMatchSnapshot('project root'); ).toMatchSnapshot('project root');
}); });


it('should render correctly for subproject', () => { it('should render correctly for subproject', () => {
expect( expect(
shallowRender({ shallowRender({
sourceViewerFile: mockSourceViewerFile({ subProject: 'foo', subProjectName: 'Foo' })
sourceViewerFile: mockSourceViewerFile('foo/bar.ts', 'my-project', {
subProject: 'foo',
subProjectName: 'Foo'
})
}) })
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
branchLike={mockMainBranch()} branchLike={mockMainBranch()}
expandable={true} expandable={true}
onExpand={jest.fn()} onExpand={jest.fn()}
sourceViewerFile={mockSourceViewerFile()}
sourceViewerFile={mockSourceViewerFile('foo/bar.ts', 'my-project')}
{...props} {...props}
/> />
); );

+ 6
- 2
server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerBase-test.tsx.snap Bestand weergeven

"name": "master", "name": "master",
}, },
"file": Object { "file": Object {
"key": "foo",
"canMarkAsFavorite": true,
"fav": false,
"key": "project:foo/bar.ts",
"leakPeriodDate": "2018-06-20T17:12:19+0200", "leakPeriodDate": "2018-06-20T17:12:19+0200",
"longName": "foo/bar.ts",
"measures": Object { "measures": Object {
"coverage": "85.2", "coverage": "85.2",
"duplicationDensity": "1.0", "duplicationDensity": "1.0",
"issues": "12", "issues": "12",
"lines": "56", "lines": "56",
}, },
"name": "foo/bar.ts",
"path": "foo/bar.ts", "path": "foo/bar.ts",
"project": "my-project",
"project": "project",
"projectName": "MyProject", "projectName": "MyProject",
"q": "FIL", "q": "FIL",
"uuid": "foo-bar", "uuid": "foo-bar",

+ 14
- 14
server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewerHeader-test.tsx.snap Bestand weergeven

> >
<a <a
className="link-with-icon" className="link-with-icon"
href="/dashboard?id=my-project"
href="/dashboard?id=project"
> >
<QualifierIcon <QualifierIcon
qualifier="TRK" qualifier="TRK"
Object { Object {
"pathname": "/code", "pathname": "/code",
"query": Object { "query": Object {
"id": "my-project",
"id": "project",
"line": undefined, "line": undefined,
"selected": "foo",
"selected": "project:foo/bar.ts",
}, },
} }
} }
<li> <li>
<a <a
className="js-raw-source" className="js-raw-source"
href="/api/sources/raw?key=foo"
href="/api/sources/raw?key=project%3Afoo%2Fbar.ts"
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> >
"query": Object { "query": Object {
"id": "my-project", "id": "my-project",
"line": undefined, "line": undefined,
"selected": "foo",
"selected": "my-project:foo/bar.ts",
}, },
} }
} }
<li> <li>
<a <a
className="js-raw-source" className="js-raw-source"
href="/api/sources/raw?key=foo"
href="/api/sources/raw?key=my-project%3Afoo%2Fbar.ts"
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> >
> >
<a <a
className="link-with-icon" className="link-with-icon"
href="/dashboard?id=my-project"
href="/dashboard?id=project"
> >
<QualifierIcon <QualifierIcon
qualifier="TRK" qualifier="TRK"
"pathname": "/project/issues", "pathname": "/project/issues",
"query": Object { "query": Object {
"files": "foo/bar.ts", "files": "foo/bar.ts",
"id": "my-project",
"id": "project",
"resolved": "false", "resolved": "false",
"types": "BUG", "types": "BUG",
}, },
"pathname": "/project/issues", "pathname": "/project/issues",
"query": Object { "query": Object {
"files": "foo/bar.ts", "files": "foo/bar.ts",
"id": "my-project",
"id": "project",
"resolved": "false", "resolved": "false",
"types": "VULNERABILITY", "types": "VULNERABILITY",
}, },
"pathname": "/project/issues", "pathname": "/project/issues",
"query": Object { "query": Object {
"files": "foo/bar.ts", "files": "foo/bar.ts",
"id": "my-project",
"id": "project",
"resolved": "false", "resolved": "false",
"types": "CODE_SMELL", "types": "CODE_SMELL",
}, },
"pathname": "/project/issues", "pathname": "/project/issues",
"query": Object { "query": Object {
"files": "foo/bar.ts", "files": "foo/bar.ts",
"id": "my-project",
"id": "project",
"resolved": "false", "resolved": "false",
"types": "SECURITY_HOTSPOT", "types": "SECURITY_HOTSPOT",
}, },
Object { Object {
"pathname": "/code", "pathname": "/code",
"query": Object { "query": Object {
"id": "my-project",
"id": "project",
"line": undefined, "line": undefined,
"selected": "foo",
"selected": "project:foo/bar.ts",
}, },
} }
} }
<li> <li>
<a <a
className="js-raw-source" className="js-raw-source"
href="/api/sources/raw?key=foo"
href="/api/sources/raw?key=project%3Afoo%2Fbar.ts"
rel="noopener noreferrer" rel="noopener noreferrer"
target="_blank" target="_blank"
> >

+ 2
- 1
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/Line-test.tsx Bestand weergeven

*/ */
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { mockIssue, mockSourceLine } from '../../../../helpers/testMocks';
import { mockSourceLine } from '../../../../helpers/mocks/sources';
import { mockIssue } from '../../../../helpers/testMocks';
import Line from '../Line'; import Line from '../Line';


it('should render correctly for last, new, and highlighted lines', () => { it('should render correctly for last, new, and highlighted lines', () => {

+ 1
- 1
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineCode-test.tsx Bestand weergeven

*/ */
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { mockSourceLine } from '../../../../helpers/testMocks';
import { mockSourceLine } from '../../../../helpers/mocks/sources';
import LineCode from '../LineCode'; import LineCode from '../LineCode';


it('render code', () => { it('render code', () => {

+ 2
- 1
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssueList-test.tsx Bestand weergeven

import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import * as React from 'react'; import * as React from 'react';
import { mockBranch } from '../../../../helpers/mocks/branch-like'; import { mockBranch } from '../../../../helpers/mocks/branch-like';
import { mockIssue, mockSourceLine } from '../../../../helpers/testMocks';
import { mockSourceLine } from '../../../../helpers/mocks/sources';
import { mockIssue } from '../../../../helpers/testMocks';
import LineIssuesList, { LineIssuesListProps } from '../LineIssuesList'; import LineIssuesList, { LineIssuesListProps } from '../LineIssuesList';


it('should render issues', () => { it('should render issues', () => {

+ 2
- 1
server/sonar-web/src/main/js/components/SourceViewer/helpers/__tests__/issueLocations-test.ts Bestand weergeven

* along with this program; if not, write to the Free Software Foundation, * along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import { mockFlowLocation, mockSourceLine } from '../../../../helpers/testMocks';
import { mockSourceLine } from '../../../../helpers/mocks/sources';
import { mockFlowLocation } from '../../../../helpers/testMocks';
import { getLinearLocations, getSecondaryIssueLocationsForLine } from '../issueLocations'; import { getLinearLocations, getSecondaryIssueLocationsForLine } from '../issueLocations';


describe('getSecondaryIssueLocationsForLine', () => { describe('getSecondaryIssueLocationsForLine', () => {

+ 78
- 0
server/sonar-web/src/main/js/helpers/mocks/sources.ts Bestand weergeven

/*
* 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 { ComponentQualifier } from '../../types/component';
import { SnippetsByComponent, SourceLine, SourceViewerFile } from '../../types/types';

export function mockSourceViewerFile(
name = 'foo/bar.ts',
project = 'project',
override?: Partial<SourceViewerFile>
): SourceViewerFile {
return {
measures: {
coverage: '85.2',
duplicationDensity: '1.0',
issues: '12',
lines: '56'
},
project,
projectName: 'MyProject',
q: ComponentQualifier.File,
uuid: 'foo-bar',
key: `${project}:${name}`,
path: name,
name,
longName: name,
fav: false,
canMarkAsFavorite: true,
...override
};
}

export function mockSourceLine(overrides: Partial<SourceLine> = {}): SourceLine {
return {
line: 16,
code: '<span class="k">import</span> java.util.<span class="sym-9 sym">ArrayList</span>;',
coverageStatus: 'covered',
coveredConditions: 2,
scmRevision: '80f564becc0c0a1c9abaa006eca83a4fd278c3f0',
scmAuthor: 'simon.brandhof@sonarsource.com',
scmDate: '2018-12-11T10:48:39+0100',
duplicated: false,
isNew: true,
...overrides
};
}

export function mockSnippetsByComponent(
file = 'main.js',
project = 'project',
lines: number[] = [16]
): SnippetsByComponent {
const sources = lines.reduce((lines: { [key: number]: SourceLine }, line) => {
lines[line] = mockSourceLine({ line });
return lines;
}, {});
return {
component: mockSourceViewerFile(file, project),
sources
};
}

+ 12
- 57
server/sonar-web/src/main/js/helpers/testMocks.ts Bestand weergeven

import { Exporter, Profile } from '../apps/quality-profiles/types'; import { Exporter, Profile } from '../apps/quality-profiles/types';
import { AppState } from '../types/appstate'; import { AppState } from '../types/appstate';
import { RuleRepository } from '../types/coding-rules'; import { RuleRepository } from '../types/coding-rules';
import { ComponentQualifier } from '../types/component';
import { EditionKey } from '../types/editions'; import { EditionKey } from '../types/editions';
import { RawIssue } from '../types/issues';
import { IssueType, RawIssue } from '../types/issues';
import { Language } from '../types/languages'; import { Language } from '../types/languages';
import { DumpStatus, DumpTask } from '../types/project-dump'; import { DumpStatus, DumpTask } from '../types/project-dump';
import { TaskStatuses } from '../types/tasks'; import { TaskStatuses } from '../types/tasks';
RuleDescriptionSections, RuleDescriptionSections,
RuleDetails, RuleDetails,
RuleParameter, RuleParameter,
SnippetsByComponent,
SourceLine,
SourceViewerFile,
SysInfoBase, SysInfoBase,
SysInfoCluster, SysInfoCluster,
SysInfoStandalone SysInfoStandalone
}; };
} }


export function mockSnippetsByComponent(
component = 'main.js',
lines: number[] = [16]
): SnippetsByComponent {
const sources = lines.reduce((lines: { [key: number]: SourceLine }, line) => {
lines[line] = mockSourceLine({ line });
return lines;
}, {});
return {
component: mockSourceViewerFile({
key: component,
path: component
}),
sources
};
}

export function mockSourceLine(overrides: Partial<SourceLine> = {}): SourceLine {
return {
line: 16,
code: '<span class="k">import</span> java.util.<span class="sym-9 sym">ArrayList</span>;',
coverageStatus: 'covered',
coveredConditions: 2,
scmRevision: '80f564becc0c0a1c9abaa006eca83a4fd278c3f0',
scmAuthor: 'simon.brandhof@sonarsource.com',
scmDate: '2018-12-11T10:48:39+0100',
duplicated: false,
isNew: true,
...overrides
};
}

export function mockCurrentUser(overrides: Partial<CurrentUser> = {}): CurrentUser { export function mockCurrentUser(overrides: Partial<CurrentUser> = {}): CurrentUser {
return { return {
isLoggedIn: false, isLoggedIn: false,


export function mockRawIssue(withLocations = false, overrides: Partial<RawIssue> = {}): RawIssue { export function mockRawIssue(withLocations = false, overrides: Partial<RawIssue> = {}): RawIssue {
const rawIssue: RawIssue = { const rawIssue: RawIssue = {
actions: [],
component: 'main.js', component: 'main.js',
key: 'AVsae-CQS-9G3txfbFN2', key: 'AVsae-CQS-9G3txfbFN2',
line: 25, line: 25,
severity: 'MAJOR', severity: 'MAJOR',
status: 'OPEN', status: 'OPEN',
textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 }, textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 },
type: IssueType.CodeSmell,
...overrides ...overrides
}; };


if (withLocations) { if (withLocations) {
const loc = mockFlowLocation; const loc = mockFlowLocation;


rawIssue.flows = [{ locations: [loc(), loc()] }];
rawIssue.flows = [
{
locations: [
loc({ component: overrides.component }),
loc({ component: overrides.component })
]
}
];
} }


return { return {
descriptionSections: [ descriptionSections: [
{ {
key: RuleDescriptionSections.ROOT_CAUSE, key: RuleDescriptionSections.ROOT_CAUSE,
content: '<b>Why<b/> Because'
content: '<b>Why</b> Because'
} }
], ],
htmlDesc: '', htmlDesc: '',
}; };
} }


export function mockSourceViewerFile(overrides: Partial<SourceViewerFile> = {}): SourceViewerFile {
return {
key: 'foo',
measures: {
coverage: '85.2',
duplicationDensity: '1.0',
issues: '12',
lines: '56'
},
path: 'foo/bar.ts',
project: 'my-project',
projectName: 'MyProject',
q: ComponentQualifier.File,
uuid: 'foo-bar',
...overrides
};
}

export function mockStandaloneSysInfo(overrides: Partial<any> = {}): SysInfoStandalone { export function mockStandaloneSysInfo(overrides: Partial<any> = {}): SysInfoStandalone {
const baseInfo = mockBaseSysInfo(overrides); const baseInfo = mockBaseSysInfo(overrides);
return { return {

+ 3
- 0
server/sonar-web/src/main/js/types/issues.ts Bestand weergeven

} }


export interface RawIssue { export interface RawIssue {
actions: string[];
assignee?: string; assignee?: string;
author?: string; author?: string;
comments?: Array<Comment>; comments?: Array<Comment>;
line?: number; line?: number;
project: string; project: string;
rule: string; rule: string;
message?: string;
severity: string; severity: string;
status: string; status: string;
subProject?: string; subProject?: string;
textRange?: TextRange; textRange?: TextRange;
type: IssueType;
} }


export interface IssueResponse { export interface IssueResponse {

+ 3
- 0
server/sonar-web/src/main/js/types/types.ts Bestand weergeven

lines?: string; lines?: string;
tests?: string; tests?: string;
}; };
canMarkAsFavorite?: boolean;
path: string; path: string;
name?: string;
longName?: string;
project: string; project: string;
projectName: string; projectName: string;
q: ComponentQualifier; q: ComponentQualifier;

Laden…
Annuleren
Opslaan