Browse Source

SONAR-18555 Migrate issues app sidebar to RTL

tags/10.0.0.68432
7PH 1 year ago
parent
commit
a35ce4635c
41 changed files with 531 additions and 2898 deletions
  1. 111
    7
      server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
  2. 176
    21
      server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
  3. 3
    0
      server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewer-test.tsx.snap
  4. 8
    1
      server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx
  5. 0
    95
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/AssigneeFacet-test.tsx
  6. 0
    57
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/AuthorFacet-test.tsx
  7. 0
    80
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/CreationDateFacet-test.tsx
  8. 0
    100
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/DirectoryFacet-test.tsx
  9. 0
    104
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/FileFacet-test.tsx
  10. 0
    53
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/PeriodFilter-test.tsx
  11. 0
    117
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/ProjectFacet-test.tsx
  12. 0
    81
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/RuleFacet-test.tsx
  13. 0
    93
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/ScopeFacet-test.tsx
  14. 129
    0
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx
  15. 0
    97
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-test.tsx
  16. 0
    267
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StandardFacet-test.tsx
  17. 0
    78
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StatusFacet-test.tsx
  18. 0
    70
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/TypeFacet-test.tsx
  19. 0
    96
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AssigneeFacet-test.tsx.snap
  20. 0
    26
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AuthorFacet-test.tsx.snap
  21. 0
    548
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/CreationDateFacet-test.tsx.snap
  22. 0
    55
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/DirectoryFacet-test.tsx.snap
  23. 0
    59
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/FileFacet-test.tsx.snap
  24. 0
    50
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/ProjectFacet-test.tsx.snap
  25. 0
    30
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/RuleFacet-test.tsx.snap
  26. 0
    210
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/ScopeFacet-test.tsx.snap
  27. 0
    127
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap
  28. 0
    180
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StandardFacet-test.tsx.snap
  29. 0
    97
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StatusFacet-test.tsx.snap
  30. 0
    90
      server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/TypeFacet-test.tsx.snap
  31. 2
    0
      server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewer-test.tsx.snap
  32. 1
    0
      server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssueList-test.tsx.snap
  33. 4
    0
      server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap
  34. 1
    0
      server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/issue-test.tsx.snap
  35. 4
    4
      server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
  36. 19
    0
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap
  37. 9
    0
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap
  38. 37
    0
      server/sonar-web/src/main/js/helpers/mocks/issues.ts
  39. 8
    5
      server/sonar-web/src/main/js/helpers/testMocks.ts
  40. 18
    0
      server/sonar-web/src/main/js/types/issues.ts
  41. 1
    0
      server/sonar-web/src/main/js/types/types.ts

+ 111
- 7
server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts View File

