diff options
author | Wouter Admiraal <wouter.admiraal@sonarsource.com> | 2022-08-10 14:29:08 +0200 |
---|---|---|
committer | sonartech <sonartech@sonarsource.com> | 2022-08-11 20:03:49 +0000 |
commit | 53e5a51f1cecc1e17a00bfa2981eba583f2d3f4a (patch) | |
tree | 059640fc332c1728ac3bca8317ebcd13a7722c01 /server/sonar-web | |
parent | f33601b3524d0b6285bcd7510f937195b0b7db08 (diff) | |
download | sonarqube-53e5a51f1cecc1e17a00bfa2981eba583f2d3f4a.tar.gz sonarqube-53e5a51f1cecc1e17a00bfa2981eba583f2d3f4a.zip |
SONAR-16782 [893362] Function cannot be performed by keyboard alone
Diffstat (limited to 'server/sonar-web')
14 files changed, 67 insertions, 119 deletions
diff --git a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx index df8ed428928..0c958bb6500 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/__tests__/SourceViewer-it.tsx @@ -54,7 +54,7 @@ it('should show a permalink on line number', async () => { }) ); await user.click( - rowScreen.getByRole('link', { + rowScreen.getByRole('button', { name: 'component_viewer.copy_permalink' }) ); @@ -89,7 +89,7 @@ it('should show a permalink on line number', async () => { ); expect( - lowerRowScreen.getByRole('link', { + lowerRowScreen.getByRole('button', { name: 'component_viewer.copy_permalink' }) ).toBeInTheDocument(); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.css b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.css index 24016edd24d..985473db381 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/Line.css +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/Line.css @@ -186,7 +186,9 @@ display: block; } -.source-line-scm [role='button'] { +.source-line-scm button { + display: block; + width: 100%; height: 18px; } diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx index 49dc0765919..038acbf733b 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineDuplicationBlock.tsx @@ -25,6 +25,7 @@ import Tooltip from '../../../components/controls/Tooltip'; import { PopupPlacement } from '../../../components/ui/popups'; import { translate } from '../../../helpers/l10n'; import { SourceLine } from '../../../types/types'; +import { ButtonPlain } from '../../controls/buttons'; export interface LineDuplicationBlockProps { blocksLoaded: boolean; @@ -43,11 +44,11 @@ export function LineDuplicationBlock(props: LineDuplicationBlockProps) { 'source-line-duplicated': duplicated }); + const tooltip = dropdownOpen ? undefined : translate('source_viewer.tooltip.duplicated_block'); + return duplicated ? ( <td className={className} data-index={index} data-line-number={line.line}> - <Tooltip - overlay={dropdownOpen ? undefined : translate('source_viewer.tooltip.duplicated_block')} - placement="right"> + <Tooltip overlay={tooltip} placement="right"> <div> <Toggler onRequestClose={() => setDropdownOpen(false)} @@ -57,7 +58,7 @@ export function LineDuplicationBlock(props: LineDuplicationBlockProps) { {props.renderDuplicationPopup(index, line.line)} </DropdownOverlay> }> - <div + <ButtonPlain aria-label={translate('source_viewer.tooltip.duplicated_block')} className="source-line-bar" onClick={() => { @@ -66,8 +67,6 @@ export function LineDuplicationBlock(props: LineDuplicationBlockProps) { props.onClick(line); } }} - role="button" - tabIndex={0} /> </Toggler> </div> diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx index 666f5c527d8..d1ddbdb2247 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineIssuesIndicator.tsx @@ -25,6 +25,7 @@ import IssueIcon from '../../../components/icons/IssueIcon'; import { sortByType } from '../../../helpers/issues'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { Issue, SourceLine } from '../../../types/types'; +import { ButtonPlain } from '../../controls/buttons'; export interface LineIssuesIndicatorProps { issues: Issue[]; @@ -65,20 +66,14 @@ export function LineIssuesIndicator(props: LineIssuesIndicatorProps) { return ( <td className={className} data-line-number={line.line}> - <span + <ButtonPlain aria-label={translate('source_viewer.issues_on_line', issuesOpen ? 'hide' : 'show')} - onClick={(e: React.MouseEvent<HTMLElement>) => { - e.preventDefault(); - e.currentTarget.blur(); - props.onClick(); - }} - role="button" - tabIndex={0}> + onClick={props.onClick}> <Tooltip overlay={tooltipContent}> <IssueIcon type={mostImportantIssue.type} /> </Tooltip> {issues.length > 1 && <span className="source-line-issues-counter">{issues.length}</span>} - </span> + </ButtonPlain> </td> ); } diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx index 348a8cae165..55ba93354d3 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineNumber.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import Toggler from '../../../components/controls/Toggler'; import { translateWithParameters } from '../../../helpers/l10n'; import { SourceLine } from '../../../types/types'; +import { ButtonPlain } from '../../controls/buttons'; import LineOptionsPopup from './LineOptionsPopup'; export interface LineNumberProps { @@ -42,15 +43,13 @@ export function LineNumber({ displayOptions, firstLineNumber, line }: LineNumber onRequestClose={() => setOpen(false)} open={isOpen} overlay={<LineOptionsPopup firstLineNumber={firstLineNumber} line={line} />}> - <span + <ButtonPlain aria-expanded={isOpen} aria-haspopup={true} aria-label={translateWithParameters('source_viewer.line_X', lineNumber)} - onClick={() => setOpen(true)} - role="button" - tabIndex={0}> + onClick={() => setOpen(true)}> {lineNumber} - </span> + </ButtonPlain> </Toggler> ) : ( lineNumber diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx index 975e2a7e41e..ef523ceb15f 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineOptionsPopup.tsx @@ -23,7 +23,7 @@ import { PopupPlacement } from '../../../components/ui/popups'; import { translate } from '../../../helpers/l10n'; import { getCodeUrl, getPathUrlAsString } from '../../../helpers/urls'; import { SourceLine } from '../../../types/types'; -import { ActionsDropdownItem } from '../../controls/ActionsDropdown'; +import { ClipboardButton } from '../../controls/clipboard'; import { SourceViewerContext } from '../SourceViewerContext'; export interface LineOptionsPopupProps { @@ -43,11 +43,14 @@ export function LineOptionsPopup({ firstLineNumber, line }: LineOptionsPopupProp className="big-spacer-left" noPadding={true} placement={isAtTop ? PopupPlacement.BottomLeft : PopupPlacement.TopLeft}> - <ul className="padded source-viewer-bubble-popup nowrap"> - <ActionsDropdownItem copyValue={codeUrl}> + <div className="padded source-viewer-bubble-popup nowrap"> + <ClipboardButton + className="button-link" + copyValue={codeUrl} + aria-label={translate('component_viewer.copy_permalink')}> {translate('component_viewer.copy_permalink')} - </ActionsDropdownItem> - </ul> + </ClipboardButton> + </div> </DropdownOverlay> ); }} diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/LineSCM.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/LineSCM.tsx index 713f02a6c06..c06c6e41c5e 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/LineSCM.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/LineSCM.tsx @@ -22,6 +22,7 @@ import Dropdown from '../../../components/controls/Dropdown'; import { PopupPlacement } from '../../../components/ui/popups'; import { translate, translateWithParameters } from '../../../helpers/l10n'; import { SourceLine } from '../../../types/types'; +import { ButtonPlain } from '../../controls/buttons'; import SCMPopup from './SCMPopup'; export interface LineSCMProps { @@ -49,9 +50,7 @@ export function LineSCM({ line, previousLine }: LineSCMProps) { return ( <td className="source-meta source-line-scm" data-line-number={line.line}> <Dropdown overlay={<SCMPopup line={line} />} overlayPlacement={PopupPlacement.RightTop}> - <div aria-label={ariaLabel} role="button"> - {cell} - </div> + <ButtonPlain aria-label={ariaLabel}>{cell}</ButtonPlain> </Dropdown> </td> ); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx index 72ad28cd99b..21d20fbb05b 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/LineIssuesIndicator-test.tsx @@ -21,6 +21,7 @@ import { shallow } from 'enzyme'; import * as React from 'react'; import { mockIssue } from '../../../../helpers/testMocks'; import { click } from '../../../../helpers/testUtils'; +import { ButtonPlain } from '../../../controls/buttons'; import { LineIssuesIndicator, LineIssuesIndicatorProps } from '../LineIssuesIndicator'; it('should render correctly', () => { @@ -43,7 +44,7 @@ it('should correctly handle click', () => { const onClick = jest.fn(); const wrapper = shallowRender({ onClick }); - click(wrapper.find('span[role="button"]')); + click(wrapper.find(ButtonPlain)); expect(onClick).toHaveBeenCalled(); }); diff --git a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesIndicator-test.tsx.snap b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesIndicator-test.tsx.snap index 5f22c660993..dc67d02a1f9 100644 --- a/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesIndicator-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/SourceViewer/components/__tests__/__snapshots__/LineIssuesIndicator-test.tsx.snap @@ -5,11 +5,9 @@ exports[`should render correctly: default 1`] = ` className="source-meta source-line-issues source-line-with-issues" data-line-number={3} > - <span + <ButtonPlain aria-label="source_viewer.issues_on_line.show" - onClick={[Function]} - role="button" - tabIndex={0} + onClick={[MockFunction]} > <Tooltip overlay="source_viewer.issues_on_line.multiple_issues" @@ -23,7 +21,7 @@ exports[`should render correctly: default 1`] = ` > 2 </span> - </span> + </ButtonPlain> </td> `; @@ -32,11 +30,9 @@ exports[`should render correctly: multiple issues, same type 1`] = ` className="source-meta source-line-issues source-line-with-issues" data-line-number={3} > - <span + <ButtonPlain aria-label="source_viewer.issues_on_line.show" - onClick={[Function]} - role="button" - tabIndex={0} + onClick={[MockFunction]} > <Tooltip overlay="source_viewer.issues_on_line.X_issues_of_type_Y.2.issue.type.VULNERABILITY.plural" @@ -50,7 +46,7 @@ exports[`should render correctly: multiple issues, same type 1`] = ` > 2 </span> - </span> + </ButtonPlain> </td> `; @@ -66,11 +62,9 @@ exports[`should render correctly: single issue 1`] = ` className="source-meta source-line-issues source-line-with-issues" data-line-number={3} > - <span + <ButtonPlain aria-label="source_viewer.issues_on_line.show" - onClick={[Function]} - role="button" - tabIndex={0} + onClick={[MockFunction]} > <Tooltip overlay="source_viewer.issues_on_line.issue_of_type_X.issue.type.VULNERABILITY" @@ -79,6 +73,6 @@ exports[`should render correctly: single issue 1`] = ` type="VULNERABILITY" /> </Tooltip> - </span> + </ButtonPlain> </td> `; diff --git a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx b/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx index 1a87832a2f2..e0184dcb0f1 100644 --- a/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx +++ b/server/sonar-web/src/main/js/components/controls/ActionsDropdown.tsx @@ -20,14 +20,11 @@ import classNames from 'classnames'; import * as React from 'react'; import { Link, To } from 'react-router-dom'; -import { translate } from '../../helpers/l10n'; import DropdownIcon from '../icons/DropdownIcon'; import SettingsIcon from '../icons/SettingsIcon'; import { PopupPlacement } from '../ui/popups'; import { Button } from './buttons'; -import { ClipboardBase } from './clipboard'; import Dropdown from './Dropdown'; -import Tooltip from './Tooltip'; export interface ActionsDropdownProps { className?: string; @@ -60,8 +57,6 @@ export default function ActionsDropdown(props: ActionsDropdownProps) { interface ItemProps { className?: string; children: React.ReactNode; - /** used to pass a string to copy to clipboard */ - copyValue?: string; destructive?: boolean; /** used to pass a name of downloaded file */ download?: string; @@ -106,22 +101,6 @@ export class ActionsDropdownItem extends React.PureComponent<ItemProps> { ); } - if (this.props.copyValue) { - return ( - <ClipboardBase> - {({ setCopyButton, copySuccess }) => ( - <Tooltip overlay={translate('copied_action')} visible={copySuccess}> - <li data-clipboard-text={this.props.copyValue} ref={setCopyButton}> - <a className={className} href="#" id={this.props.id} onClick={this.handleClick}> - {this.props.children} - </a> - </li> - </Tooltip> - )} - </ClipboardBase> - ); - } - return ( <li> <a className={className} href="#" id={this.props.id} onClick={this.handleClick}> diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/ActionsDropdown-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/ActionsDropdown-test.tsx index 8eed909458c..611920edcfd 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/ActionsDropdown-test.tsx +++ b/server/sonar-web/src/main/js/components/controls/__tests__/ActionsDropdown-test.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 { mount, shallow } from 'enzyme'; +import { shallow } from 'enzyme'; import * as React from 'react'; import { click } from '../../../helpers/testUtils'; import { PopupPlacement } from '../../ui/popups'; @@ -62,19 +62,10 @@ describe('ActionsDropdownItem', () => { expect(onClick).toBeCalled(); }); - it('should render correctly copy item', () => { - const wrapper = mountRender({ copyValue: 'my content to copy to clipboard' }); - expect(wrapper).toMatchSnapshot(); - }); - function shallowRender(props: Partial<ActionsDropdownItem['props']> = {}) { return shallow(renderContent(props)); } - function mountRender(props: Partial<ActionsDropdownItem['props']> = {}) { - return mount(renderContent(props)); - } - function renderContent(props: Partial<ActionsDropdownItem['props']> = {}) { return ( <ActionsDropdownItem className="foo" {...props}> diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ActionsDropdown-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ActionsDropdown-test.tsx.snap index 37677005279..2ebe48a75c0 100644 --- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ActionsDropdown-test.tsx.snap +++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/ActionsDropdown-test.tsx.snap @@ -103,44 +103,3 @@ exports[`ActionsDropdownItem should render correctly 3`] = ` </a> </li> `; - -exports[`ActionsDropdownItem should render correctly copy item 1`] = ` -<ActionsDropdownItem - className="foo" - copyValue="my content to copy to clipboard" -> - <ClipboardBase> - <Tooltip - overlay="copied_action" - visible={false} - > - <TooltipInner - mouseEnterDelay={0.1} - overlay="copied_action" - visible={false} - > - <li - aria-describedby="tooltip-1" - aria-labelledby="tooltip-1" - data-clipboard-text="my content to copy to clipboard" - onBlur={[Function]} - onFocus={[Function]} - onPointerEnter={[Function]} - onPointerLeave={[Function]} - tabIndex={0} - > - <a - className="foo" - href="#" - onClick={[Function]} - > - <span> - Hello world - </span> - </a> - </li> - </TooltipInner> - </Tooltip> - </ClipboardBase> -</ActionsDropdownItem> -`; diff --git a/server/sonar-web/src/main/js/components/controls/buttons.css b/server/sonar-web/src/main/js/components/controls/buttons.css index 2a78bdffbac..4e4f215dab0 100644 --- a/server/sonar-web/src/main/js/components/controls/buttons.css +++ b/server/sonar-web/src/main/js/components/controls/buttons.css @@ -118,6 +118,29 @@ /* #endregion */ +/* #region .button-plain */ +.button-plain { + display: inline-flex; + height: auto; + line-height: inherit; + margin: 0; + padding: 0; + border: none; + border-radius: 0; + background: transparent; + color: inherit; + border-bottom: 0; + font-weight: inherit; + font-size: inherit; +} + +.button-plain:hover { + color: var(--blue); + background-color: transparent; +} + +/* #endregion */ + /* #region .button-link */ .button-link { display: inline-flex; diff --git a/server/sonar-web/src/main/js/components/controls/buttons.tsx b/server/sonar-web/src/main/js/components/controls/buttons.tsx index e4cf225e3f1..67548395ccb 100644 --- a/server/sonar-web/src/main/js/components/controls/buttons.tsx +++ b/server/sonar-web/src/main/js/components/controls/buttons.tsx @@ -92,6 +92,10 @@ export function ButtonLink({ className, ...props }: ButtonProps) { return <Button {...props} className={classNames('button-link', className)} />; } +export function ButtonPlain({ className, ...props }: ButtonProps) { + return <Button {...props} className={classNames('button-plain', className)} />; +} + export function SubmitButton(props: Omit<ButtonProps, 'type'>) { // do not prevent default to actually submit a form return <Button {...props} preventDefault={false} type="submit" />; |