mockRuleDetails,
} from '../../helpers/testMocks';
import {
+ ASSIGNEE_ME,
IssueType,
RawFacet,
RawIssue,
key: 'issue101',
component: 'foo:test1.js',
message: 'Issue with no location message',
+ type: IssueType.Vulnerability,
rule: 'simpleRuleId',
textRange: {
startLine: 10,
key: 'issue11',
component: 'foo:test1.js',
message: 'FlowIssue',
+ type: IssueType.CodeSmell,
rule: 'simpleRuleId',
textRange: {
startLine: 10,
key: 'issue0',
component: 'foo:test1.js',
message: 'Issue on file',
+ assignee: mockLoggedInUser().login,
+ type: IssueType.Vulnerability,
rule: 'simpleRuleId',
textRange: undefined,
line: undefined,
key: 'issue1',
component: 'foo:test1.js',
message: 'Fix this',
+ type: IssueType.Vulnerability,
rule: 'simpleRuleId',
textRange: {
startLine: 10,
'component.key'
),
},
+ {
+ issue: mockRawIssue(false, {
+ key: 'issue1101',
+ component: 'foo:test5.js',
+ message: 'Issue on page 2',
+ rule: 'simpleRuleId',
+ textRange: undefined,
+ line: undefined,
+ }),
+ snippets: {},
+ },
];
this.list = cloneDeep(this.defaultList);
if (name === 'owaspTop10-2021') {
return this.owasp2021FacetList();
}
+ if (name === 'languages') {
+ return {
+ property: name,
+ values: [
+ {
+ val: 'java',
+ count: 25211,
+ },
+ {
+ val: 'ts',
+ count: 3174,
+ },
+ ],
+ };
+ }
return {
property: name,
values: [],
};
});
+
+ // Filter list (only supports assignee, type and severity)
+ const filteredList = this.list
+ .filter((item) => {
+ if (!query.assignees) {
+ return true;
+ }
+ if (query.assignees === ASSIGNEE_ME) {
+ return item.issue.assignee === mockLoggedInUser().login;
+ }
+ return query.assignees.split(',').includes(item.issue.assignee);
+ })
+ .filter((item) => !query.types || query.types.split(',').includes(item.issue.type))
+ .filter(
+ (item) => !query.severities || query.severities.split(',').includes(item.issue.severity)
+ );
+
+ // Splice list items according to paging using a fixed page size
+ const pageIndex = query.p || 1;
+ const pageSize = 7;
+ const listItems = filteredList.slice((pageIndex - 1) * pageSize, pageIndex * pageSize);
+
+ // Generate response
return this.reply({
- components: generateReferenceComponentsForIssues(this.list),
+ components: generateReferenceComponentsForIssues(filteredList),
effortTotal: 199629,
facets,
- issues: this.list.map((line) => line.issue),
+ issues: listItems.map((line) => line.issue),
languages: [],
- paging: mockPaging(),
+ paging: mockPaging({
+ pageIndex,
+ pageSize,
+ total: filteredList.length,
+ }),
});
};
* 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 { act, screen, waitFor, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import selectEvent from 'react-select-event';
+import { byLabelText, byRole } from 'testing-library-selector';
import ComponentsServiceMock from '../../../api/mocks/ComponentsServiceMock';
import IssuesServiceMock from '../../../api/mocks/IssuesServiceMock';
import { TabKeys } from '../../../components/rules/RuleTabViewer';
import { mockComponent } from '../../../helpers/mocks/component';
import { renderOwaspTop102021Category } from '../../../helpers/security-standard';
-import { mockCurrentUser } from '../../../helpers/testMocks';
+import { mockCurrentUser, mockLoggedInUser } from '../../../helpers/testMocks';
import { renderApp, renderAppWithComponentContext } from '../../../helpers/testReactTestingUtils';
import { ComponentQualifier } from '../../../types/component';
import { IssueType } from '../../../types/issues';
window.HTMLElement.prototype.scrollIntoView = jest.fn();
});
-it('should navigate to Why is this an issue tab', async () => {
- renderProjectIssuesApp('project/issues?issues=issue2&open=issue2&id=myproject&why=1');
- expect(
- await screen.findByRole('tab', {
- name: `coding_rules.description_section.title.root_cause`,
- selected: true,
- })
- ).toBeInTheDocument();
-});
-
-//Improve this to include all the bulk change fonctionality
-it('should be able to bulk change', async () => {
- const user = userEvent.setup();
- issuesHandler.setIsAdmin(true);
- renderIssueApp(mockCurrentUser({ isLoggedIn: true }));
-
- // Check that the bulk button has correct behavior
- expect(await screen.findByRole('button', { name: 'bulk_change' })).toBeDisabled();
- await user.click(screen.getByRole('checkbox', { name: 'issues.select_all_issues' }));
- expect(
- screen.getByRole('button', { name: 'issues.bulk_change_X_issues.500' })
- ).toBeInTheDocument();
- await user.click(screen.getByRole('button', { name: 'issues.bulk_change_X_issues.500' }));
- await user.click(screen.getByRole('button', { name: 'cancel' }));
- expect(screen.getByRole('button', { name: 'issues.bulk_change_X_issues.500' })).toHaveFocus();
- await user.click(screen.getByRole('checkbox', { name: 'issues.select_all_issues' }));
-
- // Check that we bulk change the selected issue
- const issueBoxFixThat = within(await screen.findByRole('region', { name: 'Fix that' }));
-
- expect(
- issueBoxFixThat.getByRole('button', {
- name: 'issue.type.type_x_click_to_change.issue.type.CODE_SMELL',
- })
- ).toBeInTheDocument();
-
- await user.click(screen.getByRole('checkbox', { name: 'issues.action_select.label.Fix that' }));
- expect(screen.getByRole('button', { name: 'issues.bulk_change_X_issues.1' })).toBeInTheDocument();
- await user.click(screen.getByRole('button', { name: 'issues.bulk_change_X_issues.1' }));
-
- await user.click(screen.getByRole('textbox', { name: 'issue.comment.formlink' }));
- await user.keyboard('New Comment');
- expect(screen.getByRole('button', { name: 'apply' })).toBeDisabled();
-
- await selectEvent.select(screen.getByRole('combobox', { name: 'issue.set_type' }), [
- 'issue.type.BUG',
- ]);
- await user.click(screen.getByRole('button', { name: 'apply' }));
-
- expect(
- issueBoxFixThat.getByRole('button', {
- name: 'issue.type.type_x_click_to_change.issue.type.BUG',
- })
- ).toBeInTheDocument();
-});
-
-it('should show warning when not all issues are accessible', async () => {
- const user = userEvent.setup();
- renderProjectIssuesApp('project/issues?id=myproject', {
- canBrowseAllChildProjects: false,
- qualifier: ComponentQualifier.Portfolio,
+const ui = {
+ loading: byLabelText('loading'),
+ issueItems: byRole('region'),
+
+ issueItem1: byRole('region', { name: 'Issue with no location message' }),
+ issueItem2: byRole('region', { name: 'FlowIssue' }),
+ issueItem3: byRole('region', { name: 'Issue on file' }),
+ issueItem4: byRole('region', { name: 'Fix this' }),
+ issueItem5: byRole('region', { name: 'Fix that' }),
+ issueItem6: byRole('region', { name: 'Second issue' }),
+ issueItem7: byRole('region', { name: 'Issue with tags' }),
+ issueItem8: byRole('region', { name: 'Issue on page 2' }),
+
+ codeSmellIssueTypeFilter: byRole('checkbox', { name: 'issue.type.CODE_SMELL' }),
+ clearAllFilters: byRole('button', { name: 'clear_all_filters' }),
+};
+
+async function waitOnDataLoaded() {
+ await waitFor(() => {
+ expect(ui.loading.query()).not.toBeInTheDocument();
});
- expect(await screen.findByRole('alert', { name: 'alert.tooltip.warning' })).toBeInTheDocument();
+}
- await act(async () => {
- await user.keyboard('{ArrowRight}');
+describe('issues app', () => {
+ describe('rendering', () => {
+ it('should show warning when not all issues are accessible', async () => {
+ const user = userEvent.setup();
+ renderProjectIssuesApp('project/issues?id=myproject', {
+ canBrowseAllChildProjects: false,
+ qualifier: ComponentQualifier.Portfolio,
+ });
+ expect(screen.getByRole('alert', { name: 'alert.tooltip.warning' })).toBeInTheDocument();
+
+ await act(async () => {
+ await user.keyboard('{ArrowRight}');
+ });
+
+ expect(screen.getByRole('alert', { name: 'alert.tooltip.warning' })).toBeInTheDocument();
+ });
+
+ it('should support OWASP Top 10 version 2021', async () => {
+ const user = userEvent.setup();
+ renderIssueApp();
+ await user.click(screen.getByRole('button', { name: 'issues.facet.standards' }));
+ const owaspTop102021 = screen.getByRole('button', { name: 'issues.facet.owaspTop10_2021' });
+ expect(owaspTop102021).toBeInTheDocument();
+
+ await user.click(owaspTop102021);
+ await Promise.all(
+ issuesHandler.owasp2021FacetList().values.map(async ({ val }) => {
+ const standard = await issuesHandler.getStandards();
+ /* eslint-disable-next-line testing-library/render-result-naming-convention */
+ const linkName = renderOwaspTop102021Category(standard, val);
+ expect(screen.getByRole('checkbox', { name: linkName })).toBeInTheDocument();
+ })
+ );
+ });
});
- expect(await screen.findByRole('alert', { name: 'alert.tooltip.warning' })).toBeInTheDocument();
+ describe('navigation', () => {
+ it('should handle keyboard navigation in list and open / close issues', async () => {
+ const user = userEvent.setup();
+ renderIssueApp();
+
+ // Navigate to 2nd issue
+ await user.keyboard('{ArrowDown}');
+
+ // Select it
+ await act(async () => {
+ await user.keyboard('{ArrowRight}');
+ });
+ expect(
+ screen.getByRole('heading', { name: issuesHandler.list[1].issue.message })
+ ).toBeInTheDocument();
+
+ // Go back
+ await act(async () => {
+ await user.keyboard('{ArrowLeft}');
+ });
+ expect(
+ screen.queryByRole('heading', { name: issuesHandler.list[1].issue.message })
+ ).not.toBeInTheDocument();
+
+ // Navigate to 1st issue and select it
+ await user.keyboard('{ArrowUp}');
+ await user.keyboard('{ArrowUp}');
+ await act(async () => {
+ await user.keyboard('{ArrowRight}');
+ });
+ expect(
+ screen.getByRole('heading', { name: issuesHandler.list[0].issue.message })
+ ).toBeInTheDocument();
+ });
+
+ it('should open issue and navigate', async () => {
+ const user = userEvent.setup();
+ renderIssueApp();
+
+ // Select an issue with an advanced rule
+ await user.click(await screen.findByRole('region', { name: 'Fix that' }));
+ expect(screen.getByRole('tab', { name: 'issue.tabs.code' })).toBeInTheDocument();
+
+ // Are rule headers present?
+ expect(screen.getByRole('heading', { level: 1, name: 'Fix that' })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'advancedRuleId' })).toBeInTheDocument();
+
+ // Select the "why is this an issue" tab and check its content
+ await user.click(
+ screen.getByRole('tab', { name: `coding_rules.description_section.title.root_cause` })
+ );
+ expect(screen.getByRole('heading', { name: 'Because' })).toBeInTheDocument();
+
+ // Select the "how to fix it" tab
+ await user.click(
+ screen.getByRole('tab', { name: `coding_rules.description_section.title.how_to_fix` })
+ );
+
+ // Is the context selector present with the expected values and default selection?
+ expect(screen.getByRole('button', { name: 'Context 2' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Context 3' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Spring' })).toBeInTheDocument();
+ expect(
+ screen.getByRole('button', { name: 'coding_rules.description_context.other' })
+ ).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'Spring' })).toHaveClass('selected');
+
+ // Select context 2 and check tab content
+ await user.click(screen.getByRole('button', { name: 'Context 2' }));
+ expect(screen.getByText('Context 2 content')).toBeInTheDocument();
+
+ // Select the "other" context and check tab content
+ await user.click(
+ screen.getByRole('button', { name: 'coding_rules.description_context.other' })
+ );
+ expect(screen.getByText('coding_rules.context.others.title')).toBeInTheDocument();
+ expect(screen.getByText('coding_rules.context.others.description.first')).toBeInTheDocument();
+ expect(
+ screen.getByText('coding_rules.context.others.description.second')
+ ).toBeInTheDocument();
+
+ // Select the main info tab and check its content
+ await user.click(
+ screen.getByRole('tab', { name: `coding_rules.description_section.title.more_info` })
+ );
+ expect(screen.getByRole('heading', { name: 'Link' })).toBeInTheDocument();
+
+ // Check for extended description (eslint FP)
+ // eslint-disable-next-line jest-dom/prefer-in-document
+ expect(screen.getAllByText('Extended Description')).toHaveLength(1);
+
+ // Select the previous issue (with a simple rule) through keyboard shortcut
+ await act(async () => {
+ await user.keyboard('{ArrowUp}');
+ });
+
+ // Are rule headers present?
+ expect(screen.getByRole('heading', { level: 1, name: 'Fix this' })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'simpleRuleId' })).toBeInTheDocument();
+
+ // Select the "why is this an issue tab" and check its content
+ await user.click(
+ screen.getByRole('tab', { name: `coding_rules.description_section.title.root_cause` })
+ );
+ expect(screen.getByRole('heading', { name: 'Default' })).toBeInTheDocument();
+
+ // Select the previous issue (with a simple rule) through keyboard shortcut
+ await act(async () => {
+ await user.keyboard('{ArrowUp}');
+ });
+
+ // Are rule headers present?
+ expect(screen.getByRole('heading', { level: 1, name: 'Issue on file' })).toBeInTheDocument();
+ expect(screen.getByRole('link', { name: 'simpleRuleId' })).toBeInTheDocument();
+
+ // The "Where is the issue" tab should be selected by default. Check its content
+ expect(screen.getByRole('region', { name: 'Issue on file' })).toBeInTheDocument();
+ expect(
+ screen.getByRole('row', {
+ name: '2 * SonarQube',
+ })
+ ).toBeInTheDocument();
+ });
+
+ it('should be able to navigate to other issue located in the same file', async () => {
+ const user = userEvent.setup();
+ renderIssueApp();
+
+ await user.click(await ui.issueItem5.find());
+ expect(ui.issueItem6.get()).toBeInTheDocument();
+
+ await user.click(ui.issueItem6.get());
+ expect(screen.getByRole('heading', { level: 1, name: 'Second issue' })).toBeInTheDocument();
+ });
+
+ it('should be able to show more issues', async () => {
+ const user = userEvent.setup();
+ renderIssueApp();
+
+ expect(await ui.issueItems.findAll()).toHaveLength(7);
+ expect(ui.issueItem8.query()).not.toBeInTheDocument();
+
+ await user.click(screen.getByRole('button', { name: 'show_more' }));
+ expect(ui.issueItems.getAll()).toHaveLength(8);
+ expect(ui.issueItem8.get()).toBeInTheDocument();
+ });
+
+ // Improve this to include all the bulk change fonctionality
+ it('should be able to bulk change', async () => {
+ const user = userEvent.setup();
+ issuesHandler.setIsAdmin(true);
+ renderIssueApp(mockLoggedInUser());
+
+ // Check that the bulk button has correct behavior
+ expect(screen.getByRole('button', { name: 'bulk_change' })).toBeDisabled();
+ await user.click(screen.getByRole('checkbox', { name: 'issues.select_all_issues' }));
+ expect(
+ screen.getByRole('button', { name: 'issues.bulk_change_X_issues.8' })
+ ).toBeInTheDocument();
+ await user.click(screen.getByRole('button', { name: 'issues.bulk_change_X_issues.8' }));
+ await user.click(screen.getByRole('button', { name: 'cancel' }));
+ expect(screen.getByRole('button', { name: 'issues.bulk_change_X_issues.8' })).toHaveFocus();
+ await user.click(screen.getByRole('checkbox', { name: 'issues.select_all_issues' }));
+
+ // Check that we bulk change the selected issue
+ const issueBoxFixThat = within(screen.getByRole('region', { name: 'Fix that' }));
+
+ expect(
+ issueBoxFixThat.getByRole('button', {
+ name: 'issue.type.type_x_click_to_change.issue.type.CODE_SMELL',
+ })
+ ).toBeInTheDocument();
+
+ await user.click(
+ screen.getByRole('checkbox', { name: 'issues.action_select.label.Fix that' })
+ );
+ await user.click(screen.getByRole('button', { name: 'issues.bulk_change_X_issues.1' }));
+
+ await user.click(screen.getByRole('textbox', { name: 'issue.comment.formlink' }));
+ await user.keyboard('New Comment');
+ expect(screen.getByRole('button', { name: 'apply' })).toBeDisabled();
+
+ await selectEvent.select(screen.getByRole('combobox', { name: 'issue.set_type' }), [
+ 'issue.type.BUG',
+ ]);
+ await user.click(screen.getByRole('button', { name: 'apply' }));
+
+ expect(
+ issueBoxFixThat.getByRole('button', {
+ name: 'issue.type.type_x_click_to_change.issue.type.BUG',
+ })
+ ).toBeInTheDocument();
+ });
+ });
+ describe('filtering', () => {
+ it('should allow to reset all facets', async () => {
+ const user = userEvent.setup();
+ renderIssueApp();
+
+ await user.click(ui.codeSmellIssueTypeFilter.get());
+ expect(ui.codeSmellIssueTypeFilter.get()).toBeChecked();
+ expect(ui.issueItem4.query()).not.toBeInTheDocument();
+
+ await user.click(ui.clearAllFilters.get());
+ expect(ui.codeSmellIssueTypeFilter.get()).not.toBeChecked();
+ expect(ui.issueItem4.get()).toBeInTheDocument();
+ });
+
+ it('should handle filtering from a specific issue properly', async () => {
+ const user = userEvent.setup();
+ renderIssueApp();
+
+ // Get first issue list item
+ const issueItem = await ui.issueItem2.find();
+
+ // Ensure issue type filter is unchecked
+ expect(ui.codeSmellIssueTypeFilter.get()).not.toBeChecked();
+ expect(ui.issueItem2.get()).toBeInTheDocument();
+ expect(ui.issueItem3.get()).toBeInTheDocument();
+
+ // Open filter similar issue dropdown
+ await user.click(
+ await within(issueItem).findByRole('button', { name: 'issue.filter_similar_issues' })
+ );
+
+ // Select type
+ await user.click(
+ await within(issueItem).findByRole('button', { name: 'issue.type.CODE_SMELL' })
+ );
+
+ // Ensure issue type filter is now checked
+ expect(ui.codeSmellIssueTypeFilter.get()).toBeChecked();
+ expect(ui.issueItem2.get()).toBeInTheDocument();
+ expect(ui.issueItem3.query()).not.toBeInTheDocument();
+ });
+
+ it('should allow to only show my issues', async () => {
+ const user = userEvent.setup();
+ renderIssueApp(mockLoggedInUser());
+ await waitOnDataLoaded();
+
+ // By default, it should show all issues
+ expect(ui.issueItem2.get()).toBeInTheDocument();
+ expect(ui.issueItem3.get()).toBeInTheDocument();
+
+ // Only show my issues
+ await user.click(screen.getByRole('button', { name: 'issues.my_issues' }));
+ expect(ui.issueItem2.query()).not.toBeInTheDocument();
+ expect(ui.issueItem3.get()).toBeInTheDocument();
+
+ // Show all issues again
+ await user.click(screen.getByRole('button', { name: 'all' }));
+ expect(ui.issueItem2.get()).toBeInTheDocument();
+ expect(ui.issueItem3.get()).toBeInTheDocument();
+ });
+ });
});
-it('should show secondary location even when no message is present', async () => {
- renderProjectIssuesApp('project/issues?issues=issue101&open=issue101&id=myproject');
+describe('issues item', () => {
+ it('should navigate to Why is this an issue tab', async () => {
+ renderProjectIssuesApp('project/issues?issues=issue2&open=issue2&id=myproject&why=1');
- expect(await screen.findByRole('button', { name: '1 issue.location_x.1' })).toBeInTheDocument();
- expect(screen.getByRole('button', { name: '2 issue.location_x.2' })).toBeInTheDocument();
-});
+ expect(
+ await screen.findByRole('tab', {
+ name: `coding_rules.description_section.title.root_cause`,
+ selected: true,
+ })
+ ).toBeInTheDocument();
+ });
-it('should interact with flows and locations', async () => {
- const user = userEvent.setup();
- renderProjectIssuesApp('project/issues?issues=issue11&open=issue11&id=myproject');
- const dataFlowButton = await screen.findByRole('button', { name: 'Backtracking 1' });
- const exectionFlowButton = await screen.findByRole('button', { name: 'issue.execution_flow' });
-
- let dataLocation1Button = screen.getByRole('button', { name: '1 Data location 1' });
- let dataLocation2Button = screen.getByRole('button', { name: '2 Data location 2' });
-
- expect(dataFlowButton).toBeInTheDocument();
- expect(dataLocation1Button).toBeInTheDocument();
- expect(dataLocation2Button).toBeInTheDocument();
-
- await user.click(dataFlowButton);
- // Colapsing flow
- expect(dataLocation1Button).not.toBeInTheDocument();
- expect(dataLocation2Button).not.toBeInTheDocument();
-
- await user.click(exectionFlowButton);
- expect(screen.getByRole('button', { name: '1 Execution location 1' })).toBeInTheDocument();
- expect(screen.getByRole('button', { name: '2 Execution location 2' })).toBeInTheDocument();
- expect(screen.getByRole('button', { name: '3 Execution location 3' })).toBeInTheDocument();
-
- // Keyboard interaction
- await user.click(dataFlowButton);
- dataLocation1Button = screen.getByRole('button', { name: '1 Data location 1' });
- dataLocation2Button = screen.getByRole('button', { name: '2 Data location 2' });
-
- //Location navigation
- await user.keyboard('{Alt>}{ArrowDown}{/Alt}');
- expect(dataLocation1Button).toHaveClass('selected');
- await user.keyboard('{Alt>}{ArrowDown}{/Alt}');
- expect(dataLocation1Button).not.toHaveClass('selected');
- expect(dataLocation2Button).toHaveClass('selected');
- await user.keyboard('{Alt>}{ArrowDown}{/Alt}');
- expect(dataLocation1Button).not.toHaveClass('selected');
- expect(dataLocation2Button).not.toHaveClass('selected');
- await user.keyboard('{Alt>}{ArrowUp}{/Alt}');
- expect(dataLocation1Button).not.toHaveClass('selected');
- expect(dataLocation2Button).toHaveClass('selected');
-
- //Flow navigation
- await user.keyboard('{Alt>}{ArrowRight}{/Alt}');
- expect(screen.getByRole('button', { name: '1 Execution location 1' })).toHaveClass('selected');
- await user.keyboard('{Alt>}{ArrowLeft}{/Alt}');
- expect(screen.getByRole('button', { name: '1 Data location 1' })).toHaveClass('selected');
-});
+ it('should show secondary location even when no message is present', async () => {
+ renderProjectIssuesApp('project/issues?issues=issue101&open=issue101&id=myproject');
-it('should show education principles', async () => {
- const user = userEvent.setup();
- renderProjectIssuesApp('project/issues?issues=issue2&open=issue2&id=myproject');
- await user.click(
- await screen.findByRole('tab', { name: `coding_rules.description_section.title.more_info` })
- );
- expect(screen.getByRole('heading', { name: 'Defense-In-Depth', level: 3 })).toBeInTheDocument();
-});
+ expect(await screen.findByRole('button', { name: '1 issue.location_x.1' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '2 issue.location_x.2' })).toBeInTheDocument();
+ });
-it('should open issue and navigate', async () => {
- const user = userEvent.setup();
+ it('should interact with flows and locations', async () => {
+ const user = userEvent.setup();
+ renderProjectIssuesApp('project/issues?issues=issue11&open=issue11&id=myproject');
+ const dataFlowButton = await screen.findByRole('button', { name: 'Backtracking 1' });
+ const exectionFlowButton = screen.getByRole('button', { name: 'issue.execution_flow' });
+
+ let dataLocation1Button = screen.getByRole('button', { name: '1 Data location 1' });
+ let dataLocation2Button = screen.getByRole('button', { name: '2 Data location 2' });
+
+ expect(dataFlowButton).toBeInTheDocument();
+ expect(dataLocation1Button).toBeInTheDocument();
+ expect(dataLocation2Button).toBeInTheDocument();
+
+ await user.click(dataFlowButton);
+ // Colapsing flow
+ expect(dataLocation1Button).not.toBeInTheDocument();
+ expect(dataLocation2Button).not.toBeInTheDocument();
+
+ await user.click(exectionFlowButton);
+ expect(screen.getByRole('button', { name: '1 Execution location 1' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '2 Execution location 2' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '3 Execution location 3' })).toBeInTheDocument();
+
+ // Keyboard interaction
+ await user.click(dataFlowButton);
+ dataLocation1Button = screen.getByRole('button', { name: '1 Data location 1' });
+ dataLocation2Button = screen.getByRole('button', { name: '2 Data location 2' });
+
+ // Location navigation
+ await user.keyboard('{Alt>}{ArrowDown}{/Alt}');
+ expect(dataLocation1Button).toHaveClass('selected');
+ await user.keyboard('{Alt>}{ArrowDown}{/Alt}');
+ expect(dataLocation1Button).not.toHaveClass('selected');
+ expect(dataLocation2Button).toHaveClass('selected');
+ await user.keyboard('{Alt>}{ArrowDown}{/Alt}');
+ expect(dataLocation1Button).not.toHaveClass('selected');
+ expect(dataLocation2Button).not.toHaveClass('selected');
+ await user.keyboard('{Alt>}{ArrowUp}{/Alt}');
+ expect(dataLocation1Button).not.toHaveClass('selected');
+ expect(dataLocation2Button).toHaveClass('selected');
+
+ // Flow navigation
+ await user.keyboard('{Alt>}{ArrowRight}{/Alt}');
+ expect(screen.getByRole('button', { name: '1 Execution location 1' })).toHaveClass('selected');
+ await user.keyboard('{Alt>}{ArrowLeft}{/Alt}');
+ expect(screen.getByRole('button', { name: '1 Data location 1' })).toHaveClass('selected');
+ });
- renderIssueApp(mockCurrentUser());
+ it('should show education principles', async () => {
+ const user = userEvent.setup();
+ renderProjectIssuesApp('project/issues?issues=issue2&open=issue2&id=myproject');
+ await user.click(
+ await screen.findByRole('tab', { name: `coding_rules.description_section.title.more_info` })
+ );
+ expect(screen.getByRole('heading', { name: 'Defense-In-Depth', level: 3 })).toBeInTheDocument();
+ });
- // Select an issue with an advanced rule
- expect(await screen.findByRole('region', { name: 'Fix that' })).toBeInTheDocument();
- await user.click(screen.getByRole('region', { name: 'Fix that' }));
- expect(screen.getByRole('tab', { name: 'issue.tabs.code' })).toBeInTheDocument();
+ it('should be able to perform action on issues', async () => {
+ const user = userEvent.setup();
+ issuesHandler.setIsAdmin(true);
+ renderIssueApp();
- // Are rule headers present?
- expect(screen.getByRole('heading', { level: 1, name: 'Fix that' })).toBeInTheDocument();
- expect(screen.getByRole('link', { name: 'advancedRuleId' })).toBeInTheDocument();
+ // Get 'Fix that' issue list item
+ const listItem = within(await screen.findByRole('region', { name: 'Fix that' }));
- // Select the "why is this an issue" tab and check its content
- expect(
- screen.getByRole('tab', { name: `coding_rules.description_section.title.root_cause` })
- ).toBeInTheDocument();
- await user.click(
- screen.getByRole('tab', { name: `coding_rules.description_section.title.root_cause` })
- );
- expect(screen.getByRole('heading', { name: 'Because' })).toBeInTheDocument();
-
- // Select the "how to fix it" tab
- expect(
- screen.getByRole('tab', { name: `coding_rules.description_section.title.how_to_fix` })
- ).toBeInTheDocument();
- await user.click(
- screen.getByRole('tab', { name: `coding_rules.description_section.title.how_to_fix` })
- );
+ // Change issue type
+ await user.click(
+ listItem.getByRole('button', {
+ name: `issue.type.type_x_click_to_change.issue.type.CODE_SMELL`,
+ })
+ );
+ expect(listItem.getByText('issue.type.BUG')).toBeInTheDocument();
+ expect(listItem.getByText('issue.type.VULNERABILITY')).toBeInTheDocument();
- // Is the context selector present with the expected values and default selection?
- expect(screen.getByRole('button', { name: 'Context 2' })).toBeInTheDocument();
- expect(screen.getByRole('button', { name: 'Context 3' })).toBeInTheDocument();
- expect(screen.getByRole('button', { name: 'Spring' })).toBeInTheDocument();
- expect(
- screen.getByRole('button', { name: 'coding_rules.description_context.other' })
- ).toBeInTheDocument();
- expect(screen.getByRole('button', { name: 'Spring' })).toHaveClass('selected');
-
- // Select context 2 and check tab content
- await user.click(screen.getByRole('button', { name: 'Context 2' }));
- expect(screen.getByText('Context 2 content')).toBeInTheDocument();
-
- // Select the "other" context and check tab content
- await user.click(screen.getByRole('button', { name: 'coding_rules.description_context.other' }));
- expect(screen.getByText('coding_rules.context.others.title')).toBeInTheDocument();
- expect(screen.getByText('coding_rules.context.others.description.first')).toBeInTheDocument();
- expect(screen.getByText('coding_rules.context.others.description.second')).toBeInTheDocument();
-
- // Select the main info tab and check its content
- expect(
- screen.getByRole('tab', { name: `coding_rules.description_section.title.more_info` })
- ).toBeInTheDocument();
- await user.click(
- screen.getByRole('tab', { name: `coding_rules.description_section.title.more_info` })
- );
- expect(screen.getByRole('heading', { name: 'Link' })).toBeInTheDocument();
+ await user.click(listItem.getByText('issue.type.VULNERABILITY'));
+ expect(
+ listItem.getByRole('button', {
+ name: `issue.type.type_x_click_to_change.issue.type.VULNERABILITY`,
+ })
+ ).toBeInTheDocument();
- // check for extended description
- const extendedDescriptions = screen.getAllByText('Extended Description');
+ // Change issue severity
+ expect(listItem.getByText('severity.MAJOR')).toBeInTheDocument();
- // FP
- // eslint-disable-next-line jest-dom/prefer-in-document
- expect(extendedDescriptions).toHaveLength(1);
+ await user.click(
+ listItem.getByRole('button', {
+ name: `issue.severity.severity_x_click_to_change.severity.MAJOR`,
+ })
+ );
+ expect(listItem.getByText('severity.MINOR')).toBeInTheDocument();
+ expect(listItem.getByText('severity.INFO')).toBeInTheDocument();
+ await user.click(listItem.getByText('severity.MINOR'));
+ expect(
+ listItem.getByRole('button', {
+ name: `issue.severity.severity_x_click_to_change.severity.MINOR`,
+ })
+ ).toBeInTheDocument();
- // Select the previous issue (with a simple rule) through keyboard shortcut
- await act(async () => {
- await user.keyboard('{ArrowUp}');
- });
+ // Change issue status
+ expect(listItem.getByText('issue.status.OPEN')).toBeInTheDocument();
- // Are rule headers present?
- expect(screen.getByRole('heading', { level: 1, name: 'Fix this' })).toBeInTheDocument();
- expect(screen.getByRole('link', { name: 'simpleRuleId' })).toBeInTheDocument();
+ await user.click(listItem.getByText('issue.status.OPEN'));
+ expect(listItem.getByText('issue.transition.confirm')).toBeInTheDocument();
+ expect(listItem.getByText('issue.transition.resolve')).toBeInTheDocument();
- // Select the "why is this an issue tab" and check its content
- expect(
- screen.getByRole('tab', { name: `coding_rules.description_section.title.root_cause` })
- ).toBeInTheDocument();
- await user.click(
- screen.getByRole('tab', { name: `coding_rules.description_section.title.root_cause` })
- );
- expect(screen.getByRole('heading', { name: 'Default' })).toBeInTheDocument();
+ await user.click(listItem.getByText('issue.transition.confirm'));
+ expect(
+ listItem.getByRole('button', {
+ name: `issue.transition.status_x_click_to_change.issue.status.CONFIRMED`,
+ })
+ ).toBeInTheDocument();
- // Select the previous issue (with a simple rule) through keyboard shortcut
- await act(async () => {
- await user.keyboard('{ArrowUp}');
+ // As won't fix
+ await user.click(listItem.getByText('issue.status.CONFIRMED'));
+ await user.click(listItem.getByText('issue.transition.wontfix'));
+ // Comment should open and close
+ expect(listItem.getByRole('button', { name: 'issue.comment.formlink' })).toBeInTheDocument();
+ await user.keyboard('test');
+ await user.click(listItem.getByRole('button', { name: 'issue.comment.formlink' }));
+ expect(
+ listItem.queryByRole('button', { name: 'issue.comment.submit' })
+ ).not.toBeInTheDocument();
+
+ // Assign issue to a different user
+ await user.click(
+ listItem.getByRole('button', {
+ name: `issue.assign.unassigned_click_to_assign`,
+ })
+ );
+ await user.click(listItem.getByRole('searchbox', { name: 'search.search_for_users' }));
+ await user.keyboard('luke');
+ expect(listItem.getByText('Skywalker')).toBeInTheDocument();
+ await user.keyboard('{ArrowUp}{enter}');
+ expect(
+ listItem.getByRole('button', {
+ name: 'issue.assign.assigned_to_x_click_to_change.luke',
+ })
+ ).toBeInTheDocument();
+
+ // Add comment to the issue
+ await user.click(
+ listItem.getByRole('button', {
+ name: `issue.comment.add_comment`,
+ })
+ );
+ await user.keyboard('comment');
+ await user.click(listItem.getByRole('button', { name: 'issue.comment.formlink' }));
+ expect(listItem.getByText('comment')).toBeInTheDocument();
+
+ // Cancel editing the comment
+ await user.click(listItem.getByRole('button', { name: 'issue.comment.edit' }));
+ await user.keyboard('New ');
+ await user.click(listItem.getByRole('button', { name: 'issue.comment.edit.cancel' }));
+ expect(listItem.queryByText('New comment')).not.toBeInTheDocument();
+
+ // Edit the comment
+ await user.click(listItem.getByRole('button', { name: 'issue.comment.edit' }));
+ await user.keyboard('New ');
+ await user.click(listItem.getByText('save'));
+ expect(listItem.getByText('New comment')).toBeInTheDocument();
+
+ // Delete the comment
+ await user.click(listItem.getByRole('button', { name: 'issue.comment.delete' }));
+ await user.click(listItem.getByRole('button', { name: 'delete' })); // Confirm button
+ expect(listItem.queryByText('New comment')).not.toBeInTheDocument();
+
+ // Add comment using keyboard
+ await user.click(
+ listItem.getByRole('button', {
+ name: `issue.comment.add_comment`,
+ })
+ );
+ await user.keyboard('comment');
+ await user.keyboard('{Control>}{enter}{/Control}');
+ expect(listItem.getByText('comment')).toBeInTheDocument();
+
+ // Edit the comment using keyboard
+ await user.click(listItem.getByRole('button', { name: 'issue.comment.edit' }));
+ await user.keyboard('New ');
+ await user.keyboard('{Control>}{enter}{/Control}');
+ expect(listItem.getByText('New comment')).toBeInTheDocument();
+ await user.keyboard('{Escape}');
+
+ // Change tags
+ expect(listItem.getByText('issue.no_tag')).toBeInTheDocument();
+ await user.click(listItem.getByText('issue.no_tag'));
+ expect(listItem.getByRole('searchbox', { name: 'search.search_for_tags' })).toBeInTheDocument();
+ expect(listItem.getByText('android')).toBeInTheDocument();
+ expect(listItem.getByText('accessibility')).toBeInTheDocument();
+
+ await user.click(listItem.getByText('accessibility'));
+ await user.click(listItem.getByText('android'));
+ expect(listItem.getByTitle('accessibility, android')).toBeInTheDocument();
+
+ // Unselect
+ await user.click(screen.getByText('accessibility'));
+ expect(screen.getByTitle('android')).toBeInTheDocument();
+
+ await user.click(screen.getByRole('searchbox', { name: 'search.search_for_tags' }));
+ await user.keyboard('addNewTag');
+ expect(
+ screen.getByRole('checkbox', { name: 'create_new_element: addnewtag' })
+ ).toBeInTheDocument();
});
- // Are rule headers present?
- expect(screen.getByRole('heading', { level: 1, name: 'Issue on file' })).toBeInTheDocument();
- expect(screen.getByRole('link', { name: 'simpleRuleId' })).toBeInTheDocument();
-
- // The "Where is the issue" tab should be selected by default. Check its content
- expect(screen.getByRole('region', { name: 'Issue on file' })).toBeInTheDocument();
- expect(
- screen.getByRole('row', {
- name: '2 * SonarQube',
- })
- ).toBeInTheDocument();
-});
+ it('should not allow performing actions when user does not have permission', async () => {
+ const user = userEvent.setup();
+ renderIssueApp();
-it('should be able to navigate to other issue located in the same file', async () => {
- const user = userEvent.setup();
- renderIssueApp();
- await user.click(await screen.findByRole('region', { name: 'Fix that' }));
- expect(await screen.findByRole('region', { name: 'Second issue' })).toBeInTheDocument();
- await user.click(await screen.findByRole('region', { name: 'Second issue' }));
- expect(screen.getByRole('heading', { level: 1, name: 'Second issue' })).toBeInTheDocument();
-});
+ await user.click(await ui.issueItem4.find());
-it('should support OWASP Top 10 version 2021', async () => {
- const user = userEvent.setup();
- renderIssueApp();
- await user.click(await screen.findByRole('button', { name: 'issues.facet.standards' }));
- const owaspTop102021 = screen.getByRole('button', { name: 'issues.facet.owaspTop10_2021' });
- expect(owaspTop102021).toBeInTheDocument();
-
- await user.click(owaspTop102021);
- await Promise.all(
- issuesHandler.owasp2021FacetList().values.map(async ({ val }) => {
- const standard = await issuesHandler.getStandards();
- /* eslint-disable-next-line testing-library/render-result-naming-convention */
- const linkName = renderOwaspTop102021Category(standard, val);
- expect(await screen.findByRole('checkbox', { name: linkName })).toBeInTheDocument();
- })
- );
-});
+ expect(
+ screen.queryByRole('button', {
+ name: `issue.assign.unassigned_click_to_assign`,
+ })
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByRole('button', {
+ name: `issue.type.type_x_click_to_change.issue.type.CODE_SMELL`,
+ })
+ ).not.toBeInTheDocument();
+
+ await user.click(
+ screen.getByRole('button', {
+ name: `issue.comment.add_comment`,
+ })
+ );
+ expect(screen.queryByRole('button', { name: 'issue.comment.submit' })).not.toBeInTheDocument();
+ expect(
+ screen.queryByRole('button', {
+ name: `issue.transition.status_x_click_to_change.issue.status.OPEN`,
+ })
+ ).not.toBeInTheDocument();
+ expect(
+ screen.queryByRole('button', {
+ name: `issue.severity.severity_x_click_to_change.severity.MAJOR`,
+ })
+ ).not.toBeInTheDocument();
+ });
-it('should be able to perform action on issues', async () => {
- const user = userEvent.setup();
- issuesHandler.setIsAdmin(true);
- renderIssueApp();
-
- // Select an issue with an advanced rule
- await user.click(await screen.findByRole('region', { name: 'Fix that' }));
-
- // changing issue type
- expect(
- screen.getByRole('button', {
- name: `issue.type.type_x_click_to_change.issue.type.CODE_SMELL`,
- })
- ).toBeInTheDocument();
- await user.click(
- screen.getByRole('button', {
- name: `issue.type.type_x_click_to_change.issue.type.CODE_SMELL`,
- })
- );
- expect(screen.getByText('issue.type.BUG')).toBeInTheDocument();
- expect(screen.getByText('issue.type.VULNERABILITY')).toBeInTheDocument();
-
- await user.click(screen.getByText('issue.type.VULNERABILITY'));
- expect(
- screen.getByRole('button', {
- name: `issue.type.type_x_click_to_change.issue.type.VULNERABILITY`,
- })
- ).toBeInTheDocument();
-
- // changing issue severity
- expect(screen.getByText('severity.MAJOR')).toBeInTheDocument();
-
- await user.click(
- screen.getByRole('button', {
- name: `issue.severity.severity_x_click_to_change.severity.MAJOR`,
- })
- );
- expect(screen.getByText('severity.MINOR')).toBeInTheDocument();
- expect(screen.getByText('severity.INFO')).toBeInTheDocument();
- await user.click(screen.getByText('severity.MINOR'));
- expect(
- screen.getByRole('button', {
- name: `issue.severity.severity_x_click_to_change.severity.MINOR`,
- })
- ).toBeInTheDocument();
-
- // changing issue status
- expect(screen.getByText('issue.status.OPEN')).toBeInTheDocument();
-
- await user.click(screen.getByText('issue.status.OPEN'));
- expect(screen.getByText('issue.transition.confirm')).toBeInTheDocument();
- expect(screen.getByText('issue.transition.resolve')).toBeInTheDocument();
-
- await user.click(screen.getByText('issue.transition.confirm'));
- expect(
- screen.getByRole('button', {
- name: `issue.transition.status_x_click_to_change.issue.status.CONFIRMED`,
- })
- ).toBeInTheDocument();
-
- // As won't fix
- await user.click(screen.getByText('issue.status.CONFIRMED'));
- await user.click(screen.getByText('issue.transition.wontfix'));
- // Comment should open and close
- expect(screen.getByRole('button', { name: 'issue.comment.formlink' })).toBeInTheDocument();
- await user.keyboard('test');
- await user.click(screen.getByRole('button', { name: 'issue.comment.formlink' }));
- expect(screen.queryByRole('button', { name: 'issue.comment.submit' })).not.toBeInTheDocument();
-
- // assigning issue to a different user
- expect(
- screen.getByRole('button', {
- name: `issue.assign.unassigned_click_to_assign`,
- })
- ).toBeInTheDocument();
-
- await user.click(
- screen.getByRole('button', {
- name: `issue.assign.unassigned_click_to_assign`,
- })
- );
- expect(screen.getByRole('searchbox', { name: 'search.search_for_users' })).toBeInTheDocument();
-
- await user.click(screen.getByRole('searchbox', { name: 'search.search_for_users' }));
- await user.keyboard('luke');
- expect(screen.getByText('Skywalker')).toBeInTheDocument();
- await user.keyboard('{ArrowUp}{enter}');
- expect(
- screen.getByRole('button', { name: 'issue.assign.assigned_to_x_click_to_change.luke' })
- ).toBeInTheDocument();
-
- // adding comment to the issue
- expect(
- screen.getByRole('button', {
- name: `issue.comment.add_comment`,
- })
- ).toBeInTheDocument();
-
- await user.click(
- screen.getByRole('button', {
- name: `issue.comment.add_comment`,
- })
- );
- expect(screen.getByText('issue.comment.formlink')).toBeInTheDocument();
- await user.keyboard('comment');
- await user.click(screen.getByText('issue.comment.formlink'));
- expect(screen.getByText('comment')).toBeInTheDocument();
-
- // Cancel editing the comment
- expect(screen.getByRole('button', { name: 'issue.comment.edit' })).toBeInTheDocument();
- await user.click(screen.getByRole('button', { name: 'issue.comment.edit' }));
- await user.keyboard('New ');
- await user.click(screen.getByRole('button', { name: 'issue.comment.edit.cancel' }));
- expect(screen.queryByText('New comment')).not.toBeInTheDocument();
-
- // editing the comment
- expect(screen.getByRole('button', { name: 'issue.comment.edit' })).toBeInTheDocument();
- await user.click(screen.getByRole('button', { name: 'issue.comment.edit' }));
- await user.keyboard('New ');
- await user.click(screen.getByText('save'));
- expect(screen.getByText('New comment')).toBeInTheDocument();
-
- // deleting the comment
- expect(screen.getByRole('button', { name: 'issue.comment.delete' })).toBeInTheDocument();
- await user.click(screen.getByRole('button', { name: 'issue.comment.delete' }));
- expect(screen.queryByText('New comment')).not.toBeInTheDocument();
-
- // adding comment using keyboard
- await user.click(screen.getByRole('textbox'));
- await user.keyboard('comment');
- await user.keyboard('{Control>}{enter}{/Control}');
- expect(screen.getByText('comment')).toBeInTheDocument();
-
- // editing the comment using keyboard
- await user.click(screen.getByRole('button', { name: 'issue.comment.edit' }));
- await user.keyboard('New ');
- await user.keyboard('{Control>}{enter}{/Control}');
- expect(screen.getByText('New comment')).toBeInTheDocument();
- await user.keyboard('{Escape}');
-
- // changing tags
- expect(screen.getByText('issue.no_tag')).toBeInTheDocument();
- await user.click(screen.getByText('issue.no_tag'));
- expect(screen.getByRole('searchbox', { name: 'search.search_for_tags' })).toBeInTheDocument();
- expect(screen.getByText('android')).toBeInTheDocument();
- expect(screen.getByText('accessibility')).toBeInTheDocument();
-
- await user.click(screen.getByText('accessibility'));
- await user.click(screen.getByText('android'));
- expect(screen.getByTitle('accessibility, android')).toBeInTheDocument();
-
- // Unslect
- await user.click(screen.getByText('accessibility'));
- expect(screen.getByTitle('android')).toBeInTheDocument();
-
- await user.click(screen.getByRole('searchbox', { name: 'search.search_for_tags' }));
- await user.keyboard('addNewTag');
- expect(
- screen.getByRole('checkbox', { name: 'create_new_element: addnewtag' })
- ).toBeInTheDocument();
-});
+ it('should open the actions popup using keyboard shortcut', async () => {
+ const user = userEvent.setup();
+ issuesHandler.setIsAdmin(true);
+ renderIssueApp();
+
+ // Select an issue with an advanced rule
+ await user.click(await ui.issueItem5.find());
+
+ // open severity popup on key press 'i'
+ await user.keyboard('i');
+ expect(screen.getByText('severity.MINOR')).toBeInTheDocument();
+ expect(screen.getByText('severity.INFO')).toBeInTheDocument();
+
+ // open status popup on key press 'f'
+ await user.keyboard('f');
+ expect(screen.getByText('issue.transition.confirm')).toBeInTheDocument();
+ expect(screen.getByText('issue.transition.resolve')).toBeInTheDocument();
+
+ // open comment popup on key press 'c'
+ await user.keyboard('c');
+ expect(screen.getByText('issue.comment.formlink')).toBeInTheDocument();
+ await user.keyboard('{Escape}');
+
+ // open tags popup on key press 't'
+ await user.keyboard('t');
+ expect(screen.getByRole('searchbox', { name: 'search.search_for_tags' })).toBeInTheDocument();
+ expect(screen.getByText('android')).toBeInTheDocument();
+ expect(screen.getByText('accessibility')).toBeInTheDocument();
+ // closing tags popup
+ await user.click(screen.getByText('issue.no_tag'));
+
+ // open assign popup on key press 'a'
+ await user.keyboard('a');
+ expect(screen.getByRole('searchbox', { name: 'search.search_for_tags' })).toBeInTheDocument();
+ });
-it('should not allow performing actions when user does not have permission', async () => {
- const user = userEvent.setup();
- renderIssueApp();
-
- await user.click(await screen.findByRole('region', { name: 'Fix this' }));
-
- expect(
- screen.queryByRole('button', {
- name: `issue.assign.unassigned_click_to_assign`,
- })
- ).not.toBeInTheDocument();
- expect(
- screen.queryByRole('button', {
- name: `issue.type.type_x_click_to_change.issue.type.CODE_SMELL`,
- })
- ).not.toBeInTheDocument();
-
- await user.click(
- screen.getByRole('button', {
- name: `issue.comment.add_comment`,
- })
- );
- expect(screen.queryByRole('button', { name: 'issue.comment.submit' })).not.toBeInTheDocument();
- expect(
- screen.queryByRole('button', {
- name: `issue.transition.status_x_click_to_change.issue.status.OPEN`,
- })
- ).not.toBeInTheDocument();
- expect(
- screen.queryByRole('button', {
- name: `issue.severity.severity_x_click_to_change.severity.MAJOR`,
- })
- ).not.toBeInTheDocument();
-});
+ it('should not open the actions popup using keyboard shortcut when keyboard shortcut flag is disabled', async () => {
+ localStorage.setItem('sonarqube.preferences.keyboard_shortcuts_enabled', 'false');
+ const user = userEvent.setup();
+ issuesHandler.setIsAdmin(true);
+ renderIssueApp();
-it('should open the actions popup using keyboard shortcut', async () => {
- const user = userEvent.setup();
- issuesHandler.setIsAdmin(true);
- renderIssueApp();
-
- // Select an issue with an advanced rule
- await user.click(await screen.findByRole('region', { name: 'Fix that' }));
-
- // open severity popup on key press 'i'
- await user.keyboard('i');
- expect(screen.getByText('severity.MINOR')).toBeInTheDocument();
- expect(screen.getByText('severity.INFO')).toBeInTheDocument();
-
- // open status popup on key press 'f'
- await user.keyboard('f');
- expect(screen.getByText('issue.transition.confirm')).toBeInTheDocument();
- expect(screen.getByText('issue.transition.resolve')).toBeInTheDocument();
-
- // open comment popup on key press 'c'
- await user.keyboard('c');
- expect(screen.getByText('issue.comment.formlink')).toBeInTheDocument();
- await user.keyboard('{Escape}');
-
- // open tags popup on key press 't'
- await user.keyboard('t');
- expect(screen.getByRole('searchbox', { name: 'search.search_for_tags' })).toBeInTheDocument();
- expect(screen.getByText('android')).toBeInTheDocument();
- expect(screen.getByText('accessibility')).toBeInTheDocument();
- // closing tags popup
- await user.click(screen.getByText('issue.no_tag'));
-
- // open assign popup on key press 'a'
- await user.keyboard('a');
- expect(screen.getByRole('searchbox', { name: 'search.search_for_tags' })).toBeInTheDocument();
-});
+ // Select an issue with an advanced rule
+ await user.click(await ui.issueItem5.find());
-it('should not open the actions popup using keyboard shortcut when keyboard shortcut flag is disabled', async () => {
- localStorage.setItem('sonarqube.preferences.keyboard_shortcuts_enabled', 'false');
- const user = userEvent.setup();
- issuesHandler.setIsAdmin(true);
- renderIssueApp();
+ // open status popup on key press 'f'
+ await user.keyboard('f');
+ expect(screen.queryByText('issue.transition.confirm')).not.toBeInTheDocument();
+ expect(screen.queryByText('issue.transition.resolve')).not.toBeInTheDocument();
- // Select an issue with an advanced rule
- await user.click(await screen.findByRole('region', { name: 'Fix that' }));
+ // open comment popup on key press 'c'
+ await user.keyboard('c');
+ expect(screen.queryByText('issue.comment.submit')).not.toBeInTheDocument();
+ localStorage.setItem('sonarqube.preferences.keyboard_shortcuts_enabled', 'true');
+ });
- // open status popup on key press 'f'
- await user.keyboard('f');
- expect(screen.queryByText('issue.transition.confirm')).not.toBeInTheDocument();
- expect(screen.queryByText('issue.transition.resolve')).not.toBeInTheDocument();
+ it('should show code tabs when any secondary location is selected', async () => {
+ const user = userEvent.setup();
+ renderIssueApp();
- // open comment popup on key press 'c'
- await user.keyboard('c');
- expect(screen.queryByText('issue.comment.submit')).not.toBeInTheDocument();
- localStorage.setItem('sonarqube.preferences.keyboard_shortcuts_enabled', 'true');
-});
+ await user.click(await ui.issueItem4.find());
+ expect(screen.getByRole('button', { name: '1 location 1' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: '2 location 2' })).toBeInTheDocument();
-it('should show code tabs when any secondary location is selected', async () => {
- const user = userEvent.setup();
- renderIssueApp();
+ // Select the "why is this an issue" tab
+ await user.click(
+ screen.getByRole('tab', { name: 'coding_rules.description_section.title.root_cause' })
+ );
+ expect(
+ screen.queryByRole('tab', {
+ name: `issue.tabs.${TabKeys.Code}`,
+ selected: true,
+ })
+ ).not.toBeInTheDocument();
- await user.click(await screen.findByRole('region', { name: 'Fix this' }));
- expect(screen.getByRole('button', { name: '1 location 1' })).toBeInTheDocument();
- expect(screen.getByRole('button', { name: '2 location 2' })).toBeInTheDocument();
+ await user.click(screen.getByRole('button', { name: '1 location 1' }));
+ expect(
+ screen.getByRole('tab', {
+ name: `issue.tabs.${TabKeys.Code}`,
+ selected: true,
+ })
+ ).toBeInTheDocument();
- // Select the "why is this an issue" tab
- await user.click(
- screen.getByRole('tab', { name: 'coding_rules.description_section.title.root_cause' })
- );
- expect(
- screen.queryByRole('tab', {
- name: `issue.tabs.${TabKeys.Code}`,
- selected: true,
- })
- ).not.toBeInTheDocument();
-
- await user.click(screen.getByRole('button', { name: '1 location 1' }));
- expect(
- screen.getByRole('tab', {
- name: `issue.tabs.${TabKeys.Code}`,
- selected: true,
- })
- ).toBeInTheDocument();
-
- // selecting the same selected hotspot location should also navigate back to code page
- await user.click(
- screen.getByRole('tab', { name: 'coding_rules.description_section.title.root_cause' })
- );
- expect(
- screen.queryByRole('tab', {
- name: `issue.tabs.${TabKeys.Code}`,
- selected: true,
- })
- ).not.toBeInTheDocument();
-
- await user.click(screen.getByRole('button', { name: '1 location 1' }));
- expect(
- screen.getByRole('tab', {
- name: `issue.tabs.${TabKeys.Code}`,
- selected: true,
- })
- ).toBeInTheDocument();
-});
+ // Select the same selected hotspot location should also navigate back to code page
+ await user.click(
+ screen.getByRole('tab', { name: 'coding_rules.description_section.title.root_cause' })
+ );
+ expect(
+ screen.queryByRole('tab', {
+ name: `issue.tabs.${TabKeys.Code}`,
+ selected: true,
+ })
+ ).not.toBeInTheDocument();
-it('should show issue tags if applicable', async () => {
- const user = userEvent.setup();
- issuesHandler.setIsAdmin(true);
- renderIssueApp();
+ await user.click(screen.getByRole('button', { name: '1 location 1' }));
+ expect(
+ screen.getByRole('tab', {
+ name: `issue.tabs.${TabKeys.Code}`,
+ selected: true,
+ })
+ ).toBeInTheDocument();
+ });
- // Select an issue with an advanced rule
- await user.click(await screen.findByRole('region', { name: 'Issue with tags' }));
+ it('should show issue tags if applicable', async () => {
+ const user = userEvent.setup();
+ issuesHandler.setIsAdmin(true);
+ renderIssueApp();
- expect(
- screen.getByRole('heading', {
- name: 'Issue with tags sonar-lint-icon issue.resolution.badge.DEPRECATED',
- })
- ).toBeInTheDocument();
+ // Select an issue with an advanced rule
+ await user.click(await ui.issueItem7.find());
+
+ expect(
+ screen.getByRole('heading', {
+ name: 'Issue with tags sonar-lint-icon issue.resolution.badge.DEPRECATED',
+ })
+ ).toBeInTheDocument();
+ });
});
describe('redirects', () => {
expect(screen.getByText('/security_hotspots?assignedToMe=false')).toBeInTheDocument();
});
- it('should filter out hotspots', async () => {
+ it('should filter out hotspots', () => {
renderProjectIssuesApp(
`project/issues?types=${IssueType.SecurityHotspot},${IssueType.CodeSmell}`
);
expect(
- await screen.findByRole('checkbox', { name: `issue.type.${IssueType.CodeSmell}` })
+ screen.getByRole('checkbox', { name: `issue.type.${IssueType.CodeSmell}` })
).toBeInTheDocument();
});
});
import SeverityHelper from '../../../components/shared/SeverityHelper';
import { Alert } from '../../../components/ui/Alert';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
+import { SEVERITIES } from '../../../helpers/constants';
import { throwGlobalError } from '../../../helpers/error';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Component, Dict, Issue, IssueType, Paging } from '../../../types/types';
return null;
}
- const severities = ['BLOCKER', 'CRITICAL', 'MAJOR', 'MINOR', 'INFO'];
- const options: LabelValueSelectOption[] = severities.map((severity) => ({
+ const options: LabelValueSelectOption[] = SEVERITIES.map((severity) => ({
label: translate('severity', severity),
value: severity,
}));
import { BranchLike } from '../../../types/branch-like';
import { ComponentQualifier, isPortfolioLike } from '../../../types/component';
import {
+ ASSIGNEE_ME,
Facet,
FetchIssuesPromise,
ReferencedComponent,
}
if (myIssues) {
- Object.assign(parameters, { assignees: '__me__' });
+ Object.assign(parameters, { assignees: ASSIGNEE_ME });
}
return this.fetchIssuesHelper(parameters);
};
if (myIssues) {
- Object.assign(parameters, { assignees: '__me__' });
+ Object.assign(parameters, { assignees: ASSIGNEE_ME });
}
return this.fetchIssuesHelper(parameters).then(({ facets }) => parseFacets(facets)[property]);
* 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 userEvent from '@testing-library/user-event';
import * as React from 'react';
-import { GroupBase } from 'react-select';
-import { AsyncProps } from 'react-select/async';
-import { SearchSelect } from '../../../../components/controls/Select';
-import Avatar from '../../../../components/ui/Avatar';
+import { act } from 'react-dom/test-utils';
+import { byRole } from 'testing-library-selector';
+import { mockUserBase } from '../../../../helpers/mocks/users';
import { mockCurrentUser, mockIssue, mockLoggedInUser } from '../../../../helpers/testMocks';
-import { searchAssignees } from '../../utils';
-import AssigneeSelect, {
- AssigneeOption,
- AssigneeSelectProps,
- MIN_QUERY_LENGTH,
-} from '../AssigneeSelect';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import AssigneeSelect, { AssigneeSelectProps, MIN_QUERY_LENGTH } from '../AssigneeSelect';
jest.mock('../../utils', () => ({
searchAssignees: jest.fn().mockResolvedValue({
results: [
- {
+ mockUserBase({
active: true,
- avatar: '##avatar1',
+ avatar: 'avatar1',
login: 'toto@toto',
name: 'toto',
- },
- {
+ }),
+ mockUserBase({
active: false,
- avatar: '##avatar2',
+ avatar: 'avatar2',
login: 'tata@tata',
name: 'tata',
- },
- {
+ }),
+ mockUserBase({
active: true,
- avatar: '##avatar3',
+ avatar: 'avatar3',
login: 'titi@titi',
- },
+ }),
],
}),
}));
-it('should render correctly', () => {
- expect(shallowRender()).toMatchSnapshot('default');
- expect(shallowRender({ currentUser: mockLoggedInUser(), issues: [mockIssue()] })).toMatchSnapshot(
- 'logged in & assignable issues'
- );
- expect(shallowRender({ currentUser: mockLoggedInUser() })).toMatchSnapshot(
- 'logged in & no assignable issues'
- );
- expect(shallowRender({ issues: [mockIssue(false, { assignee: 'someone' })] })).toMatchSnapshot(
- 'unassignable issues'
- );
+const ui = {
+ combobox: byRole('combobox'),
+};
+
+it('should show correct suggestions when there is assignable issue for the current user', async () => {
+ const user = userEvent.setup();
+ renderAssigneeSelect({
+ currentUser: mockLoggedInUser({ name: 'Skywalker' }),
+ issues: [mockIssue(false, { assignee: 'someone' })],
+ });
+
+ await user.click(ui.combobox.get());
+ expect(await screen.findByText('Skywalker')).toBeInTheDocument();
+});
+
+it('should show correct suggestions when all issues are already assigned to current user', async () => {
+ const user = userEvent.setup();
+ renderAssigneeSelect({
+ currentUser: mockLoggedInUser({ login: 'luke', name: 'Skywalker' }),
+ issues: [mockIssue(false, { assignee: 'luke' })],
+ });
+
+ await user.click(ui.combobox.get());
+ expect(screen.queryByText('Skywalker')).not.toBeInTheDocument();
});
-it('should render options correctly', () => {
- const wrapper = shallowRender();
-
- expect(
- shallow(
- wrapper.instance().renderAssignee({
- avatar: '##avatar1',
- value: 'toto@toto',
- label: 'toto',
- })
- )
- .find(Avatar)
- .exists()
- ).toBe(true);
-
- expect(
- shallow(
- wrapper.instance().renderAssignee({
- value: 'toto@toto',
- label: 'toto',
- })
- )
- .find(Avatar)
- .exists()
- ).toBe(false);
+it('should show correct suggestions when there is no assigneable issue', async () => {
+ const user = userEvent.setup();
+ renderAssigneeSelect({
+ currentUser: mockLoggedInUser({ name: 'Skywalker' }),
+ });
+
+ await user.click(ui.combobox.get());
+ expect(screen.queryByText('Skywalker')).not.toBeInTheDocument();
});
-it('should render noOptionsMessage correctly', () => {
- const wrapper = shallowRender();
- expect(
- wrapper.find<AsyncProps<AssigneeOption, false, GroupBase<AssigneeOption>>>(SearchSelect).props()
- .noOptionsMessage!({ inputValue: 'a' })
- ).toBe(`select2.tooShort.${MIN_QUERY_LENGTH}`);
-
- expect(
- wrapper.find<AsyncProps<AssigneeOption, false, GroupBase<AssigneeOption>>>(SearchSelect).props()
- .noOptionsMessage!({ inputValue: 'droids' })
- ).toBe('select2.noMatches');
+it('should handle assignee search correctly', async () => {
+ const user = userEvent.setup();
+ renderAssigneeSelect();
+
+ // Minimum MIN_QUERY_LENGTH charachters to trigger search
+ await user.type(ui.combobox.get(), 'a');
+ expect(await screen.findByText(`select2.tooShort.${MIN_QUERY_LENGTH}`)).toBeInTheDocument();
+
+ // Trigger search
+ await user.type(ui.combobox.get(), 'someone');
+ expect(await screen.findByText('toto')).toBeInTheDocument();
+ expect(await screen.findByText('user.x_deleted.tata')).toBeInTheDocument();
+ expect(await screen.findByText('user.x_deleted.titi@titi')).toBeInTheDocument();
});
-it('should handle assignee search', async () => {
+it('should handle assignee selection', async () => {
const onAssigneeSelect = jest.fn();
- const wrapper = shallowRender({ onAssigneeSelect });
+ const user = userEvent.setup();
+ renderAssigneeSelect({ onAssigneeSelect });
- wrapper.instance().handleAssigneeSearch('a', jest.fn());
- expect(searchAssignees).not.toHaveBeenCalled();
-
- const result = await new Promise((resolve: (opts: AssigneeOption[]) => void) => {
- wrapper.instance().handleAssigneeSearch('someone', resolve);
+ await act(async () => {
+ await user.type(ui.combobox.get(), 'tot');
});
- expect(result).toEqual([
- {
- avatar: '##avatar1',
- value: 'toto@toto',
- label: 'toto',
- },
- {
- avatar: '##avatar2',
- value: 'tata@tata',
- label: 'user.x_deleted.tata',
- },
- {
- avatar: '##avatar3',
- value: 'titi@titi',
- label: 'user.x_deleted.titi@titi',
- },
- ]);
+ // Do not select assignee until suggestion is selected
+ expect(onAssigneeSelect).not.toHaveBeenCalled();
+
+ // Select assignee when suggestion is selected
+ await user.click(screen.getByText('toto'));
+ expect(onAssigneeSelect).toHaveBeenCalledTimes(1);
});
-function shallowRender(overrides: Partial<AssigneeSelectProps> = {}) {
- return shallow<AssigneeSelect>(
+function renderAssigneeSelect(overrides: Partial<AssigneeSelectProps> = {}) {
+ return renderComponent(
<AssigneeSelect
inputId="id"
currentUser={mockCurrentUser()}
--- /dev/null
+/*
+ * 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 selectEvent from 'react-select-event';
+import { bulkChangeIssues } from '../../../../api/issues';
+import { SEVERITIES } from '../../../../helpers/constants';
+import { mockIssue, mockLoggedInUser } from '../../../../helpers/testMocks';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
+import { IssueType } from '../../../../types/issues';
+import { Issue } from '../../../../types/types';
+import BulkChangeModal, { MAX_PAGE_SIZE } from '../BulkChangeModal';
+
+jest.mock('../../../../api/issues', () => ({
+ searchIssueTags: jest.fn().mockResolvedValue(['tag1', 'tag2']),
+ bulkChangeIssues: jest.fn().mockResolvedValue({}),
+}));
+
+afterEach(() => {
+ jest.clearAllMocks();
+});
+
+it('should display error message when no issues available', async () => {
+ renderBulkChangeModal([]);
+
+ expect(await screen.findByText('issue_bulk_change.no_match')).toBeInTheDocument();
+});
+
+it('should display warning when too many issues are passed', async () => {
+ const issues: Issue[] = [];
+ for (let i = MAX_PAGE_SIZE + 1; i > 0; i--) {
+ issues.push(mockIssue());
+ }
+ renderBulkChangeModal(issues);
+
+ expect(
+ await screen.findByText(`issue_bulk_change.form.title.${MAX_PAGE_SIZE}`)
+ ).toBeInTheDocument();
+ expect(await screen.findByText('issue_bulk_change.max_issues_reached')).toBeInTheDocument();
+});
+
+it.each([
+ ['type', 'set_type'],
+ ['severity', 'set_severity'],
+])('should render select for %s', async (_field, action) => {
+ renderBulkChangeModal([mockIssue(false, { actions: [action] })]);
+
+ expect(await screen.findByText('issue.' + action)).toBeInTheDocument();
+});
+
+it('should render tags correctly', async () => {
+ renderBulkChangeModal([mockIssue(false, { actions: ['set_tags'] })]);
+
+ expect(await screen.findByRole('combobox', { name: 'issue.add_tags' })).toBeInTheDocument();
+ expect(await screen.findByRole('combobox', { name: 'issue.remove_tags' })).toBeInTheDocument();
+});
+
+it('should render transitions correctly', async () => {
+ renderBulkChangeModal([
+ mockIssue(false, { actions: ['set_transition'], transitions: ['Transition1'] }),
+ ]);
+
+ expect(await screen.findByText('issue.transition')).toBeInTheDocument();
+ expect(await screen.findByText('issue.transition.Transition1')).toBeInTheDocument();
+});
+
+it('should disable the submit button unless some change is configured', async () => {
+ const user = userEvent.setup();
+ renderBulkChangeModal([mockIssue(false, { actions: ['set_severity', 'comment'] })]);
+
+ // Apply button should be disabled
+ expect(await screen.findByRole('button', { name: 'apply' })).toBeDisabled();
+
+ // Adding a comment should not enable the submit button
+ await user.type(screen.getByRole('textbox', { name: 'issue.comment.formlink' }), 'some comment');
+ expect(screen.getByRole('button', { name: 'apply' })).toBeDisabled();
+
+ // Select a severity
+ await selectEvent.select(screen.getByRole('combobox', { name: 'issue.set_severity' }), [
+ `severity.${SEVERITIES[0]}`,
+ ]);
+
+ // Apply button should be enabled now
+ expect(screen.getByRole('button', { name: 'apply' })).toBeEnabled();
+});
+
+it('should properly submit', async () => {
+ const onDone = jest.fn();
+ const user = userEvent.setup();
+ renderBulkChangeModal(
+ [
+ mockIssue(false, {
+ key: 'issue1',
+ actions: ['assign', 'set_transition', 'set_tags', 'set_type', 'set_severity', 'comment'],
+ transitions: ['Transition1', 'Transition2'],
+ }),
+ mockIssue(false, {
+ key: 'issue2',
+ actions: ['assign', 'set_transition', 'set_tags', 'set_type', 'set_severity', 'comment'],
+ transitions: ['Transition1', 'Transition2'],
+ }),
+ ],
+ {
+ onDone,
+ currentUser: mockLoggedInUser({
+ login: 'toto',
+ name: 'Toto',
+ }),
+ }
+ );
+
+ expect(bulkChangeIssues).toHaveBeenCalledTimes(0);
+ expect(onDone).toHaveBeenCalledTimes(0);
+
+ // Assign
+ await user.click(await screen.findByRole('combobox', { name: 'issue.assign.formlink' }));
+ await user.click(await screen.findByText('Toto'));
+
+ // Transition
+ await user.click(await screen.findByText('issue.transition.Transition2'));
+
+ // Add a tag
+ await selectEvent.select(screen.getByRole('combobox', { name: 'issue.add_tags' }), [
+ 'tag1',
+ 'tag2',
+ ]);
+
+ // Select a type
+ await selectEvent.select(screen.getByRole('combobox', { name: 'issue.set_type' }), [
+ `issue.type.CODE_SMELL`,
+ ]);
+
+ // Select a severity
+ await selectEvent.select(screen.getByRole('combobox', { name: 'issue.set_severity' }), [
+ `severity.${SEVERITIES[0]}`,
+ ]);
+
+ // Severity
+ await selectEvent.select(screen.getByRole('combobox', { name: 'issue.set_severity' }), [
+ `severity.${SEVERITIES[0]}`,
+ ]);
+
+ // Comment
+ await user.type(screen.getByRole('textbox', { name: 'issue.comment.formlink' }), 'some comment');
+
+ // Send notification
+ await user.click(screen.getByRole('checkbox', { name: 'issue.send_notifications' }));
+
+ // Submit
+ await user.click(screen.getByRole('button', { name: 'apply' }));
+
+ expect(bulkChangeIssues).toHaveBeenCalledTimes(1);
+ expect(onDone).toHaveBeenCalledTimes(1);
+ expect(bulkChangeIssues).toHaveBeenCalledWith(['issue1', 'issue2'], {
+ assign: 'toto',
+ comment: 'some comment',
+ set_severity: 'BLOCKER',
+ add_tags: 'tag1,tag2',
+ do_transition: 'Transition2',
+ set_type: IssueType.CodeSmell,
+ sendNotifications: true,
+ });
+});
+
+function renderBulkChangeModal(issues: Issue[], props: Partial<BulkChangeModal['props']> = {}) {
+ return renderComponent(
+ <BulkChangeModal
+ component={undefined}
+ currentUser={{ isLoggedIn: true, dismissedNotices: {} }}
+ fetchIssues={() =>
+ Promise.resolve({
+ issues,
+ paging: {
+ pageIndex: issues.length,
+ pageSize: issues.length,
+ total: issues.length,
+ },
+ })
+ }
+ onClose={() => {}}
+ onDone={() => {}}
+ {...props}
+ />
+ );
+}
+++ /dev/null
-/*
- * 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 { Props as ReactSelectProps } from 'react-select';
-import { searchIssueTags } from '../../../../api/issues';
-import { SubmitButton } from '../../../../components/controls/buttons';
-import Select, { CreatableSelect, SearchSelect } from '../../../../components/controls/Select';
-import { mockIssue } from '../../../../helpers/testMocks';
-import { change, waitAndUpdate } from '../../../../helpers/testUtils';
-import { Issue } from '../../../../types/types';
-import BulkChangeModal, { MAX_PAGE_SIZE } from '../BulkChangeModal';
-
-jest.mock('../../../../api/issues', () => ({
- searchIssueTags: jest.fn().mockResolvedValue([undefined, []]),
-}));
-
-it('should display error message when no issues available', async () => {
- const wrapper = getWrapper([]);
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should display form when issues are present', async () => {
- const wrapper = getWrapper([mockIssue()]);
- await waitAndUpdate(wrapper);
- expect(wrapper).toMatchSnapshot();
-});
-
-it('should display warning when too many issues are passed', async () => {
- const issues: Issue[] = [];
- for (let i = MAX_PAGE_SIZE + 1; i > 0; i--) {
- issues.push(mockIssue());
- }
-
- const wrapper = getWrapper(issues);
- await waitAndUpdate(wrapper);
- expect(wrapper.find('h2')).toMatchSnapshot();
- expect(wrapper.find('Alert')).toMatchSnapshot();
-});
-
-it('should properly handle the search for tags', async () => {
- const wrapper = getWrapper([]);
- await new Promise((resolve) => {
- wrapper.instance().handleTagsSearch('query', resolve);
- });
- expect(searchIssueTags).toHaveBeenCalled();
-});
-
-it.each([
- ['type', 'set_type'],
- ['severity', 'set_severity'],
-])('should render select for %s', async (_field, action) => {
- const wrapper = getWrapper([mockIssue(false, { actions: [action] })]);
- await waitAndUpdate(wrapper);
-
- const { Option, SingleValue } = wrapper.find(Select).props().components;
-
- expect(Option({ data: { label: 'label', value: 'value' } })).toMatchSnapshot('Option');
- expect(SingleValue({ data: { label: 'label', value: 'value' } })).toMatchSnapshot('SingleValue');
-});
-
-it('should render tags correctly', async () => {
- const wrapper = getWrapper([mockIssue(false, { actions: ['set_tags'] })]);
- await waitAndUpdate(wrapper);
-
- expect(wrapper.find(CreatableSelect).exists()).toBe(true);
- expect(wrapper.find(SearchSelect).exists()).toBe(true);
-});
-
-it('should disable the submit button unless some change is configured', async () => {
- const wrapper = getWrapper([mockIssue(false, { actions: ['set_severity', 'comment'] })]);
- await waitAndUpdate(wrapper);
-
- return new Promise<void>((resolve) => {
- expect(wrapper.find(SubmitButton).props().disabled).toBe(true);
-
- // Setting a comment is not sufficient; some other change must occur.
- change(wrapper.find('#comment'), 'Some comment');
- expect(wrapper.find(SubmitButton).props().disabled).toBe(true);
-
- wrapper.find<ReactSelectProps>(Select).at(0).simulate('change', { value: 'foo' });
-
- expect(wrapper.find(SubmitButton).props().disabled).toBe(false);
- resolve();
- });
-});
-
-const getWrapper = (issues: Issue[]) => {
- return shallow<BulkChangeModal>(
- <BulkChangeModal
- component={undefined}
- currentUser={{ isLoggedIn: true, dismissedNotices: {} }}
- fetchIssues={() =>
- Promise.resolve({
- issues,
- paging: {
- pageIndex: issues.length,
- pageSize: issues.length,
- total: issues.length,
- },
- })
- }
- onClose={() => {}}
- onDone={() => {}}
- />
- );
-};
* 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 { mockComponent } from '../../../../helpers/mocks/component';
import { mockIssue } from '../../../../helpers/testMocks';
+import { renderComponent } from '../../../../helpers/testReactTestingUtils';
import { ComponentQualifier } from '../../../../types/component';
+import { Component, Issue } from '../../../../types/types';
import ComponentBreadcrumbs from '../ComponentBreadcrumbs';
const baseIssue = mockIssue(false, {
branch: 'test-branch',
});
-it('renders', () => {
- expect(
- shallow(<ComponentBreadcrumbs component={undefined} issue={baseIssue} />)
- ).toMatchSnapshot();
-});
+describe('renders properly', () => {
+ it('without component with issue', () => {
+ renderComponentBreadcrumbs(mockComponent());
+
+ expect(screen.getByLabelText('issues.on_file_x.comp-name')).toBeInTheDocument();
+ });
+
+ it('with component without issue branch', () => {
+ renderComponentBreadcrumbs(mockComponent({ qualifier: ComponentQualifier.Portfolio }), {
+ branch: undefined,
+ });
-it('renders issues properly for views', () => {
- expect(
- shallow(
- <ComponentBreadcrumbs
- component={mockComponent({ qualifier: ComponentQualifier.Portfolio })}
- issue={{ ...baseIssue, branch: undefined }}
- />
- )
- ).toMatchSnapshot();
- expect(
- shallow(
- <ComponentBreadcrumbs
- component={mockComponent({ qualifier: ComponentQualifier.Portfolio })}
- issue={{ ...baseIssue }}
- />
- )
- ).toMatchSnapshot('with branch information');
+ expect(screen.getByLabelText('issues.on_file_x.proj-name, comp-name')).toBeInTheDocument();
+ expect(screen.queryByText('test-branch')).not.toBeInTheDocument();
+ });
+
+ it('with component and issue branch', () => {
+ renderComponentBreadcrumbs(mockComponent({ qualifier: ComponentQualifier.Portfolio }));
+
+ expect(screen.getByLabelText('issues.on_file_x.proj-name, comp-name')).toBeInTheDocument();
+ expect(screen.getByText('test-branch')).toBeInTheDocument();
+ });
});
+
+function renderComponentBreadcrumbs(component?: Component, issue: Partial<Issue> = {}) {
+ return renderComponent(
+ <ComponentBreadcrumbs component={component} issue={{ ...baseIssue, ...issue }} />
+ );
+}
+++ /dev/null
-/*
- * 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 { searchIssues } from '../../../../api/issues';
-import { getRuleDetails } from '../../../../api/rules';
-import handleRequiredAuthentication from '../../../../helpers/handleRequiredAuthentication';
-import { KeyboardKeys } from '../../../../helpers/keycodes';
-import { mockPullRequest } from '../../../../helpers/mocks/branch-like';
-import { mockComponent } from '../../../../helpers/mocks/component';
-import {
- addSideBarClass,
- addWhitePageClass,
- removeSideBarClass,
- removeWhitePageClass,
-} from '../../../../helpers/pages';
-import {
- mockCurrentUser,
- mockIssue,
- mockLocation,
- mockLoggedInUser,
- mockRawIssue,
- mockRouter,
-} from '../../../../helpers/testMocks';
-import { keydown, mockEvent, waitAndUpdate } from '../../../../helpers/testUtils';
-import { ReferencedComponent } from '../../../../types/issues';
-import { Issue, Paging } from '../../../../types/types';
-import {
- disableLocationsNavigator,
- enableLocationsNavigator,
- selectNextFlow,
- selectNextLocation,
- selectPreviousFlow,
- selectPreviousLocation,
-} from '../../actions';
-import BulkChangeModal from '../BulkChangeModal';
-import { App } from '../IssuesApp';
-
-jest.mock('../../../../helpers/pages', () => ({
- addSideBarClass: jest.fn(),
- addWhitePageClass: jest.fn(),
- removeSideBarClass: jest.fn(),
- removeWhitePageClass: jest.fn(),
-}));
-
-jest.mock('../../../../helpers/handleRequiredAuthentication', () => jest.fn());
-
-jest.mock('../../../../api/issues', () => ({
- searchIssues: jest.fn().mockResolvedValue({ facets: [], issues: [] }),
-}));
-
-jest.mock('../../../../api/rules', () => ({
- getRuleDetails: jest.fn(),
-}));
-
-jest.mock('../../../../api/users', () => ({
- getCurrentUser: jest.fn().mockResolvedValue({
- dismissedNotices: {
- something: false,
- },
- }),
- dismissNotification: jest.fn(),
-}));
-
-const RAW_ISSUES = [
- mockRawIssue(false, { key: 'foo' }),
- mockRawIssue(false, { key: 'bar' }),
- mockRawIssue(true, { key: 'third' }),
- mockRawIssue(false, { key: 'fourth' }),
-];
-const ISSUES = [
- mockIssue(false, { key: 'foo' }),
- mockIssue(false, { key: 'bar' }),
- mockIssue(true, { key: 'third' }),
- mockIssue(false, { key: 'fourth' }),
-];
-const FACETS = [{ property: 'severities', values: [{ val: 'MINOR', count: 4 }] }];
-const PAGING = { pageIndex: 1, pageSize: 100, total: 4 };
-
-const referencedComponent: ReferencedComponent = { key: 'foo-key', name: 'bar', uuid: 'foo-uuid' };
-
-beforeEach(() => {
- (searchIssues as jest.Mock).mockResolvedValue({
- components: [referencedComponent],
- effortTotal: 1,
- facets: FACETS,
- issues: RAW_ISSUES,
- languages: [],
- paging: PAGING,
- rules: [],
- users: [],
- });
-
- (getRuleDetails as jest.Mock).mockResolvedValue({ rule: { test: 'test' } });
-});
-
-afterEach(() => {
- (searchIssues as jest.Mock).mockReset();
-});
-
-it('should render a list of issue', async () => {
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
- expect(wrapper.state().issues.length).toBe(4);
- expect(wrapper.state().referencedComponentsById).toEqual({ 'foo-uuid': referencedComponent });
- expect(wrapper.state().referencedComponentsByKey).toEqual({ 'foo-key': referencedComponent });
-
- expect(addSideBarClass).toHaveBeenCalled();
- expect(addWhitePageClass).toHaveBeenCalled();
-});
-
-it('should handle my issue change properly', () => {
- const push = jest.fn();
- const wrapper = shallowRender({ router: mockRouter({ push }) });
- wrapper.instance().handleMyIssuesChange(true);
-
- expect(push).toHaveBeenCalledWith({
- pathname: '/issues',
- query: {
- id: 'foo',
- author: [],
- myIssues: 'true',
- },
- });
-});
-
-it('should load search result count correcly', async () => {
- const wrapper = shallowRender();
- const count = await wrapper.instance().loadSearchResultCount('severities', {});
- expect(count).toStrictEqual({ MINOR: 4 });
-});
-
-it('should not render for anonymous user', () => {
- shallowRender({
- currentUser: mockCurrentUser({ isLoggedIn: false }),
- location: mockLocation({ query: { myIssues: true.toString() } }),
- });
- expect(handleRequiredAuthentication).toHaveBeenCalled();
-});
-
-it('should handle reset properly', () => {
- const push = jest.fn();
- const wrapper = shallowRender({ router: mockRouter({ push }) });
- wrapper.instance().handleReset();
- expect(push).toHaveBeenCalledWith({
- pathname: '/issues',
- query: {
- id: 'foo',
- myIssues: undefined,
- resolved: 'false',
- },
- });
-});
-
-it('should open standard facets for vulnerabilities and hotspots', () => {
- const wrapper = shallowRender({
- location: mockLocation({ pathname: '/issues', query: { types: 'VULNERABILITY' } }),
- });
- const instance = wrapper.instance();
- const fetchFacet = jest.spyOn(instance, 'fetchFacet');
-
- expect(wrapper.state('openFacets').standards).toEqual(true);
- expect(wrapper.state('openFacets').sonarsourceSecurity).toEqual(true);
-
- instance.handleFacetToggle('standards');
- expect(wrapper.state('openFacets').standards).toEqual(false);
- expect(fetchFacet).not.toHaveBeenCalled();
-
- instance.handleFacetToggle('standards');
- expect(wrapper.state('openFacets').standards).toEqual(true);
- expect(wrapper.state('openFacets').sonarsourceSecurity).toEqual(true);
- expect(fetchFacet).toHaveBeenLastCalledWith('sonarsourceSecurity');
-
- instance.handleFacetToggle('owaspTop10');
- expect(wrapper.state('openFacets').owaspTop10).toEqual(true);
- expect(fetchFacet).toHaveBeenLastCalledWith('owaspTop10');
-});
-
-it('should correctly bind key events for issue navigation', async () => {
- const push = jest.fn();
- const addEventListenerSpy = jest.spyOn(document, 'addEventListener');
- const wrapper = shallowRender({ router: mockRouter({ push }) });
- await waitAndUpdate(wrapper);
-
- expect(addEventListenerSpy).toHaveBeenCalledTimes(2);
-
- expect(wrapper.state('selected')).toBe(ISSUES[0].key);
-
- keydown({ key: KeyboardKeys.DownArrow });
- expect(wrapper.state('selected')).toBe(ISSUES[1].key);
-
- keydown({ key: KeyboardKeys.UpArrow });
- keydown({ key: KeyboardKeys.UpArrow });
- expect(wrapper.state('selected')).toBe(ISSUES[0].key);
-
- keydown({ key: KeyboardKeys.DownArrow });
- keydown({ key: KeyboardKeys.DownArrow });
- keydown({ key: KeyboardKeys.DownArrow });
- keydown({ key: KeyboardKeys.DownArrow });
- keydown({ key: KeyboardKeys.DownArrow });
- keydown({ key: KeyboardKeys.DownArrow });
- expect(wrapper.state('selected')).toBe(ISSUES[3].key);
-
- keydown({ key: KeyboardKeys.RightArrow, ctrlKey: true });
- expect(push).not.toHaveBeenCalled();
- keydown({ key: KeyboardKeys.RightArrow });
- expect(push).toHaveBeenCalledTimes(1);
-
- keydown({ key: KeyboardKeys.LeftArrow });
- expect(push).toHaveBeenCalledTimes(2);
-
- addEventListenerSpy.mockReset();
-});
-
-it('should correctly clean up on unmount', () => {
- const removeEventListenerSpy = jest.spyOn(document, 'removeEventListener');
- const wrapper = shallowRender();
-
- wrapper.unmount();
- expect(removeSideBarClass).toHaveBeenCalled();
- expect(removeWhitePageClass).toHaveBeenCalled();
- expect(removeEventListenerSpy).toHaveBeenCalledTimes(2);
-
- removeEventListenerSpy.mockReset();
-});
-
-it('should be able to bulk change specific issues', async () => {
- const wrapper = shallowRender({ currentUser: mockLoggedInUser() });
- await waitAndUpdate(wrapper);
-
- const instance = wrapper.instance();
- expect(wrapper.state().checked.length).toBe(0);
- instance.handleIssueCheck('foo');
- instance.handleIssueCheck('bar');
- expect(wrapper.state().checked.length).toBe(2);
-
- instance.handleOpenBulkChange();
- wrapper.update();
- expect(wrapper.find(BulkChangeModal).exists()).toBe(true);
- const { issues } = await wrapper.find(BulkChangeModal).props().fetchIssues({});
- expect(issues).toHaveLength(2);
-});
-
-it('should display the right facets open', () => {
- expect(
- shallowRender({
- location: mockLocation({ query: { types: 'BUGS' } }),
- }).state('openFacets')
- ).toEqual({
- owaspTop10: false,
- 'owaspTop10-2021': false,
- sansTop25: false,
- severities: true,
- standards: false,
- sonarsourceSecurity: false,
- types: true,
- });
- expect(
- shallowRender({
- location: mockLocation({ query: { owaspTop10: 'a1' } }),
- }).state('openFacets')
- ).toEqual({
- owaspTop10: true,
- 'owaspTop10-2021': false,
- sansTop25: false,
- severities: true,
- standards: true,
- sonarsourceSecurity: false,
- types: true,
- });
-});
-
-it('should correctly handle filter changes', () => {
- const push = jest.fn();
- const instance = shallowRender({ router: mockRouter({ push }) }).instance();
- instance.setState({ openFacets: { types: true } });
- instance.handleFilterChange({ types: ['VULNERABILITY'] });
- expect(instance.state.openFacets).toEqual({
- types: true,
- sonarsourceSecurity: true,
- standards: true,
- });
- expect(push).toHaveBeenCalled();
- instance.handleFilterChange({ types: ['BUGS'] });
- expect(instance.state.openFacets).toEqual({
- types: true,
- sonarsourceSecurity: true,
- standards: true,
- });
-});
-
-it('should fetch issues until defined', async () => {
- (searchIssues as jest.Mock).mockImplementation(mockSearchIssuesResponse());
-
- const mockDone = (_: Issue[], paging: Paging) =>
- paging.total <= paging.pageIndex * paging.pageSize;
-
- const wrapper = shallowRender({
- location: mockLocation({
- query: { open: '0' },
- }),
- });
- const instance = wrapper.instance();
- await waitAndUpdate(wrapper);
-
- const result = await instance.fetchIssuesUntil(1, mockDone);
- expect(result.issues).toHaveLength(6);
- expect(result.paging.pageIndex).toBe(3);
-});
-
-describe('keydown event handler', () => {
- const wrapper = shallowRender();
- const instance = wrapper.instance();
- jest.spyOn(instance, 'setState');
-
- beforeEach(() => {
- jest.resetAllMocks();
- });
-
- it('should handle alt', () => {
- instance.handleKeyDown(mockEvent({ key: KeyboardKeys.Alt }));
- expect(instance.setState).toHaveBeenCalledWith(enableLocationsNavigator);
- });
- it('should handle alt+↓', () => {
- instance.handleKeyDown(mockEvent({ altKey: true, key: KeyboardKeys.DownArrow }));
- expect(instance.setState).toHaveBeenCalledWith(selectNextLocation);
- });
- it('should handle alt+↑', () => {
- instance.handleKeyDown(mockEvent({ altKey: true, key: KeyboardKeys.UpArrow }));
- expect(instance.setState).toHaveBeenCalledWith(selectPreviousLocation);
- });
- it('should handle alt+←', () => {
- instance.handleKeyDown(mockEvent({ altKey: true, key: KeyboardKeys.LeftArrow }));
- expect(instance.setState).toHaveBeenCalledWith(selectPreviousFlow);
- });
- it('should handle alt+→', () => {
- instance.handleKeyDown(mockEvent({ altKey: true, key: KeyboardKeys.RightArrow }));
- expect(instance.setState).toHaveBeenCalledWith(selectNextFlow);
- });
- it('should ignore if modal is open', () => {
- wrapper.setState({ bulkChangeModal: true });
- instance.handleKeyDown(mockEvent({ key: KeyboardKeys.Alt }));
- expect(instance.setState).not.toHaveBeenCalled();
- });
-});
-
-describe('keyup event handler', () => {
- const wrapper = shallowRender();
- const instance = wrapper.instance();
- jest.spyOn(instance, 'setState');
-
- beforeEach(() => {
- jest.resetAllMocks();
- });
-
- it('should handle alt', () => {
- instance.handleKeyUp(mockEvent({ key: KeyboardKeys.Alt }));
- expect(instance.setState).toHaveBeenCalledWith(disableLocationsNavigator);
- });
-});
-
-it('should fetch more issues', async () => {
- (searchIssues as jest.Mock).mockImplementation(mockSearchIssuesResponse());
- const wrapper = shallowRender({});
- const instance = wrapper.instance();
- await waitAndUpdate(wrapper);
-
- await instance.fetchMoreIssues();
- await waitAndUpdate(wrapper);
- expect(wrapper.state('issues')).toHaveLength(4);
-});
-
-it('should refresh branch status if issues are updated', async () => {
- const fetchBranchStatus = jest.fn();
- const branchLike = mockPullRequest();
- const component = mockComponent();
- const wrapper = shallowRender({ branchLike, component, fetchBranchStatus });
- const instance = wrapper.instance();
- await waitAndUpdate(wrapper);
-
- const updatedIssue: Issue = { ...ISSUES[0], type: 'SECURITY_HOTSPOT' };
- instance.handleIssueChange(updatedIssue);
- expect(wrapper.state().issues[0].type).toEqual(updatedIssue.type);
- expect(fetchBranchStatus).toHaveBeenCalledWith(branchLike, component.key);
-
- fetchBranchStatus.mockClear();
- instance.handleBulkChangeDone();
- expect(fetchBranchStatus).toHaveBeenCalled();
-});
-
-it('should update the open issue when it is changed', async () => {
- (searchIssues as jest.Mock).mockImplementation(mockSearchIssuesResponse());
-
- const wrapper = shallowRender();
- await waitAndUpdate(wrapper);
-
- const issue = wrapper.state().issues[0];
-
- wrapper.setProps({ location: mockLocation({ query: { open: issue.key } }) });
- await waitAndUpdate(wrapper);
-
- expect(wrapper.state().openIssue).toEqual(issue);
-
- const updatedIssue: Issue = { ...issue, type: 'SECURITY_HOTSPOT' };
- wrapper.instance().handleIssueChange(updatedIssue);
-
- await waitAndUpdate(wrapper);
- expect(wrapper.state().openIssue).toEqual(updatedIssue);
-});
-
-it('should handle createAfter query param with time', async () => {
- (searchIssues as jest.Mock).mockImplementation(mockSearchIssuesResponse());
-
- const wrapper = shallowRender({
- location: mockLocation({ query: { createdAfter: '2020-10-21' } }),
- });
- expect(wrapper.instance().createdAfterIncludesTime()).toBe(false);
- await waitAndUpdate(wrapper);
-
- wrapper.setProps({ location: mockLocation({ query: { createdAfter: '2020-10-21T17:21:00Z' } }) });
- expect(wrapper.instance().createdAfterIncludesTime()).toBe(true);
-
- (searchIssues as jest.Mock).mockClear();
-
- wrapper.instance().fetchIssues({});
- expect(searchIssues).toHaveBeenCalledWith(
- expect.objectContaining({ createdAfter: '2020-10-21T17:21:00+0000' })
- );
-});
-
-function mockSearchIssuesResponse(keyCount = 0, lineCount = 1) {
- return ({ p = 1 }) =>
- Promise.resolve({
- components: [referencedComponent],
- effortTotal: 1,
- facets: FACETS,
- issues: [
- mockRawIssue(false, {
- key: `${keyCount++}`,
- textRange: {
- startLine: lineCount++,
- endLine: lineCount,
- startOffset: 0,
- endOffset: 15,
- },
- }),
- mockRawIssue(false, {
- key: `${keyCount}`,
- textRange: {
- startLine: lineCount++,
- endLine: lineCount,
- startOffset: 0,
- endOffset: 15,
- },
- }),
- ],
- languages: [],
- paging: { pageIndex: p, pageSize: 2, total: 6 },
- rules: [],
- users: [],
- });
-}
-
-function shallowRender(props: Partial<App['props']> = {}) {
- return shallow<App>(
- <App
- component={{
- breadcrumbs: [],
- key: 'foo',
- name: 'bar',
- qualifier: 'Doe',
- }}
- currentUser={mockLoggedInUser()}
- fetchBranchStatus={jest.fn()}
- location={mockLocation({ pathname: '/issues', query: {} })}
- router={mockRouter()}
- {...props}
- />
- );
-}
+++ /dev/null
-/*
- * 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 IssuesCounter from '../IssuesCounter';
-
-it('formats numbers', () => {
- expect(shallow(<IssuesCounter current={1234} total={987654321} />)).toMatchSnapshot();
-});
-
-it('does not show current', () => {
- expect(shallow(<IssuesCounter current={undefined} total={987654321} />)).toMatchSnapshot();
-});
+++ /dev/null
-/*
- * 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 IssuesList from '../IssuesList';
-
-it('should render correctly', () => {
- const wrapper = shallowRender({ issues: [] });
- expect(wrapper).toMatchSnapshot();
- wrapper.setProps({ issues: [mockIssue(), mockIssue(false, { key: 'AVsae-CQS-9G3txfbFN3' })] });
- expect(wrapper).toMatchSnapshot();
-});
-
-function shallowRender(overrides: Partial<IssuesList['props']> = {}) {
- return shallow(
- <IssuesList
- branchLike={undefined}
- checked={[]}
- component={undefined}
- issues={[mockIssue(), mockIssue(false, { key: 'AVsae-CQS-9G3txfbFN3' })]}
- onFilterChange={jest.fn()}
- onIssueChange={jest.fn()}
- onIssueCheck={jest.fn()}
- onIssueClick={jest.fn()}
- onPopupToggle={jest.fn()}
- openPopup={undefined}
- selectedIssue={undefined}
- {...overrides}
- />
- );
-}
+++ /dev/null
-/*
- * 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 { mockMainBranch } from '../../../../helpers/mocks/branch-like';
-import { mockFlowLocation, mockIssue } from '../../../../helpers/testMocks';
-import IssuesSourceViewer, { IssuesSourceViewerProps } from '../IssuesSourceViewer';
-
-it('should render SourceViewer correctly', () => {
- expect(shallowRender()).toMatchSnapshot('default');
- expect(
- shallowRender({
- issues: [mockIssue(true)],
- openIssue: mockIssue(true, { flows: [[mockFlowLocation()]] }),
- })
- ).toMatchSnapshot('single secondary location');
- expect(
- shallowRender({
- issues: [mockIssue(true)],
- openIssue: mockIssue(true, {
- flows: [[mockFlowLocation(), mockFlowLocation(), mockFlowLocation()]],
- }),
- })
- ).toMatchSnapshot('all secondary locations on same line');
-});
-
-it('should render CrossComponentSourceViewer correctly', () => {
- expect(
- shallowRender({
- issues: [mockIssue(true)],
- openIssue: mockIssue(true, {
- flows: [
- [
- mockFlowLocation(),
- mockFlowLocation({
- textRange: {
- startLine: 10,
- startOffset: 1,
- endLine: 12,
- endOffset: 2,
- },
- }),
- ],
- ],
- }),
- })
- ).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<IssuesSourceViewerProps> = {}) {
- return shallow(
- <IssuesSourceViewer
- branchLike={mockMainBranch()}
- issues={[mockIssue()]}
- locationsNavigator={true}
- onIssueSelect={jest.fn()}
- onLocationSelect={jest.fn()}
- openIssue={mockIssue()}
- selectedFlowIndex={undefined}
- selectedLocationIndex={undefined}
- {...props}
- />
- );
-}
+++ /dev/null
-/*
- * 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 ListItem from '../ListItem';
-
-it('should render correctly', () => {
- const wrapper = shallowRender();
- expect(wrapper).toMatchSnapshot();
-});
-
-function shallowRender(props: Partial<ListItem['props']> = {}) {
- return shallow<ListItem>(
- <ListItem
- branchLike={mockBranch()}
- checked={false}
- issue={mockIssue()}
- onChange={jest.fn()}
- onCheck={jest.fn()}
- onClick={jest.fn()}
- onFilterChange={jest.fn()}
- onPopupToggle={jest.fn()}
- openPopup={undefined}
- selected={false}
- {...props}
- />
- );
-}
+++ /dev/null
-/*
- * 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 PageActions from '../PageActions';
-
-it('should render', () => {
- expect(shallow(<PageActions canSetHome={true} effortTotal={125} />)).toMatchSnapshot();
-});
+++ /dev/null
-/*
- * 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 TotalEffort from '../TotalEffort';
-
-it('should render', () => {
- expect(shallow(<TotalEffort effort={125} />)).toMatchSnapshot();
-});
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly: default 1`] = `
-<SearchSelect
- className="input-super-large"
- components={
- {
- "Option": [Function],
- "SingleValue": [Function],
- }
- }
- defaultOptions={[]}
- inputId="id"
- isClearable={true}
- loadOptions={[Function]}
- noOptionsMessage={[Function]}
- onChange={[MockFunction]}
-/>
-`;
-
-exports[`should render correctly: logged in & assignable issues 1`] = `
-<SearchSelect
- className="input-super-large"
- components={
- {
- "Option": [Function],
- "SingleValue": [Function],
- }
- }
- defaultOptions={
- [
- {
- "avatar": undefined,
- "label": "Skywalker",
- "value": "luke",
- },
- ]
- }
- inputId="id"
- isClearable={true}
- loadOptions={[Function]}
- noOptionsMessage={[Function]}
- onChange={[MockFunction]}
-/>
-`;
-
-exports[`should render correctly: logged in & no assignable issues 1`] = `
-<SearchSelect
- className="input-super-large"
- components={
- {
- "Option": [Function],
- "SingleValue": [Function],
- }
- }
- defaultOptions={[]}
- inputId="id"
- isClearable={true}
- loadOptions={[Function]}
- noOptionsMessage={[Function]}
- onChange={[MockFunction]}
-/>
-`;
-
-exports[`should render correctly: unassignable issues 1`] = `
-<SearchSelect
- className="input-super-large"
- components={
- {
- "Option": [Function],
- "SingleValue": [Function],
- }
- }
- defaultOptions={
- [
- {
- "label": "unassigned",
- "value": "",
- },
- ]
- }
- inputId="id"
- isClearable={true}
- loadOptions={[Function]}
- noOptionsMessage={[Function]}
- onChange={[MockFunction]}
-/>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should display error message when no issues available 1`] = `
-<Modal
- onRequestClose={[Function]}
- size="small"
->
- <form
- id="bulk-change-form"
- onSubmit={[Function]}
- >
- <div
- className="modal-head"
- >
- <h2>
- issue_bulk_change.form.title.0
- </h2>
- </div>
- <div
- className="modal-body modal-container"
- >
- <Alert
- variant="warning"
- >
- issue_bulk_change.no_match
- </Alert>
- </div>
- <div
- className="modal-foot"
- >
- <SubmitButton
- disabled={true}
- id="bulk-change-submit"
- >
- apply
- </SubmitButton>
- <ResetButtonLink
- onClick={[Function]}
- >
- cancel
- </ResetButtonLink>
- </div>
- </form>
-</Modal>
-`;
-
-exports[`should display form when issues are present 1`] = `
-<Modal
- onRequestClose={[Function]}
- size="small"
->
- <form
- id="bulk-change-form"
- onSubmit={[Function]}
- >
- <div
- className="modal-head"
- >
- <h2>
- issue_bulk_change.form.title.1
- </h2>
- </div>
- <div
- className="modal-body modal-container"
- >
- <Checkbox
- checked={false}
- className="display-inline-block spacer-top"
- id="send-notifications"
- onCheck={[Function]}
- right={true}
- thirdState={false}
- >
- <strong
- className="little-spacer-right"
- >
- issue.send_notifications
- </strong>
- </Checkbox>
- </div>
- <div
- className="modal-foot"
- >
- <SubmitButton
- disabled={true}
- id="bulk-change-submit"
- >
- apply
- </SubmitButton>
- <ResetButtonLink
- onClick={[Function]}
- >
- cancel
- </ResetButtonLink>
- </div>
- </form>
-</Modal>
-`;
-
-exports[`should display warning when too many issues are passed 1`] = `
-<h2>
- issue_bulk_change.form.title.500
-</h2>
-`;
-
-exports[`should display warning when too many issues are passed 2`] = `
-<Alert
- variant="warning"
->
- <FormattedMessage
- defaultMessage="issue_bulk_change.max_issues_reached"
- id="issue_bulk_change.max_issues_reached"
- values={
- {
- "max": <strong>
- 500
- </strong>,
- }
- }
- />
-</Alert>
-`;
-
-exports[`should render select for severity: Option 1`] = `
-<Option
- data={
- {
- "label": "label",
- "value": "value",
- }
- }
->
- <SeverityHelper
- className="display-flex-center"
- severity="value"
- />
-</Option>
-`;
-
-exports[`should render select for severity: SingleValue 1`] = `
-<SingleValue
- data={
- {
- "label": "label",
- "value": "value",
- }
- }
->
- <SeverityHelper
- className="display-flex-center"
- severity="value"
- />
-</SingleValue>
-`;
-
-exports[`should render select for type: Option 1`] = `
-<Option
- data={
- {
- "label": "label",
- "value": "value",
- }
- }
->
- <div
- className="display-flex-center"
- >
- <IssueTypeIcon
- query="value"
- />
- <span
- className="little-spacer-left"
- >
- label
- </span>
- </div>
-</Option>
-`;
-
-exports[`should render select for type: SingleValue 1`] = `
-<SingleValue
- data={
- {
- "label": "label",
- "value": "value",
- }
- }
->
- <div
- className="display-flex-center"
- >
- <IssueTypeIcon
- query="value"
- />
- <span
- className="little-spacer-left"
- >
- label
- </span>
- </div>
-</SingleValue>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`renders 1`] = `
-<div
- aria-label="issues.on_file_x.proj-name, comp-name"
- className="component-name text-ellipsis"
->
- <QualifierIcon
- className="spacer-right"
- qualifier="FIL"
- />
- <span
- title="proj-name - test-branch"
- >
- proj-name
- <span
- className="slash-separator"
- />
- </span>
- <span
- title="comp-name"
- >
- comp-name
- </span>
-</div>
-`;
-
-exports[`renders issues properly for views 1`] = `
-<div
- aria-label="issues.on_file_x.proj-name, comp-name"
- className="component-name text-ellipsis"
->
- <QualifierIcon
- className="spacer-right"
- qualifier="FIL"
- />
- <span
- title="proj-name"
- >
- proj-name
- -
- <span
- className="badge"
- >
- branches.main_branch
- </span>
- <span
- className="slash-separator"
- />
- </span>
- <span
- title="comp-name"
- >
- comp-name
- </span>
-</div>
-`;
-
-exports[`renders issues properly for views: with branch information 1`] = `
-<div
- aria-label="issues.on_file_x.proj-name, comp-name"
- className="component-name text-ellipsis"
->
- <QualifierIcon
- className="spacer-right"
- qualifier="FIL"
- />
- <span
- title="proj-name - test-branch"
- >
- proj-name
- -
- <BranchIcon />
- <span>
- test-branch
- </span>
- <span
- className="slash-separator"
- />
- </span>
- <span
- title="comp-name"
- >
- comp-name
- </span>
-</div>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`does not show current 1`] = `
-<PageCounter
- className="spacer-left"
- label="issues.issues"
- total={987654321}
-/>
-`;
-
-exports[`formats numbers 1`] = `
-<PageCounter
- className="spacer-left"
- current={1234}
- label="issues.issues"
- total={987654321}
-/>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<div>
- <i
- className="spinner"
- />
-</div>
-`;
-
-exports[`should render correctly 2`] = `
-<ul>
- <li>
- <div
- className="issues-workspace-list-component note"
- >
- <ComponentBreadcrumbs
- 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",
- "secondaryLocations": [],
- "severity": "MAJOR",
- "status": "OPEN",
- "textRange": {
- "endLine": 26,
- "endOffset": 15,
- "startLine": 25,
- "startOffset": 0,
- },
- "transitions": [],
- "type": "BUG",
- }
- }
- />
- </div>
- </li>
- <ul>
- <ListItem
- checked={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",
- "secondaryLocations": [],
- "severity": "MAJOR",
- "status": "OPEN",
- "textRange": {
- "endLine": 26,
- "endOffset": 15,
- "startLine": 25,
- "startOffset": 0,
- },
- "transitions": [],
- "type": "BUG",
- }
- }
- key="AVsae-CQS-9G3txfbFN2"
- onChange={[MockFunction]}
- onCheck={[MockFunction]}
- onClick={[MockFunction]}
- onFilterChange={[MockFunction]}
- onPopupToggle={[MockFunction]}
- selected={false}
- />
- <ListItem
- checked={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-9G3txfbFN3",
- "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",
- "secondaryLocations": [],
- "severity": "MAJOR",
- "status": "OPEN",
- "textRange": {
- "endLine": 26,
- "endOffset": 15,
- "startLine": 25,
- "startOffset": 0,
- },
- "transitions": [],
- "type": "BUG",
- }
- }
- key="AVsae-CQS-9G3txfbFN3"
- onChange={[MockFunction]}
- onCheck={[MockFunction]}
- onClick={[MockFunction]}
- onFilterChange={[MockFunction]}
- onPopupToggle={[MockFunction]}
- selected={false}
- />
- </ul>
-</ul>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render CrossComponentSourceViewer correctly 1`] = `
-<ContextProvider
- value={
- {
- "registerPrimaryLocationRef": [Function],
- "registerSelectedSecondaryLocationRef": [Function],
- }
- }
->
- <CrossComponentSourceViewer
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- 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": 12,
- "endOffset": 2,
- "startLine": 10,
- "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",
- "secondaryLocations": [
- {
- "component": "main.js",
- "index": 0,
- "textRange": {
- "endLine": 2,
- "endOffset": 2,
- "startLine": 1,
- "startOffset": 1,
- },
- },
- {
- "component": "main.js",
- "index": 1,
- "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",
- }
- }
- issues={
- [
- {
- "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",
- "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",
- },
- ]
- }
- locations={
- [
- {
- "component": "main.js",
- "index": 0,
- "textRange": {
- "endLine": 2,
- "endOffset": 2,
- "startLine": 1,
- "startOffset": 1,
- },
- },
- {
- "component": "main.js",
- "index": 1,
- "textRange": {
- "endLine": 2,
- "endOffset": 2,
- "startLine": 1,
- "startOffset": 1,
- },
- },
- ]
- }
- onIssueSelect={[MockFunction]}
- onLocationSelect={[MockFunction]}
- />
-</ContextProvider>
-`;
-
-exports[`should render SourceViewer correctly: all secondary locations on same line 1`] = `
-<ContextProvider
- value={
- {
- "registerPrimaryLocationRef": [Function],
- "registerSelectedSecondaryLocationRef": [Function],
- }
- }
->
- <CrossComponentSourceViewer
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- 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,
- },
- },
- ],
- ],
- "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",
- "secondaryLocations": [
- {
- "component": "main.js",
- "index": 0,
- "textRange": {
- "endLine": 2,
- "endOffset": 2,
- "startLine": 1,
- "startOffset": 1,
- },
- },
- {
- "component": "main.js",
- "index": 1,
- "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",
- }
- }
- issues={
- [
- {
- "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",
- "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",
- },
- ]
- }
- locations={
- [
- {
- "component": "main.js",
- "index": 0,
- "textRange": {
- "endLine": 2,
- "endOffset": 2,
- "startLine": 1,
- "startOffset": 1,
- },
- },
- {
- "component": "main.js",
- "index": 1,
- "textRange": {
- "endLine": 2,
- "endOffset": 2,
- "startLine": 1,
- "startOffset": 1,
- },
- },
- ]
- }
- onIssueSelect={[MockFunction]}
- onLocationSelect={[MockFunction]}
- />
-</ContextProvider>
-`;
-
-exports[`should render SourceViewer correctly: default 1`] = `
-<ContextProvider
- value={
- {
- "registerPrimaryLocationRef": [Function],
- "registerSelectedSecondaryLocationRef": [Function],
- }
- }
->
- <CrossComponentSourceViewer
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- 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",
- "secondaryLocations": [],
- "severity": "MAJOR",
- "status": "OPEN",
- "textRange": {
- "endLine": 26,
- "endOffset": 15,
- "startLine": 25,
- "startOffset": 0,
- },
- "transitions": [],
- "type": "BUG",
- }
- }
- issues={
- [
- {
- "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",
- "secondaryLocations": [],
- "severity": "MAJOR",
- "status": "OPEN",
- "textRange": {
- "endLine": 26,
- "endOffset": 15,
- "startLine": 25,
- "startOffset": 0,
- },
- "transitions": [],
- "type": "BUG",
- },
- ]
- }
- locations={[]}
- onIssueSelect={[MockFunction]}
- onLocationSelect={[MockFunction]}
- />
-</ContextProvider>
-`;
-
-exports[`should render SourceViewer correctly: single secondary location 1`] = `
-<ContextProvider
- value={
- {
- "registerPrimaryLocationRef": [Function],
- "registerSelectedSecondaryLocationRef": [Function],
- }
- }
->
- <CrossComponentSourceViewer
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- }
- }
- 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,
- },
- },
- ],
- ],
- "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",
- "secondaryLocations": [
- {
- "component": "main.js",
- "index": 0,
- "textRange": {
- "endLine": 2,
- "endOffset": 2,
- "startLine": 1,
- "startOffset": 1,
- },
- },
- {
- "component": "main.js",
- "index": 1,
- "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",
- }
- }
- issues={
- [
- {
- "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",
- "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",
- },
- ]
- }
- locations={
- [
- {
- "component": "main.js",
- "index": 0,
- "textRange": {
- "endLine": 2,
- "endOffset": 2,
- "startLine": 1,
- "startOffset": 1,
- },
- },
- {
- "component": "main.js",
- "index": 1,
- "textRange": {
- "endLine": 2,
- "endOffset": 2,
- "startLine": 1,
- "startOffset": 1,
- },
- },
- ]
- }
- onIssueSelect={[MockFunction]}
- onLocationSelect={[MockFunction]}
- />
-</ContextProvider>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render correctly 1`] = `
-<li
- className="issues-workspace-list-item"
->
- <Issue
- branchLike={
- {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-6.7",
- }
- }
- checked={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",
- "secondaryLocations": [],
- "severity": "MAJOR",
- "status": "OPEN",
- "textRange": {
- "endLine": 26,
- "endOffset": 15,
- "startLine": 25,
- "startOffset": 0,
- },
- "transitions": [],
- "type": "BUG",
- }
- }
- onChange={[MockFunction]}
- onCheck={[MockFunction]}
- onClick={[MockFunction]}
- onFilter={[Function]}
- onPopupToggle={[MockFunction]}
- selected={false}
- />
-</li>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render 1`] = `
-<div
- className="display-flex-center display-flex-justify-end"
->
- <PageShortcutsTooltip
- leftAndRightLabel="issues.to_navigate"
- upAndDownLabel="issues.to_select_issues"
- />
- <div
- className="spacer-left issues-page-actions"
- >
- <TotalEffort
- effort={125}
- />
- </div>
- <withCurrentUserContext(HomePageSelect)
- className="huge-spacer-left"
- currentPage={
- {
- "type": "ISSUES",
- }
- }
- />
-</div>
-`;
+++ /dev/null
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`should render 1`] = `
-<div
- className="display-inline-block bordered-left spacer-left"
->
- <div
- className="spacer-left"
- >
- <FormattedMessage
- defaultMessage="issue.x_effort"
- id="issue.x_effort"
- values={
- {
- "0": <strong>
- work_duration.x_hours.2 work_duration.x_minutes.5
- </strong>,
- }
- }
- />
- </div>
-</div>
-`;
import { Issue, Paging, TextRange } from './types';
import { UserBase } from './users';
+export const ASSIGNEE_ME = '__me__';
+
export enum IssueType {
CodeSmell = 'CODE_SMELL',
Vulnerability = 'VULNERABILITY',