* 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() | ||||
}); | }); |
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'; | ||||
"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", | ||||
}, | }, | ||||
], | ], |
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(); |
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 }); | |||||
} | } | ||||
} | } | ||||
} | } |
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 | ||||
); | ); |
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() }) | ||||
}; | }; |
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, |
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", |
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", |
* 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); |
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'; |
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, |
<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", |
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'; |
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'; |
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' } | ||||
}) | }) |
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} | ||||
/> | /> | ||||
); | ); |
"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", |
> | > | ||||
<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" | ||||
> | > |
*/ | */ | ||||
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', () => { |
*/ | */ | ||||
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', () => { |
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', () => { |
* 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', () => { |
/* | |||||
* 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 | |||||
}; | |||||
} |
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 { |
} | } | ||||
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 { |
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; |