From 5ff75eb4adbd7df4ec11192e41a6e05178855395 Mon Sep 17 00:00:00 2001 From: Ismail Cherri Date: Mon, 9 Sep 2024 14:07:50 +0200 Subject: [PATCH] SONAR-22323 Fix a11y issues on PR overview page --- .../src/components/CoverageIndicator.tsx | 11 +- .../src/components/DonutChart.tsx | 32 +- .../src/components/DuplicationsIndicator.tsx | 11 +- .../__tests__/CoverageIndicator-test.tsx | 14 +- .../__tests__/DuplicationsIndicator-test.tsx | 9 +- .../CoverageIndicator-test.tsx.snap | 112 ++++-- .../DuplicationsIndicator-test.tsx.snap | 340 ++++++++++-------- .../overview/branches/QualityGateStatus.tsx | 2 +- .../branches/SoftwareImpactMeasureCard.tsx | 9 +- .../overview/components/AnalysisStatus.tsx | 2 + .../components/IssueMeasuresCardInner.tsx | 2 +- .../apps/overview/components/MeasuresCard.tsx | 2 +- .../components/MeasuresCardPercent.tsx | 4 +- .../__tests__/MeasureIndicator-test.tsx | 6 +- .../MeasureIndicator-test.tsx.snap | 51 +-- .../resources/org/sonar/l10n/core.properties | 1 + 16 files changed, 352 insertions(+), 256 deletions(-) diff --git a/server/sonar-web/design-system/src/components/CoverageIndicator.tsx b/server/sonar-web/design-system/src/components/CoverageIndicator.tsx index 9eeb58479b7..a6eb05c1009 100644 --- a/server/sonar-web/design-system/src/components/CoverageIndicator.tsx +++ b/server/sonar-web/design-system/src/components/CoverageIndicator.tsx @@ -28,17 +28,23 @@ const FULL_PERCENT = 100; const PAD_ANGLE = 0.1; export interface CoverageIndicatorProps { + 'aria-hidden'?: boolean | 'true' | 'false'; + 'aria-label'?: string; size?: 'xs' | 'sm' | 'md'; value?: number | string; } -export function CoverageIndicator({ size = 'sm', value }: CoverageIndicatorProps) { +export function CoverageIndicator({ + size = 'sm', + value, + ...rest +}: Readonly) { const theme = useTheme(); const width = SIZE_TO_WIDTH_MAPPING[size]; const thickness = SIZE_TO_THICKNESS_MAPPING[size]; if (value === undefined) { - return ; + return ; } const themeRed = themeColor('coverageRed')({ theme }); @@ -64,6 +70,7 @@ export function CoverageIndicator({ size = 'sm', value }: CoverageIndicatorProps padAngle={padAngle} thickness={thickness} width={width} + {...rest} /> ); } diff --git a/server/sonar-web/design-system/src/components/DonutChart.tsx b/server/sonar-web/design-system/src/components/DonutChart.tsx index 14c4ec3782b..4fc1e638445 100644 --- a/server/sonar-web/design-system/src/components/DonutChart.tsx +++ b/server/sonar-web/design-system/src/components/DonutChart.tsx @@ -25,6 +25,8 @@ export interface DataPoint { } export interface DonutChartProps { + 'aria-hidden'?: boolean | 'true' | 'false'; + 'aria-label'?: string; cornerRadius?: number; data: DataPoint[]; height: number; @@ -35,8 +37,18 @@ export interface DonutChartProps { width: number; } -export function DonutChart(props: DonutChartProps) { - const { height, cornerRadius, minPercent = 0, padding = [0, 0, 0, 0], width } = props; +export function DonutChart(props: Readonly) { + const { + height, + cornerRadius, + minPercent = 0, + padding = [0, 0, 0, 0], + width, + padAngle, + data, + thickness, + ...rest + } = props; const availableWidth = width - padding[1] - padding[3]; const availableHeight = height - padding[0] - padding[2]; @@ -44,31 +56,31 @@ export function DonutChart(props: DonutChartProps) { const size = Math.min(availableWidth, availableHeight); const radius = Math.floor(size / 2); - const total = props.data.reduce((acc, d) => acc + d.value, 0); + const total = data.reduce((acc, d) => acc + d.value, 0); const pie = d3Pie() .sort(null) .value((d) => Math.max(d.value, (total / 100) * minPercent)); - if (props.padAngle !== undefined) { - pie.padAngle(props.padAngle); + if (padAngle !== undefined) { + pie.padAngle(padAngle); } - const sectors = pie(props.data).map((d, i) => { + const sectors = pie(data).map((d, i) => { return ( ); }); return ( - + {sectors} @@ -84,7 +96,7 @@ interface SectorProps { thickness: number; } -function Sector(props: SectorProps) { +function Sector(props: Readonly) { const arc = d3Arc>() .outerRadius(props.radius) .innerRadius(props.radius - props.thickness); diff --git a/server/sonar-web/design-system/src/components/DuplicationsIndicator.tsx b/server/sonar-web/design-system/src/components/DuplicationsIndicator.tsx index f43cb8a7206..9a00372a892 100644 --- a/server/sonar-web/design-system/src/components/DuplicationsIndicator.tsx +++ b/server/sonar-web/design-system/src/components/DuplicationsIndicator.tsx @@ -24,13 +24,15 @@ import { DuplicationEnum, DuplicationLabel } from '../types/measures'; import { NoDataIcon } from './icons'; interface Props { + 'aria-hidden'?: boolean | 'true' | 'false'; + 'aria-label'?: string; rating?: DuplicationLabel; size?: 'xs' | 'sm' | 'md'; } const SIZE_TO_PX_MAPPING = { xs: 16, sm: 24, md: 36 }; -export function DuplicationsIndicator({ size = 'sm', rating }: Props) { +export function DuplicationsIndicator({ size = 'sm', rating, ...rest }: Readonly) { const theme = useTheme(); const sizePX = SIZE_TO_PX_MAPPING[size]; @@ -47,20 +49,23 @@ export function DuplicationsIndicator({ size = 'sm', rating }: Props) { rating={rating} secondaryColor={secondaryColor} size={sizePX} + {...rest} /> ); } interface SVGProps { + 'aria-hidden'?: boolean | 'true' | 'false'; + 'aria-label'?: string; primaryColor: string; rating: DuplicationLabel; secondaryColor: string; size: number; } -function RatingSVG({ primaryColor, rating, secondaryColor, size }: SVGProps) { +function RatingSVG({ primaryColor, rating, secondaryColor, size, ...rest }: Readonly) { return ( - + {isDefined(rating) && { diff --git a/server/sonar-web/design-system/src/components/__tests__/CoverageIndicator-test.tsx b/server/sonar-web/design-system/src/components/__tests__/CoverageIndicator-test.tsx index df178e6336f..648869f9ac9 100644 --- a/server/sonar-web/design-system/src/components/__tests__/CoverageIndicator-test.tsx +++ b/server/sonar-web/design-system/src/components/__tests__/CoverageIndicator-test.tsx @@ -17,20 +17,24 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { screen } from '@testing-library/react'; import { render } from '../../helpers/testUtils'; import { FCProps } from '../../types/misc'; import { CoverageIndicator } from '../CoverageIndicator'; it('should display CoverageIndicator', () => { - setupWithProps({ value: 10 }); - expect(screen.getByRole('img', { hidden: true })).toMatchSnapshot(); + const wrapper = setupWithProps({ value: 10 }); + expect(wrapper.baseElement).toMatchSnapshot(); }); it('should display CoverageIndicator without value', () => { - setupWithProps(); - expect(screen.getByRole('img', { hidden: true })).toMatchSnapshot(); + const wrapper = setupWithProps(); + expect(wrapper.baseElement).toMatchSnapshot(); +}); + +it('should display CoverageIndicator with correct aria properties', () => { + const wrapper = setupWithProps({ 'aria-label': 'label', 'aria-hidden': true }); + expect(wrapper.baseElement).toMatchSnapshot(); }); function setupWithProps(props: Partial> = {}) { diff --git a/server/sonar-web/design-system/src/components/__tests__/DuplicationsIndicator-test.tsx b/server/sonar-web/design-system/src/components/__tests__/DuplicationsIndicator-test.tsx index 65f59934ad9..f0c9fbc1405 100644 --- a/server/sonar-web/design-system/src/components/__tests__/DuplicationsIndicator-test.tsx +++ b/server/sonar-web/design-system/src/components/__tests__/DuplicationsIndicator-test.tsx @@ -17,7 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -import { screen } from '@testing-library/react'; import { render } from '../../helpers/testUtils'; import { FCProps } from '../../types/misc'; @@ -25,15 +24,15 @@ import { DuplicationLabel } from '../../types/measures'; import { DuplicationsIndicator } from '../DuplicationsIndicator'; it('should display DuplicationsIndicator without rating', () => { - setupWithProps(); - expect(screen.getByRole('img', { hidden: true })).toMatchSnapshot(); + const wrapper = setupWithProps(); + expect(wrapper.baseElement).toMatchSnapshot(); }); it.each(['A', 'B', 'C', 'D', 'E', 'F'])( 'should display DuplicationsIndicator with rating', (variant: DuplicationLabel) => { - setupWithProps({ rating: variant }); - expect(screen.getByRole('img', { hidden: true })).toMatchSnapshot(); + const wrapper = setupWithProps({ rating: variant }); + expect(wrapper.baseElement).toMatchSnapshot(); }, ); diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CoverageIndicator-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CoverageIndicator-test.tsx.snap index 8a49b54ab50..f04fb70a190 100644 --- a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CoverageIndicator-test.tsx.snap +++ b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/CoverageIndicator-test.tsx.snap @@ -1,49 +1,83 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should display CoverageIndicator 1`] = ` - - - +
+ + + + + + + + +
+ +`; + +exports[`should display CoverageIndicator with correct aria properties 1`] = ` + +
+ + +
+ `; exports[`should display CoverageIndicator without value 1`] = ` - + +
+ +
+ `; diff --git a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/DuplicationsIndicator-test.tsx.snap b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/DuplicationsIndicator-test.tsx.snap index 21ac97b1eea..677207098fe 100644 --- a/server/sonar-web/design-system/src/components/__tests__/__snapshots__/DuplicationsIndicator-test.tsx.snap +++ b/server/sonar-web/design-system/src/components/__tests__/__snapshots__/DuplicationsIndicator-test.tsx.snap @@ -1,181 +1,203 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`should display DuplicationsIndicator with rating 1`] = ` - - - - - + +
+ + + + + +
+ `; exports[`should display DuplicationsIndicator with rating 2`] = ` - - - - - + +
+ + + + + +
+ `; exports[`should display DuplicationsIndicator with rating 3`] = ` - - - - - + +
+ + + + + +
+ `; exports[`should display DuplicationsIndicator with rating 4`] = ` - - - - - + +
+ + + + + +
+ `; exports[`should display DuplicationsIndicator with rating 5`] = ` - - - - - + +
+ + + + + +
+ `; exports[`should display DuplicationsIndicator with rating 6`] = ` - - - - - + +
+ + + + + +
+ `; exports[`should display DuplicationsIndicator without rating 1`] = ` - + +
+ +
+ `; diff --git a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatus.tsx b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatus.tsx index 04715c9965f..60cb87a9c27 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatus.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/QualityGateStatus.tsx @@ -37,7 +37,7 @@ export default function QualityGateStatus(props: Readonly) {
- {translate('overview.quality_gate')} + {translate('overview.quality_gate')} {translate('overview.quality_gate.help')}
} diff --git a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx index ad0af592950..37c5e3248a8 100644 --- a/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx +++ b/server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx @@ -19,7 +19,7 @@ */ import styled from '@emotion/styled'; import { LinkHighlight, LinkStandalone, Tooltip } from '@sonarsource/echoes-react'; -import { Badge, TextBold, TextSubdued } from 'design-system'; +import { Badge, TextBold, TextSubdued, themeColor } from 'design-system'; import * as React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; import { formatMeasure } from '~sonar-aligned/helpers/measures'; @@ -98,7 +98,9 @@ export function SoftwareImpactMeasureCard(props: Readonly
- + + {intl.formatMessage({ id: `software_quality.${softwareQuality}` })} + {failed && ( @@ -174,5 +176,8 @@ export function SoftwareImpactMeasureCard(props: Readonly) { {/* TODO: replace the Link below with a lighweight/discreet button component */} {/* when it is available in Echoes */} ) { {/* TODO: replace the Link below with a lighweight/discreet button component */} {/* when it is available in Echoes */} ; + return