id: '1',
sqProjectKey: 'key',
sqProjectName: 'Gitlab project 1',
+ slug: 'Gitlab_project_1',
}),
- mockGitlabProject({ name: 'Gitlab project 2', id: '2' }),
- mockGitlabProject({ name: 'Gitlab project 3', id: '3' }),
+ mockGitlabProject({ name: 'Gitlab project 2', id: '2', slug: 'Gitlab_project_2' }),
+ mockGitlabProject({ name: 'Gitlab project 3', id: '3', slug: 'Gitlab_project_3' }),
];
defaultPagination = {
within(await screen.findByText('background_tasks.number_of_workers')).getByText('2')
).toBeInTheDocument();
- const editWorkersButton = screen.getByRole('button', { name: 'edit' });
+ const editWorkersButton = screen.getByRole('button', {
+ name: 'background_tasks.change_number_of_workers',
+ });
expect(editWorkersButton).toBeInTheDocument();
await user.click(editWorkersButton);
const { getAllByRole } = within(row);
const cell = getAllByRole('cell').at(i);
- if (cell?.textContent === value) {
- return cell;
+ if (cell === undefined) {
+ throw new Error(`Couldn't locate cell with value ${value} for header ${name}`);
}
- // eslint-disable-next-line testing-library/no-debugging-utils
- screen.debug(screen.getByRole('table'), 40000);
- throw new Error(`Couldn't locate cell with value ${value} for header ${name}`);
+ return within(cell).getByText(value);
},
};
expect(screen.getByText('BitbucketCloud Repo 1')).toBeInTheDocument();
expect(screen.getByText('BitbucketCloud Repo 2')).toBeInTheDocument();
- projectItem = screen.getByRole('row', { name: /BitbucketCloud Repo 1/ });
+ projectItem = screen.getByRole('row', {
+ name: 'bitbucketcloud_repo_1 project opens_in_new_window onboarding.create_project.bitbucketcloud.link onboarding.create_project.repository_imported',
+ });
expect(
within(projectItem).getByText('onboarding.create_project.repository_imported')
).toBeInTheDocument();
'/dashboard?id=key'
);
- projectItem = screen.getByRole('row', { name: /BitbucketCloud Repo 2/ });
+ projectItem = screen.getByRole('row', {
+ name: 'bitbucketcloud_repo_2 project opens_in_new_window onboarding.create_project.bitbucketcloud.link onboarding.create_project.set_up',
+ });
const importProjectButton = within(projectItem).getByRole('button', {
name: 'onboarding.create_project.set_up',
});
expect(screen.getByText('Gitlab project 1')).toBeInTheDocument();
expect(screen.getByText('Gitlab project 2')).toBeInTheDocument();
- projectItem = screen.getByRole('row', { name: /Gitlab project 1/ });
+ projectItem = screen.getByRole('row', {
+ name: 'Gitlab_project_1 company/best-projects opens_in_new_window onboarding.create_project.gitlab.link onboarding.create_project.repository_imported',
+ });
expect(
within(projectItem).getByText('onboarding.create_project.repository_imported')
).toBeInTheDocument();
'/dashboard?id=key'
);
- projectItem = screen.getByRole('row', { name: /Gitlab project 2/ });
+ projectItem = screen.getByRole('row', {
+ name: 'Gitlab_project_2 company/best-projects opens_in_new_window onboarding.create_project.gitlab.link onboarding.create_project.set_up',
+ });
const importProjectButton = within(projectItem).getByRole('button', {
name: 'onboarding.create_project.set_up',
});
* 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, within } from '@testing-library/react';
+import { act, screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import selectEvent from 'react-select-event';
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.click(
+ screen.getByRole('textbox', { name: 'issue.comment.formlink issue_bulk_change.comment.help' })
+ );
await user.keyboard('New Comment');
expect(screen.getByRole('button', { name: 'apply' })).toBeDisabled();
});
expect(await screen.findByRole('alert', { name: 'alert.tooltip.warning' })).toBeInTheDocument();
- await user.keyboard('{ArrowRight}');
+ await act(async () => {
+ await user.keyboard('{ArrowRight}');
+ });
expect(await screen.findByRole('alert', { name: 'alert.tooltip.warning' })).toBeInTheDocument();
});
expect(extendedDescriptions).toHaveLength(1);
// Select the previous issue (with a simple rule) through keyboard shortcut
- await user.keyboard('{ArrowUp}');
+ await act(async () => {
+ await user.keyboard('{ArrowUp}');
+ });
// Are rule headers present?
expect(screen.getByRole('heading', { level: 1, name: 'Fix this' })).toBeInTheDocument();
expect(screen.getByRole('heading', { name: 'Default' })).toBeInTheDocument();
// Select the previous issue (with a simple rule) through keyboard shortcut
- await user.keyboard('{ArrowUp}');
+ await act(async () => {
+ await user.keyboard('{ArrowUp}');
+ });
// Are rule headers present?
expect(screen.getByRole('heading', { level: 1, name: 'Issue on file' })).toBeInTheDocument();
await user.keyboard('luke');
expect(screen.getByText('Skywalker')).toBeInTheDocument();
await user.keyboard('{ArrowUp}{enter}');
- expect(screen.getByText('luke')).toBeInTheDocument();
+ expect(
+ screen.getByRole('button', { name: 'issue.assign.assigned_to_x_click_to_change.luke' })
+ ).toBeInTheDocument();
// adding comment to the issue
expect(
await user.click(screen.getByRole('searchbox', { name: 'search_verb' }));
await user.keyboard('addNewTag');
- expect(screen.getByText('+')).toBeInTheDocument();
- expect(screen.getByText('addnewtag')).toBeInTheDocument();
+ expect(
+ screen.getByRole('checkbox', { name: 'create_new_element: addnewtag' })
+ ).toBeInTheDocument();
});
it('should not allow performing actions when user does not have permission', async () => {
renderIssueApp();
await user.click(await screen.findByRole('region', { name: 'Fix this' }));
- expect(screen.getByText('location 1')).toBeInTheDocument();
- expect(screen.getByText('location 2')).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'location 1' })).toBeInTheDocument();
+ expect(screen.getByRole('button', { name: 'location 2' })).toBeInTheDocument();
// Select the "why is this an issue" tab
await user.click(
expect(
screen.getByRole('heading', {
- name: 'Issue with tags sonar-lint-icon issue.resolution.badge.DEPRECATED',
+ name: 'Issue with tags issue.quick_fix_available_with_sonarlint opens_in_new_window SonarLint rules.status.DEPRECATED.help opens_in_new_window see_x.rules',
})
).toBeInTheDocument();
});
CreatableSelect,
SearchSelect,
} from '../../../components/controls/Select';
-import Tooltip from '../../../components/controls/Tooltip';
import IssueTypeIcon from '../../../components/icons/IssueTypeIcon';
import SeverityHelper from '../../../components/shared/SeverityHelper';
import { Alert } from '../../../components/ui/Alert';
<div className="modal-foot">
{submitting && <i className="spinner spacer-right" />}
- <Tooltip overlay={!canSubmit ? translate('issue_bulk_change.no_change_selected') : null}>
- <SubmitButton
- disabled={!canSubmit || submitting || issues.length === 0}
- id="bulk-change-submit"
- >
- {translate('apply')}
- </SubmitButton>
- </Tooltip>
+ <SubmitButton
+ disabled={!canSubmit || submitting || issues.length === 0}
+ id="bulk-change-submit"
+ >
+ {translate('apply')}
+ </SubmitButton>
<ResetButtonLink onClick={this.props.onClose}>{translate('cancel')}</ResetButtonLink>
</div>
</form>
};
componentDidMount() {
- // ! \\ This prerender state variable is to enable the page to be displayed
- // immediately, displaying a loader before attempting to render the
- // list of issues. See https://jira.sonarsource.com/browse/SONAR-11681
- setTimeout(() => {
+ if (this.props.issues.length > 0) {
this.setState({ prerender: false });
- }, 42);
+ }
+ }
+
+ componentDidUpdate() {
+ if (this.props.issues.length > 0) {
+ this.setState({ prerender: false });
+ }
}
render() {
import { shallow } from 'enzyme';
import * as React from 'react';
import { mockIssue } from '../../../../helpers/testMocks';
-import { waitAndUpdate } from '../../../../helpers/testUtils';
import IssuesList from '../IssuesList';
-beforeAll(() => {
- jest.useFakeTimers();
-});
-
-afterAll(() => {
- jest.runOnlyPendingTimers();
- jest.useRealTimers();
-});
-
-it('should render correctly', async () => {
- const wrapper = shallowRender();
+it('should render correctly', () => {
+ const wrapper = shallowRender({ issues: [] });
expect(wrapper).toMatchSnapshot();
- jest.runAllTimers();
- await waitAndUpdate(wrapper);
+ wrapper.setProps({ issues: [mockIssue(), mockIssue(false, { key: 'AVsae-CQS-9G3txfbFN3' })] });
expect(wrapper).toMatchSnapshot();
});
<div
className="modal-foot"
>
- <Tooltip
- overlay="issue_bulk_change.no_change_selected"
+ <SubmitButton
+ disabled={true}
+ id="bulk-change-submit"
>
- <SubmitButton
- disabled={true}
- id="bulk-change-submit"
- >
- apply
- </SubmitButton>
- </Tooltip>
+ apply
+ </SubmitButton>
<ResetButtonLink
onClick={[Function]}
>
<div
className="modal-foot"
>
- <Tooltip
- overlay="issue_bulk_change.no_change_selected"
+ <SubmitButton
+ disabled={true}
+ id="bulk-change-submit"
>
- <SubmitButton
- disabled={true}
- id="bulk-change-submit"
- >
- apply
- </SubmitButton>
- </Tooltip>
+ apply
+ </SubmitButton>
<ResetButtonLink
onClick={[Function]}
>
overlay={
!isCaycCompliant ? translate('quality_gates.cannot_copy_no_cayc') : null
}
+ accessible={false}
>
<Button
className="little-spacer-left"
overlay={
!isCaycCompliant ? translate('quality_gates.cannot_set_default_no_cayc') : null
}
+ accessible={false}
>
<Button
className="little-spacer-left"
).toBeInTheDocument();
expect(
await screen.findByRole('menuitem', {
- name: `${handler.getBuiltInQualityGate().name} quality_gates.built_in`,
+ name: `${handler.getBuiltInQualityGate().name} quality_gates.built_in.help`,
})
).toBeInTheDocument();
});
onClick={onClick}
selected={selected}
aria-current={selected ? 'location' : false}
- aria-label={message ? `${index + 1}-${message}` : index + 1}
>
<IssueSourceViewerScrollContext.Consumer>
{(ctx) => (
return duplicated ? (
<td className={className} data-index={index} data-line-number={line.line}>
- <Tooltip overlay={tooltip} placement="right">
+ <Tooltip overlay={tooltip} placement="right" accessible={false}>
<div>
<Toggler
onRequestClose={() => setDropdownOpen(false)}
>
<LocationIndex
aria-current={false}
- aria-label="2-secondary-location-msg"
leading={false}
onClick={[Function]}
selected={false}
await user.tab({ shift: true });
expect(await ui.afterLink.find()).toHaveFocus();
await user.tab({ shift: true });
- expect(ui.helpIcon.get()).toHaveFocus();
- await user.tab();
await user.tab();
await user.tab();
await user.tab({ shift: true });
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import classNames from 'classnames';
import { throttle, uniqueId } from 'lodash';
import * as React from 'react';
import { createPortal, findDOMNode } from 'react-dom';
export type Placement = 'bottom' | 'right' | 'left' | 'top';
export interface TooltipProps {
+ accessible?: boolean;
classNameSpace?: string;
children: React.ReactElement<{}>;
mouseEnterDelay?: number;
innerRef={this.tooltipNodeRef}
style={style}
>
- <div className={`${classNameSpace}-inner`} id={this.id}>
- {this.props.overlay}
- </div>
+ {this.renderOverlay()}
<div
className={`${classNameSpace}-arrow`}
style={
);
};
+ renderOverlay() {
+ const isVisible = this.isVisible();
+ const { classNameSpace = 'tooltip' } = this.props;
+
+ return (
+ <div
+ className={classNames(`${classNameSpace}-inner`, { hidden: !isVisible })}
+ id={this.id}
+ aria-hidden={!isVisible}
+ >
+ {this.props.overlay}
+ </div>
+ );
+ }
+
render() {
const isVisible = this.isVisible();
+ const { accessible = true } = this.props;
return (
<>
{React.cloneElement(this.props.children, {
onPointerLeave: this.handleMouseLeave,
onFocus: this.handleFocus,
onBlur: this.handleBlur,
- tabIndex: 0,
+ tabIndex: accessible ? 0 : undefined,
// aria-describedby is the semantically correct property to use, but it's not
// always well supported. As a fallback, we use aria-labelledby as well.
// See https://sarahmhigley.com/writing/tooltips-in-wcag-21/
// See https://css-tricks.com/accessible-svgs/
- 'aria-describedby': isVisible ? this.id : undefined,
- 'aria-labelledby': isVisible ? this.id : undefined,
+ 'aria-describedby': accessible ? this.id : undefined,
+ 'aria-labelledby': accessible ? this.id : undefined,
})}
+ {!isVisible && this.renderOverlay()}
{isVisible && (
<EscKeydownHandler onKeydown={this.closeTooltip}>
<TooltipPortal>
exports[`should render 1`] = `
<Fragment>
<div
+ aria-describedby="tooltip-1"
+ aria-labelledby="tooltip-1"
id="tooltip"
onBlur={[Function]}
onFocus={[Function]}
onPointerLeave={[Function]}
tabIndex={0}
/>
+ <div
+ aria-hidden={true}
+ className="tooltip-inner hidden"
+ id="tooltip-1"
+ >
+ <span
+ id="overlay"
+ />
+ </div>
</Fragment>
`;
return (
<ClipboardBase>
{({ setCopyButton, copySuccess }) => (
- <Tooltip overlay={translate('copied_action')} visible={copySuccess}>
+ <Tooltip overlay={translate('copied_action')} visible={copySuccess} accessible={false}>
<Button
className={classNames('no-select', className)}
data-clipboard-text={copyValue}
className={classNames('no-select', className)}
data-clipboard-text={copyValue}
innerRef={setCopyButton}
- tooltip={translate(copySuccess ? 'copied_action' : 'copy_to_clipboard')}
- tooltipProps={copySuccess ? { visible: copySuccess } : undefined}
+ tooltip={copySuccess ? translate('copied_action') : undefined}
+ tooltipProps={copySuccess ? { visible: copySuccess, accessible: false } : undefined}
>
<CopyIcon />
</ButtonIcon>
import DropdownIcon from '../../../components/icons/DropdownIcon';
import { translate, translateWithParameters } from '../../../helpers/l10n';
import { Issue } from '../../../types/types';
-import Tooltip from '../../controls/Tooltip';
import Avatar from '../../ui/Avatar';
import SetAssigneePopup from '../popups/SetAssigneePopup';
const assigneeName = issue.assigneeName || issue.assignee;
if (assigneeName) {
+ const assigneeDisplay =
+ issue.assigneeActive === false
+ ? translateWithParameters('user.x_deleted', assigneeName)
+ : assigneeName;
return (
<>
<span className="text-top">
<Avatar className="little-spacer-right" hash={issue.assigneeAvatar} name="" size={16} />
</span>
- <span className="issue-meta-label">
- {issue.assigneeActive === false
- ? translateWithParameters('user.x_deleted', assigneeName)
- : assigneeName}
+ <span className="issue-meta-label" title={assigneeDisplay}>
+ {assigneeDisplay}
</span>
</>
);
open={isOpen}
overlay={<SetAssigneePopup onSelect={this.props.onAssign} />}
>
- <Tooltip overlay={assigneeName}>
- <ButtonLink
- aria-expanded={isOpen}
- aria-label={
- assigneeName
- ? translateWithParameters(
- 'issue.assign.assigned_to_x_click_to_change',
- assigneeName
- )
- : translate('issue.assign.unassigned_click_to_assign')
- }
- className="issue-action issue-action-with-options js-issue-assign"
- onClick={this.toggleAssign}
- >
- {this.renderAssignee()}
- <DropdownIcon className="little-spacer-left" />
- </ButtonLink>
- </Tooltip>
+ <ButtonLink
+ aria-expanded={isOpen}
+ aria-label={
+ assigneeName
+ ? translateWithParameters(
+ 'issue.assign.assigned_to_x_click_to_change',
+ assigneeName
+ )
+ : translate('issue.assign.unassigned_click_to_assign')
+ }
+ className="issue-action issue-action-with-options js-issue-assign"
+ onClick={this.toggleAssign}
+ >
+ {this.renderAssignee()}
+ <DropdownIcon className="little-spacer-left" />
+ </ButtonLink>
</Toggler>
</div>
);
/>
}
>
- <Tooltip
- overlay="John Doe"
+ <ButtonLink
+ aria-expanded={true}
+ aria-label="issue.assign.assigned_to_x_click_to_change.John Doe"
+ className="issue-action issue-action-with-options js-issue-assign"
+ onClick={[Function]}
>
- <ButtonLink
- aria-expanded={true}
- aria-label="issue.assign.assigned_to_x_click_to_change.John Doe"
- className="issue-action issue-action-with-options js-issue-assign"
- onClick={[Function]}
+ <span
+ className="text-top"
>
- <span
- className="text-top"
- >
- <withAppStateContext(Avatar)
- className="little-spacer-right"
- hash="gravatarhash"
- name=""
- size={16}
- />
- </span>
- <span
- className="issue-meta-label"
- >
- John Doe
- </span>
- <DropdownIcon
- className="little-spacer-left"
+ <withAppStateContext(Avatar)
+ className="little-spacer-right"
+ hash="gravatarhash"
+ name=""
+ size={16}
/>
- </ButtonLink>
- </Tooltip>
+ </span>
+ <span
+ className="issue-meta-label"
+ title="John Doe"
+ >
+ John Doe
+ </span>
+ <DropdownIcon
+ className="little-spacer-left"
+ />
+ </ButtonLink>
</Toggler>
</div>
`;
/>
}
>
- <Tooltip>
- <ButtonLink
- aria-expanded={false}
- aria-label="issue.assign.unassigned_click_to_assign"
- className="issue-action issue-action-with-options js-issue-assign"
- onClick={[Function]}
+ <ButtonLink
+ aria-expanded={false}
+ aria-label="issue.assign.unassigned_click_to_assign"
+ className="issue-action issue-action-with-options js-issue-assign"
+ onClick={[Function]}
+ >
+ <span
+ className="issue-meta-label"
>
- <span
- className="issue-meta-label"
- >
- unassigned
- </span>
- <DropdownIcon
- className="little-spacer-left"
- />
- </ButtonLink>
- </Tooltip>
+ unassigned
+ </span>
+ <DropdownIcon
+ className="little-spacer-left"
+ />
+ </ButtonLink>
</Toggler>
</div>
`;
/>
}
>
- <Tooltip
- overlay="John Doe"
+ <ButtonLink
+ aria-expanded={false}
+ aria-label="issue.assign.assigned_to_x_click_to_change.John Doe"
+ className="issue-action issue-action-with-options js-issue-assign"
+ onClick={[Function]}
>
- <ButtonLink
- aria-expanded={false}
- aria-label="issue.assign.assigned_to_x_click_to_change.John Doe"
- className="issue-action issue-action-with-options js-issue-assign"
- onClick={[Function]}
+ <span
+ className="text-top"
>
- <span
- className="text-top"
- >
- <withAppStateContext(Avatar)
- className="little-spacer-right"
- hash="gravatarhash"
- name=""
- size={16}
- />
- </span>
- <span
- className="issue-meta-label"
- >
- John Doe
- </span>
- <DropdownIcon
- className="little-spacer-left"
+ <withAppStateContext(Avatar)
+ className="little-spacer-right"
+ hash="gravatarhash"
+ name=""
+ size={16}
/>
- </ButtonLink>
- </Tooltip>
+ </span>
+ <span
+ className="issue-meta-label"
+ title="John Doe"
+ >
+ John Doe
+ </span>
+ <DropdownIcon
+ className="little-spacer-left"
+ />
+ </ButtonLink>
</Toggler>
</div>
`;
</span>
<span
className="issue-meta-label"
+ title="John Doe"
>
John Doe
</span>
metaModifierLabel,
});
- await user.hover(screen.getByText('←'));
+ await user.hover(
+ screen.getByLabelText(
+ 'shortcuts.on_page.intro shortcuts.on_page.up_down_x.up & down shortcuts.on_page.left_right_x.left & right shortcuts.on_page.left_x.left shortcuts.on_page.meta_x.meta'
+ )
+ );
expect(await screen.findByText(leftAndRightLabel)).toBeInTheDocument();
expect(screen.getByText(leftLabel)).toBeInTheDocument();
leftLabel,
});
- await user.hover(screen.getByText('←'));
+ await user.hover(
+ screen.getByLabelText(
+ 'shortcuts.on_page.intro shortcuts.on_page.left_right_x.left & right shortcuts.on_page.left_x.left'
+ )
+ );
expect(await screen.findByText(leftAndRightLabel)).toBeInTheDocument();
expect(screen.getByText(leftLabel)).toBeInTheDocument();
issue_bulk_change.max_issues_reached=There are more issues available than can be treated by a single bulk action. Your changes will only be applied to the first {max} issues.
issue_bulk_change.x_issues={0} issues
issue_bulk_change.no_match=There is no issue matching your filter selection
-issue_bulk_change.no_change_selected=Make at least 1 change (e.g.: change assignee) in order to update the selected issues.
-
#------------------------------------------------------------------------------
#