@@ -31,6 +31,10 @@ import {
} from '../../helpers/testMocks';
import {
ASSIGNEE_ME,
IssueResolution,
IssueScope,
IssueSeverity,
IssueStatus,
IssueType,
RawFacet,
RawIssue,
@@ -103,6 +107,7 @@ export default class IssuesServiceMock {
issue: mockRawIssue(false, {
key: 'issue101',
component: 'foo:test1.js',
creationDate: '2023-01-05T09:36:01+0100',
message: 'Issue with no location message',
type: IssueType.Vulnerability,
rule: 'simpleRuleId',
@@ -140,6 +145,9 @@ export default class IssuesServiceMock {
],
},
],
resolution: IssueResolution.WontFix,
scope: IssueScope.Main,
tags: ['tag0', 'tag1'],
}),
snippets: keyBy(
[
@@ -163,6 +171,7 @@ export default class IssuesServiceMock {
component: 'foo:test1.js',
message: 'FlowIssue',
type: IssueType.CodeSmell,
severity: IssueSeverity.Minor,
rule: 'simpleRuleId',
textRange: {
startLine: 10,
@@ -233,6 +242,7 @@ export default class IssuesServiceMock {
],
},
],
tags: ['tag1'],
}),
snippets: keyBy(
[
@@ -256,10 +266,11 @@ export default class IssuesServiceMock {
component: 'foo:test1.js',
message: 'Issue on file',
assignee: mockLoggedInUser().login,
type: IssueType.Vulnerability,
type: IssueType.CodeSmell,
rule: 'simpleRuleId',
textRange: undefined,
line: undefined,
scope: IssueScope.Test,
}),
snippets: {},
},
@@ -338,6 +349,8 @@ export default class IssuesServiceMock {
endOffset: 1,
},
ruleDescriptionContextKey: 'spring',
resolution: IssueResolution.Unresolved,
status: IssueStatus.Open,
}),
snippets: keyBy(
[
@@ -362,6 +375,8 @@ export default class IssuesServiceMock {
startOffset: 0,
endOffset: 1,
},
resolution: IssueResolution.Fixed,
status: IssueStatus.Confirmed,
}),
snippets: keyBy(
[
@@ -381,7 +396,7 @@ export default class IssuesServiceMock {
key: 'issue4',
component: 'foo:test2.js',
message: 'Issue with tags',
rule: 'external_eslint_repo:no-div-regex',
rule: 'other',
textRange: {
startLine: 25,
endLine: 25,
@@ -391,6 +406,10 @@ export default class IssuesServiceMock {
ruleDescriptionContextKey: 'spring',
ruleStatus: 'DEPRECATED',
quickFixAvailable: true,
tags: ['unused'],
project: 'org.project2',
assignee: 'email1@sonarsource.com',
author: 'email3@sonarsource.com',
}),
snippets: keyBy(
[
@@ -536,11 +555,63 @@ export default class IssuesServiceMock {
});
};

handleSearchIssues = (query: RequestData): Promise<RawIssuesResponse> => {
const facets = (query.facets ?? '').split(',').map((name: string) => {
mockFacetDetailResponse = (facetsQuery: string): RawFacet[] => {
return facetsQuery.split(',').map((name: string): RawFacet => {
if (name === 'owaspTop10-2021') {
return this.owasp2021FacetList();
}
if (name === 'tags') {
return {
property: name,
values: [
{
val: 'unused',
count: 12842,
},
{
val: 'confusing',
count: 124,
},
],
};
}
if (name === 'projects') {
return {
property: name,
values: [
{ val: 'org.project1', count: 14685 },
{ val: 'org.project2', count: 3890 },
],
};
}
if (name === 'assignees') {
return {
property: name,
values: [
{ val: 'email1@sonarsource.com', count: 675 },
{ val: 'email2@sonarsource.com', count: 531 },
],
};
}
if (name === 'author') {
return {
property: name,
values: [
{ val: 'email3@sonarsource.com', count: 421 },
{ val: 'email4@sonarsource.com', count: 123 },
],
};
}
if (name === 'rules') {
return {
property: name,
values: [
{ val: 'simpleRuleId', count: 8816 },
{ val: 'advancedRuleId', count: 2060 },
{ val: 'other', count: 1324 },
],
};
}
if (name === 'languages') {
return {
property: name,
@@ -561,6 +632,10 @@ export default class IssuesServiceMock {
values: [],
};
});
};

handleSearchIssues = (query: RequestData): Promise<RawIssuesResponse> => {
const facets = this.mockFacetDetailResponse((query.facets ?? '') as string);

// Filter list (only supports assignee, type and severity)
const filteredList = this.list
@@ -573,9 +648,33 @@ export default class IssuesServiceMock {
}
return query.assignees.split(',').includes(item.issue.assignee);
})
.filter((item) => {
if (!query.tags) {
return true;
}
if (!item.issue.tags) {
return false;
}
return item.issue.tags.some((tag) => query.tags?.split(',').includes(tag));
})
.filter(
(item) =>
!query.createdBefore || new Date(item.issue.creationDate) <= new Date(query.createdBefore)
)
.filter(
(item) =>
!query.createdAfter || new Date(item.issue.creationDate) >= new Date(query.createdAfter)
)
.filter((item) => !query.types || query.types.split(',').includes(item.issue.type))
.filter(
(item) => !query.severities || query.severities.split(',').includes(item.issue.severity)
)
.filter((item) => !query.scopes || query.scopes.split(',').includes(item.issue.scope))
.filter((item) => !query.statuses || query.statuses.split(',').includes(item.issue.status))
.filter((item) => !query.projects || query.projects.split(',').includes(item.issue.project))
.filter((item) => !query.rules || query.rules.split(',').includes(item.issue.rule))
.filter(
(item) => !query.resolutions || query.resolutions.split(',').includes(item.issue.resolution)
);

// Splice list items according to paging using a fixed page size
@@ -589,12 +688,17 @@ export default class IssuesServiceMock {
effortTotal: 199629,
facets,
issues: listItems.map((line) => line.issue),
languages: [],
languages: [{ name: 'java' }, { name: 'python' }, { name: 'ts' }],
paging: mockPaging({
pageIndex,
pageSize,
total: filteredList.length,
}),
users: [
{ login: 'login0' },
{ login: 'login1', name: 'Login 1' },
{ login: 'login2', name: 'Login 2' },
],
});
};

@@ -638,8 +742,8 @@ export default class IssuesServiceMock {
};

const resolutionMap: Dict<string> = {
wontfix: 'WONTFIX',
falsepositive: 'FALSE-POSITIVE',
wontfix: IssueResolution.WontFix,
falsepositive: IssueResolution.FalsePositive,
};

return this.getActionsResponse(

+ 176
- 21
server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx View File

@@ -64,7 +64,37 @@ const ui = {
issueItem7: byRole('region', { name: 'Issue with tags' }),
issueItem8: byRole('region', { name: 'Issue on page 2' }),

clearIssueTypeFacet: byRole('button', { name: 'clear_x_filter.issues.facet.types' }),
codeSmellIssueTypeFilter: byRole('checkbox', { name: 'issue.type.CODE_SMELL' }),
vulnerabilityIssueTypeFilter: byRole('checkbox', { name: 'issue.type.VULNERABILITY' }),
clearSeverityFacet: byRole('button', { name: 'clear_x_filter.issues.facet.severities' }),
majorSeverityFilter: byRole('checkbox', { name: 'severity.MAJOR' }),
scopeFacet: byRole('button', { name: 'issues.facet.scopes' }),
clearScopeFacet: byRole('button', { name: 'clear_x_filter.issues.facet.scopes' }),
mainScopeFilter: byRole('checkbox', { name: 'issue.scope.MAIN' }),
resolutionFacet: byRole('button', { name: 'issues.facet.resolutions' }),
clearResolutionFacet: byRole('button', { name: 'clear_x_filter.issues.facet.resolutions' }),
fixedResolutionFilter: byRole('checkbox', { name: 'issue.resolution.FIXED' }),
statusFacet: byRole('button', { name: 'issues.facet.statuses' }),
creationDateFacet: byRole('button', { name: 'issues.facet.createdAt' }),
clearCreationDateFacet: byRole('button', { name: 'clear_x_filter.issues.facet.createdAt' }),
clearStatusFacet: byRole('button', { name: 'clear_x_filter.issues.facet.statuses' }),
openStatusFilter: byRole('checkbox', { name: 'issue.status.OPEN' }),
confirmedStatusFilter: byRole('checkbox', { name: 'issue.status.CONFIRMED' }),
ruleFacet: byRole('button', { name: 'issues.facet.rules' }),
clearRuleFacet: byRole('button', { name: 'clear_x_filter.issues.facet.rules' }),
tagFacet: byRole('button', { name: 'issues.facet.tags' }),
clearTagFacet: byRole('button', { name: 'clear_x_filter.issues.facet.tags' }),
projectFacet: byRole('button', { name: 'issues.facet.projects' }),
clearProjectFacet: byRole('button', { name: 'clear_x_filter.issues.facet.projects' }),
assigneeFacet: byRole('button', { name: 'issues.facet.assignees' }),
clearAssigneeFacet: byRole('button', { name: 'clear_x_filter.issues.facet.assignees' }),
authorFacet: byRole('button', { name: 'issues.facet.authors' }),
clearAuthorFacet: byRole('button', { name: 'clear_x_filter.issues.facet.authors' }),

dateInputMonthSelect: byRole('combobox', { name: 'Month:' }),
dateInputYearSelect: byRole('combobox', { name: 'Year:' }),

clearAllFilters: byRole('button', { name: 'clear_all_filters' }),
};

@@ -305,45 +335,170 @@ describe('issues app', () => {
});
});
describe('filtering', () => {
it('should allow to reset all facets', async () => {
it('should handle filtering from a specific issue properly', async () => {
const user = userEvent.setup();
renderIssueApp();
await waitOnDataLoaded();

// Ensure issue type filter is unchecked
expect(ui.codeSmellIssueTypeFilter.get()).not.toBeChecked();
expect(ui.vulnerabilityIssueTypeFilter.get()).not.toBeChecked();
expect(ui.issueItem1.get()).toBeInTheDocument();
expect(ui.issueItem2.get()).toBeInTheDocument();

// Open filter similar issue dropdown for issue 2 (Code smell)
await user.click(
await within(ui.issueItem2.get()).findByRole('button', {
name: 'issue.filter_similar_issues',
})
);
await user.click(
await within(ui.issueItem2.get()).findByRole('button', {
name: 'issue.type.CODE_SMELL',
})
);

await user.click(ui.codeSmellIssueTypeFilter.get());
expect(ui.codeSmellIssueTypeFilter.get()).toBeChecked();
expect(ui.issueItem4.query()).not.toBeInTheDocument();
expect(ui.vulnerabilityIssueTypeFilter.get()).not.toBeChecked();
expect(ui.issueItem1.query()).not.toBeInTheDocument();
expect(ui.issueItem2.get()).toBeInTheDocument();
expect(
screen.queryByRole('button', { name: 'issues.facet.owaspTop10_2021' })
).not.toBeInTheDocument();

// Clear filters
await user.click(ui.clearAllFilters.get());

// Open filter similar issue dropdown for issue 3 (Vulnerability)
await user.click(
await within(await ui.issueItem1.find()).findByRole('button', {
name: 'issue.filter_similar_issues',
})
);
await user.click(
await within(await ui.issueItem1.find()).findByRole('button', {
name: 'issue.type.VULNERABILITY',
})
);

expect(ui.codeSmellIssueTypeFilter.get()).not.toBeChecked();
expect(ui.issueItem4.get()).toBeInTheDocument();
expect(ui.vulnerabilityIssueTypeFilter.get()).toBeChecked();
expect(ui.issueItem1.get()).toBeInTheDocument();
expect(ui.issueItem2.query()).not.toBeInTheDocument();
// Standards should now be expanded and Owasp should be visible
expect(screen.getByRole('button', { name: 'issues.facet.owaspTop10_2021' })).toBeVisible();
});

it('should handle filtering from a specific issue properly', async () => {
it('should combine sidebar filters properly', async () => {
const user = userEvent.setup();
renderIssueApp();
await waitOnDataLoaded();

// Get first issue list item
const issueItem = await ui.issueItem2.find();
// Select only code smells (should make the first issue disappear)
await user.click(ui.codeSmellIssueTypeFilter.get());

// Ensure issue type filter is unchecked
expect(ui.codeSmellIssueTypeFilter.get()).not.toBeChecked();
// Select code smells + major severity
await user.click(ui.majorSeverityFilter.get());

// Expand scope and set code smells + major severity + main scope
await user.click(ui.scopeFacet.get());
await user.click(ui.mainScopeFilter.get());

// Resolution
await user.click(ui.resolutionFacet.get());
await user.click(ui.fixedResolutionFilter.get());

// Stop to check that filters were applied as expected
expect(ui.issueItem1.query()).not.toBeInTheDocument();
expect(ui.issueItem2.query()).not.toBeInTheDocument();
expect(ui.issueItem3.query()).not.toBeInTheDocument();
expect(ui.issueItem4.query()).not.toBeInTheDocument();
expect(ui.issueItem5.query()).not.toBeInTheDocument();
expect(ui.issueItem6.get()).toBeInTheDocument();
expect(ui.issueItem7.query()).not.toBeInTheDocument();

// Status
await user.click(ui.statusFacet.get());
await user.click(ui.openStatusFilter.get());
expect(ui.issueItem6.query()).not.toBeInTheDocument(); // Issue 6 should vanish

// Ctrl+click on confirmed status
await user.keyboard('{Control>}');
await user.click(ui.confirmedStatusFilter.get());
await user.keyboard('{/Control}');
expect(ui.issueItem6.get()).toBeInTheDocument(); // Issue 6 should come back

// Clear resolution filter
await user.click(ui.clearResolutionFacet.get());

// Rule
await user.click(ui.ruleFacet.get());
await user.click(screen.getByRole('checkbox', { name: 'other' }));

// Tag
await user.click(ui.tagFacet.get());
await user.click(screen.getByRole('checkbox', { name: 'unused' }));

// Project
await user.click(ui.projectFacet.get());
await user.click(screen.getByRole('checkbox', { name: 'org.project2' }));

// Assignee
await user.click(ui.assigneeFacet.get());
await user.click(screen.getByRole('checkbox', { name: 'email2@sonarsource.com' }));
await user.click(screen.getByRole('checkbox', { name: 'email1@sonarsource.com' })); // Change assignee

// Author
await user.click(ui.authorFacet.get());
await user.click(screen.getByRole('checkbox', { name: 'email4@sonarsource.com' }));
await user.click(screen.getByRole('checkbox', { name: 'email3@sonarsource.com' })); // Change author
expect(ui.issueItem1.query()).not.toBeInTheDocument();
expect(ui.issueItem2.query()).not.toBeInTheDocument();
expect(ui.issueItem3.query()).not.toBeInTheDocument();
expect(ui.issueItem4.query()).not.toBeInTheDocument();
expect(ui.issueItem5.query()).not.toBeInTheDocument();
expect(ui.issueItem6.query()).not.toBeInTheDocument();
expect(ui.issueItem7.get()).toBeInTheDocument();

// Clear filters one by one
await user.click(ui.clearIssueTypeFacet.get());
await user.click(ui.clearSeverityFacet.get());
await user.click(ui.clearScopeFacet.get());
await user.click(ui.clearStatusFacet.get());
await user.click(ui.clearRuleFacet.get());
await user.click(ui.clearTagFacet.get());
await user.click(ui.clearProjectFacet.get());
await user.click(ui.clearAssigneeFacet.get());
await user.click(ui.clearAuthorFacet.get());
expect(ui.issueItem1.get()).toBeInTheDocument();
expect(ui.issueItem2.get()).toBeInTheDocument();
expect(ui.issueItem3.get()).toBeInTheDocument();
expect(ui.issueItem4.get()).toBeInTheDocument();
expect(ui.issueItem5.get()).toBeInTheDocument();
expect(ui.issueItem6.get()).toBeInTheDocument();
expect(ui.issueItem7.get()).toBeInTheDocument();
});

// Open filter similar issue dropdown
await user.click(
await within(issueItem).findByRole('button', { name: 'issue.filter_similar_issues' })
);
it('should allow to set creation date', async () => {
const user = userEvent.setup();
renderIssueApp(mockLoggedInUser());
await waitOnDataLoaded();

// Select type
await user.click(
await within(issueItem).findByRole('button', { name: 'issue.type.CODE_SMELL' })
);
// Select a specific date range such that only one issue matches
await user.click(ui.creationDateFacet.get());
await user.click(screen.getByPlaceholderText('start_date'));
await user.selectOptions(ui.dateInputMonthSelect.get(), 'January');
await user.selectOptions(ui.dateInputYearSelect.get(), '2023');
await user.click(screen.getByText('1'));
await user.click(screen.getByText('10'));

// Ensure issue type filter is now checked
expect(ui.codeSmellIssueTypeFilter.get()).toBeChecked();
expect(ui.issueItem2.get()).toBeInTheDocument();
expect(ui.issueItem1.get()).toBeInTheDocument();
expect(ui.issueItem2.query()).not.toBeInTheDocument();
expect(ui.issueItem3.query()).not.toBeInTheDocument();
expect(ui.issueItem4.query()).not.toBeInTheDocument();
expect(ui.issueItem5.query()).not.toBeInTheDocument();
expect(ui.issueItem6.query()).not.toBeInTheDocument();
expect(ui.issueItem7.query()).not.toBeInTheDocument();
});

it('should allow to only show my issues', async () => {
@@ -449,7 +604,7 @@ describe('issues item', () => {
issuesHandler.setIsAdmin(true);
renderIssueApp();

// Get 'Fix that' issue list item
// Get a specific issue list item
const listItem = within(await screen.findByRole('region', { name: 'Fix that' }));

// Change issue type

+ 3
- 0
server/sonar-web/src/main/js/apps/issues/crossComponentSourceViewer/__tests__/__snapshots__/CrossComponentSourceViewer-test.tsx.snap View File

@@ -106,6 +106,7 @@ exports[`should render correctly 2`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [
{
"component": "main.js",
@@ -264,6 +265,7 @@ exports[`should render correctly: no component found 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [
{
"component": "main.js",
@@ -393,6 +395,7 @@ exports[`should render correctly: no component found 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [
{
"component": "main.js",

+ 8
- 1
server/sonar-web/src/main/js/apps/issues/sidebar/ResolutionFacet.tsx View File

@@ -25,6 +25,7 @@ import FacetItem from '../../../components/facet/FacetItem';
import FacetItemsList from '../../../components/facet/FacetItemsList';
import MultipleSelectionHint from '../../../components/facet/MultipleSelectionHint';
import { translate } from '../../../helpers/l10n';
import { IssueResolution } from '../../../types/issues';
import { Dict } from '../../../types/types';
import { formatFacetStat, Query } from '../utils';

@@ -38,7 +39,13 @@ interface Props {
stats: Dict<number> | undefined;
}

const RESOLUTIONS = ['', 'FALSE-POSITIVE', 'FIXED', 'REMOVED', 'WONTFIX'];
const RESOLUTIONS = [
IssueResolution.Unresolved,
IssueResolution.FalsePositive,
IssueResolution.Fixed,
IssueResolution.Removed,
IssueResolution.WontFix,
];

export default class ResolutionFacet extends React.PureComponent<Props> {
property = 'resolutions';

+ 0
- 95
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/AssigneeFacet-test.tsx View File

@@ -1,95 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
import * as React from 'react';
import { Query } from '../../utils';
import AssigneeFacet from '../AssigneeFacet';

it('should render', () => {
expect(shallowRender({ assignees: ['foo'] })).toMatchSnapshot();
});

it('should select unassigned', () => {
expect(shallowRender({ assigned: false }).find('ListStyleFacet').prop('values')).toEqual(['']);
});

it('should call onChange', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ assignees: ['foo'], onChange });
const itemOnClick = wrapper.find('ListStyleFacet').prop<Function>('onItemClick');

itemOnClick('');
expect(onChange).toHaveBeenLastCalledWith({ assigned: false, assignees: [] });

itemOnClick('bar');
expect(onChange).toHaveBeenLastCalledWith({ assigned: true, assignees: ['bar'] });

itemOnClick('baz', true);
expect(onChange).toHaveBeenLastCalledWith({ assigned: true, assignees: ['baz', 'foo'] });
});

describe('test behavior', () => {
const instance = shallowRender({
assignees: ['foo', 'baz'],
referencedUsers: {
foo: { active: false, login: 'foo' },
baz: { active: true, login: 'baz', name: 'Name Baz' },
},
}).instance();

it('should correctly render assignee name', () => {
expect(instance.getAssigneeName('')).toBe('unassigned');
expect(instance.getAssigneeName('bar')).toBe('bar');
expect(instance.getAssigneeName('baz')).toBe('Name Baz');
expect(instance.getAssigneeName('foo')).toBe('user.x_deleted.foo');
});

it('should correctly render facet item', () => {
expect(instance.renderFacetItem('')).toBe('unassigned');
expect(instance.renderFacetItem('bar')).toBe('bar');
expect(instance.renderFacetItem('baz')).toMatchSnapshot();
expect(instance.renderFacetItem('foo')).toMatchSnapshot();
});

it('should correctly render search result correctly', () => {
expect(
instance.renderSearchResult({ active: true, login: 'bar', name: 'Name Bar' }, 'ba')
).toMatchSnapshot();
expect(instance.renderSearchResult({ active: false, login: 'foo' }, 'fo')).toMatchSnapshot();
});
});

function shallowRender(props?: Partial<AssigneeFacet['props']>) {
return shallow<AssigneeFacet>(
<AssigneeFacet
assigned={true}
assignees={[]}
fetching={false}
loadSearchResultCount={jest.fn()}
onChange={jest.fn()}
onToggle={jest.fn()}
open={true}
query={{} as Query}
referencedUsers={{ foo: { avatar: 'avatart-foo', login: 'name-foo', name: 'Name Foo' } }}
stats={{ '': 5, foo: 13, bar: 7, baz: 6 }}
{...props}
/>
);
}

+ 0
- 57
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/AuthorFacet-test.tsx View File

@@ -1,57 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
import * as React from 'react';
import ListStyleFacet from '../../../../components/facet/ListStyleFacet';
import { mockComponent } from '../../../../helpers/mocks/component';
import { Query } from '../../utils';
import AuthorFacet from '../AuthorFacet';

it('should render correctly', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
});

it('should notify of search result count correctly', () => {
const loadSearchResultCount = jest.fn();

const wrapper = shallowRender({ loadSearchResultCount });

wrapper.find(ListStyleFacet).props().loadSearchResultCount!(['1', '2']);

expect(loadSearchResultCount).toHaveBeenCalled();
});

function shallowRender(props: Partial<AuthorFacet['props']> = {}) {
return shallow<AuthorFacet>(
<AuthorFacet
component={mockComponent()}
fetching={false}
loadSearchResultCount={jest.fn()}
onChange={jest.fn()}
onToggle={jest.fn()}
open={true}
query={{} as Query}
stats={{}}
author={[]}
{...props}
/>
);
}

+ 0
- 80
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/CreationDateFacet-test.tsx View File

@@ -1,80 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
import * as React from 'react';
import { IntlShape } from 'react-intl';
import { mockComponent } from '../../../../helpers/mocks/component';
import { ComponentQualifier } from '../../../../types/component';
import { CreationDateFacet } from '../CreationDateFacet';

it('should render correctly', () => {
expect(shallowRender({ open: false })).toMatchSnapshot('closed');
expect(shallowRender()).toMatchSnapshot('clear');
expect(shallowRender({ createdAt: '2019.05.21T13:33:00Z' })).toMatchSnapshot('created at');
expect(
shallowRender({
createdAfter: new Date('2019.04.29T13:33:00Z'),
createdAfterIncludesTime: true,
})
).toMatchSnapshot('created after');
expect(
shallowRender({
createdAfter: new Date('2019.04.29T13:33:00Z'),
createdAfterIncludesTime: true,
})
).toMatchSnapshot('created after timestamp');
expect(shallowRender({ component: mockComponent() })).toMatchSnapshot('project');
expect(
shallowRender({ component: mockComponent({ qualifier: ComponentQualifier.Portfolio }) })
).toMatchSnapshot('portfolio');
});

it.each([
['week', '1w'],
['month', '1m'],
['year', '1y'],
])('should render correctly for createdInLast %s', (_, createdInLast) => {
expect(shallowRender({ component: mockComponent(), createdInLast })).toMatchSnapshot();
});

function shallowRender(props?: Partial<CreationDateFacet['props']>) {
return shallow<CreationDateFacet>(
<CreationDateFacet
component={undefined}
fetching={false}
createdAfter={undefined}
createdAfterIncludesTime={false}
createdAt=""
createdBefore={undefined}
createdInLast=""
inNewCodePeriod={false}
intl={
{
formatDate: (date: string) => 'formatted.' + date,
} as IntlShape
}
onChange={jest.fn()}
onToggle={jest.fn()}
open={true}
stats={undefined}
{...props}
/>
);
}

+ 0
- 100
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/DirectoryFacet-test.tsx View File

@@ -1,100 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
import * as React from 'react';
import { getDirectories } from '../../../../api/components';
import ListStyleFacet from '../../../../components/facet/ListStyleFacet';
import { mockBranch } from '../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../helpers/mocks/component';
import { TreeComponentWithPath } from '../../../../types/component';
import { Query } from '../../utils';
import DirectoryFacet from '../DirectoryFacet';

jest.mock('../../../../api/components', () => ({
getDirectories: jest.fn().mockResolvedValue({ components: [] }),
}));

beforeEach(() => jest.clearAllMocks());

const branch = mockBranch();
const component = mockComponent();

it('should render correctly', () => {
const wrapper = shallowRender();
const instance = wrapper.instance();
expect(wrapper).toMatchSnapshot();
expect(
instance.renderSearchResult({ path: 'foo/bar' } as TreeComponentWithPath, 'foo')
).toMatchSnapshot();
expect(instance.renderFacetItem('foo/bar')).toMatchSnapshot();
});

it('should properly search for directory', () => {
const wrapper = shallowRender();

const query = 'foo';

wrapper.find(ListStyleFacet).props().onSearch(query);

expect(getDirectories).toHaveBeenCalledWith({
branch: branch.name,
component: component.key,
q: query,
ps: 30,
p: undefined,
});
});

describe("ListStyleFacet's callback props", () => {
const wrapper = shallowRender();
const instance = wrapper.instance();

it('#getSearchResultText()', () => {
expect(instance.getSearchResultText({ path: 'bar' } as TreeComponentWithPath)).toBe('bar');
});

it('#getSearchResultKey()', () => {
expect(instance.getSearchResultKey({ path: 'foo/bar' } as TreeComponentWithPath)).toBe(
'foo/bar'
);
});

it('#getFacetItemText()', () => {
expect(instance.getFacetItemText('foo/bar')).toBe('foo/bar');
});
});

function shallowRender(props: Partial<DirectoryFacet['props']> = {}) {
return shallow<DirectoryFacet>(
<DirectoryFacet
branchLike={branch}
componentKey={component.key}
directories={['foo/', 'bar/baz/']}
fetching={false}
loadSearchResultCount={jest.fn()}
onChange={jest.fn()}
onToggle={jest.fn()}
open={false}
query={{} as Query}
stats={undefined}
{...props}
/>
);
}

+ 0
- 104
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/FileFacet-test.tsx View File

@@ -1,104 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
import * as React from 'react';
import { getFiles } from '../../../../api/components';
import ListStyleFacet from '../../../../components/facet/ListStyleFacet';
import { mockBranch } from '../../../../helpers/mocks/branch-like';
import { mockComponent } from '../../../../helpers/mocks/component';
import { TreeComponentWithPath } from '../../../../types/component';
import { Query } from '../../utils';
import FileFacet from '../FileFacet';

jest.mock('../../../../api/components', () => ({
getFiles: jest.fn().mockResolvedValue({ components: [] }),
}));

beforeEach(() => jest.clearAllMocks());

const branch = mockBranch();
const component = mockComponent();

const PATH = 'foo/bar.js';

it('should render correctly', () => {
const wrapper = shallowRender();
const instance = wrapper.instance();
expect(wrapper).toMatchSnapshot();
expect(
instance.renderSearchResult({ path: PATH } as TreeComponentWithPath, 'bar')
).toMatchSnapshot();
expect(instance.renderFacetItem('fooUuid')).toMatchSnapshot();
});

it('should properly search for file', () => {
const wrapper = shallowRender();

const query = 'foo';

wrapper.find(ListStyleFacet).props().onSearch(query);

expect(getFiles).toHaveBeenCalledWith({
branch: branch.name,
component: component.key,
q: query,
ps: 30,
p: undefined,
});
});

describe("ListStyleFacet's callback props", () => {
const wrapper = shallowRender();
const instance = wrapper.instance();

it('#getSearchResultText()', () => {
expect(instance.getSearchResultText({ path: PATH } as TreeComponentWithPath)).toBe(
'foo/bar.js'
);
});

it('#getSearchResultKey()', () => {
expect(instance.getSearchResultKey({ key: 'bar', path: 'bar' } as TreeComponentWithPath)).toBe(
'bar'
);
});

it('#getFacetItemText()', () => {
expect(instance.getFacetItemText('bar')).toBe('bar');
});
});

function shallowRender(props: Partial<FileFacet['props']> = {}) {
return shallow<FileFacet>(
<FileFacet
branchLike={branch}
componentKey={component.key}
fetching={false}
files={['foo', 'bar']}
loadSearchResultCount={jest.fn()}
onChange={jest.fn()}
onToggle={jest.fn()}
open={false}
query={{} as Query}
stats={undefined}
{...props}
/>
);
}

+ 0
- 53
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/PeriodFilter-test.tsx View File

@@ -1,53 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

import * as React from 'react';
import PeriodFilter, { PeriodFilterProps } from '../PeriodFilter';

it('should filter when clicked', async () => {
const user = userEvent.setup();
const onChange = jest.fn();

renderPeriodFilter({ onChange });

await user.click(screen.getByText('issues.new_code'));

expect(onChange).toHaveBeenCalledWith({
createdAfter: undefined,
createdAt: undefined,
createdBefore: undefined,
createdInLast: undefined,
inNewCodePeriod: true,
});
});

function renderPeriodFilter(overrides: Partial<PeriodFilterProps> = {}) {
return render(
<PeriodFilter
fetching={false}
newCodeSelected={false}
onChange={jest.fn()}
stats={{}}
{...overrides}
/>
);
}

+ 0
- 117
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/ProjectFacet-test.tsx View File

@@ -1,117 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
import { keyBy } from 'lodash';
import * as React from 'react';
import { getTree, searchProjects } from '../../../../api/components';
import { mockComponent } from '../../../../helpers/mocks/component';
import { ComponentQualifier } from '../../../../types/component';
import { ReferencedComponent } from '../../../../types/issues';
import { Query } from '../../utils';
import ProjectFacet from '../ProjectFacet';

jest.mock('../../../../api/components', () => ({
getTree: jest.fn().mockResolvedValue({ baseComponent: {}, components: [], paging: {} }),
searchProjects: jest.fn().mockResolvedValue({
components: [],
facets: [],
paging: {},
}),
}));

beforeEach(() => jest.clearAllMocks());

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should callback to load search results', () => {
const loadSearchResultCount = jest.fn();
const wrapper = shallowRender({ loadSearchResultCount });
wrapper.instance().loadSearchResultCount([
{ key: '1', name: 'first' },
{ key: '2', name: 'seecond' },
]);

expect(loadSearchResultCount).toHaveBeenCalledWith('projects', { projects: ['1', '2'] });
});

it('should handle search for projects globally', async () => {
const wrapper = shallowRender();
const query = 'my project';

await wrapper.instance().handleSearch(query);

expect(searchProjects).toHaveBeenCalled();
expect(getTree).not.toHaveBeenCalled();
});

it('should handle search for projects in portfolio', async () => {
const wrapper = shallowRender({
component: mockComponent({ qualifier: ComponentQualifier.Portfolio }),
});
const query = 'my project';

await wrapper.instance().handleSearch(query);

expect(searchProjects).not.toHaveBeenCalled();
expect(getTree).toHaveBeenCalled();
});

describe("ListStyleFacet's renderers", () => {
const components: ReferencedComponent[] = [
{ key: 'projectKey', name: 'First Project Name', uuid: '141324' },
{ key: 'projectKey2', name: 'Second Project Name', uuid: '643878' },
];
const referencedComponents = keyBy(components, (c) => c.key);
const wrapper = shallowRender({ referencedComponents });
const instance = wrapper.instance();

it('should include getProjectName', () => {
expect(instance.getProjectName(components[0].key)).toBe(components[0].name);
expect(instance.getProjectName('nonexistent')).toBe('nonexistent');
});

it('should include renderFacetItem', () => {
expect(instance.renderFacetItem(components[0].key)).toMatchSnapshot();
});

it('should include renderSearchResult', () => {
expect(instance.renderSearchResult(components[0], 'First')).toMatchSnapshot();
});
});

function shallowRender(props: Partial<ProjectFacet['props']> = {}) {
return shallow<ProjectFacet>(
<ProjectFacet
component={undefined}
fetching={false}
loadSearchResultCount={jest.fn()}
onChange={jest.fn()}
onToggle={jest.fn()}
open={false}
projects={[]}
query={{} as Query}
referencedComponents={{}}
stats={undefined}
{...props}
/>
);
}

+ 0
- 81
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/RuleFacet-test.tsx View File

@@ -1,81 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
import * as React from 'react';
import { searchRules } from '../../../../api/rules';
import { mockReferencedRule } from '../../../../helpers/mocks/issues';
import { mockRule } from '../../../../helpers/testMocks';
import { Query } from '../../utils';
import RuleFacet from '../RuleFacet';

jest.mock('../../../../api/rules', () => ({
searchRules: jest.fn().mockResolvedValue({}),
}));

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should handle search', async () => {
const wrapper = shallowRender();

const query = 'query';

await wrapper.instance().handleSearch(query);

expect(searchRules).toHaveBeenCalledWith(
expect.objectContaining({ languages: 'js,java', q: query })
);
});

describe('ListStyleFacet Renderers', () => {
const referencedRules = { r1: mockReferencedRule() };
const instance = shallowRender({ referencedRules }).instance();

it('should include renderFacetItem', () => {
const rule = referencedRules.r1;
expect(instance.getRuleName('r1')).toBe(`(${rule.langName}) ${rule.name}`);
expect(instance.getRuleName('nonexistent')).toBe('nonexistent');
});

it('should include renderSearchResult', () => {
const rule = mockRule();
expect(instance.renderSearchResult(rule)).toBe(`(${rule.langName}) ${rule.name}`);
expect(instance.renderSearchResult(mockRule({ langName: '' }))).toBe(rule.name);
});
});

function shallowRender(props: Partial<RuleFacet['props']> = {}) {
return shallow<RuleFacet>(
<RuleFacet
fetching={true}
languages={['js', 'java']}
loadSearchResultCount={jest.fn()}
onChange={jest.fn()}
onToggle={jest.fn()}
open={false}
query={{} as Query}
referencedRules={{}}
rules={['r1']}
stats={{}}
{...props}
/>
);
}

+ 0
- 93
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/ScopeFacet-test.tsx View File

@@ -1,93 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { shallow, ShallowWrapper } from 'enzyme';
import * as React from 'react';
import FacetHeader from '../../../../components/facet/FacetHeader';
import FacetItem from '../../../../components/facet/FacetItem';
import { IssueScope } from '../../../../types/issues';
import ScopeFacet, { ScopeFacetProps } from '../ScopeFacet';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ open: true })).toMatchSnapshot('open');
expect(shallowRender({ open: true, scopes: [IssueScope.Main] })).toMatchSnapshot('active facet');
expect(shallowRender({ open: true, stats: { [IssueScope.Main]: 0 } })).toMatchSnapshot(
'disabled facet'
);
});

it('should correctly handle facet header clicks', () => {
const onChange = jest.fn();
const onToggle = jest.fn();
const wrapper = shallowRender({ onChange, onToggle });

wrapper.find(FacetHeader).props().onClear!();
expect(onChange).toHaveBeenCalledWith({ scopes: [] });

wrapper.find(FacetHeader).props().onClick!();
expect(onToggle).toHaveBeenCalledWith('scopes');
});

it('should correctly handle facet item clicks', () => {
const wrapper = shallowRender({ open: true, scopes: [IssueScope.Main] });
const onChange = jest.fn(({ scopes }) => wrapper.setProps({ scopes }));
wrapper.setProps({ onChange });

clickFacetItem(wrapper, IssueScope.Test);
expect(onChange).toHaveBeenLastCalledWith({ scopes: [IssueScope.Test] });

clickFacetItem(wrapper, IssueScope.Test);
expect(onChange).toHaveBeenLastCalledWith({ scopes: [] });

clickFacetItem(wrapper, IssueScope.Test, true);
clickFacetItem(wrapper, IssueScope.Main, true);
expect(onChange).toHaveBeenLastCalledWith({
scopes: expect.arrayContaining([IssueScope.Main, IssueScope.Test]),
});

clickFacetItem(wrapper, IssueScope.Test, true);
expect(onChange).toHaveBeenLastCalledWith({ scopes: [IssueScope.Main] });
});

function clickFacetItem(
wrapper: ShallowWrapper<ScopeFacetProps>,
scope: IssueScope,
multiple = false
) {
return wrapper
.find(FacetItem)
.filterWhere((f) => f.key() === scope)
.props()
.onClick(scope, multiple);
}

function shallowRender(props: Partial<ScopeFacetProps> = {}) {
return shallow<ScopeFacetProps>(
<ScopeFacet
fetching={true}
onChange={jest.fn()}
onToggle={jest.fn()}
open={false}
scopes={[]}
stats={{}}
{...props}
/>
);
}

+ 129
- 0
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-it.tsx View File

@@ -0,0 +1,129 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { screen } from '@testing-library/react';
import * as React from 'react';
import { mockComponent } from '../../../../helpers/mocks/component';
import { mockQuery } from '../../../../helpers/mocks/issues';
import { mockAppState } from '../../../../helpers/testMocks';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { ComponentQualifier } from '../../../../types/component';
import { GlobalSettingKeys } from '../../../../types/settings';
import { Sidebar } from '../Sidebar';

it('should render correct facets for Application', () => {
renderSidebar({ component: mockComponent({ qualifier: ComponentQualifier.Application }) });
expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([
'issues.facet.types',
'issues.facet.severities',
'issues.facet.scopes',
'issues.facet.resolutions',
'issues.facet.statuses',
'issues.facet.standards',
'issues.facet.createdAt',
'issues.facet.languages',
'issues.facet.rules',
'issues.facet.tags',
'issues.facet.projects',
'issues.facet.assignees',
'clear',
'issues.facet.authors',
]);
});

it('should render correct facets for Portfolio', () => {
renderSidebar({ component: mockComponent({ qualifier: ComponentQualifier.Portfolio }) });
expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([
'issues.facet.types',
'issues.facet.severities',
'issues.facet.scopes',
'issues.facet.resolutions',
'issues.facet.statuses',
'issues.facet.standards',
'issues.facet.createdAt',
'issues.facet.languages',
'issues.facet.rules',
'issues.facet.tags',
'issues.facet.projects',
'issues.facet.assignees',
'clear',
'issues.facet.authors',
]);
});

it('should render correct facets for SubPortfolio', () => {
renderSidebar({ component: mockComponent({ qualifier: ComponentQualifier.SubPortfolio }) });
expect(screen.getAllByRole('button').map((button) => button.textContent)).toStrictEqual([
'issues.facet.types',
'issues.facet.severities',
'issues.facet.scopes',
'issues.facet.resolutions',
'issues.facet.statuses',
'issues.facet.standards',
'issues.facet.createdAt',
'issues.facet.languages',
'issues.facet.rules',
'issues.facet.tags',
'issues.facet.projects',
'issues.facet.assignees',
'clear',
'issues.facet.authors',
]);
});

it.each([
['week', '1w'],
['month', '1m'],
['year', '1y'],
])('should render correctly for createdInLast %s', (name, createdInLast) => {
renderSidebar({ component: mockComponent(), query: mockQuery({ createdInLast }) });

const text = {
week: 'issues.facet.createdAt.last_week',
month: 'issues.facet.createdAt.last_month',
year: 'issues.facet.createdAt.last_year',
}[name] as string;
expect(screen.getByText(text)).toBeInTheDocument();
});

function renderSidebar(props: Partial<Sidebar['props']> = {}) {
return renderComponent(
<Sidebar
appState={mockAppState({
settings: { [GlobalSettingKeys.DeveloperAggregatedInfoDisabled]: 'false' },
})}
component={mockComponent()}
createdAfterIncludesTime={false}
facets={{}}
loadSearchResultCount={jest.fn()}
loadingFacets={{}}
myIssues={false}
onFacetToggle={jest.fn()}
onFilterChange={jest.fn()}
openFacets={{}}
query={mockQuery()}
referencedComponentsById={{}}
referencedComponentsByKey={{}}
referencedLanguages={{}}
referencedRules={{}}
referencedUsers={{}}
{...props}
/>
);
}

+ 0
- 97
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/Sidebar-test.tsx View File

@@ -1,97 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { shallow, ShallowWrapper } from 'enzyme';
import { flatten } from 'lodash';
import * as React from 'react';
import { mockComponent } from '../../../../helpers/mocks/component';
import { mockAppState } from '../../../../helpers/testMocks';
import { ComponentQualifier } from '../../../../types/component';
import { GlobalSettingKeys } from '../../../../types/settings';
import { Query } from '../../utils';
import { Sidebar } from '../Sidebar';

it('should render facets for global page', () => {
expect(renderSidebar()).toMatchSnapshot();
});

it('should render facets for project', () => {
expect(renderSidebar({ component: mockComponent() })).toMatchSnapshot();
});

it.each([
[ComponentQualifier.Application],
[ComponentQualifier.Portfolio],
[ComponentQualifier.SubPortfolio],
])('should render facets for %p', (qualifier) => {
expect(renderSidebar({ component: mockComponent({ qualifier }) })).toMatchSnapshot();
});

it('should render facets when my issues are selected', () => {
expect(renderSidebar({ myIssues: true })).toMatchSnapshot();
});

it('should not render developer nominative facets when asked not to', () => {
expect(
renderSidebar({
appState: mockAppState({
settings: { [GlobalSettingKeys.DeveloperAggregatedInfoDisabled]: 'true' },
}),
})
).toMatchSnapshot();
});

const renderSidebar = (props?: Partial<Sidebar['props']>) => {
return flatten(
mapChildren(
shallow<Sidebar>(
<Sidebar
appState={mockAppState({
settings: { [GlobalSettingKeys.DeveloperAggregatedInfoDisabled]: 'false' },
})}
component={undefined}
createdAfterIncludesTime={false}
facets={{}}
loadSearchResultCount={jest.fn()}
loadingFacets={{}}
myIssues={false}
onFacetToggle={jest.fn()}
onFilterChange={jest.fn()}
openFacets={{}}
query={{ types: [''] } as Query}
referencedComponentsById={{}}
referencedComponentsByKey={{}}
referencedLanguages={{}}
referencedRules={{}}
referencedUsers={{}}
{...props}
/>
)
)
);

function mapChildren(wrapper: ShallowWrapper) {
return wrapper.children().map((node) => {
if (typeof node.type() === 'symbol') {
return node.children().map((n) => n.name());
}
return node.name();
});
}
};

+ 0
- 267
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StandardFacet-test.tsx View File

@@ -1,267 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
import * as React from 'react';
import ListStyleFacetFooter from '../../../../components/facet/ListStyleFacetFooter';
import { getStandards } from '../../../../helpers/security-standard';
import { click } from '../../../../helpers/testUtils';
import { Query } from '../../utils';
import StandardFacet from '../StandardFacet';

jest.mock('../../../../helpers/security-standard', () => ({
...jest.requireActual('../../../../helpers/security-standard'),
getStandards: jest.fn().mockResolvedValue({
owaspTop10: {
a1: {
title: 'Injection',
},
a2: {
title: 'Broken Authentication',
},
},
'owaspTop10-2021': {
a1: {
title: 'Injection',
},
a2: {
title: 'Broken Authentication',
},
},
cwe: {
unknown: {
title: 'No CWE associated',
},
'1004': {
title: "Sensitive Cookie Without 'HttpOnly' Flag",
},
},
sonarsourceSecurity: {
'sql-injection': {
title: 'SQL Injection',
},
'command-injection': {
title: 'Command Injection',
},
},
}),
}));

it('should render closed', () => {
expect(shallowRender()).toMatchSnapshot();
expect(getStandards).not.toHaveBeenCalled();
});

it('should toggle standards facet', () => {
const onToggle = jest.fn();
const wrapper = shallowRender({ onToggle });
click(wrapper.children('FacetHeader'));
expect(onToggle).toHaveBeenCalledWith('standards');
});

it('should clear standards facet', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange });
wrapper.children('FacetHeader').prop<Function>('onClear')();
expect(onChange).toHaveBeenCalledWith({
cwe: [],
owaspTop10: [],
'owaspTop10-2021': [],
sonarsourceSecurity: [],
standards: [],
});
});

it('should render sub-facets', () => {
expect(
shallowRender({
cwe: ['42'],
cweOpen: true,
cweStats: { 42: 5, 173: 3 },
open: true,
owaspTop10: ['a3'],
owaspTop10Open: true,
owaspTop10Stats: { a1: 15, a3: 5 },
sonarsourceSecurity: ['sql-injection'],
sonarsourceSecurityOpen: true,
sonarsourceSecurityStats: { 'sql-injection': 12 },
})
).toMatchSnapshot();
expect(getStandards).toHaveBeenCalled();
});

it('should show sonarsource facet more button', () => {
const wrapper = shallowRender({
open: true,
sonarsourceSecurity: ['traceability', 'permission', 'others'],
sonarsourceSecurityOpen: true,
sonarsourceSecurityStats: {
'buffer-overflow': 3,
'sql-injection': 3,
rce: 3,
'object-injection': 3,
'command-injection': 3,
'path-traversal-injection': 3,
'ldap-injection': 3,
'xpath-injection': 3,
'expression-lang-injection': 3,
'log-injection': 3,
xxe: 3,
xss: 3,
dos: 3,
ssrf: 3,
csrf: 3,
'http-response-splitting': 3,
'open-redirect': 3,
'weak-cryptography': 3,
auth: 3,
'insecure-conf': 3,
'file-manipulation': 3,
'encrypt-data': 3,
traceability: 3,
permission: 3,
others: 3,
},
});

expect(wrapper.find(ListStyleFacetFooter).exists()).toBe(true);

wrapper.setState({ showFullSonarSourceList: true });
expect(wrapper.find(ListStyleFacetFooter).exists()).toBe(false);
});

it('should render empty sub-facet', () => {
expect(
shallowRender({
open: true,
'owaspTop10-2021': [],
'owaspTop10-2021Open': true,
'owaspTop10-2021Stats': {},
}).find('FacetBox[property="owaspTop10-2021"]')
).toMatchSnapshot();
});

it('should select items', () => {
const onChange = jest.fn();
const wrapper = shallowRender({
cwe: ['42'],
cweOpen: true,
cweStats: { 42: 5, 173: 3 },
onChange,
open: true,
owaspTop10: ['a3'],
owaspTop10Open: true,
owaspTop10Stats: { a1: 15, a3: 5 },
sonarsourceSecurity: ['command-injection'],
sonarsourceSecurityOpen: true,
sonarsourceSecurityStats: { 'sql-injection': 10 },
});

selectAndCheck('owaspTop10', 'a1');
selectAndCheck('owaspTop10', 'a1', true, ['a1', 'a3']);
selectAndCheck('sonarsourceSecurity', 'sql-injection');

function selectAndCheck(facet: string, value: string, multiple = false, expectedValue = [value]) {
wrapper
.find(`FacetBox[property="${facet}"]`)
.find(`FacetItem[value="${value}"]`)
.prop<Function>('onClick')(value, multiple);
expect(onChange).toHaveBeenLastCalledWith({ [facet]: expectedValue });
}
});

it('should toggle sub-facets', () => {
const onToggle = jest.fn();
const wrapper = shallowRender({ onToggle, open: true });
click(wrapper.find('FacetBox[property="owaspTop10"]').children('FacetHeader'));
expect(onToggle).toHaveBeenLastCalledWith('owaspTop10');
click(wrapper.find('FacetBox[property="sonarsourceSecurity"]').children('FacetHeader'));
expect(onToggle).toHaveBeenLastCalledWith('sonarsourceSecurity');
});

it('should display correct selection', () => {
const wrapper = shallowRender({
open: true,
owaspTop10: ['a1', 'a3'],
'owaspTop10-2021': ['a1', 'a2'],
cwe: ['42', '1111', 'unknown'],
sonarsourceSecurity: ['sql-injection', 'others'],
});
checkValues('standards', [
'SONAR SQL Injection',
'Others',
'OWASP A1 - a1 title',
'OWASP A3',
'OWASP A1 - a1 title',
'OWASP A2',
'CWE-42 - cwe-42 title',
'CWE-1111',
'Unknown CWE',
]);
checkValues('owaspTop10', ['A1 - a1 title', 'A3']);
checkValues('owaspTop10-2021', ['A1 - a1 title', 'A2']);
checkValues('sonarsourceSecurity', ['SQL Injection', 'Others']);

function checkValues(property: string, values: string[]) {
expect(
wrapper.find(`FacetBox[property="${property}"]`).children('FacetHeader').prop('values')
).toEqual(values);
}
});

function shallowRender(props: Partial<StandardFacet['props']> = {}) {
const wrapper = shallow(
<StandardFacet
cwe={[]}
cweOpen={false}
cweStats={{}}
fetchingCwe={false}
fetchingOwaspTop10={false}
fetchingOwaspTop10-2021={false}
fetchingSonarSourceSecurity={false}
loadSearchResultCount={jest.fn()}
onChange={jest.fn()}
onToggle={jest.fn()}
open={false}
owaspTop10={[]}
owaspTop10Open={false}
owaspTop10Stats={{}}
owaspTop10-2021={[]}
owaspTop10-2021Open={false}
owaspTop10-2021Stats={{}}
query={{} as Query}
sonarsourceSecurity={[]}
sonarsourceSecurityOpen={false}
sonarsourceSecurityStats={{}}
{...props}
/>
);
wrapper.setState({
standards: {
owaspTop10: { a1: { title: 'a1 title' } },
'owaspTop10-2021': { a1: { title: 'a1 title' } },
cwe: { 42: { title: 'cwe-42 title' }, unknown: { title: 'Unknown CWE' } },
sonarsourceSecurity: {
'sql-injection': { title: 'SQL Injection' },
others: { title: 'Others' },
},
},
});
return wrapper;
}

+ 0
- 78
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/StatusFacet-test.tsx View File

@@ -1,78 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
import * as React from 'react';
import { click } from '../../../../helpers/testUtils';
import StatusFacet from '../StatusFacet';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should toggle status facet', () => {
const onToggle = jest.fn();
const wrapper = shallowRender({ onToggle });
click(wrapper.children('FacetHeader'));
expect(onToggle).toHaveBeenCalledWith('statuses');
});

it('should clear status facet', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange, statuses: ['CONFIRMED'] });
wrapper.children('FacetHeader').prop<Function>('onClear')();
expect(onChange).toHaveBeenCalledWith({ statuses: [] });
});

