From d65568551dd4d46a7aa62ab3021f419ad4e3e78e Mon Sep 17 00:00:00 2001 From: Mathieu Suen Date: Tue, 3 Jan 2023 17:08:36 +0100 Subject: [PATCH] SONAR-17819 Make tooltip content accessible to screen readers when entering narration mode --- .../api/mocks/AlmIntegrationsServiceMock.ts | 5 +- .../__tests__/BackgroundTasks-it.tsx | 4 +- .../main/js/apps/code/__tests__/Code-it.ts | 8 +- .../project/__tests__/BitbucketCloud-it.tsx | 8 +- .../create/project/__tests__/GitLab-it.tsx | 8 +- .../js/apps/issues/__tests__/IssuesApp-it.tsx | 33 +++-- .../issues/components/BulkChangeModal.tsx | 15 +- .../js/apps/issues/components/IssuesList.tsx | 13 +- .../components/__tests__/IssuesList-test.tsx | 17 +-- .../BulkChangeModal-test.tsx.snap | 28 ++-- .../components/DetailsHeader.tsx | 2 + .../components/__tests__/QualityGate-it.tsx | 2 +- .../SourceViewer/components/LineCode.tsx | 1 - .../components/LineDuplicationBlock.tsx | 2 +- .../__snapshots__/LineCode-test.tsx.snap | 1 - .../__tests__/DocumentationTooltip-test.tsx | 2 - .../main/js/components/controls/Tooltip.tsx | 29 +++- .../__snapshots__/Tooltip-test.tsx.snap | 11 ++ .../main/js/components/controls/clipboard.tsx | 6 +- .../issue/components/IssueAssign.tsx | 45 +++--- .../__snapshots__/IssueAssign-test.tsx.snap | 129 +++++++++--------- .../__tests__/PageShortcutsTooltip-test.tsx | 12 +- .../resources/org/sonar/l10n/core.properties | 2 - 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 {
{submitting && } - - - {translate('apply')} - - + + {translate('apply')} + {translate('cancel')}
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 { }; 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`] = `
- - - apply - - + apply + @@ -85,16 +81,12 @@ exports[`should display form when issues are present 1`] = `
- - - apply - - + apply + 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 { overlay={ !isCaycCompliant ? translate('quality_gates.cannot_copy_no_cayc') : null } + accessible={false} >
); 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`] = ` /> } > - - - - - - - John Doe - - - - + + + John Doe + + +
`; @@ -70,23 +67,21 @@ exports[`should render a fallback assignee display if assignee info are not avai /> } > - - + - - unassigned - - - - + unassigned + + + `; @@ -105,35 +100,32 @@ exports[`should render with the action 1`] = ` /> } > - - - - - - - John Doe - - - - + + + John Doe + + + `; @@ -152,6 +144,7 @@ exports[`should render without the action when the correct rights are missing 1` John Doe 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. - #------------------------------------------------------------------------------ # -- 2.39.5