Browse Source

SONAR-18448 Migrate the components/issue tests to RTL

tags/10.0.0.68432
Wouter Admiraal 1 year ago
parent
commit
2628add44f
54 changed files with 985 additions and 4845 deletions
  1. 101
    33
      server/sonar-web/src/main/js/api/mocks/IssuesServiceMock.ts
  2. 10
    4
      server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
  3. 20
    11
      server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx
  4. 0
    4
      server/sonar-web/src/main/js/components/issue/Issue.css
  5. 1
    1
      server/sonar-web/src/main/js/components/issue/Issue.tsx
  6. 554
    0
      server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx
  7. 0
    62
      server/sonar-web/src/main/js/components/issue/__tests__/IssueView-test.tsx
  8. 0
    224
      server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/IssueView-test.tsx.snap
  9. 0
    62
      server/sonar-web/src/main/js/components/issue/__tests__/__snapshots__/issue-test.tsx.snap
  10. 0
    113
      server/sonar-web/src/main/js/components/issue/__tests__/issue-test.tsx
  11. 22
    20
      server/sonar-web/src/main/js/components/issue/components/IssueActionsBar.tsx
  12. 40
    35
      server/sonar-web/src/main/js/components/issue/components/IssueChangelog.tsx
  13. 9
    9
      server/sonar-web/src/main/js/components/issue/components/IssueChangelogDiff.tsx
  14. 2
    2
      server/sonar-web/src/main/js/components/issue/components/IssueCommentLine.tsx
  15. 2
    2
      server/sonar-web/src/main/js/components/issue/components/IssueMessageTags.tsx
  16. 4
    2
      server/sonar-web/src/main/js/components/issue/components/IssueTitleBar.tsx
  17. 12
    13
      server/sonar-web/src/main/js/components/issue/components/IssueView.tsx
  18. 0
    124
      server/sonar-web/src/main/js/components/issue/components/__tests__/IssueActionsBar-test.tsx
  19. 0
    66
      server/sonar-web/src/main/js/components/issue/components/__tests__/IssueAssign-test.tsx
  20. 0
    55
      server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelog-test.tsx
  21. 47
    58
      server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelogDiff-test.tsx
  22. 0
    68
      server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentLine-test.tsx
  23. 0
    66
      server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx
  24. 0
    55
      server/sonar-web/src/main/js/components/issue/components/__tests__/IssueSeverity-test.tsx
  25. 0
    50
      server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTitleBar-test.tsx
  26. 0
    51
      server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTransition-test.tsx
  27. 0
    42
      server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.tsx
  28. 0
    910
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueActionsBar-test.tsx.snap
  29. 0
    152
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap
  30. 0
    88
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueChangelog-test.tsx.snap
  31. 0
    55
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueChangelogDiff-test.tsx.snap
  32. 0
    266
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueCommentLine-test.tsx.snap
  33. 0
    181
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueMessage-test.tsx.snap
  34. 0
    89
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueSeverity-test.tsx.snap
  35. 0
    849
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTitleBar-test.tsx.snap
  36. 0
    8
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueTransition-test.tsx.snap
  37. 0
    11
      server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueType-test.tsx.snap
  38. 2
    2
      server/sonar-web/src/main/js/components/issue/popups/ChangelogPopup.tsx
  39. 3
    3
      server/sonar-web/src/main/js/components/issue/popups/CommentForm.tsx
  40. 99
    103
      server/sonar-web/src/main/js/components/issue/popups/SimilarIssuesPopup.tsx
  41. 0
    132
      server/sonar-web/src/main/js/components/issue/popups/__tests__/ChangelogPopup-test.tsx
  42. 0
    31
      server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentDeletePopup-test.tsx
  43. 0
    50
      server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentPopup-test.tsx
  44. 0
    52
      server/sonar-web/src/main/js/components/issue/popups/__tests__/SetAssigneePopup-test.tsx
  45. 0
    59
      server/sonar-web/src/main/js/components/issue/popups/__tests__/SimilarIssuesPopup-test.tsx
  46. 0
    319
      server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/ChangelogPopup-test.tsx.snap
  47. 0
    23
      server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/CommentDeletePopup-test.tsx.snap
  48. 0
    70
      server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SetAssigneePopup-test.tsx.snap
  49. 0
    144
      server/sonar-web/src/main/js/components/issue/popups/__tests__/__snapshots__/SimilarIssuesPopup-test.tsx.snap
  50. 2
    2
      server/sonar-web/src/main/js/helpers/constants.ts
  51. 28
    8
      server/sonar-web/src/main/js/helpers/mocks/issues.ts
  52. 1
    0
      server/sonar-web/src/main/js/helpers/testMocks.ts
  53. 24
    6
      server/sonar-web/src/main/js/types/issues.ts
  54. 2
    0
      sonar-core/src/main/resources/org/sonar/l10n/core.properties

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

