aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorWouter Admiraal <wouter.admiraal@sonarsource.com>2022-08-26 15:03:06 +0200
committersonartech <sonartech@sonarsource.com>2022-08-30 20:03:14 +0000
commit3d50c83a073845113f2621ce92695a9bc297d7bf (patch)
treebecfba4591bdb0014492dbd90419343372bf6e17 /server
parenta74e990dbbddbfac7c608a537304180d6869e406 (diff)
downloadsonarqube-3d50c83a073845113f2621ce92695a9bc297d7bf.tar.gz
sonarqube-3d50c83a073845113f2621ce92695a9bc297d7bf.zip
SONAR-16855 [893325] Information or relationship only presented visually
Diffstat (limited to 'server')
-rw-r--r--server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts42
-rw-r--r--server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx24
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx14
-rw-r--r--server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap90
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTabs.tsx22
-rw-r--r--server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeTabs-test.tsx.snap250
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx10
-rw-r--r--server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap12
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap36
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx7
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-test.tsx12
-rw-r--r--server/sonar-web/src/main/js/components/controls/BoxedTabs.tsx20
-rw-r--r--server/sonar-web/src/main/js/components/controls/Tooltip.tsx5
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/BoxedTabs-test.tsx2
-rw-r--r--server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedTabs-test.tsx.snap38
-rw-r--r--server/sonar-web/src/main/js/components/rules/RuleTabViewer.tsx7
17 files changed, 369 insertions, 229 deletions
diff --git a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
index 32221bf8792..af7cf936e55 100644
--- a/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
+++ b/server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
@@ -87,23 +87,23 @@ it('should show hotspot rule section', async () => {
expect(await screen.findByRole('heading', { level: 3, name: 'Hot hotspot' })).toBeInTheDocument();
expect(screen.getByText('Introduction to this rule')).toBeInTheDocument();
expect(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.root_cause.SECURITY_HOTSPOT'
})
).toBeInTheDocument();
expect(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.assess_the_problem'
})
).toBeInTheDocument();
expect(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
).toBeInTheDocument();
// Check that we render plain html
await user.click(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
);
@@ -118,18 +118,18 @@ it('should show rule advanced section', async () => {
).toBeInTheDocument();
expect(screen.getByText('Introduction to this rule')).toBeInTheDocument();
expect(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.how_to_fix'
})
).toBeInTheDocument();
expect(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
).toBeInTheDocument();
// Check that we render plain html
await user.click(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
);
@@ -143,13 +143,13 @@ it('should show rule advanced section with context', async () => {
await screen.findByRole('heading', { level: 3, name: 'Python rule with context' })
).toBeInTheDocument();
expect(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.how_to_fix'
})
).toBeInTheDocument();
await user.click(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.how_to_fix'
})
);
@@ -384,13 +384,13 @@ it('should show notification for rule advanced section and remove it after user
name: 'Awesome Python rule with education principles'
});
expect(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
).toBeInTheDocument();
await user.click(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
);
@@ -408,12 +408,12 @@ it('should show notification for rule advanced section and remove it after user
);
// navigate away and come back
await user.click(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.how_to_fix'
})
);
await user.click(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
);
@@ -429,31 +429,31 @@ it('should show notification for rule advanced section and removes it when user
name: 'Awesome Python rule with education principles'
});
expect(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
).toBeInTheDocument();
// navigate away and come back
await user.click(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.how_to_fix'
})
);
await user.click(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
);
expect(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
).toBeInTheDocument();
await user.click(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
);
@@ -469,12 +469,12 @@ it('should show notification for rule advanced section and removes it when user
// navigate away and come back
await user.click(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.how_to_fix'
})
);
await user.click(
- screen.getByRole('button', {
+ screen.getByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
);
@@ -486,7 +486,7 @@ it('should not show notification for anonymous users', async () => {
renderCodingRulesApp(mockCurrentUser(), 'coding_rules?open=rule8');
await user.click(
- await screen.findByRole('button', {
+ await screen.findByRole('tab', {
name: 'coding_rules.description_section.title.more_info'
})
);
diff --git a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
index 0d3a43a18b8..2774e105574 100644
--- a/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
+++ b/server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
@@ -46,7 +46,7 @@ it('should show education principles', async () => {
const user = userEvent.setup();
renderProjectIssuesApp('project/issues?issues=issue2&open=issue2&id=myproject');
await user.click(
- await screen.findByRole('button', { name: `coding_rules.description_section.title.more_info` })
+ await screen.findByRole('tab', { name: `coding_rules.description_section.title.more_info` })
);
expect(screen.getByRole('heading', { name: 'Defense-In-Depth', level: 3 })).toBeInTheDocument();
});
@@ -59,7 +59,7 @@ it('should open issue and navigate', async () => {
// Select an issue with an advanced rule
expect(await screen.findByRole('region', { name: 'Fix that' })).toBeInTheDocument();
await user.click(screen.getByRole('region', { name: 'Fix that' }));
- expect(screen.getByRole('button', { name: 'issue.tabs.code' })).toBeInTheDocument();
+ expect(screen.getByRole('tab', { name: 'issue.tabs.code' })).toBeInTheDocument();
// Are rule headers present?
expect(screen.getByRole('heading', { level: 1, name: 'Fix that' })).toBeInTheDocument();
@@ -67,19 +67,19 @@ it('should open issue and navigate', async () => {
// Select the "why is this an issue" tab and check its content
expect(
- screen.getByRole('button', { name: `coding_rules.description_section.title.root_cause` })
+ screen.getByRole('tab', { name: `coding_rules.description_section.title.root_cause` })
).toBeInTheDocument();
await user.click(
- screen.getByRole('button', { name: `coding_rules.description_section.title.root_cause` })
+ screen.getByRole('tab', { name: `coding_rules.description_section.title.root_cause` })
);
expect(screen.getByRole('heading', { name: 'Because' })).toBeInTheDocument();
// Select the "how to fix it" tab
expect(
- screen.getByRole('button', { name: `coding_rules.description_section.title.how_to_fix` })
+ screen.getByRole('tab', { name: `coding_rules.description_section.title.how_to_fix` })
).toBeInTheDocument();
await user.click(
- screen.getByRole('button', { name: `coding_rules.description_section.title.how_to_fix` })
+ screen.getByRole('tab', { name: `coding_rules.description_section.title.how_to_fix` })
);
// Is the context selector present with the expected values and default selection?
@@ -103,10 +103,10 @@ it('should open issue and navigate', async () => {
// Select the main info tab and check its content
expect(
- screen.getByRole('button', { name: `coding_rules.description_section.title.more_info` })
+ screen.getByRole('tab', { name: `coding_rules.description_section.title.more_info` })
).toBeInTheDocument();
await user.click(
- screen.getByRole('button', { name: `coding_rules.description_section.title.more_info` })
+ screen.getByRole('tab', { name: `coding_rules.description_section.title.more_info` })
);
expect(screen.getByRole('heading', { name: 'Link' })).toBeInTheDocument();
@@ -123,10 +123,10 @@ it('should open issue and navigate', async () => {
// Select the "why is this an issue tab" and check its content
expect(
- screen.getByRole('button', { name: `coding_rules.description_section.title.root_cause` })
+ screen.getByRole('tab', { name: `coding_rules.description_section.title.root_cause` })
).toBeInTheDocument();
await user.click(
- screen.getByRole('button', { name: `coding_rules.description_section.title.root_cause` })
+ screen.getByRole('tab', { name: `coding_rules.description_section.title.root_cause` })
);
expect(screen.getByRole('heading', { name: 'Default' })).toBeInTheDocument();
@@ -413,7 +413,7 @@ it('should show code tabs when any secondary location is selected', async () =>
// Select the "why is this an issue" tab
await user.click(
- screen.getByRole('button', { name: 'coding_rules.description_section.title.root_cause' })
+ screen.getByRole('tab', { name: 'coding_rules.description_section.title.root_cause' })
);
expect(
screen.queryByRole('row', {
@@ -430,7 +430,7 @@ it('should show code tabs when any secondary location is selected', async () =>
// selecting the same selected hotspot location should also navigate back to code page
await user.click(
- screen.getByRole('button', { name: 'coding_rules.description_section.title.root_cause' })
+ screen.getByRole('tab', { name: 'coding_rules.description_section.title.root_cause' })
);
expect(
screen.queryByRole('row', {
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx
index 0f1e50315e5..e3be38745b2 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx
+++ b/server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx
@@ -19,7 +19,7 @@
*/
import * as React from 'react';
import { rawSizes } from '../../../app/theme';
-import BoxedTabs from '../../../components/controls/BoxedTabs';
+import BoxedTabs, { getTabId, getTabPanelId } from '../../../components/controls/BoxedTabs';
import ComponentReportActions from '../../../components/controls/ComponentReportActions';
import { Location, withRouter } from '../../../components/hoc/withRouter';
import DeferredSpinner from '../../../components/ui/DeferredSpinner';
@@ -50,8 +50,8 @@ export interface MeasuresPanelProps {
}
export enum MeasuresPanelTabs {
- New,
- Overall
+ New = 'new',
+ Overall = 'overall'
}
export function MeasuresPanel(props: MeasuresPanelProps) {
@@ -115,9 +115,13 @@ export function MeasuresPanel(props: MeasuresPanelProps) {
</div>
) : (
<>
- <BoxedTabs onSelect={selectTab} selected={tab} tabs={tabs} />
+ <BoxedTabs onSelect={key => selectTab(key)} selected={tab} tabs={tabs} />
- <div className="overview-panel-content flex-1 bordered">
+ <div
+ className="overview-panel-content flex-1 bordered"
+ role="tabpanel"
+ id={getTabPanelId(tab)}
+ aria-labelledby={getTabId(tab)}>
{!hasDiffMeasures && isNewCodeTab ? (
<MeasuresPanelNoNewCode branch={branch} component={component} period={period} />
) : (
diff --git a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap
index 97d53169603..6936ea45634 100644
--- a/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap
@@ -48,11 +48,11 @@ exports[`should render correctly for applications: default 1`] = `
</div>
<BoxedTabs
onSelect={[Function]}
- selected={0}
+ selected="new"
tabs={
Array [
Object {
- "key": 0,
+ "key": "new",
"label": <div
className="text-left overview-measures-tab"
>
@@ -64,7 +64,7 @@ exports[`should render correctly for applications: default 1`] = `
</div>,
},
Object {
- "key": 1,
+ "key": "overall",
"label": <div
className="text-left overview-measures-tab"
>
@@ -85,7 +85,10 @@ exports[`should render correctly for applications: default 1`] = `
}
/>
<div
+ aria-labelledby="tab-new"
className="overview-panel-content flex-1 bordered"
+ id="tabpanel-new"
+ role="tabpanel"
>
<MeasuresPanelIssueMeasureRow
branchLike={
@@ -770,11 +773,11 @@ exports[`should render correctly for applications: overall 1`] = `
</div>
<BoxedTabs
onSelect={[Function]}
- selected={1}
+ selected="overall"
tabs={
Array [
Object {
- "key": 0,
+ "key": "new",
"label": <div
className="text-left overview-measures-tab"
>
@@ -786,7 +789,7 @@ exports[`should render correctly for applications: overall 1`] = `
</div>,
},
Object {
- "key": 1,
+ "key": "overall",
"label": <div
className="text-left overview-measures-tab"
>
@@ -807,7 +810,10 @@ exports[`should render correctly for applications: overall 1`] = `
}
/>
<div
+ aria-labelledby="tab-overall"
className="overview-panel-content flex-1 bordered"
+ id="tabpanel-overall"
+ role="tabpanel"
>
<MeasuresPanelIssueMeasureRow
branchLike={
@@ -1702,11 +1708,11 @@ exports[`should render correctly for projects: default 1`] = `
</div>
<BoxedTabs
onSelect={[Function]}
- selected={0}
+ selected="new"
tabs={
Array [
Object {
- "key": 0,
+ "key": "new",
"label": <div
className="text-left overview-measures-tab"
>
@@ -1718,7 +1724,7 @@ exports[`should render correctly for projects: default 1`] = `
</div>,
},
Object {
- "key": 1,
+ "key": "overall",
"label": <div
className="text-left overview-measures-tab"
>
@@ -1739,7 +1745,10 @@ exports[`should render correctly for projects: default 1`] = `
}
/>
<div
+ aria-labelledby="tab-new"
className="overview-panel-content flex-1 bordered"
+ id="tabpanel-new"
+ role="tabpanel"
>
<MeasuresPanelIssueMeasureRow
branchLike={
@@ -2424,11 +2433,11 @@ exports[`should render correctly for projects: overall 1`] = `
</div>
<BoxedTabs
onSelect={[Function]}
- selected={1}
+ selected="overall"
tabs={
Array [
Object {
- "key": 0,
+ "key": "new",
"label": <div
className="text-left overview-measures-tab"
>
@@ -2440,7 +2449,7 @@ exports[`should render correctly for projects: overall 1`] = `
</div>,
},
Object {
- "key": 1,
+ "key": "overall",
"label": <div
className="text-left overview-measures-tab"
>
@@ -2461,7 +2470,10 @@ exports[`should render correctly for projects: overall 1`] = `
}
/>
<div
+ aria-labelledby="tab-overall"
className="overview-panel-content flex-1 bordered"
+ id="tabpanel-overall"
+ role="tabpanel"
>
<MeasuresPanelIssueMeasureRow
branchLike={
@@ -3356,11 +3368,11 @@ exports[`should render correctly if branch is misconfigured: hide settings 1`] =
</div>
<BoxedTabs
onSelect={[Function]}
- selected={0}
+ selected="new"
tabs={
Array [
Object {
- "key": 0,
+ "key": "new",
"label": <div
className="text-left overview-measures-tab"
>
@@ -3382,7 +3394,7 @@ exports[`should render correctly if branch is misconfigured: hide settings 1`] =
</div>,
},
Object {
- "key": 1,
+ "key": "overall",
"label": <div
className="text-left overview-measures-tab"
>
@@ -3403,7 +3415,10 @@ exports[`should render correctly if branch is misconfigured: hide settings 1`] =
}
/>
<div
+ aria-labelledby="tab-new"
className="overview-panel-content flex-1 bordered"
+ id="tabpanel-new"
+ role="tabpanel"
>
<MeasuresPanelNoNewCode
branch={
@@ -3500,11 +3515,11 @@ exports[`should render correctly if branch is misconfigured: show settings 1`] =
</div>
<BoxedTabs
onSelect={[Function]}
- selected={0}
+ selected="new"
tabs={
Array [
Object {
- "key": 0,
+ "key": "new",
"label": <div
className="text-left overview-measures-tab"
>
@@ -3526,7 +3541,7 @@ exports[`should render correctly if branch is misconfigured: show settings 1`] =
</div>,
},
Object {
- "key": 1,
+ "key": "overall",
"label": <div
className="text-left overview-measures-tab"
>
@@ -3547,7 +3562,10 @@ exports[`should render correctly if branch is misconfigured: show settings 1`] =
}
/>
<div
+ aria-labelledby="tab-new"
className="overview-panel-content flex-1 bordered"
+ id="tabpanel-new"
+ role="tabpanel"
>
<MeasuresPanelNoNewCode
branch={
@@ -3700,11 +3718,11 @@ exports[`should render correctly if there is no coverage 1`] = `
</div>
<BoxedTabs
onSelect={[Function]}
- selected={0}
+ selected="new"
tabs={
Array [
Object {
- "key": 0,
+ "key": "new",
"label": <div
className="text-left overview-measures-tab"
>
@@ -3716,7 +3734,7 @@ exports[`should render correctly if there is no coverage 1`] = `
</div>,
},
Object {
- "key": 1,
+ "key": "overall",
"label": <div
className="text-left overview-measures-tab"
>
@@ -3737,7 +3755,10 @@ exports[`should render correctly if there is no coverage 1`] = `
}
/>
<div
+ aria-labelledby="tab-new"
className="overview-panel-content flex-1 bordered"
+ id="tabpanel-new"
+ role="tabpanel"
>
<MeasuresPanelIssueMeasureRow
branchLike={
@@ -4154,11 +4175,11 @@ exports[`should render correctly if there is no new code measures 1`] = `
</div>
<BoxedTabs
onSelect={[Function]}
- selected={0}
+ selected="new"
tabs={
Array [
Object {
- "key": 0,
+ "key": "new",
"label": <div
className="text-left overview-measures-tab"
>
@@ -4170,7 +4191,7 @@ exports[`should render correctly if there is no new code measures 1`] = `
</div>,
},
Object {
- "key": 1,
+ "key": "overall",
"label": <div
className="text-left overview-measures-tab"
>
@@ -4191,7 +4212,10 @@ exports[`should render correctly if there is no new code measures 1`] = `
}
/>
<div
+ aria-labelledby="tab-new"
className="overview-panel-content flex-1 bordered"
+ id="tabpanel-new"
+ role="tabpanel"
>
<MeasuresPanelNoNewCode
branch={
@@ -4277,11 +4301,11 @@ exports[`should render correctly when code scope is new code 1`] = `
</div>
<BoxedTabs
onSelect={[Function]}
- selected={0}
+ selected="new"
tabs={
Array [
Object {
- "key": 0,
+ "key": "new",
"label": <div
className="text-left overview-measures-tab"
>
@@ -4293,7 +4317,7 @@ exports[`should render correctly when code scope is new code 1`] = `
</div>,
},
Object {
- "key": 1,
+ "key": "overall",
"label": <div
className="text-left overview-measures-tab"
>
@@ -4314,7 +4338,10 @@ exports[`should render correctly when code scope is new code 1`] = `
}
/>
<div
+ aria-labelledby="tab-new"
className="overview-panel-content flex-1 bordered"
+ id="tabpanel-new"
+ role="tabpanel"
>
<MeasuresPanelIssueMeasureRow
branchLike={
@@ -4999,11 +5026,11 @@ exports[`should render correctly when code scope is overall code 1`] = `
</div>
<BoxedTabs
onSelect={[Function]}
- selected={1}
+ selected="overall"
tabs={
Array [
Object {
- "key": 0,
+ "key": "new",
"label": <div
className="text-left overview-measures-tab"
>
@@ -5015,7 +5042,7 @@ exports[`should render correctly when code scope is overall code 1`] = `
</div>,
},
Object {
- "key": 1,
+ "key": "overall",
"label": <div
className="text-left overview-measures-tab"
>
@@ -5036,7 +5063,10 @@ exports[`should render correctly when code scope is overall code 1`] = `
}
/>
<div
+ aria-labelledby="tab-overall"
className="overview-panel-content flex-1 bordered"
+ id="tabpanel-overall"
+ role="tabpanel"
>
<MeasuresPanelIssueMeasureRow
branchLike={
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTabs.tsx b/server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTabs.tsx
index 02ea904266f..77732e65287 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTabs.tsx
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTabs.tsx
@@ -18,7 +18,7 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
-import BoxedTabs from '../../../components/controls/BoxedTabs';
+import BoxedTabs, { getTabId, getTabPanelId } from '../../../components/controls/BoxedTabs';
import BranchIcon from '../../../components/icons/BranchIcon';
import PullRequestIcon from '../../../components/icons/PullRequestIcon';
import {
@@ -128,15 +128,17 @@ export default class BranchLikeTabs extends React.PureComponent<Props, State> {
tabs={TABS}
/>
- <BranchLikeTable
- branchLikes={branchLikesToDisplay}
- component={component}
- displayPurgeSetting={isBranchMode}
- onDelete={this.handleDeleteBranchLike}
- onRename={this.handleRenameBranchLike}
- onUpdatePurgeSetting={this.handleUpdatePurgeSetting}
- title={title}
- />
+ <div role="tabpanel" id={getTabPanelId(currentTab)} aria-labelledby={getTabId(currentTab)}>
+ <BranchLikeTable
+ branchLikes={branchLikesToDisplay}
+ component={component}
+ displayPurgeSetting={isBranchMode}
+ onDelete={this.handleDeleteBranchLike}
+ onRename={this.handleRenameBranchLike}
+ onUpdatePurgeSetting={this.handleUpdatePurgeSetting}
+ title={title}
+ />
+ </div>
{deleting && (
<DeleteBranchModal
diff --git a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeTabs-test.tsx.snap b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeTabs-test.tsx.snap
index 5ad71478b28..68626aa084a 100644
--- a/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeTabs-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeTabs-test.tsx.snap
@@ -33,75 +33,81 @@ exports[`should render all tabs correctly 1`] = `
]
}
/>
- <Memo(BranchLikeTable)
- branchLikes={
- Array [
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": true,
- "name": "master",
- },
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-1",
- },
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-11",
- },
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-12",
- },
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-2",
- },
- Object {
- "analysisDate": "2018-01-01",
- "excludedFromPurge": true,
- "isMain": false,
- "name": "branch-3",
- },
- ]
- }
- component={
- Object {
- "breadcrumbs": Array [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": Object {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": Array [
+ <div
+ aria-labelledby="tab-0"
+ id="tabpanel-0"
+ role="tabpanel"
+ >
+ <Memo(BranchLikeTable)
+ branchLikes={
+ Array [
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": true,
+ "name": "master",
+ },
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-1",
+ },
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-11",
+ },
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-12",
+ },
+ Object {
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-2",
+ },
Object {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
+ "analysisDate": "2018-01-01",
+ "excludedFromPurge": true,
+ "isMain": false,
+ "name": "branch-3",
+ },
+ ]
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
"name": "Sonar way",
},
- ],
- "tags": Array [],
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
}
- }
- displayPurgeSetting={true}
- onDelete={[Function]}
- onRename={[Function]}
- onUpdatePurgeSetting={[Function]}
- title="project_branch_pull_request.table.branch"
- />
+ displayPurgeSetting={true}
+ onDelete={[Function]}
+ onRename={[Function]}
+ onUpdatePurgeSetting={[Function]}
+ title="project_branch_pull_request.table.branch"
+ />
+ </div>
</Fragment>
`;
@@ -138,64 +144,70 @@ exports[`should render all tabs correctly 2`] = `
]
}
/>
- <Memo(BranchLikeTable)
- branchLikes={
- Array [
- Object {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "1",
- "target": "master",
- "title": "PR-1",
- },
- Object {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "key": "2",
- "target": "master",
- "title": "PR-2",
- },
- Object {
- "analysisDate": "2018-01-01",
- "base": "master",
- "branch": "feature/foo/bar",
- "isOrphan": true,
- "key": "2",
- "target": "llb-100",
- "title": "PR-2",
- },
- ]
- }
- component={
- Object {
- "breadcrumbs": Array [],
- "key": "my-project",
- "name": "MyProject",
- "qualifier": "TRK",
- "qualityGate": Object {
- "isDefault": true,
- "key": "30",
- "name": "Sonar way",
- },
- "qualityProfiles": Array [
+ <div
+ aria-labelledby="tab-1"
+ id="tabpanel-1"
+ role="tabpanel"
+ >
+ <Memo(BranchLikeTable)
+ branchLikes={
+ Array [
Object {
- "deleted": false,
- "key": "my-qp",
- "language": "ts",
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "1",
+ "target": "master",
+ "title": "PR-1",
+ },
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "key": "2",
+ "target": "master",
+ "title": "PR-2",
+ },
+ Object {
+ "analysisDate": "2018-01-01",
+ "base": "master",
+ "branch": "feature/foo/bar",
+ "isOrphan": true,
+ "key": "2",
+ "target": "llb-100",
+ "title": "PR-2",
+ },
+ ]
+ }
+ component={
+ Object {
+ "breadcrumbs": Array [],
+ "key": "my-project",
+ "name": "MyProject",
+ "qualifier": "TRK",
+ "qualityGate": Object {
+ "isDefault": true,
+ "key": "30",
"name": "Sonar way",
},
- ],
- "tags": Array [],
+ "qualityProfiles": Array [
+ Object {
+ "deleted": false,
+ "key": "my-qp",
+ "language": "ts",
+ "name": "Sonar way",
+ },
+ ],
+ "tags": Array [],
+ }
}
- }
- displayPurgeSetting={false}
- onDelete={[Function]}
- onRename={[Function]}
- onUpdatePurgeSetting={[Function]}
- title="project_branch_pull_request.table.pull_request"
- />
+ displayPurgeSetting={false}
+ onDelete={[Function]}
+ onRename={[Function]}
+ onUpdatePurgeSetting={[Function]}
+ title="project_branch_pull_request.table.pull_request"
+ />
+ </div>
</Fragment>
`;
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx
index 294e89be5c9..5b0bd74fb42 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx
@@ -19,7 +19,7 @@
*/
import { groupBy } from 'lodash';
import * as React from 'react';
-import BoxedTabs from '../../../components/controls/BoxedTabs';
+import BoxedTabs, { getTabId, getTabPanelId } from '../../../components/controls/BoxedTabs';
import RuleDescription from '../../../components/rules/RuleDescription';
import { isInput, isShortcut } from '../../../helpers/keyboardEventHelpers';
import { KeyboardKeys } from '../../../helpers/keycodes';
@@ -187,7 +187,13 @@ export default class HotspotViewerTabs extends React.PureComponent<Props, State>
return (
<>
<BoxedTabs onSelect={this.handleSelectTabs} selected={currentTab.key} tabs={tabs} />
- <div className="bordered huge-spacer-bottom">{currentTab.content}</div>
+ <div
+ className="bordered huge-spacer-bottom"
+ role="tabpanel"
+ aria-labelledby={getTabId(currentTab.key)}
+ id={getTabPanelId(currentTab.key)}>
+ {currentTab.content}
+ </div>
</>
);
}
diff --git a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap
index 90ef112adf5..8f26bcf4641 100644
--- a/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap
@@ -70,7 +70,10 @@ exports[`should render correctly: fix 1`] = `
}
/>
<div
+ aria-labelledby="tab-fix"
className="bordered huge-spacer-bottom"
+ id="tabpanel-fix"
+ role="tabpanel"
>
<RuleDescription
className="big-padded"
@@ -158,7 +161,10 @@ exports[`should render correctly: risk 1`] = `
}
/>
<div
+ aria-labelledby="tab-code"
className="bordered huge-spacer-bottom"
+ id="tabpanel-code"
+ role="tabpanel"
>
<div
className="padded"
@@ -241,7 +247,10 @@ exports[`should render correctly: vulnerability 1`] = `
}
/>
<div
+ aria-labelledby="tab-vulnerability"
className="bordered huge-spacer-bottom"
+ id="tabpanel-vulnerability"
+ role="tabpanel"
>
<RuleDescription
className="big-padded"
@@ -329,7 +338,10 @@ exports[`should render correctly: with comments or changelog element 1`] = `
}
/>
<div
+ aria-labelledby="tab-code"
className="bordered huge-spacer-bottom"
+ id="tabpanel-code"
+ role="tabpanel"
>
<div
className="padded"
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx
index 102dead85c2..d2f72177858 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx
@@ -20,6 +20,7 @@
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import Link from '../../../../components/common/Link';
+import { getTabId, getTabPanelId } from '../../../../components/controls/BoxedTabs';
import { Button } from '../../../../components/controls/buttons';
import { Alert } from '../../../../components/ui/Alert';
import DeferredSpinner from '../../../../components/ui/DeferredSpinner';
@@ -77,7 +78,11 @@ export default function AlmTabRenderer(props: AlmTabRendererProps) {
const preventCreation = loadingProjectCount || (!multipleAlmEnabled && definitions.length > 0);
return (
- <div className="bordered">
+ <div
+ className="bordered"
+ role="tabpanel"
+ id={getTabPanelId(almTab)}
+ aria-labelledby={getTabId(almTab)}>
<div className="big-padded">
<DeferredSpinner loading={loadingAlmDefinitions}>
{definitions.length === 0 && (
diff --git a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap
index f6ea40b3c1c..895747d0970 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap
+++ b/server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap
@@ -2,7 +2,10 @@
exports[`should render correctly for multi-ALM binding: editing a definition 1`] = `
<div
+ aria-labelledby="tab-azure"
className="bordered"
+ id="tabpanel-azure"
+ role="tabpanel"
>
<div
className="big-padded"
@@ -48,7 +51,10 @@ exports[`should render correctly for multi-ALM binding: editing a definition 1`]
exports[`should render correctly for multi-ALM binding: loaded 1`] = `
<div
+ aria-labelledby="tab-azure"
className="bordered"
+ id="tabpanel-azure"
+ role="tabpanel"
>
<div
className="big-padded"
@@ -94,7 +100,10 @@ exports[`should render correctly for multi-ALM binding: loaded 1`] = `
exports[`should render correctly for multi-ALM binding: loading ALM definitions 1`] = `
<div
+ aria-labelledby="tab-azure"
className="bordered"
+ id="tabpanel-azure"
+ role="tabpanel"
>
<div
className="big-padded"
@@ -140,7 +149,10 @@ exports[`should render correctly for multi-ALM binding: loading ALM definitions
exports[`should render correctly for multi-ALM binding: loading project count 1`] = `
<div
+ aria-labelledby="tab-azure"
className="bordered"
+ id="tabpanel-azure"
+ role="tabpanel"
>
<div
className="big-padded"
@@ -186,7 +198,10 @@ exports[`should render correctly for multi-ALM binding: loading project count 1`
exports[`should render correctly for single-ALM binding 1`] = `
<div
+ aria-labelledby="tab-azure"
className="bordered"
+ id="tabpanel-azure"
+ role="tabpanel"
>
<div
className="big-padded"
@@ -232,7 +247,10 @@ exports[`should render correctly for single-ALM binding 1`] = `
exports[`should render correctly for single-ALM binding 2`] = `
<div
+ aria-labelledby="tab-azure"
className="bordered"
+ id="tabpanel-azure"
+ role="tabpanel"
>
<div
className="big-padded"
@@ -278,7 +296,10 @@ exports[`should render correctly for single-ALM binding 2`] = `
exports[`should render correctly for single-ALM binding 3`] = `
<div
+ aria-labelledby="tab-azure"
className="bordered"
+ id="tabpanel-azure"
+ role="tabpanel"
>
<div
className="big-padded"
@@ -324,7 +345,10 @@ exports[`should render correctly for single-ALM binding 3`] = `
exports[`should render correctly with validation: create a first 1`] = `
<div
+ aria-labelledby="tab-azure"
className="bordered"
+ id="tabpanel-azure"
+ role="tabpanel"
>
<div
className="big-padded"
@@ -360,7 +384,10 @@ exports[`should render correctly with validation: create a first 1`] = `
exports[`should render correctly with validation: create a second 1`] = `
<div
+ aria-labelledby="tab-azure"
className="bordered"
+ id="tabpanel-azure"
+ role="tabpanel"
>
<div
className="big-padded"
@@ -410,7 +437,10 @@ exports[`should render correctly with validation: create a second 1`] = `
exports[`should render correctly with validation: default 1`] = `
<div
+ aria-labelledby="tab-azure"
className="bordered"
+ id="tabpanel-azure"
+ role="tabpanel"
>
<div
className="big-padded"
@@ -460,7 +490,10 @@ exports[`should render correctly with validation: default 1`] = `
exports[`should render correctly with validation: empty 1`] = `
<div
+ aria-labelledby="tab-azure"
className="bordered"
+ id="tabpanel-azure"
+ role="tabpanel"
>
<div
className="big-padded"
@@ -496,7 +529,10 @@ exports[`should render correctly with validation: empty 1`] = `
exports[`should render correctly with validation: pass the correct key for bitbucket cloud 1`] = `
<div
+ aria-labelledby="tab-bitbucket"
className="bordered"
+ id="tabpanel-bitbucket"
+ role="tabpanel"
>
<div
className="big-padded"
diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx
index 88dd2eb57fe..0aeb3c0f341 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx
@@ -22,7 +22,7 @@ import { FormattedMessage } from 'react-intl';
import { useSearchParams } from 'react-router-dom';
import Link from '../../../../components/common/Link';
import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper';
-import BoxedTabs from '../../../../components/controls/BoxedTabs';
+import BoxedTabs, { getTabId, getTabPanelId } from '../../../../components/controls/BoxedTabs';
import { Alert } from '../../../../components/ui/Alert';
import { translate } from '../../../../helpers/l10n';
import { getBaseUrl } from '../../../../helpers/system';
@@ -130,7 +130,10 @@ export default function Authentication(props: Props) {
maxHeight: `calc(100vh - ${top + HEIGHT_ADJUSTMENT}px)`
}}
className="bordered overflow-y-auto tabbed-definitions"
- key={currentTab}>
+ key={currentTab}
+ role="tabpanel"
+ aria-labelledby={getTabId(currentTab)}
+ id={getTabPanelId(currentTab)}>
<div className="big-padded">
<Alert variant="info">
<FormattedMessage
diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-test.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-test.tsx
index 954ef528460..c429e9c33c0 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-test.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-test.tsx
@@ -27,15 +27,15 @@ it('should render tabs and allow navigation', async () => {
const user = userEvent.setup();
renderAuthentication();
- expect(screen.getAllByRole('button')).toHaveLength(4);
+ expect(screen.getAllByRole('tab')).toHaveLength(4);
- expect(screen.getByRole('button', { name: 'SAML' })).toHaveAttribute('aria-current', 'true');
+ expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'true');
- await user.click(screen.getByRole('button', { name: 'github GitHub' }));
+ await user.click(screen.getByRole('tab', { name: 'github GitHub' }));
- expect(screen.getByRole('button', { name: 'SAML' })).toHaveAttribute('aria-current', 'false');
- expect(screen.getByRole('button', { name: 'github GitHub' })).toHaveAttribute(
- 'aria-current',
+ expect(screen.getByRole('tab', { name: 'SAML' })).toHaveAttribute('aria-selected', 'false');
+ expect(screen.getByRole('tab', { name: 'github GitHub' })).toHaveAttribute(
+ 'aria-selected',
'true'
);
});
diff --git a/server/sonar-web/src/main/js/components/controls/BoxedTabs.tsx b/server/sonar-web/src/main/js/components/controls/BoxedTabs.tsx
index 53682deec15..1c5a028558c 100644
--- a/server/sonar-web/src/main/js/components/controls/BoxedTabs.tsx
+++ b/server/sonar-web/src/main/js/components/controls/BoxedTabs.tsx
@@ -21,7 +21,7 @@ import styled from '@emotion/styled';
import * as React from 'react';
import { colors, sizes } from '../../app/theme';
-export interface BoxedTabsProps<K> {
+export interface BoxedTabsProps<K extends string | number> {
className?: string;
onSelect: (key: K) => void;
selected?: K;
@@ -76,19 +76,21 @@ const ActiveBorder = styled.div<{ active: boolean }>`
top: -1px;
`;
-export default function BoxedTabs<K>(props: BoxedTabsProps<K>) {
+export default function BoxedTabs<K extends string | number>(props: BoxedTabsProps<K>) {
const { className, tabs, selected } = props;
return (
- <TabContainer className={className}>
+ <TabContainer className={className} role="tablist">
{tabs.map(({ key, label }, i) => (
<StyledTab
+ id={getTabId(key)}
active={selected === key}
- aria-current={selected === key}
+ aria-selected={selected === key}
+ aria-controls={getTabPanelId(key)}
// eslint-disable-next-line react/no-array-index-key
key={i}
onClick={() => selected !== key && props.onSelect(key)}
- type="button">
+ role="tab">
<ActiveBorder active={selected === key} />
{label}
</StyledTab>
@@ -96,3 +98,11 @@ export default function BoxedTabs<K>(props: BoxedTabsProps<K>) {
</TabContainer>
);
}
+
+export function getTabPanelId(key: string | number) {
+ return `tabpanel-${key}`;
+}
+
+export function getTabId(key: string | number) {
+ return `tab-${key}`;
+}
diff --git a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
index 57452118cb6..3103862c93c 100644
--- a/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
+++ b/server/sonar-web/src/main/js/components/controls/Tooltip.tsx
@@ -286,7 +286,10 @@ export class TooltipInner extends React.Component<TooltipProps, State> {
};
handleBlur = () => {
- this.setState({ visible: false });
+ if (this.mounted) {
+ this.setState({ visible: false });
+ }
+
if (this.props.onHide) {
this.props.onHide();
}
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/BoxedTabs-test.tsx b/server/sonar-web/src/main/js/components/controls/__tests__/BoxedTabs-test.tsx
index 746aeb63098..a2cab636bb4 100644
--- a/server/sonar-web/src/main/js/components/controls/__tests__/BoxedTabs-test.tsx
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/BoxedTabs-test.tsx
@@ -45,7 +45,7 @@ function mountRender(overrides: Partial<BoxedTabsProps<string>> = {}) {
return mount(dom(overrides));
}
-function dom<K>(overrides: Partial<BoxedTabsProps<K>>) {
+function dom(overrides: Partial<BoxedTabsProps<string>>) {
return (
<BoxedTabs
className="boxed-tabs"
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedTabs-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedTabs-test.tsx.snap
index e5c5524e456..daec2475906 100644
--- a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedTabs-test.tsx.snap
+++ b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedTabs-test.tsx.snap
@@ -108,6 +108,7 @@ exports[`should render correctly 1`] = `
>
<Styled(div)
className="boxed-tabs"
+ role="tablist"
>
<Insertion
cache={
@@ -296,13 +297,16 @@ exports[`should render correctly 1`] = `
/>
<div
className="boxed-tabs emotion-6"
+ role="tablist"
>
<Styled(button)
active={true}
- aria-current={true}
+ aria-controls="tabpanel-a"
+ aria-selected={true}
+ id="tab-a"
key="0"
onClick={[Function]}
- type="button"
+ role="tab"
>
<Insertion
cache={
@@ -510,10 +514,12 @@ exports[`should render correctly 1`] = `
}
/>
<button
- aria-current={true}
+ aria-controls="tabpanel-a"
+ aria-selected={true}
className="emotion-1"
+ id="tab-a"
onClick={[Function]}
- type="button"
+ role="tab"
>
<Styled(div)
active={true}
@@ -717,10 +723,12 @@ exports[`should render correctly 1`] = `
</Styled(button)>
<Styled(button)
active={false}
- aria-current={false}
+ aria-controls="tabpanel-b"
+ aria-selected={false}
+ id="tab-b"
key="1"
onClick={[Function]}
- type="button"
+ role="tab"
>
<Insertion
cache={
@@ -932,10 +940,12 @@ exports[`should render correctly 1`] = `
}
/>
<button
- aria-current={false}
+ aria-controls="tabpanel-b"
+ aria-selected={false}
className="emotion-3"
+ id="tab-b"
onClick={[Function]}
- type="button"
+ role="tab"
>
<Styled(div)
active={false}
@@ -1139,10 +1149,12 @@ exports[`should render correctly 1`] = `
</Styled(button)>
<Styled(button)
active={false}
- aria-current={false}
+ aria-controls="tabpanel-c"
+ aria-selected={false}
+ id="tab-c"
key="2"
onClick={[Function]}
- type="button"
+ role="tab"
>
<Insertion
cache={
@@ -1354,10 +1366,12 @@ exports[`should render correctly 1`] = `
}
/>
<button
- aria-current={false}
+ aria-controls="tabpanel-c"
+ aria-selected={false}
className="emotion-3"
+ id="tab-c"
onClick={[Function]}
- type="button"
+ role="tab"
>
<Styled(div)
active={false}
diff --git a/server/sonar-web/src/main/js/components/rules/RuleTabViewer.tsx b/server/sonar-web/src/main/js/components/rules/RuleTabViewer.tsx
index 4adc421d9e3..35bfee5a846 100644
--- a/server/sonar-web/src/main/js/components/rules/RuleTabViewer.tsx
+++ b/server/sonar-web/src/main/js/components/rules/RuleTabViewer.tsx
@@ -27,7 +27,7 @@ import { translate } from '../../helpers/l10n';
import { RuleDetails } from '../../types/types';
import { NoticeType } from '../../types/users';
import ScreenPositionHelper from '../common/ScreenPositionHelper';
-import BoxedTabs from '../controls/BoxedTabs';
+import BoxedTabs, { getTabId, getTabPanelId } from '../controls/BoxedTabs';
import MoreInfoRuleDescription from './MoreInfoRuleDescription';
import RuleDescription from './RuleDescription';
import './style.css';
@@ -312,7 +312,10 @@ export class RuleTabViewer extends React.PureComponent<RuleTabViewerProps, State
// We substract the footer height with padding (80) and the main layout padding (20)
maxHeight: scrollInTab ? `calc(100vh - ${top + 100}px)` : 'initial'
}}
- className="bordered display-flex-column">
+ className="bordered display-flex-column"
+ role="tabpanel"
+ aria-labelledby={getTabId(selectedTab.key)}
+ id={getTabPanelId(selectedTab.key)}>
{/* Adding a key to force re-rendering of the tab container, so that it resets the scroll position */}
<div className="overflow-y-auto spacer" key={selectedTab.key}>
{tabContent}