]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-22303 Fix aria-label misuse (Deque issue #1744337)
authorDavid Cho-Lerat <david.cho-lerat@sonarsource.com>
Mon, 29 Jul 2024 09:33:27 +0000 (11:33 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 30 Jul 2024 20:02:34 +0000 (20:02 +0000)
server/sonar-web/design-system/src/components/input/MultiSelectMenu.tsx
server/sonar-web/design-system/src/components/input/MultiSelectMenuOption.tsx
server/sonar-web/src/main/js/apps/projectActivity/components/__tests__/ProjectActivityApp-it.tsx
server/sonar-web/src/main/js/components/activity-graph/AddGraphMetricPopup.tsx
server/sonar-web/src/main/js/components/activity-graph/__tests__/ActivityGraph-it.tsx

index 86da7e4b5bdd97b84b359d9991f7eb70fae6e8be..0515aa6e4c75b068a0293d82c1203304c5156d9f 100644 (file)
@@ -17,6 +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 classNames from 'classnames';
 import { difference } from 'lodash';
 import React, { PureComponent } from 'react';
@@ -58,6 +59,7 @@ interface State {
 
 interface DefaultProps {
   filterSelected: (query: string, selectedElements: string[]) => string[];
+  renderAriaLabel: (element: string) => string;
   renderLabel: (element: string) => React.ReactNode;
   validateSearchInput: (value: string) => string;
 }
@@ -69,9 +71,10 @@ export class MultiSelectMenu extends PureComponent<Props, State> {
   searchInput?: HTMLInputElement | null;
   mounted = false;
 
-  static defaultProps: DefaultProps = {
+  static readonly defaultProps: DefaultProps = {
     filterSelected: (query: string, selectedElements: string[]) =>
       selectedElements.filter((elem) => elem.includes(query)),
+    renderAriaLabel: (element: string) => element,
     renderLabel: (element: string) => element,
     validateSearchInput: (value: string) => value,
   };
@@ -274,7 +277,7 @@ export class MultiSelectMenu extends PureComponent<Props, State> {
       elementsDisabled,
       renderTooltip,
     } = this.props;
-    const { renderLabel } = this.props as PropsWithDefault;
+    const { renderAriaLabel, renderLabel } = this.props as PropsWithDefault;
 
     const { query, activeIdx, selectedElements, unselectedElements } = this.state;
     const activeElement = this.getAllElements(this.props, this.state)[activeIdx];
@@ -318,6 +321,7 @@ export class MultiSelectMenu extends PureComponent<Props, State> {
                 key={element}
                 onHover={this.handleElementHover}
                 onSelectChange={this.handleSelectChange}
+                renderAriaLabel={renderAriaLabel}
                 renderLabel={renderLabel}
                 renderTooltip={renderTooltip}
                 selected
@@ -333,6 +337,7 @@ export class MultiSelectMenu extends PureComponent<Props, State> {
                 key={element}
                 onHover={this.handleElementHover}
                 onSelectChange={this.handleSelectChange}
+                renderAriaLabel={renderAriaLabel}
                 renderLabel={renderLabel}
                 renderTooltip={renderTooltip}
               />
@@ -346,6 +351,7 @@ export class MultiSelectMenu extends PureComponent<Props, State> {
               key={element}
               onHover={this.handleElementHover}
               onSelectChange={this.handleSelectChange}
+              renderAriaLabel={renderAriaLabel}
               renderLabel={renderLabel}
               renderTooltip={renderTooltip}
             />
index 2336dd914a3d6b7233aa04ee2deefaa0c892dedf..55f048869dd347168d11b102e3aabad9eae92397 100644 (file)
@@ -17,6 +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 classNames from 'classnames';
 import { identity } from 'lodash';
 import { PopupPlacement } from '../../helpers';
@@ -31,12 +32,13 @@ export interface MultiSelectOptionProps {
   element: string;
   onHover: (element: string) => void;
   onSelectChange: (selected: boolean, element: string) => void;
+  renderAriaLabel?: (element: string) => string;
   renderLabel?: (element: string) => React.ReactNode;
   renderTooltip?: (element: string, disabled: boolean) => React.ReactNode;
   selected?: boolean;
 }
 
-export function MultiSelectMenuOption(props: MultiSelectOptionProps) {
+export function MultiSelectMenuOption(props: Readonly<MultiSelectOptionProps>) {
   const {
     active,
     createElementLabel,
@@ -44,15 +46,17 @@ export function MultiSelectMenuOption(props: MultiSelectOptionProps) {
     disabled = false,
     element,
     onSelectChange,
-    selected,
+    renderAriaLabel = identity,
     renderLabel = identity,
     renderTooltip,
+    selected,
   } = props;
 
   const onHover = () => {
     props.onHover(element);
   };
 
+  const ariaLabel = renderAriaLabel(element);
   const label = renderLabel(element);
 
   return (
@@ -62,7 +66,7 @@ export function MultiSelectMenuOption(props: MultiSelectOptionProps) {
         className={classNames('sw-flex sw-py-2 sw-px-4', { active })}
         disabled={disabled}
         id={element}
-        label={element}
+        label={ariaLabel}
         onCheck={onSelectChange}
         onFocus={onHover}
         onPointerEnter={onHover}
index 908a4f2424b6c1c7d1d37e3b7cc14f166c18e865..aa54fa9174317647047dff9a9e292d7f5e315071 100644 (file)
@@ -29,6 +29,7 @@ import ApplicationServiceMock from '../../../../api/mocks/ApplicationServiceMock
 import { ProjectActivityServiceMock } from '../../../../api/mocks/ProjectActivityServiceMock';
 import { TimeMachineServiceMock } from '../../../../api/mocks/TimeMachineServiceMock';
 import { mockBranchList } from '../../../../api/mocks/data/branches';
+import { DEPRECATED_ACTIVITY_METRICS } from '../../../../helpers/constants';
 import { parseDate } from '../../../../helpers/dates';
 import { mockComponent } from '../../../../helpers/mocks/component';
 import {
@@ -564,7 +565,10 @@ function getPageObject() {
 
     // Add metrics.
     addMetricBtn: byRole('button', { name: 'project_activity.graphs.custom.add' }),
-    metricCheckbox: (name: MetricKey) => byRole('checkbox', { name }),
+    metricCheckbox: (name: MetricKey) =>
+      byRole('checkbox', {
+        name: DEPRECATED_ACTIVITY_METRICS.includes(name) ? `${name} (deprecated)` : name,
+      }),
 
     // Graph legend.
     newCodeLegend: byText('hotspot.filters.period.since_leak_period'),
index 48b5b1be8746e7583085cdd9d55db4508bb7cc4d..28cafca7be7486653c0be4e1c19fbb0c933e4606 100644 (file)
@@ -17,6 +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 { Badge, FlagMessage, MultiSelectMenu } from 'design-system';
 import * as React from 'react';
 import { FormattedMessage, useIntl } from 'react-intl';
@@ -40,7 +41,7 @@ export default function AddGraphMetricPopup({
   elements,
   metricsTypeFilter,
   ...props
-}: AddGraphMetricPopupProps) {
+}: Readonly<AddGraphMetricPopupProps>) {
   const intl = useIntl();
   let footerNode: React.ReactNode = '';
 
@@ -64,6 +65,15 @@ export default function AddGraphMetricPopup({
     );
   }
 
+  const renderAriaLabel = (key: MetricKey) => {
+    const metricName = getLocalizedMetricName({ key });
+    const isDeprecated = DEPRECATED_ACTIVITY_METRICS.includes(key);
+
+    return isDeprecated
+      ? `${metricName} (${intl.formatMessage({ id: 'deprecated' })})`
+      : metricName;
+  };
+
   const renderLabel = (key: MetricKey) => {
     const metricName = getLocalizedMetricName({ key });
     const isDeprecated = DEPRECATED_ACTIVITY_METRICS.includes(key);
@@ -101,6 +111,7 @@ export default function AddGraphMetricPopup({
       onSelect={(item: string) => elements.includes(item) && props.onSelect(item)}
       onUnselect={props.onUnselect}
       placeholder={translate('search.search_for_metrics')}
+      renderAriaLabel={renderAriaLabel}
       renderLabel={renderLabel}
       renderTooltip={renderTooltip}
       selectedElements={props.selectedElements}
index 4c75bd42d875829692681068eedacf639500213d..64e93c3963c26c86c2e25270f6dddd8451f6e3fb 100644 (file)
@@ -28,7 +28,10 @@ import {
   byText,
 } from '~sonar-aligned/helpers/testSelector';
 import { MetricKey, MetricType } from '~sonar-aligned/types/metrics';
-import { CCT_SOFTWARE_QUALITY_METRICS } from '../../../helpers/constants';
+import {
+  CCT_SOFTWARE_QUALITY_METRICS,
+  DEPRECATED_ACTIVITY_METRICS,
+} from '../../../helpers/constants';
 import { parseDate } from '../../../helpers/dates';
 import { mockHistoryItem, mockMeasureHistory } from '../../../helpers/mocks/project-activity';
 import { mockMetric } from '../../../helpers/testMocks';
@@ -191,7 +194,9 @@ function getPageObject() {
     maintainabilityIssuesCheckbox: byRole('checkbox', { name: MetricKey.maintainability_issues }),
     newBugsCheckbox: byRole('checkbox', { name: MetricKey.new_bugs }),
     burnedBudgetCheckbox: byRole('checkbox', { name: MetricKey.burned_budget }),
-    vulnerabilityCheckbox: byRole('checkbox', { name: MetricKey.vulnerabilities }),
+    vulnerabilityCheckbox: byRole('checkbox', {
+      name: `${MetricKey.vulnerabilities} (deprecated)`,
+    }),
     hiddenOptionsAlert: byText('project_activity.graphs.custom.type_x_message', {
       exact: false,
     }),
@@ -237,7 +242,11 @@ function getPageObject() {
         await user.type(ui.filterMetrics.get(), text);
       },
       async clickOnMetric(name: MetricKey) {
-        await user.click(screen.getByRole('checkbox', { name }));
+        await user.click(
+          screen.getByRole('checkbox', {
+            name: DEPRECATED_ACTIVITY_METRICS.includes(name) ? `${name} (deprecated)` : name,
+          }),
+        );
       },
       async removeMetric(metric: MetricKey) {
         await user.click(ui.legendRemoveMetricBtn(metric).get());