diff options
author | Mathieu Suen <mathieu.suen@sonarsource.com> | 2023-01-03 17:08:36 +0100 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2023-01-09 20:03:08 +0000 |
commit | d65568551dd4d46a7aa62ab3021f419ad4e3e78e (patch) | |
tree | 14526e2fa2ea805170896c4f87ae2849dd71c478 | |
parent | bc3ce5a8d0f27ac96814d7edcefcd3d834b02dc3 (diff) | |
download | sonarqube-d65568551dd4d46a7aa62ab3021f419ad4e3e78e.tar.gz sonarqube-d65568551dd4d46a7aa62ab3021f419ad4e3e78e.zip |
SONAR-17819 Make tooltip content accessible to screen readers when entering narration mode
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. - #------------------------------------------------------------------------------ # |