it('should select a status', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange });
clickAndCheck('OPEN');
clickAndCheck('CONFIRMED', true, ['CONFIRMED', 'OPEN']);
clickAndCheck('CLOSED');

function clickAndCheck(status: string, multiple = false, expected = [status]) {
wrapper.find(`FacetItemsList`).find(`FacetItem[value="${status}"]`).prop<Function>('onClick')(
status,
multiple
);
expect(onChange).toHaveBeenLastCalledWith({ statuses: expected });
wrapper.setProps({ statuses: expected });
}
});

function shallowRender(props: Partial<StatusFacet['props']> = {}) {
return shallow(
<StatusFacet
fetching={false}
onChange={jest.fn()}
onToggle={jest.fn()}
open={true}
stats={{
OPEN: 104,
CONFIRMED: 8,
REOPENED: 0,
RESOLVED: 0,
CLOSED: 8,
}}
statuses={[]}
{...props}
/>
);
}

+ 0
- 70
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/TypeFacet-test.tsx View File

@@ -1,70 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2023 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 { shallow } from 'enzyme';
import * as React from 'react';
import { click } from '../../../../helpers/testUtils';
import TypeFacet from '../TypeFacet';

it('should render open by default', () => {
expect(shallowRender({ types: ['VULNERABILITY', 'CODE_SMELL'] })).toMatchSnapshot();
});

