From 8597e7bdd6628abb8c29986f36babf99a928bf64 Mon Sep 17 00:00:00 2001 From: Wouter Admiraal Date: Wed, 6 Mar 2019 11:54:33 +0100 Subject: [PATCH] SONAR-11749 Add Jump to content links for assistive technology users --- .../js/app/components/GlobalContainer.tsx | 30 ++++---- .../app/components/ProjectAdminContainer.tsx | 8 ++- .../js/app/components/a11y/A11yContext.tsx | 32 +++++++++ .../js/app/components/a11y/A11yProvider.tsx | 60 ++++++++++++++++ .../js/app/components/a11y/A11ySkipLinks.css | 40 +++++++++++ .../js/app/components/a11y/A11ySkipLinks.tsx | 36 ++++++++++ .../js/app/components/a11y/A11ySkipTarget.tsx | 67 ++++++++++++++++++ .../a11y/__tests__/A11yProvider-test.tsx | 68 +++++++++++++++++++ .../a11y/__tests__/A11ySkipLinks-test.tsx | 36 ++++++++++ .../a11y/__tests__/A11ySkipTarget-test.tsx | 42 ++++++++++++ .../__snapshots__/A11ySkipLinks-test.tsx.snap | 20 ++++++ .../A11ySkipTarget-test.tsx.snap | 32 +++++++++ .../components/extensions/exposeLibraries.ts | 2 + server/sonar-web/src/main/js/app/types.d.ts | 6 ++ .../js/apps/about/components/AboutApp.tsx | 3 + .../js/apps/account/components/Account.tsx | 2 + .../src/main/js/apps/code/components/App.tsx | 2 + .../js/apps/coding-rules/components/App.tsx | 7 ++ .../components/MeasureContent.tsx | 3 + .../components/MeasureOverview.tsx | 3 + .../component-measures/sidebar/Sidebar.tsx | 7 ++ .../__snapshots__/Sidebar-test.tsx.snap | 5 ++ .../organization/CreateOrganization.tsx | 3 + .../__tests__/CreateOrganization-test.tsx | 8 ++- .../CreateOrganization-test.tsx.snap | 18 +++++ .../apps/create/project/CreateProjectPage.tsx | 14 ++-- .../js/apps/documentation/components/App.tsx | 10 +++ .../main/js/apps/issues/components/App.tsx | 10 +++ .../OrganizationMembers.tsx | 2 + .../OrganizationMembers-test.tsx.snap | 6 ++ .../apps/overview/components/OverviewApp.tsx | 3 + .../components/ProjectActivityApp.tsx | 3 + .../ProjectActivityApp-test.tsx.snap | 3 + .../main/js/apps/projectQualityGate/App.tsx | 2 + .../js/apps/projectQualityProfiles/App.tsx | 3 + .../apps/projects/components/AllProjects.tsx | 9 +++ .../__snapshots__/AllProjects-test.tsx.snap | 12 ++++ .../apps/securityReports/components/App.tsx | 2 + .../__tests__/__snapshots__/App-test.tsx.snap | 15 ++++ .../js/apps/web-api/components/WebApiApp.tsx | 3 + .../src/main/js/helpers/testMocks.ts | 11 +++ .../resources/org/sonar/l10n/core.properties | 7 ++ 42 files changed, 636 insertions(+), 19 deletions(-) create mode 100644 server/sonar-web/src/main/js/app/components/a11y/A11yContext.tsx create mode 100644 server/sonar-web/src/main/js/app/components/a11y/A11yProvider.tsx create mode 100644 server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.css create mode 100644 server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.tsx create mode 100644 server/sonar-web/src/main/js/app/components/a11y/A11ySkipTarget.tsx create mode 100644 server/sonar-web/src/main/js/app/components/a11y/__tests__/A11yProvider-test.tsx create mode 100644 server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipLinks-test.tsx create mode 100644 server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipTarget-test.tsx create mode 100644 server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipLinks-test.tsx.snap create mode 100644 server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipTarget-test.tsx.snap diff --git a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx index cd7623acb3d..3e5e40382ee 100644 --- a/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/GlobalContainer.tsx @@ -23,6 +23,8 @@ import StartupModal from './StartupModal'; import GlobalFooterContainer from './GlobalFooterContainer'; import GlobalMessagesContainer from './GlobalMessagesContainer'; import SuggestionsProvider from './embed-docs-modal/SuggestionsProvider'; +import A11yProvider from './a11y/A11yProvider'; +import A11ySkipLinks from './a11y/A11ySkipLinks'; import Workspace from '../../components/workspace/Workspace'; interface Props { @@ -36,20 +38,24 @@ export default function GlobalContainer(props: Props) { const { footer = } = props; return ( - -
-
-
- - - - {props.children} - + + + + +
+
+
+ + + + {props.children} + +
+ {footer}
- {footer} -
- + + ); } diff --git a/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx b/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx index f9ac7bbe2cf..c8bdcf5659c 100644 --- a/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx +++ b/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; +import A11ySkipTarget from './a11y/A11ySkipTarget'; import handleRequiredAuthorization from '../utils/handleRequiredAuthorization'; interface Props { @@ -57,6 +58,11 @@ export default class ProjectAdminContainer extends React.PureComponent { } const { children, ...props } = this.props; - return React.cloneElement(children, props); + return ( + <> + + {React.cloneElement(children, props)} + + ); } } diff --git a/server/sonar-web/src/main/js/app/components/a11y/A11yContext.tsx b/server/sonar-web/src/main/js/app/components/a11y/A11yContext.tsx new file mode 100644 index 00000000000..42e1245a8ce --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/a11y/A11yContext.tsx @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import { createContext } from 'react'; + +export interface A11yContextShape { + addA11ySkipLink: (link: T.A11ySkipLink) => void; + removeA11ySkipLink: (link: T.A11ySkipLink) => void; + links: T.A11ySkipLink[]; +} + +export const A11yContext = createContext({ + addA11ySkipLink: () => {}, + removeA11ySkipLink: () => {}, + links: [] +}); diff --git a/server/sonar-web/src/main/js/app/components/a11y/A11yProvider.tsx b/server/sonar-web/src/main/js/app/components/a11y/A11yProvider.tsx new file mode 100644 index 00000000000..3f9dc7f13a8 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/a11y/A11yProvider.tsx @@ -0,0 +1,60 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { sortBy } from 'lodash'; +import { A11yContext } from './A11yContext'; + +interface State { + links: T.A11ySkipLink[]; +} + +export default class A11yProvider extends React.Component<{}, State> { + keys: string[] = []; + state: State = { links: [] }; + + addA11ySkipLink = (link: T.A11ySkipLink) => { + this.setState(prevState => { + const links = [...prevState.links]; + links.push({ ...link, weight: link.weight || 0 }); + return { links }; + }); + }; + + removeA11ySkipLink = (link: T.A11ySkipLink) => { + this.setState(prevState => { + const links = prevState.links.filter(l => l.key !== link.key); + return { links }; + }); + }; + + render() { + const links = sortBy(this.state.links, 'weight'); + return ( + + {this.props.children} + + ); + } +} diff --git a/server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.css b/server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.css new file mode 100644 index 00000000000..597a7a572a1 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.css @@ -0,0 +1,40 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +.a11y-skip-link { + width: 0; + height: 0; + padding: var(--gridSize); + position: absolute; + left: -9999px; + top: -9999px; + border: 0; + font-size: 1rem; + text-align: center; + z-index: 999; +} + +.a11y-skip-link:focus { + width: auto; + height: auto; + left: 6px; + top: 6px; + color: white; + text-decoration: underline; +} diff --git a/server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.tsx b/server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.tsx new file mode 100644 index 00000000000..97fda3ff3e7 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/a11y/A11ySkipLinks.tsx @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { A11yContext } from './A11yContext'; +import './A11ySkipLinks.css'; + +export default function A11ySkipLinks() { + return ( + + {({ links }) => + links.map(link => ( + + {link.label} + + )) + } + + ); +} diff --git a/server/sonar-web/src/main/js/app/components/a11y/A11ySkipTarget.tsx b/server/sonar-web/src/main/js/app/components/a11y/A11ySkipTarget.tsx new file mode 100644 index 00000000000..d660403288a --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/a11y/A11ySkipTarget.tsx @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { A11yContext } from './A11yContext'; +import { translate } from '../../../helpers/l10n'; + +interface Props { + anchor: string; + label?: string; + weight?: number; +} + +export default function A11ySkipTarget(props: Props) { + return ( + + {({ addA11ySkipLink, removeA11ySkipLink }) => ( + + )} + + ); +} + +interface InnerProps { + addA11ySkipLink: (link: T.A11ySkipLink) => void; + removeA11ySkipLink: (link: T.A11ySkipLink) => void; +} + +export class A11ySkipTargetInner extends React.PureComponent { + componentDidMount() { + this.props.addA11ySkipLink(this.getLink()); + } + + componentWillUnmount() { + this.props.removeA11ySkipLink(this.getLink()); + } + + getLink = (): T.A11ySkipLink => { + const { anchor: key, label = translate('skip_to_content'), weight } = this.props; + return { key, label, weight }; + }; + + render() { + const { anchor } = this.props; + return ; + } +} diff --git a/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11yProvider-test.tsx b/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11yProvider-test.tsx new file mode 100644 index 00000000000..98f64d3db6f --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11yProvider-test.tsx @@ -0,0 +1,68 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import { A11yContextShape } from '../A11yContext'; +import A11yProvider from '../A11yProvider'; +import { waitAndUpdate } from '../../../../helpers/testUtils'; + +const link1 = { key: 'link1', label: 'Link 1' }; +const link2 = { key: 'link2', label: 'Link 2', weight: -10 }; +const link3 = { key: 'link3', label: 'Link 3' }; + +it('should allow to register new skip links', () => { + const wrapper = shallowRender(); + const instance = wrapper.instance(); + expect(wrapper.state('links')).toEqual([]); + + instance.addA11ySkipLink(link1); + expect(wrapper.state('links')).toEqual([link1]); + + instance.addA11ySkipLink(link2); + expect(wrapper.state('links')).toEqual([link1, link2]); +}); + +it('should pass the ordered links to the consumers', () => { + const wrapper = shallowRender(); + const instance = wrapper.instance(); + instance.setState({ links: [link1, link2, link3] }); + waitAndUpdate(wrapper); + expect((wrapper.prop('value') as A11yContextShape).links).toEqual([link2, link1, link3]); +}); + +it('should allow to unregister skip links', () => { + const wrapper = shallowRender(); + const instance = wrapper.instance(); + instance.setState({ links: [link1, link2, link3] }); + + instance.removeA11ySkipLink(link1); + expect(wrapper.state('links')).toEqual([link2, link3]); + + instance.removeA11ySkipLink(link2); + expect(wrapper.state('links')).toEqual([link3]); +}); + +function shallowRender() { + return shallow( + +
+ + ); +} diff --git a/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipLinks-test.tsx b/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipLinks-test.tsx new file mode 100644 index 00000000000..77d8c37f35e --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipLinks-test.tsx @@ -0,0 +1,36 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { shallow } from 'enzyme'; +import A11ySkipLinks from '../A11ySkipLinks'; + +jest.mock('../A11yContext', () => ({ + A11yContext: { + Consumer: ({ children }: any) => { + return children({ + links: [{ key: 'link1', label: 'Label 1' }, { key: 'link2', label: 'Label 2' }] + }); + } + } +})); + +it('should render correctly', () => { + expect(shallow().dive()).toMatchSnapshot(); +}); diff --git a/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipTarget-test.tsx b/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipTarget-test.tsx new file mode 100644 index 00000000000..49a261ed482 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/a11y/__tests__/A11ySkipTarget-test.tsx @@ -0,0 +1,42 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +import * as React from 'react'; +import { mount } from 'enzyme'; +import { A11ySkipTargetInner } from '../A11ySkipTarget'; + +it('should render correctly, and (un)register the link when (un)mounted', () => { + const link = { key: 'main', label: 'Skip to content' }; + const addA11ySkipLink = jest.fn(); + const removeA11ySkipLink = jest.fn(); + + const wrapper = mount( + + ); + + expect(wrapper).toMatchSnapshot(); + expect(addA11ySkipLink).toBeCalledWith(link); + wrapper.unmount(); + expect(removeA11ySkipLink).toBeCalledWith(link); +}); diff --git a/server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipLinks-test.tsx.snap b/server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipLinks-test.tsx.snap new file mode 100644 index 00000000000..a8a148a13c6 --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipLinks-test.tsx.snap @@ -0,0 +1,20 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly 1`] = ` +Array [ + + Label 1 + , + + Label 2 + , +] +`; diff --git a/server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipTarget-test.tsx.snap b/server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipTarget-test.tsx.snap new file mode 100644 index 00000000000..f0130990dac --- /dev/null +++ b/server/sonar-web/src/main/js/app/components/a11y/__tests__/__snapshots__/A11ySkipTarget-test.tsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`should render correctly, and (un)register the link when (un)mounted 1`] = ` + + + +`; diff --git a/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts b/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts index f83e46b7d5a..fadd45ff98e 100644 --- a/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts +++ b/server/sonar-web/src/main/js/app/components/extensions/exposeLibraries.ts @@ -63,6 +63,7 @@ import ConfirmButton from '../../../components/controls/ConfirmButton'; import SimpleModal from '../../../components/controls/SimpleModal'; import SearchSelect from '../../../components/controls/SearchSelect'; import RadioToggle from '../../../components/controls/RadioToggle'; +import A11ySkipTarget from '../a11y/A11ySkipTarget'; import { Alert } from '../../../components/ui/Alert'; const exposeLibraries = () => { @@ -73,6 +74,7 @@ const exposeLibraries = () => { global.SonarMeasures = measures; global.SonarRequest = { ...request, throwGlobalError, addGlobalSuccessMessage }; global.SonarComponents = { + A11ySkipTarget, ActionsDropdown, ActionsDropdownItem, Alert, diff --git a/server/sonar-web/src/main/js/app/types.d.ts b/server/sonar-web/src/main/js/app/types.d.ts index 2d6442dd7b4..24d81014308 100644 --- a/server/sonar-web/src/main/js/app/types.d.ts +++ b/server/sonar-web/src/main/js/app/types.d.ts @@ -22,6 +22,12 @@ declare namespace T { // Type ordered alphabetically to prevent merge conflicts + export interface A11ySkipLink { + key: string; + label: string; + weight?: number; + } + export interface AlmApplication extends IdentityProvider { installationUrl: string; } diff --git a/server/sonar-web/src/main/js/apps/about/components/AboutApp.tsx b/server/sonar-web/src/main/js/apps/about/components/AboutApp.tsx index 1fb0652ab44..061b7c1b3ad 100644 --- a/server/sonar-web/src/main/js/apps/about/components/AboutApp.tsx +++ b/server/sonar-web/src/main/js/apps/about/components/AboutApp.tsx @@ -31,6 +31,7 @@ import AboutLeakPeriod from './AboutLeakPeriod'; import AboutStandards from './AboutStandards'; import AboutScanners from './AboutScanners'; import EntryIssueTypes from './EntryIssueTypes'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import GlobalContainer from '../../../app/components/GlobalContainer'; import { searchProjects } from '../../../api/components'; import { getFacet } from '../../../api/issues'; @@ -123,6 +124,8 @@ class AboutApp extends React.PureComponent { return (
+ +

{translate('layout.sonar.slogan')}

diff --git a/server/sonar-web/src/main/js/apps/account/components/Account.tsx b/server/sonar-web/src/main/js/apps/account/components/Account.tsx index 20d1a516acf..0cb548b71ed 100644 --- a/server/sonar-web/src/main/js/apps/account/components/Account.tsx +++ b/server/sonar-web/src/main/js/apps/account/components/Account.tsx @@ -25,6 +25,7 @@ import UserCard from './UserCard'; import { getCurrentUser, areThereCustomOrganizations, Store } from '../../../store/rootReducer'; import { translate } from '../../../helpers/l10n'; import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import '../account.css'; @@ -52,6 +53,7 @@ class Account extends React.PureComponent {
+
diff --git a/server/sonar-web/src/main/js/apps/code/components/App.tsx b/server/sonar-web/src/main/js/apps/code/components/App.tsx index 7ecfe534e19..10b3fc08349 100644 --- a/server/sonar-web/src/main/js/apps/code/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/code/components/App.tsx @@ -29,6 +29,7 @@ import Search from './Search'; import SourceViewerWrapper from './SourceViewerWrapper'; import { addComponent, addComponentBreadcrumbs, clearBucket } from '../bucket'; import { retrieveComponentChildren, retrieveComponent, loadMoreChildren } from '../utils'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import ListFooter from '../../../components/controls/ListFooter'; import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import { fetchMetrics } from '../../../store/rootActions'; @@ -250,6 +251,7 @@ export class App extends React.PureComponent {
+ {
+ {
+ {this.state.openRule ? ( {translate('coding_rules.return_to_list')} diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx index 55d400e4ec3..80920d1895e 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureContent.tsx @@ -25,6 +25,7 @@ import MeasureHeader from './MeasureHeader'; import MeasureViewSelect from './MeasureViewSelect'; import PageActions from '../../../components/ui/PageActions'; import { complementary } from '../config/complementary'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import SourceViewer from '../../../components/SourceViewer/SourceViewer'; import FilesView from '../drilldown/FilesView'; import TreeMapView from '../drilldown/TreeMapView'; @@ -317,6 +318,8 @@ export default class MeasureContent extends React.PureComponent { return (
(this.container = container)}> + +
diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx index e20de149159..3cd2bbd362c 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx +++ b/server/sonar-web/src/main/js/apps/component-measures/components/MeasureOverview.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import Breadcrumbs from './Breadcrumbs'; import LeakPeriodLegend from './LeakPeriodLegend'; import MeasureContentHeader from './MeasureContentHeader'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import PageActions from '../../../components/ui/PageActions'; import BubbleChart from '../drilldown/BubbleChart'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; @@ -139,6 +140,8 @@ export default class MeasureOverview extends React.PureComponent { return (
+ +
{ const { showFullMeasures } = this.props; return (
+ +
+ +

{header} diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx b/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx index cdc8020444b..5ab45e611d6 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx +++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/CreateOrganization-test.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { times } from 'lodash'; import { Location } from 'history'; import { shallow, mount } from 'enzyme'; +import { Provider } from 'react-redux'; import { CreateOrganization } from '../CreateOrganization'; import { bindAlmOrganization, @@ -37,7 +38,8 @@ import { mockOrganizationWithAlm, mockAlmOrganization, mockCurrentUser, - mockLocation + mockLocation, + mockStore } from '../../../../helpers/testMocks'; import { waitAndUpdate } from '../../../../helpers/testUtils'; @@ -326,7 +328,9 @@ it('should bind org and redirect to org home when coming from org binding', asyn }); function mountRender(props: Partial = {}) { - return mount(createComponent(props)); + return mount( + {createComponent(props)} + ); } function shallowRender(props: Partial = {}) { diff --git a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/CreateOrganization-test.tsx.snap b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/CreateOrganization-test.tsx.snap index 488d7bb62f3..7223dbfa007 100644 --- a/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/CreateOrganization-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/create/organization/__tests__/__snapshots__/CreateOrganization-test.tsx.snap @@ -17,6 +17,9 @@ exports[`should render with auto personal organization bind page 2`] = `
+
@@ -95,6 +98,9 @@ exports[`should render with auto tab displayed 1`] = `
+
@@ -182,6 +188,9 @@ exports[`should render with auto tab selected and manual disabled 2`] = `
+
@@ -306,6 +315,9 @@ exports[`should render with manual tab displayed 1`] = `
+
@@ -363,6 +375,9 @@ exports[`should render with organization bind page 2`] = `
+
@@ -488,6 +503,9 @@ exports[`should switch tabs 1`] = `
+
diff --git a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx index 7a5c60883ba..16cb1496fb3 100644 --- a/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx +++ b/server/sonar-web/src/main/js/apps/create/project/CreateProjectPage.tsx @@ -21,12 +21,18 @@ import * as React from 'react'; import { WithRouterProps } from 'react-router'; import CreateProjectPageSonarCloud from './CreateProjectPageSonarCloud'; import CreateProjectPageSonarQube from './CreateProjectPageSonarQube'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import { isSonarCloud } from '../../../helpers/system'; export default function CreateProjectPage(props: WithRouterProps) { - return isSonarCloud() ? ( - - ) : ( - + return ( + <> + + {isSonarCloud() ? ( + + ) : ( + + )} + ); } diff --git a/server/sonar-web/src/main/js/apps/documentation/components/App.tsx b/server/sonar-web/src/main/js/apps/documentation/components/App.tsx index 790e04b8ee6..27e3cc941e8 100644 --- a/server/sonar-web/src/main/js/apps/documentation/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/documentation/components/App.tsx @@ -24,6 +24,7 @@ import * as navigationTreeSonarQube from 'Docs/../static/SonarQubeNavigationTree import * as navigationTreeSonarCloud from 'Docs/../static/SonarCloudNavigationTree.json'; import Sidebar from './Sidebar'; import getPages from '../pages'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import NotFound from '../../../app/components/NotFound'; import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; import DocMarkdownBlock from '../../../components/docs/DocMarkdownBlock'; @@ -63,6 +64,7 @@ export default class App extends React.PureComponent { + ); @@ -82,6 +84,12 @@ export default class App extends React.PureComponent {
+ +

{translate('documentation.page')}

@@ -96,6 +104,8 @@ export default class App extends React.PureComponent {
+ + { {({ top }) => (
+ {openIssue ? this.renderConciseIssuesList() : this.renderFacets()}
@@ -1094,6 +1102,8 @@ export class App extends React.PureComponent {
+ + {this.renderBulkChange(openIssue)} {openIssue ? (
diff --git a/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx index bd141ec20e3..ae0fae4bcbf 100644 --- a/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx +++ b/server/sonar-web/src/main/js/apps/organizationMembers/OrganizationMembers.tsx @@ -22,6 +22,7 @@ import Helmet from 'react-helmet'; import MembersPageHeader from './MembersPageHeader'; import MembersListHeader from './MembersListHeader'; import MembersList from './MembersList'; +import A11ySkipTarget from '../../app/components/a11y/A11ySkipTarget'; import Suggestions from '../../app/components/embed-docs-modal/Suggestions'; import ListFooter from '../../components/controls/ListFooter'; import { translate } from '../../helpers/l10n'; @@ -200,6 +201,7 @@ export default class OrganizationMembers extends React.PureComponent + + + { return (
+ + {this.renderMain()}
diff --git a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx index 4a76f2491b2..96552de1ff4 100644 --- a/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx +++ b/server/sonar-web/src/main/js/apps/projectActivity/components/ProjectActivityApp.tsx @@ -25,6 +25,7 @@ import ProjectActivityGraphs from './ProjectActivityGraphs'; import { MeasureHistory, Query, ParsedAnalysis } from '../utils'; import { parseDate } from '../../../helpers/dates'; import { translate } from '../../../helpers/l10n'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import './projectActivity.css'; @@ -57,6 +58,8 @@ export default function ProjectActivityApp(props: Props) { + + + {
+
{loading ? ( diff --git a/server/sonar-web/src/main/js/apps/projectQualityProfiles/App.tsx b/server/sonar-web/src/main/js/apps/projectQualityProfiles/App.tsx index 303176ced8c..6f168892b31 100644 --- a/server/sonar-web/src/main/js/apps/projectQualityProfiles/App.tsx +++ b/server/sonar-web/src/main/js/apps/projectQualityProfiles/App.tsx @@ -27,6 +27,7 @@ import { searchQualityProfiles, Profile } from '../../api/quality-profiles'; +import A11ySkipTarget from '../../app/components/a11y/A11ySkipTarget'; import Suggestions from '../../app/components/embed-docs-modal/Suggestions'; import addGlobalSuccessMessage from '../../app/utils/addGlobalSuccessMessage'; import handleRequiredAuthorization from '../../app/utils/handleRequiredAuthorization'; @@ -121,6 +122,8 @@ export default class QualityProfiles extends React.PureComponent { + +
{loading ? ( diff --git a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx index 55fa2f8f48e..8c52c9be55c 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx +++ b/server/sonar-web/src/main/js/apps/projects/components/AllProjects.tsx @@ -24,6 +24,7 @@ import PageHeader from './PageHeader'; import ProjectsList from './ProjectsList'; import PageSidebar from './PageSidebar'; import handleRequiredAuthentication from '../../../app/utils/handleRequiredAuthentication'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; import ListFooter from '../../../components/controls/ListFooter'; import OrganizationEmpty from '../../organizations/components/OrganizationEmpty'; @@ -248,6 +249,12 @@ export class AllProjects extends React.PureComponent {
+ + { {!organizationEmpty && this.renderSide()}
+ + {organizationEmpty && organization ? ( {openProjectOnboarding => ( diff --git a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap index 1c09c537190..09bb32b5c55 100644 --- a/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/projects/components/__tests__/__snapshots__/AllProjects-test.tsx.snap @@ -21,6 +21,9 @@ exports[`renders 1`] = `
+
@@ -163,6 +166,9 @@ exports[`renders 2`] = `
+
@@ -271,6 +277,9 @@ exports[`renders correctly empty organization 2`] = `
+ @@ -299,6 +308,9 @@ exports[`renders correctly empty organization 3`] = `
+
diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx b/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx index 07611c76775..4b7d090e331 100755 --- a/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx +++ b/server/sonar-web/src/main/js/apps/securityReports/components/App.tsx @@ -23,6 +23,7 @@ import { Link } from 'react-router'; import VulnerabilityList from './VulnerabilityList'; import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import { translate } from '../../../helpers/l10n'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import DeferredSpinner from '../../../components/common/DeferredSpinner'; import Checkbox from '../../../components/controls/Checkbox'; import NotFound from '../../../app/components/NotFound'; @@ -148,6 +149,7 @@ export class App extends React.PureComponent {
+

{translate('security_reports', type, 'page')}

{translate('security_reports', type, 'description')} diff --git a/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap index 4d50db28396..91094ac224e 100644 --- a/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/securityReports/components/__tests__/__snapshots__/App-test.tsx.snap @@ -16,6 +16,9 @@ exports[`handle checkbox for cwe display 1`] = `
+

