aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMathieu Suen <mathieu.suen@sonarsource.com>2023-01-03 17:08:36 +0100
committersonartech <sonartech@sonarsource.com>2023-01-09 20:03:08 +0000
commitd65568551dd4d46a7aa62ab3021f419ad4e3e78e (patch)
tree14526e2fa2ea805170896c4f87ae2849dd71c478
parentbc3ce5a8d0f27ac96814d7edcefcd3d834b02dc3 (diff)
downloadsonarqube-d65568551dd4d46a7aa62ab3021f419ad4e3e78e.tar.gz
sonarqube-d65568551dd4d46a7aa62ab3021f419ad4e3e78e.zip
SONAR-17819 Make tooltip content accessible to screen readers when entering narration mode
-rw-r--r--server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts5
-rw-r--r--server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx4
-rw-r--r--server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts8
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx8
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx15
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx13
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx17
-rw-r--r--server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap28
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx1
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap1
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/controls/Tooltip.tsx29
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Tooltip-test.tsx.snap11
-rw-r--r--server/sonar-web/src/main/js/components/controls/clipboard.tsx6
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/IssueAssign.tsx45
-rw-r--r--server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap129
-rw-r--r--server/sonar-web/src/main/js/components/ui/__tests__/PageShortcutsTooltip-test.tsx12
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties2
23 files changed, 204 insertions, 179 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts
index 1dfd4027323..48ce63147f5 100644
--- a/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts
@@ -89,9 +89,10 @@ export default class AlmIntegrationsServiceMock {
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 = {
diff --git a/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx b/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx
index 1022a1127fb..30da2bf09f7 100644
--- a/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx
+++ b/server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx
@@ -53,7 +53,9 @@ describe('The Global background task page', () => {
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);
diff --git a/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts b/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts
index 88011dfa728..22a367dc1b4 100644
--- a/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts
+++ b/server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts
@@ -393,13 +393,11 @@ function getPageObject(user: UserEvent) {
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);
},
};
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx
index 2f415d42bb5..60cbc8eb988 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx
@@ -113,7 +113,9 @@ it('should show import project feature when PAT is already set', async () => {
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();
@@ -126,7 +128,9 @@ it('should show import project feature when PAT is already set', async () => {
'/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',
});
diff --git a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx
index 9030742c843..d650df1467a 100644
--- a/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx
+++ b/server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx
@@ -89,7 +89,9 @@ it('should show import project feature when PAT is already set', async () => {
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();
@@ -99,7 +101,9 @@ it('should show import project feature when PAT is already set', async () => {
'/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',
});
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
index 9c0ac32197a..c0488f82806 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
@@ -17,7 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { 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';
@@ -90,7 +90,9 @@ it('should be able to bulk change', async () => {
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();
@@ -114,7 +116,9 @@ it('should show warning when not all issues are accessible', async () => {
});
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();
});
@@ -243,7 +247,9 @@ it('should open issue and navigate', async () => {
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();
@@ -259,7 +265,9 @@ it('should open issue and navigate', async () => {
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();
@@ -388,7 +396,9 @@ it('should be able to perform action on issues', async () => {
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(
@@ -456,8 +466,9 @@ it('should be able to perform action on issues', async () => {
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 () => {
@@ -556,8 +567,8 @@ it('should show code tabs when any secondary location is selected', 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(
@@ -608,7 +619,7 @@ it('should show issue tags if applicable', async () => {
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();
});
diff --git a/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx b/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx
index 249cfbdd687..1f6b087f5fc 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx
@@ -33,7 +33,6 @@ import Select, {
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';
@@ -517,14 +516,12 @@ export default class BulkChangeModal extends React.PureComponent<Props, State> {
<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>
diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx
index 8fe79199d5a..19862799f17 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx
@@ -47,12 +47,15 @@ export default class IssuesList extends React.PureComponent<Props, State> {
};
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() {
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx
index b5ede0b5d8e..70b9b0f5e60 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx
@@ -20,23 +20,12 @@
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();
});
diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap
index a1055d09fab..25d869b3512 100644
--- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap
@@ -28,16 +28,12 @@ exports[`should display error message when no issues available 1`] = `
<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]}
>
@@ -85,16 +81,12 @@ exports[`should display form when issues are present 1`] = `
<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]}
>
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx
index e8bc53a60be..3565f5c0a8d 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx
@@ -113,6 +113,7 @@ export default class DetailsHeader extends React.PureComponent<Props> {
overlay={
!isCaycCompliant ? translate('quality_gates.cannot_copy_no_cayc') : null
}
+ accessible={false}
>
<Button
className="little-spacer-left"
@@ -131,6 +132,7 @@ export default class DetailsHeader extends React.PureComponent<Props> {
overlay={
!isCaycCompliant ? translate('quality_gates.cannot_set_default_no_cayc') : null
}
+ accessible={false}
>
<Button
className="little-spacer-left"
diff --git a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx
index 5937dd9e770..5b58db2852f 100644
--- a/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx
+++ b/server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx
@@ -55,7 +55,7 @@ it('should list all quality gates', async () => {
).toBeInTheDocument();
expect(
await screen.findByRole('menuitem', {
- name: `${handler.getBuiltInQualityGate().name} quality_gates.built_in`,
+ name: `${handler.getBuiltInQualityGate().name} quality_gates.built_in.help`,
})
).toBeInTheDocument();
});
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
index ffe4071021b..87745243401 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
@@ -140,7 +140,6 @@ export default class LineCode extends React.PureComponent<React.PropsWithChildre
onClick={onClick}
selected={selected}
aria-current={selected ? 'location' : false}
- aria-label={message ? `${index + 1}-${message}` : index + 1}
>
<IssueSourceViewerScrollContext.Consumer>
{(ctx) => (
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx
index 86d2fde8b8c..bc4b9c4ff56 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx
@@ -48,7 +48,7 @@ export function LineDuplicationBlock(props: LineDuplicationBlockProps) {
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)}
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
index f1aa3c446c3..f7d93519ce9 100644
--- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
@@ -113,7 +113,6 @@ exports[`render code: with secondary location 1`] = `
>
<LocationIndex
aria-current={false}
- aria-label="2-secondary-location-msg"
leading={false}
onClick={[Function]}
selected={false}
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx
index 1db0b24a607..38cd4696e82 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx
+++ b/server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx
@@ -52,8 +52,6 @@ it('should correctly navigate through TAB', async () => {
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 });
diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
index 6edce0a4245..4570f8d105e 100644
--- a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
+++ b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import classNames from 'classnames';
import { throttle, uniqueId } from 'lodash';
import * as React from 'react';
import { createPortal, findDOMNode } from 'react-dom';
@@ -29,6 +30,7 @@ import './Tooltip.css';
export type Placement = 'bottom' | 'right' | 'left' | 'top';
export interface TooltipProps {
+ accessible?: boolean;
classNameSpace?: string;
children: React.ReactElement<{}>;
mouseEnterDelay?: number;
@@ -381,9 +383,7 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
innerRef={this.tooltipNodeRef}
style={style}
>
- <div className={`${classNameSpace}-inner`} id={this.id}>
- {this.props.overlay}
- </div>
+ {this.renderOverlay()}
<div
className={`${classNameSpace}-arrow`}
style={
@@ -396,8 +396,24 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
);
};
+ 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, {
@@ -405,14 +421,15 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
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>
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Tooltip-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Tooltip-test.tsx.snap
index 219cc2689cf..52a4190126a 100644
--- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Tooltip-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Tooltip-test.tsx.snap
@@ -15,6 +15,8 @@ exports[`should not render empty tooltips 2`] = `
exports[`should render 1`] = `
<Fragment>
<div
+ aria-describedby="tooltip-1"
+ aria-labelledby="tooltip-1"
id="tooltip"
onBlur={[Function]}
onFocus={[Function]}
@@ -22,6 +24,15 @@ exports[`should render 1`] = `
onPointerLeave={[Function]}
tabIndex={0}
/>
+ <div
+ aria-hidden={true}
+ className="tooltip-inner hidden"
+ id="tooltip-1"
+ >
+ <span
+ id="overlay"
+ />
+ </div>
</Fragment>
`;
diff --git a/server/sonar-web/src/main/js/components/controls/clipboard.tsx b/server/sonar-web/src/main/js/components/controls/clipboard.tsx
index 1037e10e34e..f8a2192044a 100644
--- a/server/sonar-web/src/main/js/components/controls/clipboard.tsx
+++ b/server/sonar-web/src/main/js/components/controls/clipboard.tsx
@@ -111,7 +111,7 @@ export function ClipboardButton({
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}
@@ -148,8 +148,8 @@ export function ClipboardIconButton(props: ClipboardIconButtonProps) {
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>
diff --git a/server/sonar-web/src/main/js/components/issue/components/IssueAssign.tsx b/server/sonar-web/src/main/js/components/issue/components/IssueAssign.tsx
index 56d9e4fced8..524dd06ab50 100644
--- a/server/sonar-web/src/main/js/components/issue/components/IssueAssign.tsx
+++ b/server/sonar-web/src/main/js/components/issue/components/IssueAssign.tsx
@@ -23,7 +23,6 @@ import Toggler from '../../../components/controls/Toggler';
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';
@@ -49,15 +48,17 @@ export default class IssueAssign extends React.PureComponent<Props> {
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>
</>
);
@@ -79,24 +80,22 @@ export default class IssueAssign extends React.PureComponent<Props> {
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>
);
diff --git a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap
index 3eef94a990f..5a25cdc3ba2 100644
--- a/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap
@@ -23,35 +23,32 @@ exports[`should open the popup when the button is clicked 2`] = `
/>
}
>
- <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>
`;
@@ -70,23 +67,21 @@ exports[`should render a fallback assignee display if assignee info are not avai
/>
}
>
- <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>
`;
@@ -105,35 +100,32 @@ exports[`should render with the action 1`] = `
/>
}
>
- <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>
`;
@@ -152,6 +144,7 @@ exports[`should render without the action when the correct rights are missing 1`
</span>
<span
className="issue-meta-label"
+ title="John Doe"
>
John Doe
</span>
diff --git a/server/sonar-web/src/main/js/components/ui/__tests__/PageShortcutsTooltip-test.tsx b/server/sonar-web/src/main/js/components/ui/__tests__/PageShortcutsTooltip-test.tsx
index 7e145c467b3..44abd4428af 100644
--- a/server/sonar-web/src/main/js/components/ui/__tests__/PageShortcutsTooltip-test.tsx
+++ b/server/sonar-web/src/main/js/components/ui/__tests__/PageShortcutsTooltip-test.tsx
@@ -38,7 +38,11 @@ it('should render all the labels', async () => {
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();
@@ -54,7 +58,11 @@ it('should render left & right labels without up&down', async () => {
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();
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 68cfea13680..034ee7b4aa4 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -1021,8 +1021,6 @@ issue_bulk_change.comment.help=This comment will be applied only to issues that
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.
-
#------------------------------------------------------------------------------
#