it('should toggle type facet', () => {
const onToggle = jest.fn();
const wrapper = shallowRender({ onToggle });
click(wrapper.children('FacetHeader'));
expect(onToggle).toHaveBeenCalledWith('types');
});

it('should clear types facet', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange, types: ['BUGS'] });
wrapper.children('FacetHeader').prop<Function>('onClear')();
expect(onChange).toHaveBeenCalledWith({ types: [] });
});

it('should select a type', () => {
const onChange = jest.fn();
const wrapper = shallowRender({ onChange });
clickAndCheck('CODE_SMELL');
clickAndCheck('VULNERABILITY', true, ['CODE_SMELL', 'VULNERABILITY']);

function clickAndCheck(type: string, multiple = false, expected = [type]) {
wrapper.find(`FacetItemsList`).find(`FacetItem[value="${type}"]`).prop<Function>('onClick')(
type,
multiple
);
expect(onChange).toHaveBeenLastCalledWith({ types: expected });
wrapper.setProps({ types: expected });
}
});

function shallowRender(props: Partial<TypeFacet['props']> = {}) {
return shallow(
<TypeFacet
fetching={false}
onChange={jest.fn()}
onToggle={jest.fn()}
stats={{ BUG: 0, VULNERABILITY: 2, CODE_SMELL: 5, SECURITY_HOTSPOT: 1 }}
types={[]}
{...props}
/>
);
}

