aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWouter Admiraal <wouter.admiraal@sonarsource.com>2019-12-23 13:59:18 +0100
committerSonarTech <sonartech@sonarsource.com>2020-01-02 20:46:12 +0100
commitc8d4cd257944527198147e53aaa017b6aaa55822 (patch)
tree182d2f6c13a78a381c050dc03e83948221987a12
parentdbd57ddfa8ca3eab4814b66c49ca3552727cbc61 (diff)
downloadsonarqube-c8d4cd257944527198147e53aaa017b6aaa55822.tar.gz
sonarqube-c8d4cd257944527198147e53aaa017b6aaa55822.zip
SONAR-12880 Improve tag select accessibility
-rw-r--r--server/sonar-web/src/main/js/components/common/MultiSelect.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/common/MultiSelectOption.tsx70
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/MultiSelectOption-test.tsx41
-rw-r--r--server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelectOption-test.tsx.snap158
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties1
5 files changed, 142 insertions, 130 deletions
diff --git a/server/sonar-web/src/main/js/components/common/MultiSelect.tsx b/server/sonar-web/src/main/js/components/common/MultiSelect.tsx
index 9a167fd29fe..dd348bb4691 100644
--- a/server/sonar-web/src/main/js/components/common/MultiSelect.tsx
+++ b/server/sonar-web/src/main/js/components/common/MultiSelect.tsx
@@ -119,7 +119,7 @@ export default class MultiSelect extends React.PureComponent<Props, State> {
}
}
- handleSelectChange = (item: string, selected: boolean) => {
+ handleSelectChange = (selected: boolean, item: string) => {
if (selected) {
this.onSelectItem(item);
} else {
diff --git a/server/sonar-web/src/main/js/components/common/MultiSelectOption.tsx b/server/sonar-web/src/main/js/components/common/MultiSelectOption.tsx
index 309cdd503f4..9ae37b8f55c 100644
--- a/server/sonar-web/src/main/js/components/common/MultiSelectOption.tsx
+++ b/server/sonar-web/src/main/js/components/common/MultiSelectOption.tsx
@@ -19,51 +19,47 @@
*/
import * as classNames from 'classnames';
import * as React from 'react';
+import Checkbox from 'sonar-ui-common/components/controls/Checkbox';
+import { translate } from 'sonar-ui-common/helpers/l10n';
-interface Props {
+export interface MultiSelectOptionProps {
active?: boolean;
custom?: boolean;
disabled?: boolean;
element: string;
- onHover: (elem: string) => void;
- onSelectChange: (elem: string, selected: boolean) => void;
+ onHover: (element: string) => void;
+ onSelectChange: (selected: boolean, element: string) => void;
renderLabel: (element: string) => React.ReactNode;
selected?: boolean;
}
-export default class MultiSelectOption extends React.PureComponent<Props> {
- handleSelect = (evt: React.SyntheticEvent<HTMLAnchorElement>) => {
- evt.stopPropagation();
- evt.preventDefault();
- evt.currentTarget.blur();
+export default function MultiSelectOption(props: MultiSelectOptionProps) {
+ const { active, custom, disabled, element, selected } = props;
+ const onHover = () => props.onHover(element);
+ const className = classNames({ active, disabled });
+ const label = props.renderLabel(element);
- if (!this.props.disabled) {
- this.props.onSelectChange(this.props.element, !this.props.selected);
- }
- };
-
- handleHover = () => this.props.onHover(this.props.element);
-
- render() {
- const { selected, disabled } = this.props;
- const className = classNames('icon-checkbox', {
- 'icon-checkbox-checked': selected,
- 'icon-checkbox-invisible': disabled
- });
- const activeClass = classNames({ active: this.props.active, disabled });
-
- return (
- <li>
- <a
- className={activeClass}
- href="#"
- onClick={this.handleSelect}
- onFocus={this.handleHover}
- onMouseOver={this.handleHover}>
- <i className={className} /> {this.props.custom && '+ '}
- {this.props.renderLabel(this.props.element)}
- </a>
- </li>
- );
- }
+ return (
+ <li onFocus={onHover} onMouseOver={onHover}>
+ <Checkbox
+ checked={Boolean(selected)}
+ className={className}
+ disabled={disabled}
+ id={element}
+ onCheck={props.onSelectChange}>
+ {custom ? (
+ <span
+ aria-label={`${translate('create_new_element')}: ${label}`}
+ className="little-spacer-left">
+ <span aria-hidden={true} className="little-spacer-right">
+ +
+ </span>
+ {label}
+ </span>
+ ) : (
+ <span className="little-spacer-left">{label}</span>
+ )}
+ </Checkbox>
+ </li>
+ );
}
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/MultiSelectOption-test.tsx b/server/sonar-web/src/main/js/components/common/__tests__/MultiSelectOption-test.tsx
index 0213f79b8f6..f3e706a5020 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/MultiSelectOption-test.tsx
+++ b/server/sonar-web/src/main/js/components/common/__tests__/MultiSelectOption-test.tsx
@@ -19,31 +19,24 @@
*/
import { shallow } from 'enzyme';
import * as React from 'react';
-import MultiSelectOption from '../MultiSelectOption';
-
-const props = {
- element: 'mytag',
- onSelectChange: () => {},
- onHover: () => {},
- renderLabel: (element: string) => element
-};
+import MultiSelectOption, { MultiSelectOptionProps } from '../MultiSelectOption';
it('should render standard element', () => {
- expect(shallow(<MultiSelectOption {...props} />)).toMatchSnapshot();
-});
-
-it('should render selected element', () => {
- expect(shallow(<MultiSelectOption {...props} selected={true} />)).toMatchSnapshot();
-});
-
-it('should render custom element', () => {
- expect(shallow(<MultiSelectOption {...props} custom={true} />)).toMatchSnapshot();
-});
-
-it('should render active element', () => {
- expect(shallow(<MultiSelectOption {...props} active={true} selected={true} />)).toMatchSnapshot();
+ expect(shallowRender()).toMatchSnapshot('default element');
+ expect(shallowRender({ selected: true })).toMatchSnapshot('selected element');
+ expect(shallowRender({ custom: true })).toMatchSnapshot('custom element');
+ expect(shallowRender({ active: true, selected: true })).toMatchSnapshot('active element');
+ expect(shallowRender({ disabled: true })).toMatchSnapshot('disabled element');
});
-it('should render disabled element', () => {
- expect(shallow(<MultiSelectOption {...props} disabled={true} />)).toMatchSnapshot();
-});
+function shallowRender(props: Partial<MultiSelectOptionProps> = {}) {
+ return shallow(
+ <MultiSelectOption
+ element="mytag"
+ onHover={jest.fn()}
+ onSelectChange={jest.fn()}
+ renderLabel={(element: string) => element}
+ {...props}
+ />
+ );
+}
diff --git a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelectOption-test.tsx.snap b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelectOption-test.tsx.snap
index ff5048c163c..ee6ce1bf244 100644
--- a/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelectOption-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelectOption-test.tsx.snap
@@ -1,92 +1,114 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
-exports[`should render active element 1`] = `
-<li>
- <a
+exports[`should render standard element: active element 1`] = `
+<li
+ onFocus={[Function]}
+ onMouseOver={[Function]}
+>
+ <Checkbox
+ checked={true}
className="active"
- href="#"
- onClick={[Function]}
- onFocus={[Function]}
- onMouseOver={[Function]}
+ id="mytag"
+ onCheck={[MockFunction]}
+ thirdState={false}
>
- <i
- className="icon-checkbox icon-checkbox-checked"
- />
-
- mytag
- </a>
+ <span
+ className="little-spacer-left"
+ >
+ mytag
+ </span>
+ </Checkbox>
</li>
`;
-exports[`should render custom element 1`] = `
-<li>
- <a
+exports[`should render standard element: custom element 1`] = `
+<li
+ onFocus={[Function]}
+ onMouseOver={[Function]}
+>
+ <Checkbox
+ checked={false}
className=""
- href="#"
- onClick={[Function]}
- onFocus={[Function]}
- onMouseOver={[Function]}
+ id="mytag"
+ onCheck={[MockFunction]}
+ thirdState={false}
>
- <i
- className="icon-checkbox"
- />
-
- +
- mytag
- </a>
+ <span
+ aria-label="create_new_element: mytag"
+ className="little-spacer-left"
+ >
+ <span
+ aria-hidden={true}
+ className="little-spacer-right"
+ >
+ +
+ </span>
+ mytag
+ </span>
+ </Checkbox>
</li>
`;
-exports[`should render disabled element 1`] = `
-<li>
- <a
- className="disabled"
- href="#"
- onClick={[Function]}
- onFocus={[Function]}
- onMouseOver={[Function]}
+exports[`should render standard element: default element 1`] = `
+<li
+ onFocus={[Function]}
+ onMouseOver={[Function]}
+>
+ <Checkbox
+ checked={false}
+ className=""
+ id="mytag"
+ onCheck={[MockFunction]}
+ thirdState={false}
>
- <i
- className="icon-checkbox icon-checkbox-invisible"
- />
-
- mytag
- </a>
+ <span
+ className="little-spacer-left"
+ >
+ mytag
+ </span>
+ </Checkbox>
</li>
`;
-exports[`should render selected element 1`] = `
-<li>
- <a
- className=""
- href="#"
- onClick={[Function]}
- onFocus={[Function]}
- onMouseOver={[Function]}
+exports[`should render standard element: disabled element 1`] = `
+<li
+ onFocus={[Function]}
+ onMouseOver={[Function]}
+>
+ <Checkbox
+ checked={false}
+ className="disabled"
+ disabled={true}
+ id="mytag"
+ onCheck={[MockFunction]}
+ thirdState={false}
>
- <i
- className="icon-checkbox icon-checkbox-checked"
- />
-
- mytag
- </a>
+ <span
+ className="little-spacer-left"
+ >
+ mytag
+ </span>
+ </Checkbox>
</li>
`;
-exports[`should render standard element 1`] = `
-<li>
- <a
+exports[`should render standard element: selected element 1`] = `
+<li
+ onFocus={[Function]}
+ onMouseOver={[Function]}
+>
+ <Checkbox
+ checked={true}
className=""
- href="#"
- onClick={[Function]}
- onFocus={[Function]}
- onMouseOver={[Function]}
+ id="mytag"
+ onCheck={[MockFunction]}
+ thirdState={false}
>
- <i
- className="icon-checkbox"
- />
-
- mytag
- </a>
+ <span
+ className="little-spacer-left"
+ >
+ mytag
+ </span>
+ </Checkbox>
</li>
`;
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 331c0984a35..f016798e583 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -47,6 +47,7 @@ confirm=Confirm
continue=Continue
copy=Copy
create=Create
+create_new_element=Create new element
created=Created
created_on=Created on
critical=Critical