]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16855 [893325] Information or relationship only presented visually
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Fri, 26 Aug 2022 13:03:06 +0000 (15:03 +0200)
committersonartech <sonartech@sonarsource.com>
Tue, 30 Aug 2022 20:03:14 +0000 (20:03 +0000)
17 files changed:
server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
server/sonar-web/src/main/js/apps/overview/branches/MeasuresPanel.tsx
server/sonar-web/src/main/js/apps/overview/branches/__tests__/__snapshots__/MeasuresPanel-test.tsx.snap
server/sonar-web/src/main/js/apps/projectBranches/components/BranchLikeTabs.tsx
server/sonar-web/src/main/js/apps/projectBranches/components/__tests__/__snapshots__/BranchLikeTabs-test.tsx.snap
server/sonar-web/src/main/js/apps/security-hotspots/components/HotspotViewerTabs.tsx
server/sonar-web/src/main/js/apps/security-hotspots/components/__tests__/__snapshots__/HotspotViewerTabs-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/almIntegration/AlmTabRenderer.tsx
server/sonar-web/src/main/js/apps/settings/components/almIntegration/__tests__/__snapshots__/AlmTabRenderer-test.tsx.snap
server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx
server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-test.tsx
server/sonar-web/src/main/js/components/controls/BoxedTabs.tsx
server/sonar-web/src/main/js/components/controls/Tooltip.tsx
server/sonar-web/src/main/js/components/controls/__tests__/BoxedTabs-test.tsx
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/BoxedTabs-test.tsx.snap
server/sonar-web/src/main/js/components/rules/RuleTabViewer.tsx

index 32221bf8792e21f66497711e8cb9bbfe4bae3a62..af7cf936e5533673fbc3eec968aba2d85cfadfe3 100644 (file)
@@ -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'
     })
   );
index 0d3a43a18b8060d05e368998cf2a73cc295a8d8b..2774e105574f20dda7c4069b6ef2e96269070e89 100644 (file)
@@ -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', {
index 0f1e50315e5bc09663f7651e05e668fff9c912a2..e3be38745b25b83402712dab8c2f645791d07df9 100644 (file)
@@ -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} />
             ) : (
index 97d531696031193f7d7fc13aab2d6eddaae7b96f..6936ea456340055ca69d473fc8ba110369470869 100644 (file)
@@ -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={
index 02ea904266f0d297cdf92bddf544abd592d71d42..77732e6528751c5cea5820324cdfbd50d6e1e0bd 100644 (file)
@@ -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
index 5ad71478b287c403dbb45196fb9531e31cdf39f6..68626aa084ae4bf5039715cb8596b9379abf30b2 100644 (file)
@@ -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>
 `;
 
index 294e89be5c9721a31542d034132af08c61d8dfd8..5b0bd74fb425d4cbfcc5a76e4627163592064a2e 100644 (file)
@@ -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>
       </>
     );
   }
index 90ef112adf52a752ebd8422cf0ac4892de60655a..8f26bcf4641725c3343487a3eb734f84d911f5bd 100644 (file)
@@ -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"
index 102dead85c2332a98526281e6560cca4f68ed3f0..d2f7217785877f40352224a96bcd0934d8bec917 100644 (file)
@@ -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 && (
index f6ea40b3c1c7fa17920041418bff2680a1bcc637..895747d09705d3bb2ffe237f233795b17554c505 100644 (file)
@@ -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"
index 88dd2eb57fe9b954152c2ccd75357fade2af10d4..0aeb3c0f3412961b1a355248573a8c1416ca53d8 100644 (file)
@@ -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
index 954ef528460acaf679e9c7cad266070cfdd079ae..c429e9c33c06c309f244f5013a86e19e8c60cc6e 100644 (file)
@@ -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'
   );
 });
index 53682deec152cab064ad9abfb2cff784a8b9afcb..1c5a028558c39e268a66fd57acb3257510564cd4 100644 (file)
@@ -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}`;
+}
index 57452118cb6b83926ab6e6033d34af808f8b34ac..3103862c93c6751ab0c3a770a15d8c7ac1f6a0ee 100644 (file)
@@ -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();
     }
index 746aeb63098273f337c1bc90b703da14cafe210e..a2cab636bb4bc015e9de49eb30a8ce12998dc509 100644 (file)
@@ -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"
index e5c5524e456083de4491ea8918ad9cb083101ac7..daec247590616e92c5bd59906ec8931d1d49beda 100644 (file)
@@ -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}
index 4adc421d9e307c240b01c573cb1fea0abefe39cd..35bfee5a8468264c4bb6515378e5c2efc7d936f5 100644 (file)
@@ -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}