+ 0
- 96
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AssigneeFacet-test.tsx.snap View File

@@ -1,96 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render 1`] = `
<ListStyleFacet
facetHeader="issues.facet.assignees"
fetching={false}
getFacetItemText={[Function]}
getSearchResultKey={[Function]}
getSearchResultText={[Function]}
getSortedItems={[Function]}
loadSearchResultCount={[Function]}
maxInitialItems={15}
maxItems={100}
minSearchLength={2}
onChange={[MockFunction]}
onClear={[Function]}
onItemClick={[Function]}
onSearch={[Function]}
onToggle={[MockFunction]}
open={true}
property="assignees"
query={{}}
renderFacetItem={[Function]}
renderSearchResult={[Function]}
searchPlaceholder="search.search_for_users"
stats={
{
"": 5,
"bar": 7,
"baz": 6,
"foo": 13,
}
}
values={
[
"foo",
]
}
/>
`;

exports[`test behavior should correctly render facet item 1`] = `
<React.Fragment>
<withAppStateContext(Avatar)
className="little-spacer-right"
name="Name Baz"
size={16}
/>
Name Baz
</React.Fragment>
`;

exports[`test behavior should correctly render facet item 2`] = `
<React.Fragment>
<withAppStateContext(Avatar)
className="little-spacer-right"
name="foo"
size={16}
/>
user.x_deleted.foo
</React.Fragment>
`;

exports[`test behavior should correctly render search result correctly 1`] = `
<React.Fragment>
<withAppStateContext(Avatar)
className="little-spacer-right"
name="Name Bar"
size={16}
/>
<React.Fragment>
Name
<mark>
Ba
</mark>
r
</React.Fragment>
</React.Fragment>
`;

exports[`test behavior should correctly render search result correctly 2`] = `
<React.Fragment>
<withAppStateContext(Avatar)
className="little-spacer-right"
name="foo"
size={16}
/>
<React.Fragment>
user.x_deleted.
<mark>
fo
</mark>
o
</React.Fragment>
</React.Fragment>
`;

+ 0
- 26
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/AuthorFacet-test.tsx.snap View File

@@ -1,26 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<ListStyleFacet
facetHeader="issues.facet.authors"
fetching={false}
getFacetItemText={[Function]}
getSearchResultKey={[Function]}
getSearchResultText={[Function]}
loadSearchResultCount={[Function]}
maxInitialItems={15}
maxItems={100}
minSearchLength={2}
onChange={[MockFunction]}
onSearch={[Function]}
onToggle={[MockFunction]}
open={true}
property="author"
query={{}}
renderFacetItem={[Function]}
renderSearchResult={[Function]}
searchPlaceholder="search.search_for_authors"
stats={{}}
values={[]}
/>
`;

