From d41e3872516b02e2fa55b745313b3948509c08ff Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Thu, 23 Mar 2023 12:28:13 +0100 Subject: [PATCH] SONAR-18847 Help tooltips are not read by screen readers in narration mode --- .../main/js/apps/code/components/CodeApp.tsx | 1 - .../components/ComponentMeasuresApp.tsx | 3 -- .../ComponentMeasuresApp-test.tsx.snap | 1 - .../js/apps/issues/__tests__/IssuesApp-it.tsx | 2 +- .../js/apps/issues/components/IssuesApp.tsx | 1 - .../__tests__/BulkChangeModal-it.tsx | 4 +- .../components/__tests__/IssueLabel-test.tsx | 4 +- .../__tests__/PermissionTemplatesApp-it.tsx | 30 ++++++++----- .../PermissionTemplatesApp-it.tsx.snap | 4 +- .../components/DetailsHeader.tsx | 4 +- .../js/apps/quality-gates/components/List.tsx | 13 +++--- .../components/__tests__/QualityGate-it.tsx | 4 +- .../components/LineDuplicationBlock.tsx | 2 +- .../__tests__/DocumentationTooltip-test.tsx | 4 +- .../js/components/controls/HelpTooltip.tsx | 44 ++++++++++--------- .../main/js/components/controls/Tooltip.tsx | 27 +++++++----- .../__snapshots__/HelpTooltip-test.tsx.snap | 5 +++ .../__snapshots__/Tooltip-test.tsx.snap | 2 - .../main/js/components/controls/clipboard.tsx | 16 ++++--- .../main/js/components/facet/FacetHeader.tsx | 2 +- .../components/facet/__tests__/Facet-it.tsx | 4 +- .../src/main/js/components/icons/Icon.tsx | 8 +++- .../other/__tests__/OtherTutorial-it.tsx | 8 ++-- .../js/components/ui/PageShortcutsTooltip.tsx | 1 - .../main/js/helpers/testReactTestingUtils.tsx | 8 +++- .../resources/org/sonar/l10n/core.properties | 1 + 26 files changed, 114 insertions(+), 89 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx index 5bd09e3c39d..281d41988b3 100644 --- a/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/CodeApp.tsx @@ -300,7 +300,6 @@ class CodeApp extends React.Component { {translate('code_viewer.not_all_measures_are_shown')} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx index b5d7f9da79c..36f66810afc 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/ComponentMeasuresApp.tsx @@ -309,9 +309,6 @@ export class ComponentMeasuresApp extends React.PureComponent { {translate('component_measures.not_all_measures_are_shown')} component_measures.not_all_measures_are_shown 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 f1043548c44..76629db8f91 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 @@ -288,7 +288,7 @@ describe('issues app', () => { ); 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/ })); await user.keyboard('New Comment'); expect(screen.getByRole('button', { name: 'apply' })).toBeDisabled(); diff --git a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx index 3a7ffd27df2..1cdb44ae166 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/IssuesApp.tsx @@ -991,7 +991,6 @@ export class App extends React.PureComponent { {translate('issues.not_all_issue_show')} diff --git a/server/sonar-web/src/main/js/apps/issues/components/__tests__/BulkChangeModal-it.tsx b/server/sonar-web/src/main/js/apps/issues/components/__tests__/BulkChangeModal-it.tsx index bb0566cb831..d629ed13c86 100644 --- a/server/sonar-web/src/main/js/apps/issues/components/__tests__/BulkChangeModal-it.tsx +++ b/server/sonar-web/src/main/js/apps/issues/components/__tests__/BulkChangeModal-it.tsx @@ -90,7 +90,7 @@ it('should disable the submit button unless some change is configured', async () expect(await screen.findByRole('button', { name: 'apply' })).toBeDisabled(); // Adding a comment should not enable the submit button - await user.type(screen.getByRole('textbox', { name: 'issue.comment.formlink' }), 'some comment'); + await user.type(screen.getByRole('textbox', { name: /issue.comment.formlink/ }), 'some comment'); expect(screen.getByRole('button', { name: 'apply' })).toBeDisabled(); // Select a severity @@ -159,7 +159,7 @@ it('should properly submit', async () => { ]); // Comment - await user.type(screen.getByRole('textbox', { name: 'issue.comment.formlink' }), 'some comment'); + await user.type(screen.getByRole('textbox', { name: /issue.comment.formlink/ }), 'some comment'); // Send notification await user.click(screen.getByRole('checkbox', { name: 'issue.send_notifications' })); diff --git a/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx b/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx index f09ee72491c..8b349a7ae4d 100644 --- a/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/components/__tests__/IssueLabel-test.tsx @@ -22,7 +22,7 @@ import * as React from 'react'; import { mockPullRequest } from '../../../../helpers/mocks/branch-like'; import { mockComponent } from '../../../../helpers/mocks/component'; import { mockMeasureEnhanced, mockMetric } from '../../../../helpers/testMocks'; -import { renderComponent } from '../../../../helpers/testReactTestingUtils'; +import { findTooltipWithContent, renderComponent } from '../../../../helpers/testReactTestingUtils'; import { IssueType } from '../../../../types/issues'; import { MetricKey } from '../../../../types/metrics'; import { IssueLabel, IssueLabelProps } from '../IssueLabel'; @@ -71,7 +71,7 @@ it('should render correctly for hotspots with tooltip', async () => { }) ).toBeInTheDocument(); - expect(await screen.findByText('tooltip text')).toBeInTheDocument(); + expect(findTooltipWithContent('tooltip text')).toBeInTheDocument(); }); function renderIssueLabel(props: Partial = {}) { diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/PermissionTemplatesApp-it.tsx b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/PermissionTemplatesApp-it.tsx index 97b58d1a82b..a4d8002edb9 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/PermissionTemplatesApp-it.tsx +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/PermissionTemplatesApp-it.tsx @@ -26,7 +26,10 @@ import PermissionsServiceMock from '../../../../api/mocks/PermissionsServiceMock import { mockPermissionGroup, mockPermissionUser } from '../../../../helpers/mocks/permissions'; import { PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE } from '../../../../helpers/permissions'; import { mockAppState } from '../../../../helpers/testMocks'; -import { renderAppWithAdminContext } from '../../../../helpers/testReactTestingUtils'; +import { + findTooltipWithContent, + renderAppWithAdminContext, +} from '../../../../helpers/testReactTestingUtils'; import { ComponentQualifier } from '../../../../types/component'; import { Permissions } from '../../../../types/permissions'; import { PermissionGroup, PermissionUser } from '../../../../types/types'; @@ -51,16 +54,16 @@ describe('rendering', () => { // Shows all permission table headers. PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE.forEach((permission, i) => { - expect(ui.getTableHeaderHelpTooltip(i + 1)).toHaveTextContent( - `projects_role.${permission}.desc` - ); + expect( + ui.getTableHeaderHelpTooltip(i + 1, `projects_role.${permission}.desc`) + ).toBeInTheDocument(); }); // Shows warning for browse and code viewer permissions. [Permissions.Browse, Permissions.CodeViewer].forEach((_permission, i) => { - expect(ui.getTableHeaderHelpTooltip(i + 1)).toHaveTextContent( - 'projects_role.public_projects_warning' - ); + expect( + ui.getTableHeaderHelpTooltip(i + 1, 'projects_role.public_projects_warning') + ).toBeInTheDocument(); }); // Check summaries. @@ -94,7 +97,9 @@ describe('rendering', () => { expect(screen.getByText('This is permission template 1')).toBeInTheDocument(); PERMISSIONS_ORDER_FOR_PROJECT_TEMPLATE.forEach((permission, i) => { expect(ui.permissionCheckbox('johndoe', permission).get()).toBeInTheDocument(); - expect(ui.getTableHeaderHelpTooltip(i)).toHaveTextContent(`projects_role.${permission}.desc`); + expect( + ui.getTableHeaderHelpTooltip(i, `projects_role.${permission}.desc`) + ).toBeInTheDocument(); }); }); }); @@ -511,13 +516,16 @@ function getPageObject(user: UserEvent) { await user.click(ui.cogMenuBtn(name).get()); await user.click(ui.setDefaultBtn(qualifier).get()); }, - getTableHeaderHelpTooltip(i: number) { + getTableHeaderHelpTooltip(i: number, text: string) { const th = byRole('columnheader').getAll().at(i); if (th === undefined) { throw new Error(`Couldn't locate the at index ${i}`); } - within(th).getByTestId('help-tooltip-activator').focus(); - return screen.getByRole('tooltip'); + return findTooltipWithContent((_content, element) => { + // For some reason, using the `content` parameter doesn't work for 1 of the + // tests. Explicitly using the element's `textContent` always works. + return Boolean(element?.textContent?.includes(text)); + }, th); }, }; } diff --git a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/PermissionTemplatesApp-it.tsx.snap b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/PermissionTemplatesApp-it.tsx.snap index e05b7037600..eebb77c2e35 100644 --- a/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/PermissionTemplatesApp-it.tsx.snap +++ b/server/sonar-web/src/main/js/apps/permission-templates/components/__tests__/__snapshots__/PermissionTemplatesApp-it.tsx.snap @@ -14,7 +14,7 @@ exports[`rendering should render the list of templates: Permission Template 1: u exports[`rendering should render the list of templates: Permission Template 2: admin 1`] = `"0 user(s)0 group(s)"`; -exports[`rendering should render the list of templates: Permission Template 2: codeviewer 1`] = `"permission_templates.project_creatorspermission_templates.project_creators.explanation0 user(s)0 group(s)"`; +exports[`rendering should render the list of templates: Permission Template 2: codeviewer 1`] = `"permission_templates.project_creatorspermission_templates.project_creators.explanationpermission_templates.project_creators.explanation0 user(s)0 group(s)"`; exports[`rendering should render the list of templates: Permission Template 2: issueadmin 1`] = `"0 user(s)0 group(s)"`; @@ -22,4 +22,4 @@ exports[`rendering should render the list of templates: Permission Template 2: s exports[`rendering should render the list of templates: Permission Template 2: securityhotspotadmin 1`] = `"0 user(s)0 group(s)"`; -exports[`rendering should render the list of templates: Permission Template 2: user 1`] = `"permission_templates.project_creatorspermission_templates.project_creators.explanation0 user(s)0 group(s)"`; +exports[`rendering should render the list of templates: Permission Template 2: user 1`] = `"permission_templates.project_creatorspermission_templates.project_creators.explanationpermission_templates.project_creators.explanation0 user(s)0 group(s)"`; 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 0ed90728d12..cca136bfc21 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 @@ -74,7 +74,7 @@ export default class DetailsHeader extends React.PureComponent { {qualityGate.isBuiltIn && } {qualityGate.caycStatus === CaycStatus.NonCompliant && ( } mouseLeaveDelay={TOOLTIP_MOUSE_LEAVE_DELAY}> - + } /> )} @@ -114,7 +114,6 @@ export default class DetailsHeader extends React.PureComponent { ? translate('quality_gates.cannot_copy_no_cayc') : null } - accessible={false} >