]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-17819 Make tooltip content accessible to screen readers when entering narration...
authorMathieu Suen <mathieu.suen@sonarsource.com>
Tue, 3 Jan 2023 16:08:36 +0000 (17:08 +0100)
committersonartech <sonartech@sonarsource.com>
Mon, 9 Jan 2023 20:03:08 +0000 (20:03 +0000)
23 files changed:
server/sonar-web/src/main/js/api/mocks/AlmIntegrationsServiceMock.ts
server/sonar-web/src/main/js/apps/background-tasks/__tests__/BackgroundTasks-it.tsx
server/sonar-web/src/main/js/apps/code/__tests__/Code-it.ts
server/sonar-web/src/main/js/apps/create/project/__tests__/BitbucketCloud-it.tsx
server/sonar-web/src/main/js/apps/create/project/__tests__/GitLab-it.tsx
server/sonar-web/src/main/js/apps/issues/__tests__/IssuesApp-it.tsx
server/sonar-web/src/main/js/apps/issues/components/BulkChangeModal.tsx
server/sonar-web/src/main/js/apps/issues/components/IssuesList.tsx
server/sonar-web/src/main/js/apps/issues/components/__tests__/IssuesList-test.tsx
server/sonar-web/src/main/js/apps/issues/components/__tests__/__snapshots__/BulkChangeModal-test.tsx.snap
server/sonar-web/src/main/js/apps/quality-gates/components/DetailsHeader.tsx
server/sonar-web/src/main/js/apps/quality-gates/components/__tests__/QualityGate-it.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/LineCode.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx
server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineCode-test.tsx.snap
server/sonar-web/src/main/js/components/common/__tests__/DocumentationTooltip-test.tsx
server/sonar-web/src/main/js/components/controls/Tooltip.tsx
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/Tooltip-test.tsx.snap
server/sonar-web/src/main/js/components/controls/clipboard.tsx
server/sonar-web/src/main/js/components/issue/components/IssueAssign.tsx
server/sonar-web/src/main/js/components/issue/components/__tests__/__snapshots__/IssueAssign-test.tsx.snap
server/sonar-web/src/main/js/components/ui/__tests__/PageShortcutsTooltip-test.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 1dfd402732391ad9c7ceb661a2180c336f305413..48ce63147f51c84755fbaa38048700d8bb9aa943 100644 (file)
@@ -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 = {
index 1022a1127fb04d16cdc2aa94498d69f1b96f7861..30da2bf09f7240b42d88a8c88d4cbb9e0cf3ff52 100644 (file)
@@ -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);
index 88011dfa7286d8615df2d2650ce6e2f0def00ea6..22a367dc1b458037d906b4a3a05b612d29934fd5 100644 (file)
@@ -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);
     },
   };
 
index 2f415d42bb5fda0ad62b6c7b91f1359c2340e7f7..60cbc8eb988a8df79bad1e5089be72108a56cd51 100644 (file)
@@ -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',
   });
index 9030742c8433371ee4fef07de5647f91c30298c2..d650df1467a5dd9e4820dd4936bf77db77456eb9 100644 (file)
@@ -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',
   });
index 9c0ac32197ae7dfd8e5dc3dd3101004799e191a1..c0488f8280673addfcbcaae49593eaf539009c12 100644 (file)
@@ -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();
 });
index 249cfbdd687b71abc7923c43aa564b6152f62ba8..1f6b087f5fc3244cb8e63f2d16263d7e03837b0a 100644 (file)
@@ -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>
index 8fe79199d5a73037c50774a689aff9517f376176..19862799f17bd9c10d44e743b3a86a29947b8498 100644 (file)
@@ -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() {
index b5ede0b5d8e21963a997cf95fffba21d03fe706b..70b9b0f5e60328222f21439b0fb8609256b63edd 100644 (file)
 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();
 });
 
index a1055d09fabb8245d66de0f422518a820233581d..25d869b3512c16fa245801d9f49bfff0ec16571c 100644 (file)
@@ -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]}
       >
index e8bc53a60be47338f9b1ba57488f4999c0e69d1a..3565f5c0a8d83731edd3e3b31042986b33dfdff6 100644 (file)
@@ -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"
index 5937dd9e77066267b3798c3575225a30cd9b4a84..5b58db2852f541429455249f46c2d2f386accb92 100644 (file)
@@ -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();
 });
index ffe4071021bd7e06a1855ddefa109e19d0c98a19..87745243401c7770667c1f19581d42a95c728a4e 100644 (file)
@@ -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) => (
index 86d2fde8b8cbb848cbaa27c800b5821989e365da..bc4b9c4ff56a15101082d10ffe7a4a340f6c9be1 100644 (file)
@@ -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)}
index f1aa3c446c3d737fa42e441c587cfafccb662940..f7d93519ce90cbfa874313b4bc69aaf55843245b 100644 (file)
@@ -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}
index 1db0b24a607ec625d0e863021bb78dcf46885264..38cd4696e82ade0e7875b15a523174f18ad06208 100644 (file)
@@ -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 });
index 6edce0a42454a0920beda918abfa1323f2f78f3f..4570f8d105e1abac07530ed198a8f78ce1e39ced 100644 (file)
@@ -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>
index 219cc2689cf72e3a71a70d86937a9d973e0392cf..52a4190126aecad2ec8260736c1f26fc7716baa8 100644 (file)
@@ -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>
 `;
 
index 1037e10e34e5e290245922fca328d513c069460d..f8a2192044a343bbe4a668de7c322d4a2918c8b8 100644 (file)
@@ -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>
index 56d9e4fced806ef1e64b8aebe6f032f3c3f42f31..524dd06ab50d8457ec74ced902a92d1ca900623c 100644 (file)
@@ -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>
       );
index 3eef94a990fa5ec592ef8f38c61c2a6e03c22356..5a25cdc3ba2642acdeecdd9395c3d478163e11dd 100644 (file)
@@ -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>
index 7e145c467b3e822e68a79a3bfc20719deab0236f..44abd4428af32af2a38bd9e2f1fd4f779334475d 100644 (file)
@@ -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();
index 68cfea13680a8d9f68f871610c73e09b272253db..034ee7b4aa4f13db76b3de1636896210001c1f1d 100644 (file)
@@ -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.
-
 
 #------------------------------------------------------------------------------
 #