+ 0
- 548
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/CreationDateFacet-test.tsx.snap View File

@@ -1,548 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly for createdInLast month 1`] = `
<FacetBox
property="createdAt"
>
<FacetHeader
fetching={false}
name="issues.facet.createdAt"
onClear={[Function]}
onClick={[Function]}
open={true}
values={
[
"issues.facet.createdAt.last_month",
]
}
/>
<div>
<div
className="search-navigator-date-facet-selection"
>
<DateRangeInput
alignEndDateCalandarRight={true}
onChange={[Function]}
value={
{
"from": undefined,
"to": undefined,
}
}
/>
</div>
<div
className="spacer-top issues-predefined-periods"
>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.all"
onClick={[Function]}
tooltip="issues.facet.createdAt.all"
value=""
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_week"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_week"
value="1w"
/>
<FacetItem
active={true}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_month"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_month"
value="1m"
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_year"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_year"
value="1y"
/>
</div>
</div>
</FacetBox>
`;

exports[`should render correctly for createdInLast week 1`] = `
<FacetBox
property="createdAt"
>
<FacetHeader
fetching={false}
name="issues.facet.createdAt"
onClear={[Function]}
onClick={[Function]}
open={true}
values={
[
"issues.facet.createdAt.last_week",
]
}
/>
<div>
<div
className="search-navigator-date-facet-selection"
>
<DateRangeInput
alignEndDateCalandarRight={true}
onChange={[Function]}
value={
{
"from": undefined,
"to": undefined,
}
}
/>
</div>
<div
className="spacer-top issues-predefined-periods"
>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.all"
onClick={[Function]}
tooltip="issues.facet.createdAt.all"
value=""
/>
<FacetItem
active={true}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_week"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_week"
value="1w"
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_month"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_month"
value="1m"
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_year"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_year"
value="1y"
/>
</div>
</div>
</FacetBox>
`;

exports[`should render correctly for createdInLast year 1`] = `
<FacetBox
property="createdAt"
>
<FacetHeader
fetching={false}
name="issues.facet.createdAt"
onClear={[Function]}
onClick={[Function]}
open={true}
values={
[
"issues.facet.createdAt.last_year",
]
}
/>
<div>
<div
className="search-navigator-date-facet-selection"
>
<DateRangeInput
alignEndDateCalandarRight={true}
onChange={[Function]}
value={
{
"from": undefined,
"to": undefined,
}
}
/>
</div>
<div
className="spacer-top issues-predefined-periods"
>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.all"
onClick={[Function]}
tooltip="issues.facet.createdAt.all"
value=""
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_week"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_week"
value="1w"
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_month"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_month"
value="1m"
/>
<FacetItem
active={true}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_year"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_year"
value="1y"
/>
</div>
</div>
</FacetBox>
`;

exports[`should render correctly: clear 1`] = `
<FacetBox
property="createdAt"
>
<FacetHeader
fetching={false}
name="issues.facet.createdAt"
onClear={[Function]}
onClick={[Function]}
open={true}
values={[]}
/>
<div>
<div
className="search-navigator-date-facet-selection"
>
<DateRangeInput
alignEndDateCalandarRight={true}
onChange={[Function]}
value={
{
"from": undefined,
"to": undefined,
}
}
/>
</div>
<div
className="spacer-top issues-predefined-periods"
>
<FacetItem
active={true}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.all"
onClick={[Function]}
tooltip="issues.facet.createdAt.all"
value=""
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_week"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_week"
value="1w"
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_month"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_month"
value="1m"
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_year"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_year"
value="1y"
/>
</div>
</div>
</FacetBox>
`;

exports[`should render correctly: closed 1`] = `
<FacetBox
property="createdAt"
>
<FacetHeader
fetching={false}
name="issues.facet.createdAt"
onClear={[Function]}
onClick={[Function]}
open={false}
values={[]}
/>
</FacetBox>
`;

exports[`should render correctly: created after 1`] = `
<FacetBox
property="createdAt"
>
<FacetHeader
fetching={false}
name="issues.facet.createdAt"
onClear={[Function]}
onClick={[Function]}
open={true}
values={
[
"formatted.Invalid Date",
]
}
/>
<div
className="search-navigator-facet-container"
>
<strong>
after
</strong>
<DateTimeFormatter
date={Date { NaN }}
/>
</div>
</FacetBox>
`;

exports[`should render correctly: created after timestamp 1`] = `
<FacetBox
property="createdAt"
>
<FacetHeader
fetching={false}
name="issues.facet.createdAt"
onClear={[Function]}
onClick={[Function]}
open={true}
values={
[
"formatted.Invalid Date",
]
}
/>
<div
className="search-navigator-facet-container"
>
<strong>
after
</strong>
<DateTimeFormatter
date={Date { NaN }}
/>
</div>
</FacetBox>
`;

exports[`should render correctly: created at 1`] = `
<FacetBox
property="createdAt"
>
<FacetHeader
fetching={false}
name="issues.facet.createdAt"
onClear={[Function]}
onClick={[Function]}
open={true}
values={
[
"formatted.2019.05.21T13:33:00Z",
]
}
/>
<div
className="search-navigator-facet-container"
>
<DateTimeFormatter
date="2019.05.21T13:33:00Z"
/>
<br />
<span
className="note"
>
<DateFromNow
date="2019.05.21T13:33:00Z"
/>
</span>
</div>
</FacetBox>
`;

exports[`should render correctly: portfolio 1`] = `
<FacetBox
property="createdAt"
>
<FacetHeader
fetching={false}
name="issues.facet.createdAt"
onClear={[Function]}
onClick={[Function]}
open={true}
values={[]}
/>
<div>
<div
className="search-navigator-date-facet-selection"
>
<DateRangeInput
alignEndDateCalandarRight={true}
onChange={[Function]}
value={
{
"from": undefined,
"to": undefined,
}
}
/>
</div>
<div
className="spacer-top issues-predefined-periods"
>
<FacetItem
active={true}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.all"
onClick={[Function]}
tooltip="issues.facet.createdAt.all"
value=""
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_week"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_week"
value="1w"
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_month"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_month"
value="1m"
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_year"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_year"
value="1y"
/>
</div>
</div>
</FacetBox>
`;

exports[`should render correctly: project 1`] = `
<FacetBox
property="createdAt"
>
<FacetHeader
fetching={false}
name="issues.facet.createdAt"
onClear={[Function]}
onClick={[Function]}
open={true}
values={[]}
/>
<div>
<div
className="search-navigator-date-facet-selection"
>
<DateRangeInput
alignEndDateCalandarRight={true}
onChange={[Function]}
value={
{
"from": undefined,
"to": undefined,
}
}
/>
</div>
<div
className="spacer-top issues-predefined-periods"
>
<FacetItem
active={true}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.all"
onClick={[Function]}
tooltip="issues.facet.createdAt.all"
value=""
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_week"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_week"
value="1w"
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_month"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_month"
value="1m"
/>
<FacetItem
active={false}
halfWidth={false}
loading={false}
name="issues.facet.createdAt.last_year"
onClick={[Function]}
tooltip="issues.facet.createdAt.last_year"
value="1y"
/>
</div>
</div>
</FacetBox>
`;

+ 0
- 55
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/DirectoryFacet-test.tsx.snap View File

@@ -1,55 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<ListStyleFacet
facetHeader="issues.facet.directories"
fetching={false}
getFacetItemText={[Function]}
getSearchResultKey={[Function]}
getSearchResultText={[Function]}
loadSearchResultCount={[Function]}
maxInitialItems={15}
maxItems={100}
minSearchLength={3}
onChange={[MockFunction]}
onSearch={[Function]}
onToggle={[MockFunction]}
open={false}
property="directories"
query={{}}
renderFacetItem={[Function]}
renderSearchResult={[Function]}
searchPlaceholder="search.search_for_directories"
values={
[
"foo/",
"bar/baz/",
]
}
/>
`;

exports[`should render correctly 2`] = `
<React.Fragment>
<QualifierIcon
className="little-spacer-right"
qualifier="DIR"
/>
<React.Fragment>
<mark>
foo
</mark>
/bar
</React.Fragment>
</React.Fragment>
`;

exports[`should render correctly 3`] = `
<React.Fragment>
<QualifierIcon
className="little-spacer-right"
qualifier="DIR"
/>
foo/bar
</React.Fragment>
`;

+ 0
- 59
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/FileFacet-test.tsx.snap View File