@@ -17,13 +17,13 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { cloneDeep, keyBy, times } from 'lodash';
import { cloneDeep, keyBy, times, uniqueId } from 'lodash';
import { RuleDescriptionSections } from '../../apps/coding-rules/rule';
import { mockIssueChangelog } from '../../helpers/mocks/issues';
import { mockSnippetsByComponent } from '../../helpers/mocks/sources';
import { RequestData } from '../../helpers/request';
import { getStandards } from '../../helpers/security-standard';
import {
mockCurrentUser,
mockLoggedInUser,
mockPaging,
mockRawIssue,
@@ -31,10 +31,12 @@ import {
} from '../../helpers/testMocks';
import {
ASSIGNEE_ME,
IssueActions,
IssueResolution,
IssueScope,
IssueSeverity,
IssueStatus,
IssueTransition,
IssueType,
RawFacet,
RawIssue,
@@ -49,12 +51,13 @@ import {
RuleDetails,
SnippetsByComponent,
} from '../../types/types';
import { NoticeType } from '../../types/users';
import { LoggedInUser, NoticeType } from '../../types/users';
import {
addIssueComment,
bulkChangeIssues,
deleteIssueComment,
editIssueComment,
getIssueChangelog,
getIssueFlowSnippets,
searchIssues,
searchIssueTags,
@@ -97,11 +100,13 @@ interface IssueData {

export default class IssuesServiceMock {
isAdmin = false;
currentUser: LoggedInUser;
standards?: Standards;
defaultList: IssueData[];
list: IssueData[];

constructor() {
this.currentUser = mockLoggedInUser();
this.defaultList = [
{
issue: mockRawIssue(false, {
@@ -336,7 +341,7 @@ export default class IssuesServiceMock {
},
{
issue: mockRawIssue(false, {
actions: ['set_type', 'set_tags', 'comment', 'set_severity', 'assign'],
actions: Object.values(IssueActions),
transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
key: 'issue2',
component: 'foo:test2.js',
@@ -391,7 +396,7 @@ export default class IssuesServiceMock {
},
{
issue: mockRawIssue(false, {
actions: ['set_type', 'set_tags', 'comment', 'set_severity', 'assign'],
actions: Object.values(IssueActions),
transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
key: 'issue4',
component: 'foo:test2.js',
@@ -444,19 +449,29 @@ export default class IssuesServiceMock {
(getCurrentUser as jest.Mock).mockImplementation(this.handleGetCurrentUser);
(dismissNotice as jest.Mock).mockImplementation(this.handleDismissNotification);
(setIssueType as jest.Mock).mockImplementation(this.handleSetIssueType);
(setIssueAssignee as jest.Mock).mockImplementation(this.handleSetIssueAssignee);
jest.mocked(setIssueAssignee).mockImplementation(this.handleSetIssueAssignee);
(setIssueSeverity as jest.Mock).mockImplementation(this.handleSetIssueSeverity);
(setIssueTransition as jest.Mock).mockImplementation(this.handleSetIssueTransition);
(setIssueTags as jest.Mock).mockImplementation(this.handleSetIssueTags);
(addIssueComment as jest.Mock).mockImplementation(this.handleAddComment);
(editIssueComment as jest.Mock).mockImplementation(this.handleEditComment);
(deleteIssueComment as jest.Mock).mockImplementation(this.handleDeleteComment);
jest.mocked(addIssueComment).mockImplementation(this.handleAddComment);
jest.mocked(editIssueComment).mockImplementation(this.handleEditComment);
jest.mocked(deleteIssueComment).mockImplementation(this.handleDeleteComment);
(searchUsers as jest.Mock).mockImplementation(this.handleSearchUsers);
(searchIssueTags as jest.Mock).mockImplementation(this.handleSearchIssueTags);
jest.mocked(getIssueChangelog).mockImplementation(this.handleGetIssueChangelog);
}

reset = () => {
this.list = cloneDeep(this.defaultList);
this.currentUser = mockLoggedInUser();
};

setCurrentUser = (user: LoggedInUser) => {
this.currentUser = user;
};

setIssueList = (list: IssueData[]) => {
this.list = list;
};

async getStandards(): Promise<Standards> {
@@ -703,7 +718,7 @@ export default class IssuesServiceMock {
};

handleGetCurrentUser = () => {
return this.reply(mockCurrentUser());
return this.reply(this.currentUser);
};

handleDismissNotification = (noticeType: NoticeType) => {
@@ -723,27 +738,45 @@ export default class IssuesServiceMock {
};

handleSetIssueAssignee = (data: { issue: string; assignee?: string }) => {
return this.getActionsResponse({ assignee: data.assignee }, data.issue);
return this.getActionsResponse(
{ assignee: data.assignee === '_me' ? this.currentUser.login : data.assignee },
data.issue
);
};

handleSetIssueTransition = (data: { issue: string; transition: string }) => {
const statusMap: { [key: string]: string } = {
confirm: 'CONFIRMED',
unconfirm: 'REOPENED',
resolve: 'RESOLVED',
wontfix: 'RESOLVED',
falsepositive: 'RESOLVED',
const statusMap: { [key: string]: IssueStatus } = {
[IssueTransition.Confirm]: IssueStatus.Confirmed,
[IssueTransition.UnConfirm]: IssueStatus.Reopened,
[IssueTransition.Resolve]: IssueStatus.Resolved,
[IssueTransition.WontFix]: IssueStatus.Resolved,
[IssueTransition.FalsePositive]: IssueStatus.Resolved,
};
const transitionMap: Dict<string[]> = {
REOPENED: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
OPEN: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
CONFIRMED: ['resolve', 'unconfirm', 'falsepositive', 'wontfix'],
RESOLVED: ['reopen'],
const transitionMap: Dict<IssueTransition[]> = {
[IssueStatus.Reopened]: [
IssueTransition.Confirm,
IssueTransition.Resolve,
IssueTransition.FalsePositive,
IssueTransition.WontFix,
],
[IssueStatus.Open]: [
IssueTransition.Confirm,
IssueTransition.Resolve,
IssueTransition.FalsePositive,
IssueTransition.WontFix,
],
[IssueStatus.Confirmed]: [
IssueTransition.Resolve,
IssueTransition.UnConfirm,
IssueTransition.FalsePositive,
IssueTransition.WontFix,
],
[IssueStatus.Resolved]: [IssueTransition.Reopen],
};

const resolutionMap: Dict<string> = {
wontfix: IssueResolution.WontFix,
falsepositive: IssueResolution.FalsePositive,
[IssueTransition.WontFix]: IssueResolution.WontFix,
[IssueTransition.FalsePositive]: IssueResolution.FalsePositive,
};

return this.getActionsResponse(
@@ -762,14 +795,13 @@ export default class IssuesServiceMock {
};

handleAddComment = (data: { issue: string; text: string }) => {
// For comment its little more complex to get comment Id
return this.getActionsResponse(
{
comments: [
{
createdAt: '2022-07-28T11:30:04+0200',
htmlText: data.text,
key: '1234',
key: uniqueId(),
login: 'admin',
markdown: data.text,
updatable: true,
@@ -781,31 +813,40 @@ export default class IssuesServiceMock {
};

handleEditComment = (data: { comment: string; text: string }) => {
// For comment its little more complex to get comment Id
const issueKey = this.list.find((i) => i.issue.comments?.some((c) => c.key === data.comment))
?.issue.key;
if (!issueKey) {
throw new Error(`Couldn't find issue for comment ${data.comment}`);
}
return this.getActionsResponse(
{
comments: [
{
createdAt: '2022-07-28T11:30:04+0200',
htmlText: data.text,
key: '1234',
key: data.comment,
login: 'admin',
markdown: data.text,
updatable: true,
},
],
},
'issue2'
issueKey
);
};

handleDeleteComment = () => {
// For comment its little more complex to get comment Id
handleDeleteComment = (data: { comment: string }) => {
const issue = this.list.find((i) =>
i.issue.comments?.some((c) => c.key === data.comment)
)?.issue;
if (!issue) {
throw new Error(`Couldn't find issue for comment ${data.comment}`);
}
return this.getActionsResponse(
{
comments: [],
comments: issue.comments?.filter((c) => c.key !== data.comment),
},
'issue2'
issue.key
);
};

@@ -817,6 +858,33 @@ export default class IssuesServiceMock {
return this.reply(['accessibility', 'android']);
};

handleGetIssueChangelog = (_issue: string) => {
return this.reply({
changelog: [
mockIssueChangelog({
creationDate: '2018-09-01',
diffs: [
{
key: 'status',
newValue: IssueStatus.Reopened,
oldValue: IssueStatus.Confirmed,
},
],
}),
mockIssueChangelog({
creationDate: '2018-10-01',
diffs: [
{
key: 'assign',
newValue: 'darth.vader',
oldValue: 'luke.skywalker',
},
],
}),
],
});
};

getActionsResponse = (overrides: Partial<RawIssue>, issueKey: string) => {
const issueDataSelected = this.list.find((l) => l.issue.key === issueKey);


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

@@ -290,8 +290,10 @@ describe('issues app', () => {
// Improve this to include all the bulk change fonctionality
it('should be able to bulk change', async () => {
const user = userEvent.setup();
const currentUser = mockLoggedInUser();
issuesHandler.setIsAdmin(true);
renderIssueApp(mockLoggedInUser());
issuesHandler.setCurrentUser(currentUser);
renderIssueApp(currentUser);

// Check that the bulk button has correct behavior
expect(screen.getByRole('button', { name: 'bulk_change' })).toBeDisabled();
@@ -481,7 +483,9 @@ describe('issues app', () => {

it('should allow to set creation date', async () => {
const user = userEvent.setup();
renderIssueApp(mockLoggedInUser());
const currentUser = mockLoggedInUser();
issuesHandler.setCurrentUser(currentUser);
renderIssueApp(currentUser);
await waitOnDataLoaded();

// Select a specific date range such that only one issue matches
@@ -503,7 +507,9 @@ describe('issues app', () => {

it('should allow to only show my issues', async () => {
const user = userEvent.setup();
renderIssueApp(mockLoggedInUser());
const currentUser = mockLoggedInUser();
issuesHandler.setCurrentUser(currentUser);
renderIssueApp(currentUser);
await waitOnDataLoaded();

// By default, it should show all issues
@@ -895,7 +901,7 @@ describe('issues item', () => {

expect(
screen.getByRole('heading', {
name: 'Issue with tags sonar-lint-icon issue.resolution.badge.DEPRECATED',
name: 'Issue with tags issue.quick_fix_available_with_sonarlint_no_link issue.resolution.badge.DEPRECATED',
})
).toBeInTheDocument();
});

+ 20
- 11
server/sonar-web/src/main/js/components/SourceViewer/components/MeasuresOverlay.tsx View File

@@ -39,7 +39,8 @@ import {
} from '../../../helpers/measures';
import { getBranchLikeUrl } from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
import { IssueType as IssueTypeEnum } from '../../../types/issues';
import { IssueSeverity, IssueType as IssueTypeEnum } from '../../../types/issues';
import { MetricType } from '../../../types/metrics';
import { FacetValue, IssueType, MeasureEnhanced, SourceViewerFile } from '../../../types/types';
import Measure from '../../measure/Measure';
import SeverityHelper from '../../shared/SeverityHelper';
@@ -199,7 +200,9 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
<IssueTypeIcon className="little-spacer-right" query={f.val} />
{translate('issue.type', f.val)}
</span>
<span className="measure-value">{formatMeasure(f.count, 'SHORT_INT')}</span>
<span className="measure-value">
{formatMeasure(f.count, MetricType.ShortInteger)}
</span>
</div>
)
)}
@@ -207,14 +210,18 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
)}
{severitiesFacet && (
<div className="measures">
{sortBy(severitiesFacet, (f) => SEVERITIES.indexOf(f.val)).map((f) => (
<div className="measure measure-one-line" key={f.val}>
<span className="measure-name">
<SeverityHelper severity={f.val} />
</span>
<span className="measure-value">{formatMeasure(f.count, 'SHORT_INT')}</span>
</div>
))}
{sortBy(severitiesFacet, (f) => SEVERITIES.indexOf(f.val as IssueSeverity)).map(
(f) => (
<div className="measure measure-one-line" key={f.val}>
<span className="measure-name">
<SeverityHelper severity={f.val} />
</span>
<span className="measure-value">
{formatMeasure(f.count, MetricType.ShortInteger)}
</span>
</div>
)
)}
</div>
)}
{tagsFacet && (
@@ -225,7 +232,9 @@ export default class MeasuresOverlay extends React.PureComponent<Props, State> {
<TagsIcon className="little-spacer-right" />
{f.val}
</span>
<span className="measure-value">{formatMeasure(f.count, 'SHORT_INT')}</span>
<span className="measure-value">
{formatMeasure(f.count, MetricType.ShortInteger)}
</span>
</div>
))}
</div>

+ 0
- 4
server/sonar-web/src/main/js/components/issue/Issue.css View File

@@ -31,10 +31,6 @@
cursor: initial;
}

.issue.hotspot {
background-color: var(--hotspotBgColor);
}

.issue.selected,
.issue-message-box.selected {
box-shadow: none;

+ 1
- 1
server/sonar-web/src/main/js/components/issue/Issue.tsx View File

@@ -25,8 +25,8 @@ import { getKeyboardShortcutEnabled } from '../../helpers/preferences';
import { BranchLike } from '../../types/branch-like';
import { Issue as TypeIssue } from '../../types/types';
import { updateIssue } from './actions';
import IssueView from './components/IssueView';
import './Issue.css';
import IssueView from './IssueView';

interface Props {
branchLike?: BranchLike;

+ 554
- 0
server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx View File

@@ -0,0 +1,554 @@
/*
* 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 { act, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { omit, pick } from 'lodash';
import * as React from 'react';
import { byRole, byText } from 'testing-library-selector';
import IssuesServiceMock from '../../../api/mocks/IssuesServiceMock';
import { KeyboardKeys } from '../../../helpers/keycodes';
import { mockIssueComment } from '../../../helpers/mocks/issues';
import { mockIssue, mockLoggedInUser, mockRawIssue } from '../../../helpers/testMocks';
import { findTooltipWithContent, renderApp } from '../../../helpers/testReactTestingUtils';
import {
IssueActions,
IssueSeverity,
IssueStatus,
IssueTransition,
IssueType,
} from '../../../types/issues';
import { RuleStatus } from '../../../types/rules';
import { IssueComment } from '../../../types/types';
import Issue from '../Issue';

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

jest.mock('../../../helpers/preferences', () => ({
getKeyboardShortcutEnabled: jest.fn(() => true),
}));

const issuesHandler = new IssuesServiceMock();

beforeEach(() => {
issuesHandler.reset();
});

describe('rendering', () => {
it('should render correctly with comments', () => {
const { ui } = getPageObject();
renderIssue({ issue: mockIssue(false, { comments: [mockIssueCommentPosted4YearsAgo()] }) });

const comments = within(ui.commentsList());
expect(comments.getByText('Leïa Skywalker')).toBeInTheDocument();
expect(comments.getByRole('listitem')).toHaveTextContent('This is a comment, bud');
expect(comments.getByRole('listitem')).toHaveTextContent('issue.comment.posted_on4 years ago');
});

it('should render correctly for locations, issue message, line, permalink, why, and effort', async () => {
const { ui } = getPageObject();
const issue = mockIssue(true, { effort: '2 days', message: 'This is an issue' });
const onClick = jest.fn();
renderIssue({ issue, displayLocationsCount: true, displayWhyIsThisAnIssue: true, onClick });

expect(ui.locationsBadge(7).get()).toBeInTheDocument();
expect(ui.lineInfo(26).get()).toBeInTheDocument();
expect(ui.permalink.get()).toHaveAttribute(
'href',
`/project/issues?issues=${issue.key}&open=${issue.key}&id=${issue.project}`
);
expect(ui.whyLink.get()).toBeInTheDocument();
expect(ui.effort('2 days').get()).toBeInTheDocument();
await ui.clickIssueMessage();
expect(onClick).toHaveBeenCalledWith(issue.key);
});

it.each([RuleStatus.Deprecated, RuleStatus.Removed])(
'should render correctly for a %s rule',
(ruleStatus) => {
const { ui } = getPageObject();
renderIssue({ issue: mockIssue(false, { ruleStatus }) });
expect(ui.ruleStatusBadge(ruleStatus).get()).toBeInTheDocument();
}
);

it('should render correctly for external rule engines', () => {
renderIssue({ issue: mockIssue(true, { externalRuleEngine: 'ESLINT' }) });
expect(screen.getByText('ESLINT')).toBeInTheDocument();
});

it('should render the SonarLint icon correctly', () => {
renderIssue({ issue: mockIssue(false, { quickFixAvailable: true }) });
expect(
findTooltipWithContent('issue.quick_fix_available_with_sonarlint_no_link')
).toBeInTheDocument();
});

it('should render correctly with a checkbox', async () => {
const { ui } = getPageObject();
const onCheck = jest.fn();
const issue = mockIssue();
renderIssue({ onCheck, issue });
await ui.toggleCheckbox();
expect(onCheck).toHaveBeenCalledWith(issue.key);
});

it('should correctly render the changelog', async () => {
const { ui } = getPageObject();
renderIssue();

await ui.showChangelog();
expect(
ui.changelogRow('status', IssueStatus.Confirmed, IssueStatus.Reopened).get()
).toBeInTheDocument();
expect(ui.changelogRow('assign', 'luke.skywalker', 'darth.vader').get()).toBeInTheDocument();
});
});

describe('updating', () => {
it('should allow updating the type', async () => {
const { ui } = getPageObject();
const issue = mockRawIssue(false, {
type: IssueType.Bug,
actions: [IssueActions.SetType],
});
issuesHandler.setIssueList([{ issue, snippets: {} }]);
renderIssue({ issue: mockIssue(false, { ...pick(issue, 'actions', 'key', 'type') }) });

await ui.updateType(IssueType.Bug, IssueType.CodeSmell);
expect(ui.updateTypeBtn(IssueType.CodeSmell).get()).toBeInTheDocument();
});

it('should allow updating the severity', async () => {
const { ui } = getPageObject();
const issue = mockRawIssue(false, {
severity: IssueSeverity.Blocker,
actions: [IssueActions.SetSeverity],
});
issuesHandler.setIssueList([{ issue, snippets: {} }]);
renderIssue({ issue: mockIssue(false, { ...pick(issue, 'actions', 'key', 'severity') }) });

await ui.updateSeverity(IssueSeverity.Blocker, IssueSeverity.Minor);
expect(ui.updateSeverityBtn(IssueSeverity.Minor).get()).toBeInTheDocument();
});

it('should allow updating the status', async () => {
const { ui } = getPageObject();
const issue = mockRawIssue(false, {
status: IssueStatus.Open,
transitions: [IssueTransition.Confirm, IssueTransition.UnConfirm],
});
issuesHandler.setIssueList([{ issue, snippets: {} }]);
renderIssue({
issue: mockIssue(false, { ...pick(issue, 'key', 'status', 'transitions') }),
});

await ui.updateStatus(IssueStatus.Open, IssueTransition.Confirm);
expect(ui.updateStatusBtn(IssueStatus.Confirmed).get()).toBeInTheDocument();
});

it('should allow assigning', async () => {
const { ui } = getPageObject();
const issue = mockRawIssue(false, {
assignee: 'leia',
actions: [IssueActions.Assign],
});
issuesHandler.setIssueList([{ issue, snippets: {} }]);
renderIssue({
issue: mockIssue(false, { ...pick(issue, 'actions', 'key', 'assignee') }),
});

await ui.updateAssignee('leia', 'Skywalker');
expect(ui.updateAssigneeBtn('luke').get()).toBeInTheDocument();
});

it('should allow commenting', async () => {
const { ui } = getPageObject();
const issue = mockRawIssue(false, {
actions: [IssueActions.Comment],
});
issuesHandler.setIssueList([{ issue, snippets: {} }]);
renderIssue({ issue: mockIssue(false, { ...pick(issue, 'actions', 'key') }) });

// Create
await ui.addComment('Original content');
const comments = within(ui.commentsList());
expect(comments.getByRole('listitem')).toHaveTextContent('Original content');

// Update
await ui.updateComment('New content');
expect(comments.getByRole('listitem')).toHaveTextContent('New content');

// Delete
await ui.deleteComment();
expect(comments.getByRole('listitem')).toHaveTextContent('New content');
});

it('should allow updating the tags', async () => {
const { ui } = getPageObject();
const issue = mockRawIssue(false, {
tags: [],
actions: [IssueActions.SetTags],
});
issuesHandler.setIssueList([{ issue, snippets: {} }]);
renderIssue({ issue: mockIssue(false, { ...pick(issue, 'actions', 'key', 'tags') }) });

await ui.addTag('accessibility');
await ui.addTag('android', ['accessibility']);
expect(ui.updateTagsBtn(['accessibility', 'android']).get()).toBeInTheDocument();
});
});

it('should correctly handle keyboard shortcuts', async () => {
const { ui } = getPageObject();
const onCheck = jest.fn();
const issue = mockRawIssue(false, {
actions: Object.values(IssueActions),
assignee: 'luke',
transitions: [IssueTransition.Confirm, IssueTransition.UnConfirm],
});
issuesHandler.setIssueList([{ issue, snippets: {} }]);
issuesHandler.setCurrentUser(mockLoggedInUser({ login: 'leia', name: 'Organa' }));
renderIssue({
onCheck,
selected: true,
issue: mockIssue(false, { ...pick(issue, 'actions', 'key', 'assignee', 'transitions') }),
});

await ui.pressTransitionShortcut();
expect(ui.setStatusBtn(IssueTransition.UnConfirm).get()).toBeInTheDocument();
await ui.pressDismissShortcut();

await ui.pressAssignShortcut();
expect(ui.setAssigneeBtn(/Organa/).get()).toBeInTheDocument();
await ui.pressDismissShortcut();

await ui.pressSeverityShortcut();
expect(ui.setSeverityBtn(IssueSeverity.Minor).get()).toBeInTheDocument();
await ui.pressDismissShortcut();

await ui.pressCommentShortcut();
expect(ui.commentTextInput.get()).toBeInTheDocument();
await ui.pressDismissShortcut();

await ui.pressTagsShortcut();
expect(ui.tagsSearchInput.get()).toBeInTheDocument();
await ui.pressDismissShortcut();

await ui.pressCheckShortcut();
expect(onCheck).toHaveBeenCalled();

expect(ui.updateAssigneeBtn('luke').get()).toBeInTheDocument();
await ui.pressAssignToMeShortcut();
expect(ui.updateAssigneeBtn('leia').get()).toBeInTheDocument();
});

it('should correctly handle similar issues filtering', async () => {
const { ui, user } = getPageObject();
const onFilter = jest.fn();
const issue = mockIssue(false, {
ruleName: 'Rule Foo',
tags: ['accessibility', 'owasp'],
projectName: 'Project Bar',
componentLongName: 'main.js',
});
renderIssue({ onFilter, issue });

await ui.showSimilarIssues();
await user.click(ui.similarIssueTypeLink.get());
expect(onFilter).toHaveBeenLastCalledWith('type', issue);

await ui.showSimilarIssues();
await user.click(ui.similarIssueSeverityLink.get());
expect(onFilter).toHaveBeenLastCalledWith('severity', issue);

await ui.showSimilarIssues();
await user.click(ui.similarIssueStatusLink.get());
expect(onFilter).toHaveBeenLastCalledWith('status', issue);

await ui.showSimilarIssues();
await user.click(ui.similarIssueResolutionLink.get());
expect(onFilter).toHaveBeenLastCalledWith('resolution', issue);

await ui.showSimilarIssues();
await user.click(ui.similarIssueAssigneeLink.get());
expect(onFilter).toHaveBeenLastCalledWith('assignee', issue);

await ui.showSimilarIssues();
await user.click(ui.similarIssueRuleLink.get());
expect(onFilter).toHaveBeenLastCalledWith('rule', issue);

await ui.showSimilarIssues();
await user.click(ui.similarIssueTagLink('accessibility').get());
expect(onFilter).toHaveBeenLastCalledWith('tag###accessibility', issue);

await ui.showSimilarIssues();
await user.click(ui.similarIssueTagLink('owasp').get());
expect(onFilter).toHaveBeenLastCalledWith('tag###owasp', issue);

await ui.showSimilarIssues();
await user.click(ui.similarIssueProjectLink.get());
expect(onFilter).toHaveBeenLastCalledWith('project', issue);

await ui.showSimilarIssues();
await user.click(ui.similarIssueFileLink.get());
expect(onFilter).toHaveBeenLastCalledWith('file', issue);
});

function mockIssueCommentPosted4YearsAgo(overrides: Partial<IssueComment> = {}) {
const date = new Date();
date.setFullYear(date.getFullYear() - 4);
return mockIssueComment({
authorName: 'Leïa Skywalker',
createdAt: date.toISOString(),
...overrides,
});
}

function getPageObject() {
const user = userEvent.setup();

const selectors = {
// Issue
ruleStatusBadge: (status: RuleStatus) => byText(`issue.resolution.badge.${status}`),
locationsBadge: (count: number) => byText(count),
lineInfo: (line: number) => byText(`L${line}`),
permalink: byRole('link', { name: 'permalink' }),
effort: (effort: string) => byText(`issue.x_effort.${effort}`),
whyLink: byRole('link', { name: 'issue.why_this_issue.long' }),
checkbox: byRole('checkbox'),
issueMessageBtn: byRole('button', { name: 'This is an issue' }),

// Changelog
toggleChangelogBtn: byRole('button', {
name: /issue.changelog.found_on_x_show_more/,
}),
changelogRow: (key: string, oldValue: string, newValue: string) =>
byRole('row', {
name: new RegExp(
`issue\\.changelog\\.changed_to\\.issue\\.changelog\\.field\\.${key}\\.${newValue} \\(issue\\.changelog\\.was\\.${oldValue}\\)`
),
}),

// Similar issues
toggleSimilarIssuesBtn: byRole('button', { name: 'issue.filter_similar_issues' }),
similarIssueTypeLink: byRole('button', { name: 'issue.type.BUG' }),
similarIssueSeverityLink: byRole('button', { name: 'severity.MAJOR' }),
similarIssueStatusLink: byRole('button', { name: 'issue.status.OPEN' }),
similarIssueResolutionLink: byRole('button', { name: 'unresolved' }),
similarIssueAssigneeLink: byRole('button', { name: 'unassigned' }),
similarIssueRuleLink: byRole('button', { name: 'Rule Foo' }),
similarIssueTagLink: (name: string) => byRole('button', { name }),
similarIssueProjectLink: byRole('button', { name: 'qualifier.TRK Project Bar' }),
similarIssueFileLink: byRole('button', { name: 'qualifier.FIL main.js' }),

// Comment
commentsList: () => {
const list = byRole('list')
.getAll()
.find((el) => el.getAttribute('data-testid') === 'issue-comments');
if (list === undefined) {
throw new Error('Could not find comments list');
}
return list;
},
commentAddBtn: byRole('button', { name: 'issue.comment.add_comment' }),
commentEditBtn: byRole('button', { name: 'issue.comment.edit' }),
commentTextInput: byRole('textbox', { name: 'issue.comment.enter_comment' }),
commentSaveBtn: byRole('button', { name: 'issue.comment.formlink' }),
commentUpdateBtn: byRole('button', { name: 'save' }),
commentDeleteBtn: byRole('button', { name: 'issue.comment.delete' }),
commentConfirmDeleteBtn: byRole('button', { name: 'delete' }),

// Type
updateTypeBtn: (currentType: IssueType) =>
byRole('button', { name: `issue.type.type_x_click_to_change.issue.type.${currentType}` }),
setTypeBtn: (type: IssueType) => byRole('button', { name: `issue.type.${type}` }),

// Severity
updateSeverityBtn: (currentSeverity: IssueSeverity) =>
byRole('button', {
name: `issue.severity.severity_x_click_to_change.severity.${currentSeverity}`,
}),
setSeverityBtn: (severity: IssueSeverity) => byRole('button', { name: `severity.${severity}` }),

// Status
updateStatusBtn: (currentStatus: IssueStatus) =>
byRole('button', {
name: `issue.transition.status_x_click_to_change.issue.status.${currentStatus}`,
}),
setStatusBtn: (transition: IssueTransition) =>
byRole('button', { name: `issue.transition.${transition}` }),

// Assignee
assigneeSearchInput: byRole('searchbox'),
updateAssigneeBtn: (currentAssignee: string) =>
byRole('button', {
name: `issue.assign.assigned_to_x_click_to_change.${currentAssignee}`,
}),
setAssigneeBtn: (name: RegExp) => byRole('button', { name }),

// Tags
tagsSearchInput: byRole('searchbox'),
updateTagsBtn: (currentTags?: string[]) =>
byRole('button', {
name: `tags_list_x.${currentTags ? currentTags.join(', ') : 'issue.no_tag'}`,
}),
toggleTagCheckbox: (name: string) => byRole('checkbox', { name }),
};

const ui = {
...selectors,
async addComment(content: string) {
await user.click(selectors.commentAddBtn.get());
await user.type(selectors.commentTextInput.get(), content);
await act(async () => {
await user.click(selectors.commentSaveBtn.get());
});
},
async updateComment(content: string) {
await user.click(selectors.commentEditBtn.get());
await user.type(selectors.commentTextInput.get(), content);
await act(async () => {
await user.keyboard(`{Control>}{${KeyboardKeys.Enter}}{/Control}`);
});
},
async deleteComment() {
await user.click(selectors.commentDeleteBtn.get());
await act(async () => {
await user.click(selectors.commentConfirmDeleteBtn.get());
});
},
async updateType(currentType: IssueType, newType: IssueType) {
await user.click(selectors.updateTypeBtn(currentType).get());
await act(async () => {
await user.click(selectors.setTypeBtn(newType).get());
});
},
async updateSeverity(currentSeverity: IssueSeverity, newSeverity: IssueSeverity) {
await user.click(selectors.updateSeverityBtn(currentSeverity).get());
await act(async () => {
await user.click(selectors.setSeverityBtn(newSeverity).get());
});
},
async updateStatus(currentStatus: IssueStatus, transition: IssueTransition) {
await user.click(selectors.updateStatusBtn(currentStatus).get());
await act(async () => {
await user.click(selectors.setStatusBtn(transition).get());
});
},
async updateAssignee(currentAssignee: string, newAssignee: string) {
await user.click(selectors.updateAssigneeBtn(currentAssignee).get());
await user.type(selectors.assigneeSearchInput.get(), newAssignee);
await act(async () => {
await user.click(selectors.setAssigneeBtn(new RegExp(newAssignee)).get());
});
},
async addTag(tag: string, currentTagList?: string[]) {
await user.click(selectors.updateTagsBtn(currentTagList).get());
await act(async () => {
await user.click(selectors.toggleTagCheckbox(tag).get());
});
await act(async () => {
await user.keyboard('{Escape}');
});
},
async showChangelog() {
await user.click(selectors.toggleChangelogBtn.get());
},
async showSimilarIssues() {
await user.click(selectors.toggleSimilarIssuesBtn.get());
},
async toggleCheckbox() {
await user.click(selectors.checkbox.get());
},
async clickIssueMessage() {
await user.click(selectors.issueMessageBtn.get());
},
async pressDismissShortcut() {
await act(async () => {
await user.keyboard(`{${KeyboardKeys.Escape}}`);
});
},
async pressTransitionShortcut() {
await act(async () => {
await user.keyboard(`{${KeyboardKeys.KeyF}}`);
});
},
async pressAssignShortcut() {
await act(async () => {
await user.keyboard(`{${KeyboardKeys.KeyA}}`);
});
},
async pressAssignToMeShortcut() {
await act(async () => {
await user.keyboard(`{${KeyboardKeys.KeyM}}`);
});
},
async pressSeverityShortcut() {
await act(async () => {
await user.keyboard(`{${KeyboardKeys.KeyI}}`);
});
},
async pressCommentShortcut() {
await act(async () => {
await user.keyboard(`{${KeyboardKeys.KeyC}}`);
});
},
async pressTagsShortcut() {
await act(async () => {
await user.keyboard(`{${KeyboardKeys.KeyT}}`);
});
},
async pressCheckShortcut() {
await act(async () => {
await user.keyboard(`{${KeyboardKeys.Space}}`);
});
},
};

return { ui, user };
}

function renderIssue(props: Partial<Omit<Issue['props'], 'onChange' | 'onPopupToggle'>> = {}) {
function Wrapper(wrapperProps: Omit<Issue['props'], 'onChange' | 'onPopupToggle'>) {
const [issue, setIssue] = React.useState(wrapperProps.issue);
const [openPopup, setOpenPopup] = React.useState<string | undefined>();
return (
<Issue
issue={issue}
openPopup={openPopup}
onChange={(newIssue) => {
setIssue({ ...issue, ...newIssue });
}}
onPopupToggle={(_key, popup, open) => {
setOpenPopup(open === false ? undefined : popup);
}}
{...omit(wrapperProps, 'issue')}
/>
);
}

return renderApp('/', <Wrapper issue={mockIssue()} selected={false} {...props} />, {
currentUser: mockLoggedInUser({ login: 'leia', name: 'Organa' }),
});
}

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

@@ -1,62 +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 { mockIssue } from '../../../helpers/testMocks';
import IssueView from '../IssueView';

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

it('should render hotspots correctly', () => {
expect(
shallowRender({ issue: mockIssue(false, { type: 'SECURITY_HOTSPOT' }) })
).toMatchSnapshot();
});

function shallowRender(props: Partial<IssueView['props']> = {}) {
return shallow(
<IssueView
issue={mockIssue(false, {
comments: [
{
key: '1',
htmlText: 'My comment',
markdown: 'My comment',
updatable: false,
createdAt: '2017-07-05T09:33:29+0200',
author: 'admin',
authorLogin: 'admin',
authorName: 'Admin',
authorAvatar: 'admin-avatar',
authorActive: true,
},
],
})}
onAssign={jest.fn()}
onChange={jest.fn()}
onClick={jest.fn()}
selected={true}
togglePopup={jest.fn()}
{...props}
/>
);
}

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

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

exports[`should render hotspots correctly 1`] = `
<div
aria-label="Reduce the number of conditional operators (4) used in the expression"
className="issue hotspot selected"
onClick={[Function]}
role="region"
>
<IssueTitleBar
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "SECURITY_HOTSPOT",
}
}
onClick={[Function]}
togglePopup={[MockFunction]}
/>
<IssueActionsBar
className="padded-left"
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "SECURITY_HOTSPOT",
}
}
onAssign={[MockFunction]}
onChange={[MockFunction]}
togglePopup={[MockFunction]}
/>
</div>
`;

exports[`should render issues correctly 1`] = `
<div
aria-label="Reduce the number of conditional operators (4) used in the expression"
className="issue selected"
onClick={[Function]}
role="region"
>
<IssueTitleBar
issue={
{
"actions": [],
"comments": [
{
"author": "admin",
"authorActive": true,
"authorAvatar": "admin-avatar",
"authorLogin": "admin",
"authorName": "Admin",
"createdAt": "2017-07-05T09:33:29+0200",
"htmlText": "My comment",
"key": "1",
"markdown": "My comment",
"updatable": false,
},
],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onClick={[Function]}
togglePopup={[MockFunction]}
/>
<IssueActionsBar
className="padded-left"
issue={
{
"actions": [],
"comments": [
{
"author": "admin",
"authorActive": true,
"authorAvatar": "admin-avatar",
"authorLogin": "admin",
"authorName": "Admin",
"createdAt": "2017-07-05T09:33:29+0200",
"htmlText": "My comment",
"key": "1",
"markdown": "My comment",
"updatable": false,
},
],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onAssign={[MockFunction]}
onChange={[MockFunction]}
togglePopup={[MockFunction]}
/>
<div
className="issue-comments"
>
<IssueCommentLine
comment={
{
"author": "admin",
"authorActive": true,
"authorAvatar": "admin-avatar",
"authorLogin": "admin",
"authorName": "Admin",
"createdAt": "2017-07-05T09:33:29+0200",
"htmlText": "My comment",
"key": "1",
"markdown": "My comment",
"updatable": false,
}
}
key="1"
onDelete={[Function]}
onEdit={[Function]}
/>
</div>
</div>
`;

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

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

exports[`should render issues correctly 1`] = `
<IssueView
displayLocationsCount={true}
displayLocationsLink={false}
issue={
{
"actions": [],
"comments": [
{
"author": "admin",
"authorActive": true,
"authorAvatar": "admin-avatar",
"authorLogin": "admin",
"authorName": "Admin",
"createdAt": "2017-07-05T09:33:29+0200",
"htmlText": "My comment",
"key": "1",
"markdown": "My comment",
"updatable": false,
},
],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onAssign={[Function]}
onChange={[MockFunction]}
onCheck={[MockFunction]}
onClick={[MockFunction]}
onFilter={[MockFunction]}
selected={true}
togglePopup={[Function]}
/>
`;

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

@@ -1,113 +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 { KeyboardKeys } from '../../../helpers/keycodes';
import { mockIssue } from '../../../helpers/testMocks';
import { keydown } from '../../../helpers/testUtils';
import Issue from '../Issue';

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

it('should call the proper function with the proper props when pressing shortcuts (FAMICT)', () => {
const onPopupToggle = jest.fn();
const onCheck = jest.fn();
const issue = mockIssue(false, {
comments: [
{
key: '1',
htmlText: 'My comment',
markdown: 'My comment',
updatable: false,
createdAt: '2017-07-05T09:33:29+0200',
author: 'admin',
authorLogin: 'admin',
authorName: 'Admin',
authorAvatar: 'admin-avatar',
authorActive: true,
},
],
actions: ['assign'],
});

shallowRender({ onPopupToggle, issue, onCheck });
keydown({ key: KeyboardKeys.KeyF, metaKey: true });
expect(onPopupToggle).not.toHaveBeenCalledWith(issue.key, 'transition', undefined);

keydown({ key: KeyboardKeys.KeyF });
expect(onPopupToggle).toHaveBeenCalledWith(issue.key, 'transition', undefined);

keydown({ key: KeyboardKeys.KeyA });
expect(onPopupToggle).toHaveBeenCalledWith(issue.key, 'assign', undefined);
keydown({ key: KeyboardKeys.Escape });

keydown({ key: KeyboardKeys.KeyM });
expect(onPopupToggle).toHaveBeenCalledWith(issue.key, 'assign', false);

keydown({ key: KeyboardKeys.KeyI });
expect(onPopupToggle).toHaveBeenCalledWith(issue.key, 'set-severity', undefined);

keydown({ key: KeyboardKeys.KeyC, metaKey: true });
expect(onPopupToggle).not.toHaveBeenCalledWith(issue.key, 'comment', undefined);

keydown({ key: KeyboardKeys.KeyC });
expect(onPopupToggle).toHaveBeenCalledWith(issue.key, 'comment', undefined);
keydown({ key: KeyboardKeys.Escape });

keydown({ key: KeyboardKeys.KeyT });
expect(onPopupToggle).toHaveBeenCalledWith(issue.key, 'edit-tags', undefined);

keydown({ key: KeyboardKeys.Space });
expect(onCheck).toHaveBeenCalledWith(issue.key);
});

function shallowRender(props: Partial<Issue['props']> = {}) {
return shallow<Issue>(
<Issue
displayLocationsCount={true}
displayLocationsLink={false}
issue={mockIssue(false, {
comments: [
{
key: '1',
htmlText: 'My comment',
markdown: 'My comment',
updatable: false,
createdAt: '2017-07-05T09:33:29+0200',
author: 'admin',
authorLogin: 'admin',
authorName: 'Admin',
authorAvatar: 'admin-avatar',
authorActive: true,
},
],
})}
onChange={jest.fn()}
onCheck={jest.fn()}
onClick={jest.fn()}
onFilter={jest.fn()}
onPopupToggle={jest.fn()}
selected={true}
{...props}
/>
);
}

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

@@ -20,7 +20,12 @@
import classNames from 'classnames';
import * as React from 'react';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { IssueResolution, IssueResponse, IssueType as IssueTypeEnum } from '../../../types/issues';
import {
IssueActions,
IssueResolution,
IssueResponse,
IssueType as IssueTypeEnum,
} from '../../../types/issues';
import { Issue, RawQuery } from '../../../types/types';
import { updateIssue } from '../actions';
import IssueAssign from './IssueAssign';
@@ -90,13 +95,12 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> {

render() {
const { issue, className, showCommentsInPopup } = this.props;
const canAssign = issue.actions.includes('assign');
const canComment = issue.actions.includes('comment');
const canSetSeverity = issue.actions.includes('set_severity');
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 === IssueTypeEnum.SecurityHotspot;
const canAssign = issue.actions.includes(IssueActions.Assign);
const canComment = issue.actions.includes(IssueActions.Comment);
const canSetSeverity = issue.actions.includes(IssueActions.SetSeverity);
const canSetType = issue.actions.includes(IssueActions.SetType);
const canSetTags = issue.actions.includes(IssueActions.SetTags);
const hasTransitions = issue.transitions.length > 0;

return (
<div className={classNames(className, 'issue-actions')}>
@@ -110,17 +114,15 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> {
togglePopup={this.props.togglePopup}
/>
</div>
{!isSecurityHotspot && (
<div className="issue-meta">
<IssueSeverity
canSetSeverity={canSetSeverity}
isOpen={this.props.currentPopup === 'set-severity' && canSetSeverity}
issue={issue}
setIssueProperty={this.setIssueProperty}
togglePopup={this.props.togglePopup}
/>
</div>
)}
<div className="issue-meta">
<IssueSeverity
canSetSeverity={canSetSeverity}
isOpen={this.props.currentPopup === 'set-severity' && canSetSeverity}
issue={issue}
setIssueProperty={this.setIssueProperty}
togglePopup={this.props.togglePopup}
/>
</div>
<div className="issue-meta">
<IssueTransition
hasTransitions={hasTransitions}
@@ -139,7 +141,7 @@ export default class IssueActionsBar extends React.PureComponent<Props, State> {
togglePopup={this.props.togglePopup}
/>
</div>
{!isSecurityHotspot && issue.effort && (
{issue.effort && (
<div className="issue-meta">
<span className="issue-meta-label">
{translateWithParameters('issue.x_effort', issue.effort)}

+ 40
- 35
server/sonar-web/src/main/js/components/issue/components/IssueChangelog.tsx View File

@@ -18,53 +18,58 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { injectIntl, WrappedComponentProps } from 'react-intl';
import { ButtonLink } from '../../../components/controls/buttons';
import Toggler from '../../../components/controls/Toggler';
import DropdownIcon from '../../../components/icons/DropdownIcon';
import { translateWithParameters } from '../../../helpers/l10n';
import { Issue } from '../../../types/types';
import { formatterOption } from '../../intl/DateFormatter';
import DateFromNow from '../../intl/DateFromNow';
import ChangelogPopup from '../popups/ChangelogPopup';

interface Props {
export interface IssueChangelogProps extends WrappedComponentProps {
isOpen: boolean;
issue: Pick<Issue, 'author' | 'creationDate' | 'key'>;
creationDate: string;
togglePopup: (popup: string, show?: boolean) => void;
}

export default class IssueChangelog extends React.PureComponent<Props> {
toggleChangelog = (open?: boolean) => {
this.props.togglePopup('changelog', open);
};

handleClick = () => {
this.toggleChangelog();
};

handleClose = () => {
this.toggleChangelog(false);
};

render() {
return (
<div className="dropdown">
<Toggler
onRequestClose={this.handleClose}
open={this.props.isOpen}
overlay={<ChangelogPopup issue={this.props.issue} />}
function IssueChangelog(props: IssueChangelogProps) {
const {
isOpen,
issue,
creationDate,
intl: { formatDate },
} = props;
return (
<div className="dropdown">
<Toggler
onRequestClose={() => {
props.togglePopup('changelog', false);
}}
open={isOpen}
overlay={<ChangelogPopup issue={issue} />}
>
<ButtonLink
aria-expanded={isOpen}
aria-label={translateWithParameters(
'issue.changelog.found_on_x_show_more',
formatDate(creationDate, formatterOption)
)}
className="issue-action issue-action-with-options js-issue-show-changelog"
onClick={() => {
props.togglePopup('changelog');
}}
>
<ButtonLink
aria-expanded={this.props.isOpen}
className="issue-action issue-action-with-options js-issue-show-changelog"
onClick={this.handleClick}
>
<span className="issue-meta-label">
<DateFromNow date={this.props.creationDate} />
</span>
<DropdownIcon className="little-spacer-left" />
</ButtonLink>
</Toggler>
</div>
);
}
<span className="issue-meta-label">
<DateFromNow date={creationDate} />
</span>
<DropdownIcon className="little-spacer-left" />
</ButtonLink>
</Toggler>
</div>
);
}

export default injectIntl(IssueChangelog);

+ 9
- 9
server/sonar-web/src/main/js/components/issue/components/IssueChangelogDiff.tsx View File

@@ -22,18 +22,18 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import { IssueChangelogDiff as TypeIssueChangelogDiff } from '../../../types/types';

interface Props {
export interface IssueChangelogDiffProps {
diff: TypeIssueChangelogDiff;
}

export default function IssueChangelogDiff({ diff }: Props) {
export default function IssueChangelogDiff({ diff }: IssueChangelogDiffProps) {
if (diff.key === 'file') {
return (
<p>
{translateWithParameters(
'issue.change.file_move',
diff.oldValue || '',
diff.newValue || ''
diff.oldValue ?? '',
diff.newValue ?? ''
)}
</p>
);
@@ -42,8 +42,8 @@ export default function IssueChangelogDiff({ diff }: Props) {
<p>
{translateWithParameters(
'issue.change.from_branch',
diff.oldValue || '',
diff.newValue || ''
diff.oldValue ?? '',
diff.newValue ?? ''
)}
</p>
);
@@ -53,13 +53,13 @@ export default function IssueChangelogDiff({ diff }: Props) {
<p>
{translateWithParameters(
'issue.change.from_non_branch',
diff.oldValue || '',
diff.newValue || ''
diff.oldValue ?? '',
diff.newValue ?? ''
)}
</p>
);
} else if (diff.key === 'line') {
return <p>{translateWithParameters('issue.changelog.line_removed_X', diff.oldValue || '')}</p>;
return <p>{translateWithParameters('issue.changelog.line_removed_X', diff.oldValue ?? '')}</p>;
}

let message;

+ 2
- 2
server/sonar-web/src/main/js/components/issue/components/IssueCommentLine.tsx View File

@@ -85,7 +85,7 @@ export default class IssueCommentLine extends React.PureComponent<Props, State>
? translateWithParameters('user.x_deleted', author)
: author;
return (
<div className="issue-comment">
<li className="issue-comment">
<div className="issue-comment-author" title={displayName}>
<Avatar
className="little-spacer-right"
@@ -145,7 +145,7 @@ export default class IssueCommentLine extends React.PureComponent<Props, State>
</div>
)}
</div>
</div>
</li>
);
}
}

+ 2
- 2
server/sonar-web/src/main/js/components/issue/components/IssueMessageTags.tsx View File

@@ -37,7 +37,7 @@ export default function IssueMessageTags(props: IssueMessageTagsProps) {
const { engine, quickFixAvailable, ruleStatus } = props;

const { externalRulesRepoNames } = React.useContext(WorkspaceContext);
const ruleEngine = (engine && externalRulesRepoNames && externalRulesRepoNames[engine]) || engine;
const ruleEngine = (engine && externalRulesRepoNames[engine]) || engine;

return (
<>
@@ -64,7 +64,7 @@ export default function IssueMessageTags(props: IssueMessageTagsProps) {
<SonarLintIcon
className="it__issues-sonarlint-quick-fix spacer-right"
size={15}
label="sonar-lint-icon"
description={translate('issue.quick_fix_available_with_sonarlint_no_link')}
/>
</Tooltip>
)}

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

@@ -26,6 +26,8 @@ import { translate, translateWithParameters } from '../../../helpers/l10n';
import { formatMeasure } from '../../../helpers/measures';
import { getComponentIssuesUrl } from '../../../helpers/urls';
import { BranchLike } from '../../../types/branch-like';
import { IssueType } from '../../../types/issues';
import { MetricType } from '../../../types/metrics';
import { Issue } from '../../../types/types';
import LocationIndex from '../../common/LocationIndex';
import IssueChangelog from './IssueChangelog';
@@ -57,7 +59,7 @@ export default function IssueTitleBar(props: IssueTitleBarProps) {
<Tooltip
overlay={translateWithParameters(
'issue.this_issue_involves_x_code_locations',
formatMeasure(locationsCount, 'INT')
formatMeasure(locationsCount, MetricType.Integer)
)}
>
<LocationIndex>{locationsCount}</LocationIndex>
@@ -70,7 +72,7 @@ export default function IssueTitleBar(props: IssueTitleBarProps) {
...getBranchLikeQuery(props.branchLike),
issues: issue.key,
open: issue.key,
types: issue.type === 'SECURITY_HOTSPOT' ? issue.type : undefined,
types: issue.type === IssueType.SecurityHotspot ? issue.type : undefined,
});

return (

server/sonar-web/src/main/js/components/issue/IssueView.tsx → server/sonar-web/src/main/js/components/issue/components/IssueView.tsx View File

@@ -19,15 +19,15 @@
*/
import classNames from 'classnames';
import * as React from 'react';
import { deleteIssueComment, editIssueComment } from '../../api/issues';
import Checkbox from '../../components/controls/Checkbox';
import { translate, translateWithParameters } from '../../helpers/l10n';
import { BranchLike } from '../../types/branch-like';
import { Issue } from '../../types/types';
import { updateIssue } from './actions';
import IssueActionsBar from './components/IssueActionsBar';
import IssueCommentLine from './components/IssueCommentLine';
import IssueTitleBar from './components/IssueTitleBar';
import { deleteIssueComment, editIssueComment } from '../../../api/issues';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { BranchLike } from '../../../types/branch-like';
import { Issue } from '../../../types/types';
import Checkbox from '../../controls/Checkbox';
import { updateIssue } from '../actions';
import IssueActionsBar from './IssueActionsBar';
import IssueCommentLine from './IssueCommentLine';
import IssueTitleBar from './IssueTitleBar';

interface Props {
branchLike?: BranchLike;
@@ -89,7 +89,6 @@ export default class IssueView extends React.PureComponent<Props> {

const issueClass = classNames('issue', {
'no-click': this.props.onClick === undefined,
hotspot: issue.type === 'SECURITY_HOTSPOT',
'issue-with-checkbox': hasCheckbox,
selected: this.props.selected,
});
@@ -103,7 +102,7 @@ export default class IssueView extends React.PureComponent<Props> {
>
{hasCheckbox && (
<Checkbox
checked={checked || false}
checked={checked ?? false}
className="issue-checkbox-container"
onCheck={this.handleCheck}
label={translateWithParameters('issues.action_select.label', issue.message)}
@@ -130,7 +129,7 @@ export default class IssueView extends React.PureComponent<Props> {
togglePopup={this.props.togglePopup}
/>
{issue.comments && issue.comments.length > 0 && (
<div className="issue-comments">
<ul className="issue-comments" data-testid="issue-comments">
{issue.comments.map((comment) => (
<IssueCommentLine
comment={comment}
@@ -139,7 +138,7 @@ export default class IssueView extends React.PureComponent<Props> {
onEdit={this.editComment}
/>
))}
</div>
</ul>
)}
</div>
);

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

@@ -1,124 +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 { mockIssue } from '../../../../helpers/testMocks';
import { Issue } from '../../../../types/types';
import IssueActionsBar from '../IssueActionsBar';

jest.mock('../../actions', () => ({ updateIssue: jest.fn() }));

it('should render issue correctly', () => {
const element = shallow(
<IssueActionsBar
issue={mockIssue()}
onAssign={jest.fn()}
onChange={jest.fn()}
togglePopup={jest.fn()}
/>
);
expect(element).toMatchSnapshot();
});

it('should render security hotspot correctly', () => {
const element = shallow(
<IssueActionsBar
issue={mockIssue(false, { type: 'SECURITY_HOTSPOT' })}
onAssign={jest.fn()}
onChange={jest.fn()}
togglePopup={jest.fn()}
/>
);
expect(element).toMatchSnapshot();
});

it('should render commentable correctly', () => {
const element = shallow(
<IssueActionsBar
issue={mockIssue(false, { actions: ['comment'] })}
onAssign={jest.fn()}
onChange={jest.fn()}
togglePopup={jest.fn()}
/>
);
expect(element).toMatchSnapshot();
});

it('should render effort correctly', () => {
const element = shallow(
<IssueActionsBar
issue={mockIssue(false, { effort: 'great' })}
onAssign={jest.fn()}
onChange={jest.fn()}
togglePopup={jest.fn()}
/>
);
expect(element).toMatchSnapshot();
});

describe('callback', () => {
const issue: Issue = mockIssue();
const onChangeMock = jest.fn();
const togglePopupMock = jest.fn();

const element = shallow<IssueActionsBar>(
<IssueActionsBar
issue={issue}
onAssign={jest.fn()}
onChange={onChangeMock}
togglePopup={togglePopupMock}
/>
);

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

it('handleTransition should call onChange', () => {
const instance = element.instance();
instance.handleTransition(issue);
expect(onChangeMock).toHaveBeenCalledTimes(1);
expect(togglePopupMock).toHaveBeenCalledTimes(0);
});

it('setIssueProperty should call togglePopup', () => {
const instance = element.instance();

const apiCallMock = jest.fn();

instance.setIssueProperty('author', 'popup', apiCallMock, 'Jay');
expect(togglePopupMock).toHaveBeenCalledTimes(1);
expect(apiCallMock).toHaveBeenCalledTimes(1);
});

it('toggleComment should call togglePopup and update state', () => {
const instance = element.instance();

expect(element.state('commentAutoTriggered')).toBe(false);
expect(element.state('commentPlaceholder')).toBe('');

instance.toggleComment(false, 'hold my place', true);

expect(element.state('commentAutoTriggered')).toBe(true);
expect(element.state('commentPlaceholder')).toBe('hold my place');

expect(togglePopupMock).toHaveBeenCalledTimes(1);
});
});

+ 0
- 66
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueAssign-test.tsx View File

@@ -1,66 +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 { mockIssue } from '../../../../helpers/testMocks';
import { click } from '../../../../helpers/testUtils';
import IssueAssign from '../IssueAssign';

const issue = mockIssue(false, {
assignee: 'john',
assigneeAvatar: 'gravatarhash',
assigneeName: 'John Doe',
});

it('should render without the action when the correct rights are missing', () => {
expect(shallowRender({ canAssign: false })).toMatchSnapshot();
});

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

it('should render a fallback assignee display if assignee info are not available', () => {
expect(
shallowRender({ issue: mockIssue(false, { assignee: undefined, assigneeName: undefined }) })
).toMatchSnapshot();
});

it('should open the popup when the button is clicked', () => {
const togglePopup = jest.fn();
const element = shallowRender({ togglePopup });
click(element.find('ButtonLink'));
expect(togglePopup.mock.calls).toMatchSnapshot();
element.setProps({ isOpen: true });
expect(element).toMatchSnapshot();
});

function shallowRender(props: Partial<IssueAssign['props']> = {}) {
return shallow<IssueAssign>(
<IssueAssign
canAssign={true}
isOpen={false}
issue={issue}
onAssign={jest.fn()}
togglePopup={jest.fn()}
{...props}
/>
);
}

+ 0
- 55
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelog-test.tsx View File

@@ -1,55 +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 IssueChangelog from '../IssueChangelog';

const issue = {
key: 'issuekey',
author: 'john.david.dalton@gmail.com',
creationDate: '2017-03-01T09:36:01+0100',
};

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

it('should open the popup when the button is clicked', () => {
const togglePopup = jest.fn();
const element = shallowRender({ togglePopup });
click(element.find('ButtonLink'));
expect(togglePopup.mock.calls).toMatchSnapshot();
element.setProps({ isOpen: true });
expect(element).toMatchSnapshot();
});

function shallowRender(props: Partial<IssueChangelog['props']> = {}) {
return shallow(
<IssueChangelog
creationDate="2017-03-01T09:36:01+0100"
isOpen={false}
issue={issue}
togglePopup={jest.fn()}
{...props}
/>
);
}

+ 47
- 58
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueChangelogDiff-test.tsx View File

@@ -17,67 +17,56 @@
* 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 { screen } from '@testing-library/react';
import * as React from 'react';
import { IssueChangelogDiff as TypeIssueChangelogDiff } from '../../../../types/types';
import IssueChangelogDiff from '../IssueChangelogDiff';
import { mockIssueChangelogDiff } from '../../../../helpers/mocks/issues';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import IssueChangelogDiff, { IssueChangelogDiffProps } from '../IssueChangelogDiff';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot();
});
jest.mock('../../../../helpers/measures', () => ({
formatMeasure: jest
.fn()
.mockImplementation((value: string, type: string) => `formatted.${value}.as.${type}`),
}));

it('should render correctly file diff', () => {
expect(
shallowRender({ diff: { key: 'file', oldValue: 'foo/bar.js', newValue: 'bar/baz.js' } })
).toMatchSnapshot();
});
it.each([
['file', 'issue.change.file_move.oldValue.newValue', undefined],
['from_branch', 'issue.change.from_branch.oldValue.newValue', undefined],
['line', 'issue.changelog.line_removed_X.oldValue', undefined],
[
'effort',
'issue.changelog.changed_to.issue.changelog.field.effort.formatted.12.as.WORK_DUR',
{ newValue: '12', oldValue: undefined },
],
[
'effort',
'issue.changelog.removed.issue.changelog.field.effort (issue.changelog.was.formatted.14.as.WORK_DUR)',
{ newValue: undefined, oldValue: '14' },
],
[
'effort',
'issue.changelog.removed.issue.changelog.field.effort',
{ newValue: undefined, oldValue: undefined },
],
[
'assign',
'issue.changelog.changed_to.issue.changelog.field.assign.newValue (issue.changelog.was.oldValue)',
undefined,
],
['from_short_branch', 'issue.change.from_non_branch.oldValue.newValue', undefined],

it('should render correctly branch diff', () => {
expect(
shallowRender({
diff: {
// Legacy key
key: 'from_long_branch',
oldValue: 'foo',
newValue: 'bar',
},
})
).toMatchSnapshot();
// This should be deprecated. Can this still happen?
['from_long_branch', 'issue.change.from_branch.oldValue.newValue', undefined],
])(
'should render correctly for "%s" diff types',
(key, expected, diff?: Partial<IssueChangelogDiffProps['diff']>) => {
renderIssueChangelogDiff({
diff: mockIssueChangelogDiff({ key, newValue: 'newValue', oldValue: 'oldValue', ...diff }),
});
expect(screen.getByText(expected)).toBeInTheDocument();
}
);

expect(
shallowRender({
diff: {
// Legacy key
key: 'from_short_branch',
oldValue: 'foo',
newValue: 'bar',
},
})
).toMatchSnapshot();

expect(
shallowRender({
diff: {
key: 'from_branch',
oldValue: 'foo',
newValue: 'bar',
},
})
).toMatchSnapshot();
});

it('should render correctly line diff', () => {
expect(shallowRender({ diff: { key: 'line', oldValue: '80' } })).toMatchSnapshot();
});

it('should render correctly effort diff', () => {
expect(shallowRender({ diff: { key: 'effort', newValue: '12' } })).toMatchSnapshot();
expect(
shallowRender({ diff: { key: 'effort', newValue: '12', oldValue: '10' } })
).toMatchSnapshot();
expect(shallowRender({ diff: { key: 'effort', oldValue: '10' } })).toMatchSnapshot();
});

function shallowRender(props: Partial<{ diff: TypeIssueChangelogDiff }> = {}) {
return shallow(<IssueChangelogDiff diff={{ key: 'foo' }} {...props} />);
function renderIssueChangelogDiff(props: Partial<IssueChangelogDiffProps> = {}) {
return renderComponent(<IssueChangelogDiff diff={mockIssueChangelogDiff()} {...props} />);
}

+ 0
- 68
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueCommentLine-test.tsx View File

@@ -1,68 +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 { IssueComment } from '../../../../types/types';
import IssueCommentLine from '../IssueCommentLine';

const comment: IssueComment = {
author: 'john.doe',
authorActive: true,
authorAvatar: 'gravatarhash',
authorName: 'John Doe',
createdAt: '2017-03-01T09:36:01+0100',
htmlText: '<b>test</b>',
key: 'comment-key',
markdown: '*test*',
updatable: true,
};

it('should render correctly a comment that is updatable', () => {
expect(shallowRender()).toMatchSnapshot();
});

it('should render correctly a comment that is not updatable', () => {
expect(shallowRender({ comment: { ...comment, updatable: false } })).toMatchSnapshot();
});

it('should open the right popups when the buttons are clicked', () => {
const wrapper = shallowRender();
click(wrapper.find('.js-issue-comment-edit'));
expect(wrapper.state()).toMatchSnapshot();
click(wrapper.find('.js-issue-comment-delete'));
expect(wrapper.state()).toMatchSnapshot();
wrapper.update();
expect(wrapper).toMatchSnapshot();
});

it('should render correctly a comment with a deleted author', () => {
expect(
shallowRender({
comment: { ...comment, authorActive: false, authorName: undefined },
}).find('.issue-comment-author')
).toMatchSnapshot();
});

function shallowRender(props: Partial<IssueCommentLine['props']> = {}) {
return shallow(
<IssueCommentLine comment={comment} onDelete={jest.fn()} onEdit={jest.fn()} {...props} />
);
}

+ 0
- 66
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueMessage-test.tsx View File

@@ -1,66 +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 { mockBranch } from '../../../../helpers/mocks/branch-like';
import { mockIssue } from '../../../../helpers/testMocks';
import { RuleStatus } from '../../../../types/rules';
import IssueMessage, { IssueMessageProps } from '../IssueMessage';

jest.mock('react', () => {
return {
...jest.requireActual('react'),
useContext: jest
.fn()
.mockImplementation(() => ({ externalRulesRepoNames: {}, openRule: jest.fn() })),
};
});

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ issue: mockIssue(false, { externalRuleEngine: 'js' }) })).toMatchSnapshot(
'with engine info'
);
expect(shallowRender({ issue: mockIssue(false, { quickFixAvailable: true }) })).toMatchSnapshot(
'with quick fix'
);
expect(
shallowRender({ issue: mockIssue(false, { ruleStatus: RuleStatus.Deprecated }) })
).toMatchSnapshot('is deprecated rule');
expect(
shallowRender({ issue: mockIssue(false, { ruleStatus: RuleStatus.Removed }) })
).toMatchSnapshot('is removed rule');
expect(shallowRender({ displayWhyIsThisAnIssue: false })).toMatchSnapshot(
'hide why is it an issue'
);
});

function shallowRender(props: Partial<IssueMessageProps> = {}) {
return shallow<IssueMessageProps>(
<IssueMessage
issue={mockIssue(false, {
message: 'Reduce the number of conditional operators (4) used in the expression',
})}
displayWhyIsThisAnIssue={true}
branchLike={mockBranch()}
{...props}
/>
);
}

+ 0
- 55
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueSeverity-test.tsx View File

@@ -1,55 +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 IssueSeverity from '../IssueSeverity';

const issue = { severity: 'BLOCKER' };

it('should render without the action when the correct rights are missing', () => {
expect(shallowRender({ canSetSeverity: false })).toMatchSnapshot();
});

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

it('should open the popup when the button is clicked', () => {
const togglePopup = jest.fn();
const element = shallowRender({ togglePopup });
click(element.find('ButtonLink'));
expect(togglePopup.mock.calls).toMatchSnapshot();
element.setProps({ isOpen: true });
expect(element).toMatchSnapshot();
});

function shallowRender(props: Partial<IssueSeverity['props']> = {}) {
return shallow(
<IssueSeverity
canSetSeverity={true}
isOpen={false}
issue={issue}
setIssueProperty={jest.fn()}
togglePopup={jest.fn()}
{...props}
/>
);
}

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

@@ -1,50 +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 { mockBranch } from '../../../../helpers/mocks/branch-like';
import { mockIssue } from '../../../../helpers/testMocks';
import IssueTitleBar, { IssueTitleBarProps } from '../IssueTitleBar';

it('should render correctly', () => {
expect(shallowRender()).toMatchSnapshot('default');
expect(shallowRender({ onFilter: jest.fn() })).toMatchSnapshot('with filter');
expect(shallowRender({ displayLocationsCount: true, issue: mockIssue(true) })).toMatchSnapshot(
'with multi locations'
);
expect(
shallowRender({
branchLike: mockBranch(),
displayLocationsCount: true,
displayLocationsLink: true,
issue: mockIssue(true),
})
).toMatchSnapshot('with multi locations and link');
});

function shallowRender(props: Partial<IssueTitleBarProps> = {}) {
return shallow<IssueTitleBarProps>(
<IssueTitleBar
issue={mockIssue(false, { externalRuleEngine: 'foo' })}
togglePopup={jest.fn()}
{...props}
/>
);
}

+ 0
- 51
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueTransition-test.tsx View File

@@ -1,51 +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 IssueTransition from '../IssueTransition';

const issue: IssueTransition['props']['issue'] = {
key: 'foo1234',
transitions: ['confirm', 'resolve', 'falsepositive', 'wontfix'],
status: 'OPEN',
type: 'BUG',
};

it('should render without the action when there is no transitions', () => {
expect(
shallowRender({
hasTransitions: false,
issue: { key: 'foo1234', transitions: [], status: 'CLOSED', type: 'BUG' },
})
).toMatchSnapshot();
});

function shallowRender(props: Partial<IssueTransition['props']> = {}) {
return shallow(
<IssueTransition
hasTransitions={true}
isOpen={false}
issue={issue}
onChange={jest.fn()}
togglePopup={jest.fn()}
{...props}
/>
);
}

+ 0
- 42
server/sonar-web/src/main/js/components/issue/components/__tests__/IssueType-test.tsx View File

@@ -1,42 +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 { Issue } from '../../../../types/types';
import IssueType from '../IssueType';

const issue: Pick<Issue, 'type'> = { type: 'BUG' };

it('should render without the action when the correct rights are missing', () => {
expect(shallowRender({ canSetType: false })).toMatchSnapshot();
});

function shallowRender(props: Partial<IssueType['props']> = {}) {
return shallow(
<IssueType
canSetType={true}
isOpen={false}
issue={issue}
setIssueProperty={jest.fn()}
togglePopup={jest.fn()}
{...props}
/>
);
}

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

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

exports[`should render commentable correctly 1`] = `
<div
className="issue-actions"
>
<div
className="issue-meta-list"
>
<div
className="issue-meta"
>
<IssueType
canSetType={false}
isOpen={false}
issue={
{
"actions": [
"comment",
],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
setIssueProperty={[Function]}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<IssueSeverity
canSetSeverity={false}
isOpen={false}
issue={
{
"actions": [
"comment",
],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
setIssueProperty={[Function]}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<IssueTransition
hasTransitions={false}
isOpen={false}
issue={
{
"actions": [
"comment",
],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onChange={[Function]}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<IssueAssign
canAssign={false}
isOpen={false}
issue={
{
"actions": [
"comment",
],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onAssign={[MockFunction]}
togglePopup={[MockFunction]}
/>
</div>
<IssueCommentAction
canComment={true}
commentAutoTriggered={false}
commentPlaceholder=""
issueKey="AVsae-CQS-9G3txfbFN2"
onChange={[MockFunction]}
toggleComment={[Function]}
/>
</div>
<div
className="list-inline"
>
<div
className="issue-meta js-issue-tags"
>
<IssueTags
canSetTags={false}
isOpen={false}
issue={
{
"actions": [
"comment",
],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onChange={[MockFunction]}
togglePopup={[MockFunction]}
/>
</div>
</div>
</div>
`;

exports[`should render effort correctly 1`] = `
<div
className="issue-actions"
>
<div
className="issue-meta-list"
>
<div
className="issue-meta"
>
<IssueType
canSetType={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"effort": "great",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
setIssueProperty={[Function]}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<IssueSeverity
canSetSeverity={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"effort": "great",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
setIssueProperty={[Function]}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<IssueTransition
hasTransitions={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"effort": "great",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onChange={[Function]}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<IssueAssign
canAssign={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"effort": "great",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onAssign={[MockFunction]}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<span
className="issue-meta-label"
>
issue.x_effort.great
</span>
</div>
</div>
<div
className="list-inline"
>
<div
className="issue-meta js-issue-tags"
>
<IssueTags
canSetTags={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"effort": "great",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onChange={[MockFunction]}
togglePopup={[MockFunction]}
/>
</div>
</div>
</div>
`;

exports[`should render issue correctly 1`] = `
<div
className="issue-actions"
>
<div
className="issue-meta-list"
>
<div
className="issue-meta"
>
<IssueType
canSetType={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
setIssueProperty={[Function]}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<IssueSeverity
canSetSeverity={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
setIssueProperty={[Function]}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<IssueTransition
hasTransitions={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onChange={[Function]}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<IssueAssign
canAssign={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onAssign={[MockFunction]}
togglePopup={[MockFunction]}
/>
</div>
</div>
<div
className="list-inline"
>
<div
className="issue-meta js-issue-tags"
>
<IssueTags
canSetTags={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onChange={[MockFunction]}
togglePopup={[MockFunction]}
/>
</div>
</div>
</div>
`;

exports[`should render security hotspot correctly 1`] = `
<div
className="issue-actions"
>
<div
className="issue-meta-list"
>
<div
className="issue-meta"
>
<IssueType
canSetType={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "SECURITY_HOTSPOT",
}
}
setIssueProperty={[Function]}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<IssueTransition
hasTransitions={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "SECURITY_HOTSPOT",
}
}
onChange={[Function]}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<IssueAssign
canAssign={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "SECURITY_HOTSPOT",
}
}
onAssign={[MockFunction]}
togglePopup={[MockFunction]}
/>
</div>
</div>
<div
className="list-inline"
>
<div
className="issue-meta js-issue-tags"
>
<IssueTags
canSetTags={false}
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "SECURITY_HOTSPOT",
}
}
onChange={[MockFunction]}
togglePopup={[MockFunction]}
/>
</div>
</div>
</div>
`;

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

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

exports[`should open the popup when the button is clicked 1`] = `
[
[
"assign",
undefined,
],
]
`;

exports[`should open the popup when the button is clicked 2`] = `
<div
className="dropdown"
>
<Toggler
closeOnEscape={true}
onRequestClose={[Function]}
open={true}
overlay={
<withCurrentUserContext(SetAssigneePopup)
onSelect={[MockFunction]}
/>
}
>
<ButtonLink
aria-expanded={true}
aria-label="issue.assign.assigned_to_x_click_to_change.John Doe"
className="issue-action issue-action-with-options js-issue-assign"
onClick={[Function]}
>
<span
className="text-top"
>
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name=""
size={16}
/>
</span>
<span
className="issue-meta-label"
title="John Doe"
>
John Doe
</span>
<DropdownIcon
className="little-spacer-left"
/>
</ButtonLink>
</Toggler>
</div>
`;

exports[`should render a fallback assignee display if assignee info are not available 1`] = `
<div
className="dropdown"
>
<Toggler
closeOnEscape={true}
onRequestClose={[Function]}
open={false}
overlay={
<withCurrentUserContext(SetAssigneePopup)
onSelect={[MockFunction]}
/>
}
>
<ButtonLink
aria-expanded={false}
aria-label="issue.assign.unassigned_click_to_assign"
className="issue-action issue-action-with-options js-issue-assign"
onClick={[Function]}
>
<span
className="issue-meta-label"
>
unassigned
</span>
<DropdownIcon
className="little-spacer-left"
/>
</ButtonLink>
</Toggler>
</div>
`;

exports[`should render with the action 1`] = `
<div
className="dropdown"
>
<Toggler
closeOnEscape={true}
onRequestClose={[Function]}
open={false}
overlay={
<withCurrentUserContext(SetAssigneePopup)
onSelect={[MockFunction]}
/>
}
>
<ButtonLink
aria-expanded={false}
aria-label="issue.assign.assigned_to_x_click_to_change.John Doe"
className="issue-action issue-action-with-options js-issue-assign"
onClick={[Function]}
>
<span
className="text-top"
>
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name=""
size={16}
/>
</span>
<span
className="issue-meta-label"
title="John Doe"
>
John Doe
</span>
<DropdownIcon
className="little-spacer-left"
/>
</ButtonLink>
</Toggler>
</div>
`;

exports[`should render without the action when the correct rights are missing 1`] = `
<Fragment>
<span
className="text-top"
>
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name=""
size={16}
/>
</span>
<span
className="issue-meta-label"
title="John Doe"
>
John Doe
</span>
</Fragment>
`;

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

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

exports[`should open the popup when the button is clicked 1`] = `
[
[
"changelog",
undefined,
],
]
`;

exports[`should open the popup when the button is clicked 2`] = `
<div
className="dropdown"
>
<Toggler
onRequestClose={[Function]}
open={true}
overlay={
<ChangelogPopup
issue={
{
"author": "john.david.dalton@gmail.com",
"creationDate": "2017-03-01T09:36:01+0100",
"key": "issuekey",
}
}
/>
}
>
<ButtonLink
aria-expanded={true}
className="issue-action issue-action-with-options js-issue-show-changelog"
onClick={[Function]}
>
<span
className="issue-meta-label"
>
<DateFromNow
date="2017-03-01T09:36:01+0100"
/>
</span>
<DropdownIcon
className="little-spacer-left"
/>
</ButtonLink>
</Toggler>
</div>
`;

exports[`should render correctly 1`] = `
<div
className="dropdown"
>
<Toggler
onRequestClose={[Function]}
open={false}
overlay={
<ChangelogPopup
issue={
{
"author": "john.david.dalton@gmail.com",
"creationDate": "2017-03-01T09:36:01+0100",
"key": "issuekey",
}
}
/>
}
>
<ButtonLink
aria-expanded={false}
className="issue-action issue-action-with-options js-issue-show-changelog"
onClick={[Function]}
>
<span
className="issue-meta-label"
>
<DateFromNow
date="2017-03-01T09:36:01+0100"
/>
</span>
<DropdownIcon
className="little-spacer-left"
/>
</ButtonLink>
</Toggler>
</div>
`;

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

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

exports[`should render correctly 1`] = `
<p>
issue.changelog.removed.issue.changelog.field.foo
</p>
`;

exports[`should render correctly branch diff 1`] = `
<p>
issue.change.from_branch.foo.bar
</p>
`;

exports[`should render correctly branch diff 2`] = `
<p>
issue.change.from_non_branch.foo.bar
</p>
`;

exports[`should render correctly branch diff 3`] = `
<p>
issue.change.from_branch.foo.bar
</p>
`;

exports[`should render correctly effort diff 1`] = `
<p>
issue.changelog.changed_to.issue.changelog.field.effort.work_duration.x_minutes.12
</p>
`;

exports[`should render correctly effort diff 2`] = `
<p>
issue.changelog.changed_to.issue.changelog.field.effort.work_duration.x_minutes.12 (issue.changelog.was.work_duration.x_minutes.10)
</p>
`;

exports[`should render correctly effort diff 3`] = `
<p>
issue.changelog.removed.issue.changelog.field.effort (issue.changelog.was.work_duration.x_minutes.10)
</p>
`;

exports[`should render correctly file diff 1`] = `
<p>
issue.change.file_move.foo/bar.js.bar/baz.js
</p>
`;

exports[`should render correctly line diff 1`] = `
<p>
issue.changelog.line_removed_X.80
</p>
`;

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

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

exports[`should open the right popups when the buttons are clicked 1`] = `
{
"openPopup": "edit",
}
`;

exports[`should open the right popups when the buttons are clicked 2`] = `
{
"openPopup": "delete",
}
`;

exports[`should open the right popups when the buttons are clicked 3`] = `
<div
className="issue-comment"
>
<div
className="issue-comment-author"
title="John Doe"
>
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name="John Doe"
size={16}
/>
John Doe
</div>
<div
className="issue-comment-text markdown"
dangerouslySetInnerHTML={
{
"__html": "<b>test</b>",
}
}
/>
<div
className="issue-comment-age"
>
<span
className="a11y-hidden"
>
issue.comment.posted_on
</span>
<DateFromNow
date="2017-03-01T09:36:01+0100"
/>
</div>
<div
className="issue-comment-actions"
>
<div
className="dropdown"
>
<Toggler
closeOnClickOutside={false}
onRequestClose={[Function]}
open={false}
overlay={
<CommentPopup
comment={
{
"author": "john.doe",
"authorActive": true,
"authorAvatar": "gravatarhash",
"authorName": "John Doe",
"createdAt": "2017-03-01T09:36:01+0100",
"htmlText": "<b>test</b>",
"key": "comment-key",
"markdown": "*test*",
"updatable": true,
}
}
onComment={[Function]}
placeholder=""
placement="bottom-right"
toggleComment={[Function]}
/>
}
>
<EditButton
aria-label="issue.comment.edit"
className="js-issue-comment-edit button-small"
onClick={[Function]}
/>
</Toggler>
</div>
<div
className="dropdown"
>
<Toggler
onRequestClose={[Function]}
open={true}
overlay={
<CommentDeletePopup
onDelete={[Function]}
/>
}
>
<DeleteButton
aria-label="issue.comment.delete"
className="js-issue-comment-delete button-small"
onClick={[Function]}
/>
</Toggler>
</div>
</div>
</div>
`;

exports[`should render correctly a comment that is not updatable 1`] = `
<div
className="issue-comment"
>
<div
className="issue-comment-author"
title="John Doe"
>
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name="John Doe"
size={16}
/>
John Doe
</div>
<div
className="issue-comment-text markdown"
dangerouslySetInnerHTML={
{
"__html": "<b>test</b>",
}
}
/>
<div
className="issue-comment-age"
>
<span
className="a11y-hidden"
>
issue.comment.posted_on
</span>
<DateFromNow
date="2017-03-01T09:36:01+0100"
/>
</div>
<div
className="issue-comment-actions"
/>
</div>
`;

exports[`should render correctly a comment that is updatable 1`] = `
<div
className="issue-comment"
>
<div
className="issue-comment-author"
title="John Doe"
>
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name="John Doe"
size={16}
/>
John Doe
</div>
<div
className="issue-comment-text markdown"
dangerouslySetInnerHTML={
{
"__html": "<b>test</b>",
}
}
/>
<div
className="issue-comment-age"
>
<span
className="a11y-hidden"
>
issue.comment.posted_on
</span>
<DateFromNow
date="2017-03-01T09:36:01+0100"
/>
</div>
<div
className="issue-comment-actions"
>
<div
className="dropdown"
>
<Toggler
closeOnClickOutside={false}
onRequestClose={[Function]}
open={false}
overlay={
<CommentPopup
comment={
{
"author": "john.doe",
"authorActive": true,
"authorAvatar": "gravatarhash",
"authorName": "John Doe",
"createdAt": "2017-03-01T09:36:01+0100",
"htmlText": "<b>test</b>",
"key": "comment-key",
"markdown": "*test*",
"updatable": true,
}
}
onComment={[Function]}
placeholder=""
placement="bottom-right"
toggleComment={[Function]}
/>
}
>
<EditButton
aria-label="issue.comment.edit"
className="js-issue-comment-edit button-small"
onClick={[Function]}
/>
</Toggler>
</div>
<div
className="dropdown"
>
<Toggler
onRequestClose={[Function]}
open={false}
overlay={
<CommentDeletePopup
onDelete={[Function]}
/>
}
>
<DeleteButton
aria-label="issue.comment.delete"
className="js-issue-comment-delete button-small"
onClick={[Function]}
/>
</Toggler>
</div>
</div>
</div>
`;

exports[`should render correctly a comment with a deleted author 1`] = `
<div
className="issue-comment-author"
title="user.x_deleted.john.doe"
>
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name="john.doe"
size={16}
/>
user.x_deleted.john.doe
</div>
`;

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

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

exports[`should render correctly: default 1`] = `
<Fragment>
<div
className="display-inline-flex-center issue-message break-word"
>
<span
className="spacer-right"
>
<IssueMessageHighlighting
message="Reduce the number of conditional operators (4) used in the expression"
/>
</span>
<IssueMessageTags />
</div>
<ForwardRef(Link)
aria-label="issue.why_this_issue.long"
className="spacer-right"
target="_blank"
to={
{
"hash": "",
"pathname": "/project/issues",
"search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
}
}
>
issue.why_this_issue
</ForwardRef(Link)>
</Fragment>
`;

exports[`should render correctly: hide why is it an issue 1`] = `
<Fragment>
<div
className="display-inline-flex-center issue-message break-word"
>
<span
className="spacer-right"
>
<IssueMessageHighlighting
message="Reduce the number of conditional operators (4) used in the expression"
/>
</span>
<IssueMessageTags />
</div>
</Fragment>
`;

exports[`should render correctly: is deprecated rule 1`] = `
<Fragment>
<div
className="display-inline-flex-center issue-message break-word"
>
<span
className="spacer-right"
>
<IssueMessageHighlighting
message="Reduce the number of conditional operators (4) used in the expression"
/>
</span>
<IssueMessageTags
ruleStatus="DEPRECATED"
/>
</div>
<ForwardRef(Link)
aria-label="issue.why_this_issue.long"
className="spacer-right"
target="_blank"
to={
{
"hash": "",
"pathname": "/project/issues",
"search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
}
}
>
issue.why_this_issue
</ForwardRef(Link)>
</Fragment>
`;

exports[`should render correctly: is removed rule 1`] = `
<Fragment>
<div
className="display-inline-flex-center issue-message break-word"
>
<span
className="spacer-right"
>
<IssueMessageHighlighting
message="Reduce the number of conditional operators (4) used in the expression"
/>
</span>
<IssueMessageTags
ruleStatus="REMOVED"
/>
</div>
<ForwardRef(Link)
aria-label="issue.why_this_issue.long"
className="spacer-right"
target="_blank"
to={
{
"hash": "",
"pathname": "/project/issues",
"search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
}
}
>
issue.why_this_issue
</ForwardRef(Link)>
</Fragment>
`;

exports[`should render correctly: with engine info 1`] = `
<Fragment>
<div
className="display-inline-flex-center issue-message break-word"
>
<span
className="spacer-right"
>
<IssueMessageHighlighting
message="Reduce the number of conditional operators (4) used in the expression"
/>
</span>
<IssueMessageTags
engine="js"
/>
</div>
<ForwardRef(Link)
aria-label="issue.why_this_issue.long"
className="spacer-right"
target="_blank"
to={
{
"hash": "",
"pathname": "/project/issues",
"search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
}
}
>
issue.why_this_issue
</ForwardRef(Link)>
</Fragment>
`;

exports[`should render correctly: with quick fix 1`] = `
<Fragment>
<div
className="display-inline-flex-center issue-message break-word"
>
<span
className="spacer-right"
>
<IssueMessageHighlighting
message="Reduce the number of conditional operators (4) used in the expression"
/>
</span>
<IssueMessageTags
quickFixAvailable={true}
/>
</div>
<ForwardRef(Link)
aria-label="issue.why_this_issue.long"
className="spacer-right"
target="_blank"
to={
{
"hash": "",
"pathname": "/project/issues",
"search": "?branch=branch-6.7&files=main.js&open=AVsae-CQS-9G3txfbFN2&resolved=false&why=1&id=myproject",
}
}
>
issue.why_this_issue
</ForwardRef(Link)>
</Fragment>
`;

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

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

exports[`should open the popup when the button is clicked 1`] = `
[
[
"set-severity",
undefined,
],
]
`;

exports[`should open the popup when the button is clicked 2`] = `
<div
className="dropdown"
>
<Toggler
onRequestClose={[Function]}
open={true}
overlay={
<SetSeverityPopup
issue={
{
"severity": "BLOCKER",
}
}
onSelect={[Function]}
/>
}
>
<ButtonLink
aria-expanded={true}
aria-label="issue.severity.severity_x_click_to_change.severity.BLOCKER"
className="issue-action issue-action-with-options js-issue-set-severity"
onClick={[Function]}
>
<SeverityHelper
className="issue-meta-label"
severity="BLOCKER"
/>
<DropdownIcon
className="little-spacer-left"
/>
</ButtonLink>
</Toggler>
</div>
`;

exports[`should render with the action 1`] = `
<div
className="dropdown"
>
<Toggler
onRequestClose={[Function]}
open={false}
overlay={
<SetSeverityPopup
issue={
{
"severity": "BLOCKER",
}
}
onSelect={[Function]}
/>
}
>
<ButtonLink
aria-expanded={false}
aria-label="issue.severity.severity_x_click_to_change.severity.BLOCKER"
className="issue-action issue-action-with-options js-issue-set-severity"
onClick={[Function]}
>
<SeverityHelper
className="issue-meta-label"
severity="BLOCKER"
/>
<DropdownIcon
className="little-spacer-left"
/>
</ButtonLink>
</Toggler>
</div>
`;

exports[`should render without the action when the correct rights are missing 1`] = `
<SeverityHelper
className="issue-meta-label"
severity="BLOCKER"
/>
`;

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

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

exports[`should render correctly: default 1`] = `
<div
className="issue-row"
>
<IssueMessage
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"externalRuleEngine": "foo",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
/>
<div
className="issue-row-meta"
>
<div
className="issue-meta-list"
>
<div
className="issue-meta"
>
<IssueChangelog
creationDate="2017-03-01T09:36:01+0100"
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"externalRuleEngine": "foo",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<span
className="issue-meta-label"
title="line_number"
>
L
26
</span>
</div>
<div
className="issue-meta"
>
<ForwardRef(Link)
className="js-issue-permalink link-no-underline"
target="_blank"
title="permalink"
to={
{
"hash": "",
"pathname": "/project/issues",
"search": "?issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
}
}
>
<LinkIcon />
</ForwardRef(Link)>
</div>
</div>
</div>
</div>
`;

exports[`should render correctly: with filter 1`] = `
<div
className="issue-row"
>
<IssueMessage
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"externalRuleEngine": "foo",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
/>
<div
className="issue-row-meta"
>
<div
className="issue-meta-list"
>
<div
className="issue-meta"
>
<IssueChangelog
creationDate="2017-03-01T09:36:01+0100"
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"externalRuleEngine": "foo",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<span
className="issue-meta-label"
title="line_number"
>
L
26
</span>
</div>
<div
className="issue-meta"
>
<ForwardRef(Link)
className="js-issue-permalink link-no-underline"
target="_blank"
title="permalink"
to={
{
"hash": "",
"pathname": "/project/issues",
"search": "?issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
}
}
>
<LinkIcon />
</ForwardRef(Link)>
</div>
<div
className="issue-meta"
>
<SimilarIssuesFilter
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"externalRuleEngine": "foo",
"flows": [],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
onFilter={[MockFunction]}
togglePopup={[MockFunction]}
/>
</div>
</div>
</div>
</div>
`;

exports[`should render correctly: with multi locations 1`] = `
<div
className="issue-row"
>
<IssueMessage
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [
[
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
],
[
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
],
],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
/>
<div
className="issue-row-meta"
>
<div
className="issue-meta-list"
>
<div
className="issue-meta"
>
<IssueChangelog
creationDate="2017-03-01T09:36:01+0100"
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [
[
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
],
[
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
],
],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<span
className="issue-meta-label"
title="line_number"
>
L
26
</span>
</div>
<div
className="issue-meta"
>
<Tooltip
overlay="issue.this_issue_involves_x_code_locations.7"
>
<LocationIndex>
7
</LocationIndex>
</Tooltip>
</div>
<div
className="issue-meta"
>
<ForwardRef(Link)
className="js-issue-permalink link-no-underline"
target="_blank"
title="permalink"
to={
{
"hash": "",
"pathname": "/project/issues",
"search": "?issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
}
}
>
<LinkIcon />
</ForwardRef(Link)>
</div>
</div>
</div>
</div>
`;

exports[`should render correctly: with multi locations and link 1`] = `
<div
className="issue-row"
>
<IssueMessage
branchLike={
{
"analysisDate": "2018-01-01",
"excludedFromPurge": true,
"isMain": false,
"name": "branch-6.7",
}
}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [
[
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
],
[
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
],
],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
/>
<div
className="issue-row-meta"
>
<div
className="issue-meta-list"
>
<div
className="issue-meta"
>
<IssueChangelog
creationDate="2017-03-01T09:36:01+0100"
isOpen={false}
issue={
{
"actions": [],
"component": "main.js",
"componentEnabled": true,
"componentLongName": "main.js",
"componentQualifier": "FIL",
"componentUuid": "foo1234",
"creationDate": "2017-03-01T09:36:01+0100",
"flows": [
[
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
],
[
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
],
],
"flowsWithType": [],
"key": "AVsae-CQS-9G3txfbFN2",
"line": 25,
"message": "Reduce the number of conditional operators (4) used in the expression",
"project": "myproject",
"projectKey": "foo",
"projectName": "Foo",
"rule": "javascript:S1067",
"ruleName": "foo",
"scope": "MAIN",
"secondaryLocations": [
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
{
"component": "main.js",
"textRange": {
"endLine": 2,
"endOffset": 2,
"startLine": 1,
"startOffset": 1,
},
},
],
"severity": "MAJOR",
"status": "OPEN",
"textRange": {
"endLine": 26,
"endOffset": 15,
"startLine": 25,
"startOffset": 0,
},
"transitions": [],
"type": "BUG",
}
}
togglePopup={[MockFunction]}
/>
</div>
<div
className="issue-meta"
>
<span
className="issue-meta-label"
title="line_number"
>
L
26
</span>
</div>
<div
className="issue-meta"
>
<ForwardRef(Link)
target="_blank"
to={
{
"hash": "",
"pathname": "/project/issues",
"search": "?branch=branch-6.7&issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
}
}
>
<Tooltip
overlay="issue.this_issue_involves_x_code_locations.7"
>
<LocationIndex>
7
</LocationIndex>
</Tooltip>
</ForwardRef(Link)>
</div>
<div
className="issue-meta"
>
<ForwardRef(Link)
className="js-issue-permalink link-no-underline"
target="_blank"
title="permalink"
to={
{
"hash": "",
"pathname": "/project/issues",
"search": "?branch=branch-6.7&issues=AVsae-CQS-9G3txfbFN2&open=AVsae-CQS-9G3txfbFN2&id=myproject",
}
}
>
<LinkIcon />
</ForwardRef(Link)>
</div>
</div>
</div>
</div>
`;

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

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

exports[`should render without the action when there is no transitions 1`] = `
<StatusHelper
className="issue-meta-label"
status="CLOSED"
/>
`;

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

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

exports[`should render without the action when the correct rights are missing 1`] = `
<span>
<IssueTypeIcon
className="little-spacer-right"
query="BUG"
/>
issue.type.BUG
</span>
`;

+ 2
- 2
server/sonar-web/src/main/js/components/issue/popups/ChangelogPopup.tsx View File

@@ -85,7 +85,7 @@ export default class ChangelogPopup extends React.PureComponent<Props, State> {
<DateTimeFormatter date={item.creationDate} />
</td>
<td className="text-left text-top">
<p>
<div>
{userName && (
<>
<Avatar
@@ -104,7 +104,7 @@ export default class ChangelogPopup extends React.PureComponent<Props, State> {
'issue.changelog.webhook_source',
item.webhookSource
)}
</p>
</div>
{item.diffs.map((diff) => (
<IssueChangelogDiff diff={diff} key={diff.key} />
))}

+ 3
- 3
server/sonar-web/src/main/js/components/issue/popups/CommentForm.tsx View File

@@ -44,9 +44,9 @@ export default function CommentForm(props: CommentFormProps) {
style={{ resize: 'vertical' }}
placeholder={placeholder}
aria-label={translate('issue.comment.enter_comment')}
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) =>
setEditComment(event.target.value)
}
onChange={(event: React.ChangeEvent<HTMLTextAreaElement>) => {
setEditComment(event.target.value);
}}
onKeyDown={(event: React.KeyboardEvent) => {
if (event.nativeEvent.key === KeyboardKeys.Enter && (event.metaKey || event.ctrlKey)) {
props.onSaveComment(editComment);

+ 99
- 103
server/sonar-web/src/main/js/components/issue/popups/SimilarIssuesPopup.tsx View File

@@ -24,6 +24,7 @@ import QualifierIcon from '../../../components/icons/QualifierIcon';
import TagsIcon from '../../../components/icons/TagsIcon';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { fileFromPath, limitComponentName } from '../../../helpers/path';
import { ComponentQualifier } from '../../../types/component';
import { Issue } from '../../../types/types';
import SelectList from '../../common/SelectList';
import SelectListItem from '../../common/SelectListItem';
@@ -31,112 +32,107 @@ import SeverityHelper from '../../shared/SeverityHelper';
import StatusHelper from '../../shared/StatusHelper';
import Avatar from '../../ui/Avatar';

interface Props {
interface SimilarIssuesPopupProps {
issue: Issue;
onFilter: (property: string, issue: Issue) => void;
}

export default class SimilarIssuesPopup extends React.PureComponent<Props> {
handleSelect = (property: string) => {
this.props.onFilter(property, this.props.issue);
};

render() {
const { issue } = this.props;

const items = [
'type',
'severity',
'status',
'resolution',
'assignee',
'rule',
...(issue.tags || []).map((tag) => `tag###${tag}`),
'project',
'file',
].filter((item) => item) as string[];

const assignee = issue.assigneeName || issue.assignee;

return (
<DropdownOverlay noPadding={true}>
<header className="menu-search">
<h6>{translate('issue.filter_similar_issues')}</h6>
</header>

<SelectList
className="issues-similar-issues-menu"
currentItem={items[0]}
items={items}
onSelect={this.handleSelect}
>
<SelectListItem className="display-flex-center" item="type">
<IssueTypeIcon className="little-spacer-right" query={issue.type} />
{translate('issue.type', issue.type)}
export default function SimilarIssuesPopup(props: SimilarIssuesPopupProps) {
const { issue } = props;

const items = [
'type',
'severity',
'status',
'resolution',
'assignee',
'rule',
...(issue.tags ?? []).map((tag) => `tag###${tag}`),
'project',
'file',
].filter((item) => item) as string[];

const assignee = issue.assigneeName ?? issue.assignee;

return (
<DropdownOverlay noPadding={true}>
<div className="menu-search">
<h6>{translate('issue.filter_similar_issues')}</h6>
</div>

<SelectList
className="issues-similar-issues-menu"
currentItem={items[0]}
items={items}
onSelect={(property: string) => {
props.onFilter(property, issue);
}}
>
<SelectListItem className="display-flex-center" item="type">
<IssueTypeIcon className="little-spacer-right" query={issue.type} />
{translate('issue.type', issue.type)}
</SelectListItem>

<SelectListItem item="severity">
<SeverityHelper className="display-flex-center" severity={issue.severity} />
</SelectListItem>

<SelectListItem item="status">
<StatusHelper
className="display-flex-center"
resolution={undefined}
status={issue.status}
/>
</SelectListItem>

<SelectListItem item="resolution">
{issue.resolution != null
? translate('issue.resolution', issue.resolution)
: translate('unresolved')}
</SelectListItem>

<SelectListItem item="assignee">
{assignee ? (
<span>
{translate('assigned_to')}
<Avatar
className="little-spacer-left little-spacer-right"
hash={issue.assigneeAvatar}
name={assignee}
size={16}
/>
{issue.assigneeActive === false
? translateWithParameters('user.x_deleted', assignee)
: assignee}
</span>
) : (
translate('unassigned')
)}
</SelectListItem>

<li className="divider" />

<SelectListItem item="rule">{limitComponentName(issue.ruleName)}</SelectListItem>

{issue.tags?.map((tag) => (
<SelectListItem item={`tag###${tag}`} key={`tag###${tag}`}>
<TagsIcon className="little-spacer-right text-middle" />
<span className="text-middle">{tag}</span>
</SelectListItem>

<SelectListItem item="severity">
<SeverityHelper className="display-flex-center" severity={issue.severity} />
</SelectListItem>

<SelectListItem item="status">
<StatusHelper
className="display-flex-center"
resolution={undefined}
status={issue.status}
/>
</SelectListItem>

<SelectListItem item="resolution">
{issue.resolution != null
? translate('issue.resolution', issue.resolution)
: translate('unresolved')}
</SelectListItem>

<SelectListItem item="assignee">
{assignee ? (
<span>
{translate('assigned_to')}
<Avatar
className="little-spacer-left little-spacer-right"
hash={issue.assigneeAvatar}
name={assignee}
size={16}
/>
{issue.assigneeActive === false
? translateWithParameters('user.x_deleted', assignee)
: assignee}
</span>
) : (
translate('unassigned')
)}
</SelectListItem>

<li className="divider" />

<SelectListItem item="rule">{limitComponentName(issue.ruleName)}</SelectListItem>

{issue.tags != null &&
issue.tags.map((tag) => (
<SelectListItem item={`tag###${tag}`} key={`tag###${tag}`}>
<TagsIcon className="little-spacer-right text-middle" />
<span className="text-middle">{tag}</span>
</SelectListItem>
))}

<li className="divider" />

<SelectListItem item="project">
<QualifierIcon className="little-spacer-right" qualifier="TRK" />
{issue.projectName}
</SelectListItem>

<SelectListItem item="file">
<QualifierIcon className="little-spacer-right" qualifier={issue.componentQualifier} />
{fileFromPath(issue.componentLongName)}
</SelectListItem>
</SelectList>
</DropdownOverlay>
);
}
))}

<li className="divider" />

<SelectListItem item="project">
<QualifierIcon className="little-spacer-right" qualifier={ComponentQualifier.Project} />
{issue.projectName}
</SelectListItem>

<SelectListItem item="file">
<QualifierIcon className="little-spacer-right" qualifier={issue.componentQualifier} />
{fileFromPath(issue.componentLongName)}
</SelectListItem>
</SelectList>
</DropdownOverlay>
);
}

+ 0
- 132
server/sonar-web/src/main/js/components/issue/popups/__tests__/ChangelogPopup-test.tsx View File

@@ -1,132 +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 { getIssueChangelog } from '../../../../api/issues';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import ChangelogPopup from '../ChangelogPopup';

jest.mock('../../../../api/issues', () => ({
getIssueChangelog: jest.fn().mockResolvedValue({
changelog: [
{
creationDate: '2017-03-01T09:36:01+0100',
user: 'john.doe',
isUserActive: true,
userName: 'John Doe',
avatar: 'gravatarhash',
diffs: [{ key: 'severity', newValue: 'MINOR', oldValue: 'CRITICAL' }],
},
],
}),
}));

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

it('should render the changelog popup correctly', async () => {
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(getIssueChangelog).toHaveBeenCalledWith('issuekey');
expect(wrapper).toMatchSnapshot();
});

it('should render the changelog popup when we have a deleted user', async () => {
(getIssueChangelog as jest.Mock).mockResolvedValueOnce({
changelog: [
{
creationDate: '2017-03-01T09:36:01+0100',
user: 'john.doe',
isUserActive: false,
diffs: [{ key: 'severity', newValue: 'MINOR', oldValue: 'CRITICAL' }],
},
],
});
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});

it('should render the changelog popup when change was triggered by a webhook with external user', async () => {
(getIssueChangelog as jest.Mock).mockResolvedValueOnce({
changelog: [
{
creationDate: '2017-03-01T09:36:01+0100',
user: null,
isUserActive: false,
diffs: [{ key: 'severity', newValue: 'MINOR', oldValue: 'CRITICAL' }],
webhookSource: 'GitHub',
externalUser: 'toto@github.com',
},
],
});
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});

it('should render the changelog popup when change was triggered by a webhook without user', async () => {
(getIssueChangelog as jest.Mock).mockResolvedValueOnce({
changelog: [
{
creationDate: '2017-03-01T09:36:01+0100',
user: null,
isUserActive: false,
diffs: [{ key: 'severity', newValue: 'MINOR', oldValue: 'CRITICAL' }],
webhookSource: 'GitHub',
},
],
});
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});

it('should render the changelog popup with SQ user when both SQ and external user are presents', async () => {
(getIssueChangelog as jest.Mock).mockResolvedValueOnce({
changelog: [
{
creationDate: '2017-03-01T09:36:01+0100',
user: 'toto@sonarqube.com',
isUserActive: false,
diffs: [{ key: 'severity', newValue: 'MINOR', oldValue: 'CRITICAL' }],
webhookSource: 'GitHub',
externalUser: 'toto@github.com',
},
],
});
const wrapper = shallowRender();
await waitAndUpdate(wrapper);
expect(wrapper).toMatchSnapshot();
});

function shallowRender(props: Partial<ChangelogPopup['props']> = {}) {
return shallow(
<ChangelogPopup
issue={{
key: 'issuekey',
author: 'john.david.dalton@gmail.com',
creationDate: '2017-03-01T09:36:01+0100',
}}
{...props}
/>
);
}

+ 0
- 31
server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentDeletePopup-test.tsx View File

@@ -1,31 +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 CommentDeletePopup from '../CommentDeletePopup';

it('should render the comment delete popup correctly', () => {
const onDelete = jest.fn();
const element = shallow(<CommentDeletePopup onDelete={onDelete} />);
expect(element).toMatchSnapshot();
click(element.find('Button'));
expect(onDelete.mock.calls.length).toBe(1);
});

+ 0
- 50
server/sonar-web/src/main/js/components/issue/popups/__tests__/CommentPopup-test.tsx View File

@@ -1,50 +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 { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import * as React from 'react';
import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import CommentPopup, { CommentPopupProps } from '../CommentPopup';

it('should trigger comment change', async () => {
const user = userEvent.setup();
const onComment = jest.fn();
const toggleComment = jest.fn();
shallowRender({ onComment, toggleComment });

expect(await screen.findByRole('textbox')).toHaveFocus();
await user.keyboard('test');
await user.keyboard('{Control>}{Enter}{/Control}');
expect(onComment).toHaveBeenCalledWith('test');

await user.click(screen.getByRole('button', { name: 'issue.comment.add_comment.cancel' }));
expect(toggleComment).toHaveBeenCalledWith(false);
});

function shallowRender(overrides: Partial<CommentPopupProps> = {}) {
return renderComponent(
<CommentPopup
onComment={jest.fn()}
placeholder="placeholder test"
toggleComment={jest.fn()}
{...overrides}
/>
);
}

+ 0
- 52
server/sonar-web/src/main/js/components/issue/popups/__tests__/SetAssigneePopup-test.tsx View File

@@ -1,52 +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 { searchUsers } from '../../../../api/users';
import { mockLoggedInUser, mockUser } from '../../../../helpers/testMocks';
import { waitAndUpdate } from '../../../../helpers/testUtils';
import { SetAssigneePopup } from '../SetAssigneePopup';

jest.mock('../../../../api/users', () => {
const { mockUser } = jest.requireActual('../../../../helpers/testMocks');
return { searchUsers: jest.fn().mockResolvedValue({ users: [mockUser()] }) };
});

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

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

it('should allow to search for a user on SQ', async () => {
const wrapper = shallowRender();
wrapper.find('SearchBox').prop<Function>('onChange')('o');
await waitAndUpdate(wrapper);
expect(searchUsers).toHaveBeenCalledWith({ q: 'o', ps: 10 });
expect(wrapper.state('users')).toEqual([mockUser()]);
});

function shallowRender(props: Partial<SetAssigneePopup['props']> = {}) {
return shallow(
<SetAssigneePopup currentUser={mockLoggedInUser()} onSelect={jest.fn()} {...props} />
);
}

+ 0
- 59
server/sonar-web/src/main/js/components/issue/popups/__tests__/SimilarIssuesPopup-test.tsx View File

@@ -1,59 +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 { mockIssue } from '../../../../helpers/testMocks';
import SimilarIssuesPopup from '../SimilarIssuesPopup';

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

it('should render correctly when assigned', () => {
expect(
shallowRender({
issue: mockIssue(false, { assignee: 'luke', assigneeName: 'Luke Skywalker' }),
}).find('SelectListItem[item="assignee"]')
).toMatchSnapshot();

expect(
shallowRender({ issue: mockIssue(false, { assignee: 'luke', assigneeActive: false }) }).find(
'SelectListItem[item="assignee"]'
)
).toMatchSnapshot();
});

it('should filter properly', () => {
const issue = mockIssue();
const onFilter = jest.fn();
const wrapper = shallowRender({ issue, onFilter });
wrapper.find('SelectList').prop<Function>('onSelect')('assignee');
expect(onFilter).toHaveBeenCalledWith('assignee', issue);
});

function shallowRender(props: Partial<SimilarIssuesPopup['props']> = {}) {
return shallow(
<SimilarIssuesPopup
issue={mockIssue(false, { tags: ['test-tag'] })}
onFilter={jest.fn()}
{...props}
/>
);
}

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

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

exports[`should render the changelog popup correctly 1`] = `
<DropdownOverlay
placement="bottom-right"
>
<div
className="menu is-container issue-changelog"
>
<table
className="spaced"
>
<tbody>
<tr>
<td
className="thin text-left text-top nowrap"
>
<DateTimeFormatter
date="2017-03-01T09:36:01+0100"
/>
</td>
<td
className="text-left text-top"
>
created_by john.david.dalton@gmail.com
</td>
</tr>
<tr
key="0"
>
<td
className="thin text-left text-top nowrap"
>
<DateTimeFormatter
date="2017-03-01T09:36:01+0100"
/>
</td>
<td
className="text-left text-top"
>
<p>
<withAppStateContext(Avatar)
className="little-spacer-right"
hash="gravatarhash"
name="John Doe"
size={16}
/>
John Doe
</p>
<IssueChangelogDiff
diff={
{
"key": "severity",
"newValue": "MINOR",
"oldValue": "CRITICAL",
}
}
key="severity"
/>
</td>
</tr>
</tbody>
</table>
</div>
</DropdownOverlay>
`;

exports[`should render the changelog popup when change was triggered by a webhook with external user 1`] = `
<DropdownOverlay
placement="bottom-right"
>
<div
className="menu is-container issue-changelog"
>
<table
className="spaced"
>
<tbody>
<tr>
<td
className="thin text-left text-top nowrap"
>
<DateTimeFormatter
date="2017-03-01T09:36:01+0100"
/>
</td>
<td
className="text-left text-top"
>
created_by john.david.dalton@gmail.com
</td>
</tr>
<tr
key="0"
>
<td
className="thin text-left text-top nowrap"
>
<DateTimeFormatter
date="2017-03-01T09:36:01+0100"
/>
</td>
<td
className="text-left text-top"
>
<p>
<withAppStateContext(Avatar)
className="little-spacer-right"
name="toto@github.com"
size={16}
/>
toto@github.com
issue.changelog.webhook_source.GitHub
</p>
<IssueChangelogDiff
diff={
{
"key": "severity",
"newValue": "MINOR",
"oldValue": "CRITICAL",
}
}
key="severity"
/>
</td>
</tr>
</tbody>
</table>
</div>
</DropdownOverlay>
`;

exports[`should render the changelog popup when change was triggered by a webhook without user 1`] = `
<DropdownOverlay
placement="bottom-right"
>
<div
className="menu is-container issue-changelog"
>
<table
className="spaced"
>
<tbody>
<tr>
<td
className="thin text-left text-top nowrap"
>
<DateTimeFormatter
date="2017-03-01T09:36:01+0100"
/>
</td>
<td
className="text-left text-top"
>
created_by john.david.dalton@gmail.com
</td>
</tr>
<tr
key="0"
>
<td
className="thin text-left text-top nowrap"
>
<DateTimeFormatter
date="2017-03-01T09:36:01+0100"
/>
</td>
<td
className="text-left text-top"
>
<p>
issue.changelog.webhook_source.GitHub
</p>
<IssueChangelogDiff
diff={
{
"key": "severity",
"newValue": "MINOR",
"oldValue": "CRITICAL",
}
}
key="severity"
/>
</td>
</tr>
</tbody>
</table>
</div>
</DropdownOverlay>
`;

exports[`should render the changelog popup when we have a deleted user 1`] = `
<DropdownOverlay
placement="bottom-right"
>
<div
className="menu is-container issue-changelog"
>
<table
className="spaced"
>
<tbody>
<tr>
<td
className="thin text-left text-top nowrap"
>
<DateTimeFormatter
date="2017-03-01T09:36:01+0100"
/>
</td>
<td
className="text-left text-top"
>
created_by john.david.dalton@gmail.com
</td>
</tr>
<tr
key="0"
>
<td
className="thin text-left text-top nowrap"
>
<DateTimeFormatter
date="2017-03-01T09:36:01+0100"
/>
</td>
<td
className="text-left text-top"
>
<p>
<withAppStateContext(Avatar)
className="little-spacer-right"
name="john.doe"
size={16}
/>
user.x_deleted.john.doe
</p>
<IssueChangelogDiff
diff={
{
"key": "severity",
"newValue": "MINOR",
"oldValue": "CRITICAL",
}
}
key="severity"
/>
</td>
</tr>
</tbody>
</table>
</div>
</DropdownOverlay>
`;

exports[`should render the changelog popup with SQ user when both SQ and external user are presents 1`] = `
<DropdownOverlay
placement="bottom-right"
>
<div
className="menu is-container issue-changelog"
>
<table
className="spaced"
>
<tbody>
<tr>
<td
className="thin text-left text-top nowrap"
>
<DateTimeFormatter
date="2017-03-01T09:36:01+0100"
/>
</td>
<td
className="text-left text-top"
>
created_by john.david.dalton@gmail.com
</td>
</tr>
<tr
key="0"
>
<td
className="thin text-left text-top nowrap"
>
<DateTimeFormatter
date="2017-03-01T09:36:01+0100"
/>
</td>
<td
className="text-left text-top"
>
<p>
<withAppStateContext(Avatar)
className="little-spacer-right"
name="toto@sonarqube.com"
size={16}
/>
toto@sonarqube.com
issue.changelog.webhook_source.GitHub
</p>
<IssueChangelogDiff
diff={
{
"key": "severity",
"newValue": "MINOR",
"oldValue": "CRITICAL",
}
}
key="severity"
/>
</td>
</tr>
</tbody>
</table>
</div>
</DropdownOverlay>
`;

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

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

exports[`should render the comment delete popup correctly 1`] = `
<DropdownOverlay
placement="bottom-right"
>
<div
className="menu is-container"
>
<div
className="spacer-bottom"
>
issue.comment.delete_confirm_message
</div>
<Button
className="button-red"
onClick={[MockFunction]}
>
delete
</Button>
</div>
</DropdownOverlay>
`;

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

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

exports[`should render correctly 1`] = `
<DropdownOverlay
noPadding={true}
>
<div
className="multi-select"
>
<div
className="menu-search"
>
<SearchBox
autoFocus={true}
className="little-spacer-top"
minLength={2}
onChange={[Function]}
placeholder="search.search_for_users"
value=""
/>
</div>
<SelectList
currentItem="luke"
items={
[
"luke",
"",
]
}
onSelect={[MockFunction]}
>
<SelectListItem
item="luke"
key="luke"
>
<withAppStateContext(Avatar)
className="spacer-right"
name="Skywalker"
size={16}
/>
<span
className="text-middle"
style={
{
"marginLeft": 24,
}
}
>
Skywalker
</span>
</SelectListItem>
<SelectListItem
item=""
key=""
>
<span
className="text-middle"
style={
{
"marginLeft": undefined,
}
}
>
unassigned
</span>
</SelectListItem>
</SelectList>
</div>
</DropdownOverlay>
`;

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

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

exports[`should render correctly 1`] = `
<DropdownOverlay
noPadding={true}
>
<header
className="menu-search"
>
<h6>
issue.filter_similar_issues
</h6>
</header>
<SelectList
className="issues-similar-issues-menu"
currentItem="type"
items={
[
"type",
"severity",
"status",
"resolution",
"assignee",
"rule",
"tag###test-tag",
"project",
"file",
]
}
onSelect={[Function]}
>
<SelectListItem
className="display-flex-center"
item="type"
>
<IssueTypeIcon
className="little-spacer-right"
query="BUG"
/>
issue.type.BUG
</SelectListItem>
<SelectListItem
item="severity"
>
<SeverityHelper
className="display-flex-center"
severity="MAJOR"
/>
</SelectListItem>
<SelectListItem
item="status"
>
<StatusHelper
className="display-flex-center"
status="OPEN"
/>
</SelectListItem>
<SelectListItem
item="resolution"
>
unresolved
</SelectListItem>
<SelectListItem
item="assignee"
>
unassigned
</SelectListItem>
<li
className="divider"
/>
<SelectListItem
item="rule"
>
foo
</SelectListItem>
<SelectListItem
item="tag###test-tag"
key="tag###test-tag"
>
<TagsIcon
className="little-spacer-right text-middle"
/>
<span
className="text-middle"
>
test-tag
</span>
</SelectListItem>
<li
className="divider"
/>
<SelectListItem
item="project"
>
<QualifierIcon
className="little-spacer-right"
qualifier="TRK"
/>
Foo
</SelectListItem>
<SelectListItem
item="file"
>
<QualifierIcon
className="little-spacer-right"
qualifier="FIL"
/>
main.js
</SelectListItem>
</SelectList>
</DropdownOverlay>
`;

exports[`should render correctly when assigned 1`] = `
<SelectListItem
item="assignee"
>
<span>
assigned_to
<withAppStateContext(Avatar)
className="little-spacer-left little-spacer-right"
name="Luke Skywalker"
size={16}
/>
Luke Skywalker
</span>
</SelectListItem>
`;

exports[`should render correctly when assigned 2`] = `
<SelectListItem
item="assignee"
>
<span>
assigned_to
<withAppStateContext(Avatar)
className="little-spacer-left little-spacer-right"
name="luke"
size={16}
/>
user.x_deleted.luke
</span>
</SelectListItem>
`;

+ 2
- 2
server/sonar-web/src/main/js/helpers/constants.ts View File

@@ -20,10 +20,10 @@
import { colors } from '../app/theme';
import { AlmKeys } from '../types/alm-settings';
import { ComponentQualifier } from '../types/component';
import { IssueScope, IssueType } from '../types/issues';
import { IssueScope, IssueSeverity, IssueType } from '../types/issues';
import { RuleType } from '../types/types';

export const SEVERITIES = ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO'];
export const SEVERITIES = Object.values(IssueSeverity);
export const STATUSES = ['OPEN', 'REOPENED', 'CONFIRMED', 'RESOLVED', 'CLOSED'];
export const ISSUE_TYPES: IssueType[] = [
IssueType.Bug,

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

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

export function mockReferencedRule(overrides: Partial<ReferencedRule> = {}): ReferencedRule {
return {
@@ -35,13 +36,32 @@ export function mockIssueChangelog(overrides: Partial<IssueChangelog> = {}): Iss
isUserActive: true,
user: 'luke.skywalker',
userName: 'Luke Skywalker',
diffs: [
{
key: 'assign',
newValue: 'darth.vader',
oldValue: 'luke.skywalker',
},
],
diffs: [mockIssueChangelogDiff()],
...overrides,
};
}

export function mockIssueChangelogDiff(
overrides: Partial<IssueChangelogDiff> = {}
): IssueChangelogDiff {
return {
key: 'assign',
newValue: 'darth.vader',
oldValue: 'luke.skywalker',
...overrides,
};
}

export function mockIssueComment(overrides: Partial<IssueComment> = {}): IssueComment {
return {
author: 'luke.skywalker',
authorLogin: 'luke.skywalker',
authorName: 'Luke Skywalker',
createdAt: '2018-10-01',
htmlText: 'This is a <strong>comment</strong>, <code>bud</code>',
key: uniqueId(),
markdown: 'This is a *comment*, `bud`',
updatable: false,
...overrides,
};
}

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

@@ -295,6 +295,7 @@ export function mockRawIssue(withLocations = false, overrides: Partial<RawIssue>
status: IssueStatus.Open,
textRange: { startLine: 25, endLine: 26, startOffset: 0, endOffset: 15 },
type: IssueType.CodeSmell,
transitions: [],
scope: IssueScope.Main,
...overrides,
};

+ 24
- 6
server/sonar-web/src/main/js/types/issues.ts View File

@@ -29,12 +29,13 @@ export enum IssueType {
SecurityHotspot = 'SECURITY_HOTSPOT',
}

// Keep this enum in the correct order (most severe to least severe).
export enum IssueSeverity {
Blocker = 'BLOCKER',
Minor = 'MINOR',
Critical = 'CRITICAL',
Info = 'INFO',
Major = 'MAJOR',
Minor = 'MINOR',
Info = 'INFO',
}

export enum IssueScope {
@@ -58,6 +59,23 @@ export enum IssueStatus {
Closed = 'CLOSED',
}

export enum IssueActions {
SetType = 'set_type',
SetTags = 'set_tags',
SetSeverity = 'set_severity',
Comment = 'comment',
Assign = 'assign',
}

export enum IssueTransition {
Confirm = 'confirm',
UnConfirm = 'unconfirm',
Resolve = 'resolve',
FalsePositive = 'falsepositive',
WontFix = 'wontfix',
Reopen = 'reopen',
}

interface Comment {
createdAt: string;
htmlText: string;
@@ -87,11 +105,11 @@ export interface RawFlowLocation {

export interface RawIssue {
actions: string[];
transitions?: string[];
transitions: string[];
tags?: string[];
assignee?: string;
author?: string;
comments?: Array<Comment>;
comments?: Comment[];
creationDate: string;
component: string;
flows?: Array<{
@@ -120,7 +138,7 @@ export interface IssueResponse {
components?: Array<{ key: string; name: string }>;
issue: RawIssue;
rules?: Array<{}>;
users?: Array<UserBase>;
users?: UserBase[];
}

export interface RawIssuesResponse {
@@ -131,7 +149,7 @@ export interface RawIssuesResponse {
languages: ReferencedLanguage[];
paging: Paging;
rules?: Array<{}>;
users?: Array<UserBase>;
users?: UserBase[];
}

export interface FetchIssuesPromise {

+ 2
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -846,6 +846,7 @@ issue.assign.unassigned_click_to_assign=Unassigned, click to assign issue
issue.assign.formlink=Assign
issue.assign.to_me=to me
issue.quick_fix_available_with_sonarlint=Quick fix available in {link}
issue.quick_fix_available_with_sonarlint_no_link=Quick fix available in SonarLint
issue.comment.add_comment=Add Comment
issue.comment.add_comment.cancel=Cancel adding comment
issue.comment.enter_comment=Enter Comment
@@ -969,6 +970,7 @@ issues.not_all_issue_show_why=You do not have access to all projects in this por
# ISSUE CHANGELOG
#
#------------------------------------------------------------------------------
issue.changelog.found_on_x_show_more=Found on {0}; click to see changelog
issue.changelog.changed_to={0} changed to {1}
issue.changelog.was=was {0}
issue.changelog.webhook_source= (change triggered by a {0} webhook)

Loading…
Cancel
Save