]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-12880 Improve tag select accessibility
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Mon, 23 Dec 2019 12:59:18 +0000 (13:59 +0100)
committerSonarTech <sonartech@sonarsource.com>
Thu, 2 Jan 2020 19:46:12 +0000 (20:46 +0100)
server/sonar-web/src/main/js/components/common/MultiSelect.tsx
server/sonar-web/src/main/js/components/common/MultiSelectOption.tsx
server/sonar-web/src/main/js/components/common/__tests__/MultiSelectOption-test.tsx
server/sonar-web/src/main/js/components/common/__tests__/__snapshots__/MultiSelectOption-test.tsx.snap
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 9a167fd29fe91466b2f930f27a5c9407b311d152..dd348bb4691aaa4269339faccbc8b50e407dfa2f 100644 (file)
@@ -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 {
index 309cdd503f42c498c9a3e386e2b42494a9d8e51b..9ae37b8f55cd315e9fa0aebcd73f2dbe4163b5f7 100644 (file)
  */
 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>
+  );
 }
index 0213f79b8f6b685afe96aa104cc98cb87dde7b6b..f3e706a5020b1a7ba1e0deccba09cd4be37ef61c 100644 (file)
  */
 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}
+    />
+  );
+}
index ff5048c163cb43bad071e891db2a2f8307e6bbf2..ee6ce1bf244786ecf2a760b57416da57283f5be7 100644 (file)
 // 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>
 `;
index 331c0984a358186a9ba7c6c0ed77a6b688deff6a..f016798e58322e55f20e54654de74bafe7431f86 100644 (file)
@@ -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