@@ -1,59 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<ListStyleFacet
facetHeader="issues.facet.files"
fetching={false}
getFacetItemText={[Function]}
getSearchResultKey={[Function]}
getSearchResultText={[Function]}
loadSearchResultCount={[Function]}
maxInitialItems={15}
maxItems={100}
minSearchLength={3}
onChange={[MockFunction]}
onSearch={[Function]}
onToggle={[MockFunction]}
open={false}
property="files"
query={{}}
renderFacetItem={[Function]}
renderSearchResult={[Function]}
searchPlaceholder="search.search_for_files"
values={
[
"foo",
"bar",
]
}
/>
`;

exports[`should render correctly 2`] = `
<React.Fragment>
<QualifierIcon
className="little-spacer-right"
qualifier="FIL"
/>
<React.Fragment>
foo
/
<React.Fragment>
<mark>
bar
</mark>
.js
</React.Fragment>
</React.Fragment>
</React.Fragment>
`;

exports[`should render correctly 3`] = `
<React.Fragment>
<QualifierIcon
className="little-spacer-right"
qualifier="FIL"
/>
fooUuid
</React.Fragment>
`;

+ 0
- 50
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/ProjectFacet-test.tsx.snap View File

@@ -1,50 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ListStyleFacet's renderers should include renderFacetItem 1`] = `
<span>
<QualifierIcon
className="little-spacer-right"
qualifier="TRK"
/>
First Project Name
</span>
`;

exports[`ListStyleFacet's renderers should include renderSearchResult 1`] = `
<React.Fragment>
<QualifierIcon
className="little-spacer-right"
qualifier="TRK"
/>
<React.Fragment>
<mark>
First
</mark>
Project Name
</React.Fragment>
</React.Fragment>
`;

exports[`should render correctly 1`] = `
<ListStyleFacet
facetHeader="issues.facet.projects"
fetching={false}
getFacetItemText={[Function]}
getSearchResultKey={[Function]}
getSearchResultText={[Function]}
loadSearchResultCount={[Function]}
maxInitialItems={15}
maxItems={100}
minSearchLength={2}
onChange={[MockFunction]}
onSearch={[Function]}
onToggle={[MockFunction]}
open={false}
property="projects"
query={{}}
renderFacetItem={[Function]}
renderSearchResult={[Function]}
searchPlaceholder="search.search_for_projects"
values={[]}
/>
`;

+ 0
- 30
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/RuleFacet-test.tsx.snap View File

@@ -1,30 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<ListStyleFacet
facetHeader="issues.facet.rules"
fetching={true}
getFacetItemText={[Function]}
getSearchResultKey={[Function]}
getSearchResultText={[Function]}
loadSearchResultCount={[Function]}
maxInitialItems={15}
maxItems={100}
minSearchLength={2}
onChange={[MockFunction]}
onSearch={[Function]}
onToggle={[MockFunction]}
open={false}
property="rules"
query={{}}
renderFacetItem={[Function]}
renderSearchResult={[Function]}
searchPlaceholder="search.search_for_rules"
stats={{}}
values={
[
"r1",
]
}
/>
`;

+ 0
- 210
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/ScopeFacet-test.tsx.snap View File

@@ -1,210 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly: active facet 1`] = `
<FacetBox
property="scopes"
>
<FacetHeader
fetching={true}
name="issues.facet.scopes"
onClear={[Function]}
onClick={[Function]}
open={true}
values={
[
"issue.scope.MAIN",
]
}
/>
<FacetItemsList>
<FacetItem
active={true}
halfWidth={false}
key="MAIN"
loading={false}
name={
<span
className="display-flex-center"
>
<QualifierIcon
aria-hidden={true}
className="little-spacer-right"
qualifier="FIL"
/>
issue.scope.MAIN
</span>
}
onClick={[Function]}
value="MAIN"
/>
<FacetItem
active={false}
halfWidth={false}
key="TEST"
loading={false}
name={
<span
className="display-flex-center"
>
<QualifierIcon
aria-hidden={true}
className="little-spacer-right"
qualifier="UTS"
/>
issue.scope.TEST
</span>
}
onClick={[Function]}
value="TEST"
/>
</FacetItemsList>
<MultipleSelectionHint
options={0}
values={1}
/>
</FacetBox>
`;

exports[`should render correctly: default 1`] = `
<FacetBox
property="scopes"
>
<FacetHeader
fetching={true}
name="issues.facet.scopes"
onClear={[Function]}
onClick={[Function]}
open={false}
values={[]}
/>
</FacetBox>
`;

exports[`should render correctly: disabled facet 1`] = `
<FacetBox
property="scopes"
>
<FacetHeader
fetching={true}
name="issues.facet.scopes"
onClear={[Function]}
onClick={[Function]}
open={true}
values={[]}
/>
<FacetItemsList>
<FacetItem
active={false}
halfWidth={false}
key="MAIN"
loading={false}
name={
<span
className="display-flex-center"
>
<QualifierIcon
aria-hidden={true}
className="little-spacer-right"
qualifier="FIL"
/>
issue.scope.MAIN
</span>
}
onClick={[Function]}
stat={0}
value="MAIN"
/>
<FacetItem
active={false}
halfWidth={false}
key="TEST"
loading={false}
name={
<span
className="display-flex-center"
>
<QualifierIcon
aria-hidden={true}
className="little-spacer-right"
qualifier="UTS"
/>
issue.scope.TEST
</span>
}
onClick={[Function]}
value="TEST"
/>
</FacetItemsList>
<MultipleSelectionHint
options={1}
values={0}
/>
</FacetBox>
`;

exports[`should render correctly: open 1`] = `
<FacetBox
property="scopes"
>
<FacetHeader
fetching={true}
name="issues.facet.scopes"
onClear={[Function]}
onClick={[Function]}
open={true}
values={[]}
/>
<FacetItemsList>
<FacetItem
active={false}
halfWidth={false}
key="MAIN"
loading={false}
name={
<span
className="display-flex-center"
>
<QualifierIcon
aria-hidden={true}
className="little-spacer-right"
qualifier="FIL"
/>
issue.scope.MAIN
</span>
}
onClick={[Function]}
value="MAIN"
/>
<FacetItem
active={false}
halfWidth={false}
key="TEST"
loading={false}
name={
<span
className="display-flex-center"
>
<QualifierIcon
aria-hidden={true}
className="little-spacer-right"
qualifier="UTS"
/>
issue.scope.TEST
</span>
}
onClick={[Function]}
value="TEST"
/>
</FacetItemsList>
<MultipleSelectionHint
options={0}
values={0}
/>
</FacetBox>
`;

+ 0
- 127
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/Sidebar-test.tsx.snap View File

@@ -1,127 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should not render developer nominative facets when asked not to 1`] = `
[
"TypeFacet",
"SeverityFacet",
"ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
"withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
]
`;

exports[`should render facets for "APP" 1`] = `
[
"PeriodFilter",
"TypeFacet",
"SeverityFacet",
"ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
"withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
"AssigneeFacet",
"AuthorFacet",
]
`;

exports[`should render facets for "SVW" 1`] = `
[
"TypeFacet",
"SeverityFacet",
"ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
"withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
"AssigneeFacet",
"AuthorFacet",
]
`;

exports[`should render facets for "VW" 1`] = `
[
"TypeFacet",
"SeverityFacet",
"ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
"withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
"AssigneeFacet",
"AuthorFacet",
]
`;

exports[`should render facets for global page 1`] = `
[
"TypeFacet",
"SeverityFacet",
"ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
"withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
"AssigneeFacet",
"AuthorFacet",
]
`;

exports[`should render facets for project 1`] = `
[
"PeriodFilter",
"TypeFacet",
"SeverityFacet",
"ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
"withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"DirectoryFacet",
"FileFacet",
"AssigneeFacet",
"AuthorFacet",
]
`;

exports[`should render facets when my issues are selected 1`] = `
[
"TypeFacet",
"SeverityFacet",
"ScopeFacet",
"ResolutionFacet",
"StatusFacet",
"StandardFacet",
"injectIntl(CreationDateFacet)",
"withLanguagesContext(LanguageFacet)",
"RuleFacet",
"TagFacet",
"ProjectFacet",
"AuthorFacet",
]
`;

+ 0
- 180
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StandardFacet-test.tsx.snap View File

@@ -1,180 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render closed 1`] = `
<FacetBox
property="standards"
>
<FacetHeader
name="issues.facet.standards"
onClear={[Function]}
onClick={[Function]}
open={false}
values={[]}
/>
</FacetBox>
`;

exports[`should render empty sub-facet 1`] = `
<FacetBox
className="is-inner"
property="owaspTop10-2021"
>
<FacetHeader
fetching={false}
name="issues.facet.owaspTop10_2021"
onClick={[Function]}
open={true}
values={[]}
/>
<div
className="search-navigator-facet-empty little-spacer-top"
>
no_results
</div>
<MultipleSelectionHint
options={0}
values={0}
/>
</FacetBox>
`;

exports[`should render sub-facets 1`] = `
<FacetBox
property="standards"
>
<FacetHeader
name="issues.facet.standards"
onClear={[Function]}
onClick={[Function]}
open={true}
values={
[
"SONAR SQL Injection",
"OWASP A3",
"CWE-42 - cwe-42 title",
]
}
/>
<FacetBox
className="is-inner"
property="sonarsourceSecurity"
>
<FacetHeader
fetching={false}
name="issues.facet.sonarsourceSecurity"
onClick={[Function]}
open={true}
values={
[
"SQL Injection",
]
}
/>
<FacetItemsList>
<FacetItem
active={true}
halfWidth={false}
key="sql-injection"
loading={false}
name="SQL Injection"
onClick={[Function]}
stat="12"
tooltip="SQL Injection"
value="sql-injection"
/>
</FacetItemsList>
<MultipleSelectionHint
options={1}
values={1}
/>
</FacetBox>
<FacetBox
className="is-inner"
property="owaspTop10-2021"
>
<FacetHeader
fetching={false}
name="issues.facet.owaspTop10_2021"
onClick={[Function]}
open={false}
values={[]}
/>
</FacetBox>
<FacetBox
className="is-inner"
property="owaspTop10"
>
<FacetHeader
fetching={false}
name="issues.facet.owaspTop10"
onClick={[Function]}
open={true}
values={
[
"A3",
]
}
/>
<FacetItemsList>
<FacetItem
active={false}
halfWidth={false}
key="a1"
loading={false}
name="A1 - a1 title"
onClick={[Function]}
stat="15"
tooltip="A1 - a1 title"
value="a1"
/>
<FacetItem
active={true}
halfWidth={false}
key="a3"
loading={false}
name="A3"
onClick={[Function]}
stat="5"
tooltip="A3"
value="a3"
/>
</FacetItemsList>
<MultipleSelectionHint
options={2}
values={1}
/>
</FacetBox>
<ListStyleFacet
className="is-inner"
facetHeader="issues.facet.cwe"
fetching={false}
getFacetItemText={[Function]}
getSearchResultKey={[Function]}
getSearchResultText={[Function]}
loadSearchResultCount={[Function]}
maxInitialItems={15}
maxItems={100}
minSearchLength={2}
onChange={[MockFunction]}
onSearch={[Function]}
onToggle={[MockFunction]}
open={true}
property="cwe"
query={{}}
renderFacetItem={[Function]}
renderSearchResult={[Function]}
searchPlaceholder="search.search_for_cwe"
stats={
{
"173": 3,
"42": 5,
}
}
values={
[
"42",
]
}
/>
</FacetBox>
`;

+ 0
- 97
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/StatusFacet-test.tsx.snap View File