@@ -99,6 +102,9 @@ exports[`handle checkbox for cwe display 2`] = `
+

@@ -246,6 +252,9 @@ exports[`renders owaspTop10 1`] = `
+

@@ -387,6 +396,9 @@ exports[`renders sansTop25 1`] = `
+

@@ -470,6 +482,9 @@ exports[`renders with cwe 1`] = `
+

diff --git a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx index 0b23fd94694..31112b25bfd 100644 --- a/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx +++ b/server/sonar-web/src/main/js/apps/web-api/components/WebApiApp.tsx @@ -24,6 +24,7 @@ import { maxBy } from 'lodash'; import Domain from './Domain'; import Menu from './Menu'; import Search from './Search'; +import A11ySkipTarget from '../../../app/components/a11y/A11ySkipTarget'; import ScreenPositionHelper from '../../../components/common/ScreenPositionHelper'; import Suggestions from '../../../app/components/embed-docs-modal/Suggestions'; import { fetchWebApi } from '../../../api/web-api'; @@ -163,6 +164,8 @@ class WebApiApp extends React.PureComponent {
+ +

{translate('api_documentation.page')}

diff --git a/server/sonar-web/src/main/js/helpers/testMocks.ts b/server/sonar-web/src/main/js/helpers/testMocks.ts index 937d1f062d0..b9399a8e991 100644 --- a/server/sonar-web/src/main/js/helpers/testMocks.ts +++ b/server/sonar-web/src/main/js/helpers/testMocks.ts @@ -18,6 +18,7 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import { InjectedRouter } from 'react-router'; +import { Store } from 'redux'; import { Location } from 'history'; import { ParsedAnalysis } from '../apps/projectActivity/utils'; import { Profile } from '../apps/quality-profiles/types'; @@ -360,3 +361,13 @@ export function mockLongLivingBranch( ...overrides }; } + +export function mockStore(overrides: Partial = {}): Store { + return { + dispatch: jest.fn(), + getState: jest.fn(), + subscribe: jest.fn(), + replaceReducer: jest.fn(), + ...overrides + }; +} 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 dbd7952dca2..43e6d90ecf9 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -173,6 +173,7 @@ x_selected={0} selected x_of_y_shown={0} of {1} shown size=Size skip=Skip +skip_to_content=Skip to main content status=Status support=Support table=Table @@ -546,6 +547,8 @@ permissions.page=Permissions quality_profiles.page=Quality Profiles quality_gates.page=Quality Gates issues.page=Issues +issues.skip_to_filters=Skip to issue filters +issues.skip_to_list=Skip to issues list view_projects.page=Projects portfolios.page=Portfolios project_activity.page=Activity @@ -777,6 +780,7 @@ projects.configure_analysis=Configure analysis projects.last_analysis_on_x=Last analysis: {0} projects.search=Search by project name or key projects.perspective=Perspective +projects.skip_to_filters=Skip to project filters projects.sort_by=Sort by projects.sort_ascending=Result sorted in ascending order projects.sort_descending=Result sorted in descending order @@ -1330,6 +1334,7 @@ coding_rules.rule_template=Rule Template coding_rules.rule_template.title=This rule can be used as a template to create custom rules, it cannot be activated on a profile coding_rules._rules=rules coding_rules.show_template=Show Template +coding_rules.skip_to_filters=Skip to rules filters coding_rules.to_select_rules=to select rules coding_rules.type.tooltip.CODE_SMELL=Code Smell Detection Rule coding_rules.type.tooltip.BUG=Bug Detection Rule @@ -2553,6 +2558,7 @@ api_documentation.search=Search by name... documentation.page=Documentation documentation.page_title=SonarCloud Docs documentation.on_this_page=On this page +documentation.skip_to_nav=Skip to documentation navigation #------------------------------------------------------------------------------ @@ -2589,6 +2595,7 @@ component_measures.to_select_files=to select files component_measures.to_navigate=to navigate component_measures.to_navigate_files=to next/previous file component_measures.hidden_best_score_metrics=There are {0} hidden components with a score of {1}. +component_measures.skip_to_filters=Skip to measure filters component_measures.overview.project_overview.facet=Project Overview component_measures.overview.project_overview.title=Risk -- 2.39.5