@@ -1,97 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<FacetBox
property="statuses"
>
<FacetHeader
fetching={false}
name="issues.facet.statuses"
onClear={[Function]}
onClick={[Function]}
open={true}
values={[]}
/>
<FacetItemsList>
<FacetItem
active={false}
halfWidth={true}
key="OPEN"
loading={false}
name={
<StatusHelper
status="OPEN"
/>
}
onClick={[Function]}
stat="104"
tooltip="issue.status.OPEN"
value="OPEN"
/>
<FacetItem
active={false}
halfWidth={true}
key="CONFIRMED"
loading={false}
name={
<StatusHelper
status="CONFIRMED"
/>
}
onClick={[Function]}
stat="8"
tooltip="issue.status.CONFIRMED"
value="CONFIRMED"
/>
<FacetItem
active={false}
halfWidth={true}
key="REOPENED"
loading={false}
name={
<StatusHelper
status="REOPENED"
/>
}
onClick={[Function]}
stat={0}
tooltip="issue.status.REOPENED"
value="REOPENED"
/>
<FacetItem
active={false}
halfWidth={true}
key="RESOLVED"
loading={false}
name={
<StatusHelper
status="RESOLVED"
/>
}
onClick={[Function]}
stat={0}
tooltip="issue.status.RESOLVED"
value="RESOLVED"
/>
<FacetItem
active={false}
halfWidth={true}
key="CLOSED"
loading={false}
name={
<StatusHelper
status="CLOSED"
/>
}
onClick={[Function]}
stat="8"
tooltip="issue.status.CLOSED"
value="CLOSED"
/>
</FacetItemsList>
<MultipleSelectionHint
options={5}
values={0}
/>
</FacetBox>
`;

+ 0
- 90
server/sonar-web/src/main/js/apps/issues/sidebar/__tests__/__snapshots__/TypeFacet-test.tsx.snap View File

@@ -1,90 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render open by default 1`] = `
<FacetBox
property="types"
>
<FacetHeader
fetching={false}
name="issues.facet.types"
onClear={[Function]}
onClick={[Function]}
open={true}
values={
[
"issue.type.VULNERABILITY",
"issue.type.CODE_SMELL",
]
}
/>
<FacetItemsList>
<FacetItem
active={false}
halfWidth={false}
key="BUG"
loading={false}
name={
<span
className="display-flex-center"
>
<IssueTypeIcon
className="little-spacer-right"
query="BUG"
/>
issue.type.BUG
</span>
}
onClick={[Function]}
stat={0}
value="BUG"
/>
<FacetItem
active={true}
halfWidth={false}
key="VULNERABILITY"
loading={false}
name={
<span
className="display-flex-center"
>
<IssueTypeIcon
className="little-spacer-right"
query="VULNERABILITY"
/>
issue.type.VULNERABILITY
</span>
}
onClick={[Function]}
stat="2"
value="VULNERABILITY"
/>
<FacetItem
active={true}
halfWidth={false}
key="CODE_SMELL"
loading={false}
name={
<span
className="display-flex-center"
>
<IssueTypeIcon
className="little-spacer-right"
query="CODE_SMELL"
/>
issue.type.CODE_SMELL
</span>
}
onClick={[Function]}
stat="5"
value="CODE_SMELL"
/>
</FacetItemsList>
<MultipleSelectionHint
options={4}
values={2}
/>
</FacetBox>
`;

+ 2
- 0
server/sonar-web/src/main/js/components/SourceViewer/__tests__/__snapshots__/SourceViewer-test.tsx.snap View File

@@ -93,6 +93,7 @@ exports[`should render correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -128,6 +129,7 @@ exports[`should render correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",

+ 1
- 0
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssueList-test.tsx.snap View File

@@ -33,6 +33,7 @@ exports[`should render issues 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",

+ 4
- 0
server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap View File

@@ -27,6 +27,7 @@ exports[`should render hotspots correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -64,6 +65,7 @@ exports[`should render hotspots correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -125,6 +127,7 @@ exports[`should render issues correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -176,6 +179,7 @@ exports[`should render issues correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",

+ 1
- 0
server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/issue-test.tsx.snap View File

@@ -37,6 +37,7 @@ exports[`should render issues correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",

+ 4
- 4
server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx View File

@@ -20,7 +20,7 @@
import classNames from 'classnames';
import * as React from 'react';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { IssueResponse } from '../../../types/issues';
import { IssueResolution, IssueResponse, IssueType as IssueTypeEnum } from '../../../types/issues';
import { Issue, RawQuery } from '../../../types/types';
import { updateIssue } from '../actions';
import IssueAssign from './IssueAssign';
@@ -81,8 +81,8 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> {
handleTransition = (issue: Issue) => {
this.props.onChange(issue);
if (
issue.resolution === 'FALSE-POSITIVE' ||
(issue.resolution === 'WONTFIX' && issue.type !== 'SECURITY_HOTSPOT')
issue.resolution === IssueResolution.FalsePositive ||
(issue.resolution === IssueResolution.WontFix && issue.type !== IssueTypeEnum.SecurityHotspot)
) {
this.toggleComment(true, translate('issue.comment.explain_why'), true);
}
@@ -96,7 +96,7 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> {
const canSetType = issue.actions.includes('set_type');
const canSetTags = issue.actions.includes('set_tags');
const hasTransitions = issue.transitions && issue.transitions.length > 0;
const isSecurityHotspot = issue.type === 'SECURITY_HOTSPOT';
const isSecurityHotspot = issue.type === IssueTypeEnum.SecurityHotspot;

return (
<div className={classNames(className, 'issue-actions')}>

+ 19
- 0
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap View File

@@ -34,6 +34,7 @@ exports[`should render commentable correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -78,6 +79,7 @@ exports[`should render commentable correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -122,6 +124,7 @@ exports[`should render commentable correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -166,6 +169,7 @@ exports[`should render commentable correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -222,6 +226,7 @@ exports[`should render commentable correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -276,6 +281,7 @@ exports[`should render effort correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -319,6 +325,7 @@ exports[`should render effort correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -362,6 +369,7 @@ exports[`should render effort correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -405,6 +413,7 @@ exports[`should render effort correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -461,6 +470,7 @@ exports[`should render effort correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -514,6 +524,7 @@ exports[`should render issue correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -556,6 +567,7 @@ exports[`should render issue correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -598,6 +610,7 @@ exports[`should render issue correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -640,6 +653,7 @@ exports[`should render issue correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -686,6 +700,7 @@ exports[`should render issue correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -739,6 +754,7 @@ exports[`should render security hotspot correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -781,6 +797,7 @@ exports[`should render security hotspot correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -823,6 +840,7 @@ exports[`should render security hotspot correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -869,6 +887,7 @@ exports[`should render security hotspot correctly 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",

+ 9
- 0
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap View File

@@ -25,6 +25,7 @@ exports[`should render correctly: default 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -71,6 +72,7 @@ exports[`should render correctly: default 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -146,6 +148,7 @@ exports[`should render correctly: with filter 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -192,6 +195,7 @@ exports[`should render correctly: with filter 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -262,6 +266,7 @@ exports[`should render correctly: with filter 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
@@ -358,6 +363,7 @@ exports[`should render correctly: with multi locations 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [
{
"component": "main.js",
@@ -472,6 +478,7 @@ exports[`should render correctly: with multi locations 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [
{
"component": "main.js",
@@ -634,6 +641,7 @@ exports[`should render correctly: with multi locations and link 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [
{
"component": "main.js",
@@ -748,6 +756,7 @@ exports[`should render correctly: with multi locations and link 1`] = `
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [
{
"component": "main.js",

+ 37
- 0
server/sonar-web/src/main/js/helpers/mocks/issues.ts View File

@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { Query } from '../../apps/issues/utils';
import { ReferencedRule } from '../../types/issues';
import { IssueChangelog } from '../../types/types';

@@ -44,3 +45,39 @@ export function mockIssueChangelog(overrides: Partial<IssueChangelog> = {}): Iss
...overrides,
};
}

export function mockQuery(overrides: Partial<Query> = {}): Query {
return {
assigned: false,
assignees: [],
author: [],
createdAfter: undefined,
createdAt: '',
createdBefore: undefined,
createdInLast: '',
cwe: [],
directories: [],
files: [],
issues: [],
languages: [],
owaspTop10: [],
'owaspTop10-2021': [],
'pciDss-3.2': [],
'pciDss-4.0': [],
'owaspAsvs-4.0': [],
owaspAsvsLevel: '',
projects: [],
resolutions: [],
resolved: false,
rules: [],
scopes: [],
severities: [],
inNewCodePeriod: false,
sonarsourceSecurity: [],
sort: '',
statuses: [],
tags: [],
types: [],
...overrides,
};
}

+ 8
- 5
server/sonar-web/src/main/js/helpers/testMocks.ts View File

@@ -25,7 +25,7 @@ import { Location, Router } from '../components/hoc/withRouter';
import { AppState } from '../types/appstate';
import { RuleRepository } from '../types/coding-rules';
import { EditionKey } from '../types/editions';
import { IssueType, RawIssue } from '../types/issues';
import { IssueScope, IssueSeverity, IssueStatus, IssueType, RawIssue } from '../types/issues';
import { Language } from '../types/languages';
import { DumpStatus, DumpTask } from '../types/project-dump';
import { TaskStatuses } from '../types/tasks';
@@ -286,13 +286,15 @@ export function mockRawIssue(withLocations = false, overrides: Partial<RawIssue>
actions: [],
component: 'main.js',
key: 'AVsae-CQS-9G3txfbFN2',
creationDate: '2023-01-15T09:36:01+0100',
line: 25,
project: 'myproject',
rule: 'javascript:S1067',
severity: 'MAJOR',
status: 'OPEN',
severity: IssueSeverity.Major,
status: IssueStatus.Open,
textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 },
type: IssueType.CodeSmell,
scope: IssueScope.Main,
...overrides,
};

@@ -334,9 +336,10 @@ export function mockIssue(withLocations = false, overrides: Partial<Issue> = {})
projectName: 'Foo',
rule: 'javascript:S1067',
ruleName: 'foo',
scope: IssueScope.Main,
secondaryLocations: [],
severity: 'MAJOR',
status: 'OPEN',
severity: IssueSeverity.Major,
status: IssueStatus.Open,
textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 },
transitions: [],
type: 'BUG',

+ 18
- 0
server/sonar-web/src/main/js/types/issues.ts View File

@@ -29,11 +29,27 @@ export enum IssueType {
SecurityHotspot = 'SECURITY_HOTSPOT',
}

export enum IssueSeverity {
Blocker = 'BLOCKER',
Minor = 'MINOR',
Critical = 'CRITICAL',
Info = 'INFO',
Major = 'MAJOR',
}

export enum IssueScope {
Main = 'MAIN',
Test = 'TEST',
}

export enum IssueResolution {
Unresolved = '',
FalsePositive = 'FALSE-POSITIVE',
Fixed = 'FIXED',
Removed = 'REMOVED',
WontFix = 'WONTFIX',
}

export enum IssueStatus {
Open = 'OPEN',
Confirmed = 'CONFIRMED',
@@ -76,6 +92,7 @@ export interface RawIssue {
assignee?: string;
author?: string;
comments?: Array<Comment>;
creationDate: string;
component: string;
flows?: Array<{
type?: string;
@@ -93,6 +110,7 @@ export interface RawIssue {
status: string;
textRange?: TextRange;
type: IssueType;
scope: string;
ruleDescriptionContextKey?: string;
ruleStatus?: string;
quickFixAvailable?: boolean;

+ 1
- 0
server/sonar-web/src/main/js/types/types.ts View File

@@ -268,6 +268,7 @@ export interface Issue {
ruleDescriptionContextKey?: string;
ruleName: string;
ruleStatus?: string;
scope: string;
secondaryLocations: FlowLocation[];
severity: string;
status: string;

Loading…
Cancel
Save