]> source.dussan.org Git - sonarqube.git/commitdiff
[No JIRA] add jest-axe
authorguillaume-peoch-sonarsource <guillaume.peoch@sonarsource.com>
Tue, 23 May 2023 10:37:05 +0000 (12:37 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 3 Jul 2023 20:03:12 +0000 (20:03 +0000)
20 files changed:
server/sonar-web/.eslintrc
server/sonar-web/config/jest/SetupJestAxe.ts [new file with mode: 0644]
server/sonar-web/config/jest/SetupReactTestingLibrary.ts
server/sonar-web/eslint-local-rules/__tests__/use-await-expect-async-matcher-test.js [new file with mode: 0644]
server/sonar-web/eslint-local-rules/__tests__/use-await-expect-tohaveatooltipwithcontent-test.js [deleted file]
server/sonar-web/eslint-local-rules/index.js
server/sonar-web/eslint-local-rules/use-await-expect-async-matcher.js [new file with mode: 0644]
server/sonar-web/eslint-local-rules/use-await-expect-tohaveatooltipwithcontent.js [deleted file]
server/sonar-web/jest.config.js
server/sonar-web/package.json
server/sonar-web/src/main/js/apps/projectsManagement/__tests__/ProjectManagementApp-it.tsx
server/sonar-web/src/main/js/apps/users/__tests__/UsersApp-it.tsx
server/sonar-web/src/main/js/apps/users/components/PasswordForm.tsx
server/sonar-web/src/main/js/components/controls/HelpTooltip.tsx
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/HelpTooltip-test.tsx.snap
server/sonar-web/src/main/js/components/icons/Icon.tsx
server/sonar-web/src/main/js/components/icons/__tests__/__snapshots__/Icon-test.tsx.snap
server/sonar-web/src/main/js/components/ui/__tests__/__snapshots__/Alert-test.tsx.snap
server/sonar-web/src/main/js/types/jest.d.ts
server/sonar-web/yarn.lock

index d42654a5813cc8644b26a525f6653f6d77e0f0ce..eb6d15c4886230c69deadbfa7b5ef0efe70f32b5 100644 (file)
@@ -15,6 +15,6 @@
     "local-rules/convert-class-to-function-component": "warn",
     "local-rules/no-conditional-rendering-of-deferredspinner": "warn",
     "local-rules/use-jest-mocked": "warn",
-    "local-rules/use-await-expect-tohaveatooltipwithcontent": "warn"
+    "local-rules/use-await-expect-async-matcher": "warn"
   }
 }
diff --git a/server/sonar-web/config/jest/SetupJestAxe.ts b/server/sonar-web/config/jest/SetupJestAxe.ts
new file mode 100644 (file)
index 0000000..91eb4a1
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+import '@testing-library/jest-dom/extend-expect';
+import { axe, toHaveNoViolations } from 'jest-axe';
+
+expect.extend({
+  async toHaveNoA11yViolations(received: HTMLElement) {
+    const result = await axe(received);
+    return toHaveNoViolations.toHaveNoViolations(result);
+  },
+});
index c7fcfac0aee34dee5e31900548c57582d24106f1..04589b32dc0af26b0234c56e8e419d623bd75dbc 100644 (file)
@@ -24,7 +24,6 @@ configure({
   asyncUtilTimeout: 3000,
 });
 
-// Don't forget to update src/main/js/types/jest.d.ts when registering custom matchers.
 expect.extend({
   async toHaveATooltipWithContent(received: any, content: string) {
     if (!(received instanceof Element)) {
diff --git a/server/sonar-web/eslint-local-rules/__tests__/use-await-expect-async-matcher-test.js b/server/sonar-web/eslint-local-rules/__tests__/use-await-expect-async-matcher-test.js
new file mode 100644 (file)
index 0000000..f87e32a
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+const { RuleTester } = require('eslint');
+const useJestMocked = require('../use-await-expect-async-matcher');
+
+const ruleTester = new RuleTester({
+  parser: require.resolve('@typescript-eslint/parser'),
+});
+
+ruleTester.run('use-await-expect-tohaveatooltipwithcontent', useJestMocked, {
+  valid: [
+    {
+      code: `await expect(node).toHaveATooltipWithContent("Help text");`,
+    },
+  ],
+  invalid: [
+    {
+      code: `expect(node).toHaveATooltipWithContent("Help text");`,
+      errors: [
+        {
+          message:
+            'expect.toHaveATooltipWithContent() is asynchronous; you must prefix expect() with await',
+        },
+      ],
+      output: `await expect(node).toHaveATooltipWithContent("Help text");`,
+    },
+  ],
+});
diff --git a/server/sonar-web/eslint-local-rules/__tests__/use-await-expect-tohaveatooltipwithcontent-test.js b/server/sonar-web/eslint-local-rules/__tests__/use-await-expect-tohaveatooltipwithcontent-test.js
deleted file mode 100644 (file)
index 5c738aa..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-const { RuleTester } = require('eslint');
-const useJestMocked = require('../use-await-expect-tohaveatooltipwithcontent');
-
-const ruleTester = new RuleTester({
-  parser: require.resolve('@typescript-eslint/parser'),
-});
-
-ruleTester.run('use-await-expect-tohaveatooltipwithcontent', useJestMocked, {
-  valid: [
-    {
-      code: `await expect(node).toHaveATooltipWithContent("Help text");`,
-    },
-  ],
-  invalid: [
-    {
-      code: `expect(node).toHaveATooltipWithContent("Help text");`,
-      errors: [{ messageId: 'useAwaitExpectToHaveATooltipWithContent' }],
-    },
-  ],
-});
index 930d73182433b30b51fff79220272ebdac10bbaf..6f38c65a64578c8f7f791b2a29767868c330ac01 100644 (file)
@@ -25,5 +25,5 @@ module.exports = {
   'use-componentqualifier-enum': require('./use-componentqualifier-enum'),
   'use-metrickey-enum': require('./use-metrickey-enum'),
   'use-metrictype-enum': require('./use-metrictype-enum'),
-  'use-await-expect-tohaveatooltipwithcontent': require('./use-await-expect-tohaveatooltipwithcontent'),
+  'use-await-expect-async-matcher': require('./use-await-expect-async-matcher'),
 };
diff --git a/server/sonar-web/eslint-local-rules/use-await-expect-async-matcher.js b/server/sonar-web/eslint-local-rules/use-await-expect-async-matcher.js
new file mode 100644 (file)
index 0000000..336c070
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+module.exports = {
+  meta: {
+    fixable: 'code',
+  },
+  create(context) {
+    return {
+      Identifier(node) {
+        if (
+          ['toHaveATooltipWithContent', 'toHaveNoA11yViolations'].includes(node.name) &&
+          node.parent?.parent?.parent?.type !== 'AwaitExpression'
+        ) {
+          context.report({
+            node: node.parent?.parent?.parent,
+            message: `expect.${node.name}() is asynchronous; you must prefix expect() with await`,
+            fix(fixer) {
+              return fixer.insertTextBefore(node.parent?.parent?.parent, 'await ');
+            },
+          });
+        }
+      },
+    };
+  },
+};
diff --git a/server/sonar-web/eslint-local-rules/use-await-expect-tohaveatooltipwithcontent.js b/server/sonar-web/eslint-local-rules/use-await-expect-tohaveatooltipwithcontent.js
deleted file mode 100644 (file)
index 05c2650..0000000
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2023 SonarSource SA
- * mailto:info AT sonarsource DOT com
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-module.exports = {
-  meta: {
-    messages: {
-      useAwaitExpectToHaveATooltipWithContent:
-        'expect.toHaveATooltipWithContent() is asynchronous; you must prefix expect() with await',
-    },
-  },
-  create(context) {
-    return {
-      Identifier(node) {
-        if (
-          node.name === 'toHaveATooltipWithContent' &&
-          node.parent?.parent?.parent?.type !== 'AwaitExpression'
-        ) {
-          context.report({ node, messageId: 'useAwaitExpectToHaveATooltipWithContent' });
-        }
-      },
-    };
-  },
-};
index 58d840de95a2b7ea62b77931c92b068e6272fa10..567f049d18de8e90a60f54a691a3d2c1aaea47bd 100644 (file)
@@ -19,7 +19,10 @@ module.exports = {
     '<rootDir>/config/jest/SetupTestEnvironment.ts',
     '<rootDir>/config/jest/SetupTheme.js',
   ],
-  setupFilesAfterEnv: ['<rootDir>/config/jest/SetupReactTestingLibrary.ts'],
+  setupFilesAfterEnv: [
+    '<rootDir>/config/jest/SetupReactTestingLibrary.ts',
+    '<rootDir>/config/jest/SetupJestAxe.ts',
+  ],
   snapshotSerializers: ['enzyme-to-json/serializer', '@emotion/jest/serializer'],
   testEnvironment: 'jsdom',
   testPathIgnorePatterns: [
index eb0daa0571bd3dc7fafa81835f08418a6d3562b5..6d42f3de7bb4657246ec11bc8696a96e8d4efc20 100644 (file)
@@ -60,6 +60,7 @@
     "@types/dompurify": "3.0.2",
     "@types/enzyme": "3.10.13",
     "@types/jest": "29.5.2",
+    "@types/jest-axe": "3.5.5",
     "@types/lodash": "4.14.195",
     "@types/node": "18.16.18",
     "@types/react": "17.0.39",
@@ -93,6 +94,7 @@
     "fs-extra": "11.1.1",
     "http-proxy": "1.18.1",
     "jest": "29.5.0",
+    "jest-axe": "7.0.1",
     "jest-environment-jsdom": "29.5.0",
     "jest-junit": "16.0.0",
     "jsdom": "21.1.1",
index 5a0ae3110960d0c8d6dff6f6f011d5e4e1c485a4..ef615cd608dbe09e3dcf777e34a498fffb090967 100644 (file)
@@ -99,7 +99,7 @@ const ui = {
   qualifierFilter: byRole('combobox', { name: 'projects_management.filter_by_component' }),
   analysisDateFilter: byPlaceholderText('last_analysis_before'),
   provisionedFilter: byRole('checkbox', {
-    name: 'provisioning.only_provisioned provisioning.only_provisioned.tooltip',
+    name: 'provisioning.only_provisioned help',
   }),
   searchFilter: byRole('searchbox', { name: 'search.search_by_name_or_key' }),
 
index 56e63dc6293d595577b4ded1b1dccb29e2dbd82a..e455cc52725ea05ea0b79b12f95f50b1cde7594c 100644 (file)
@@ -58,6 +58,7 @@ const ui = {
   bobUpdateButton: byRole('button', { name: 'users.manage_user.bob.marley' }),
   scmAddButton: byRole('button', { name: 'add_verb' }),
   createUserDialogButton: byRole('button', { name: 'create' }),
+  cancelButton: byRole('button', { name: 'cancel' }),
   reloadButton: byRole('button', { name: 'reload' }),
   doneButton: byRole('button', { name: 'done' }),
   changeButton: byRole('button', { name: 'change_verb' }),
@@ -295,13 +296,7 @@ describe('in non managed mode', () => {
     const user = userEvent.setup();
     renderUsersApp();
 
-    await act(async () =>
-      user.click(
-        await within(await ui.aliceRow.find()).findByRole('button', {
-          name: 'users.update_users_groups.alice.merveille',
-        })
-      )
-    );
+    await act(async () => user.click(await ui.aliceUpdateGroupButton.find()));
     expect(await ui.dialogGroups.find()).toBeInTheDocument();
 
     expect(ui.getGroups()).toHaveLength(2);
@@ -334,13 +329,7 @@ describe('in non managed mode', () => {
     const user = userEvent.setup();
     renderUsersApp();
 
-    await act(async () =>
-      user.click(
-        await within(await ui.aliceRow.find()).findByRole('button', {
-          name: 'users.manage_user.alice.merveille',
-        })
-      )
-    );
+    await act(async () => user.click(await ui.aliceUpdateButton.find()));
     await user.click(
       await within(ui.aliceRow.get()).findByRole('button', { name: 'update_details' })
     );
@@ -360,13 +349,7 @@ describe('in non managed mode', () => {
     const user = userEvent.setup();
     renderUsersApp();
 
-    await act(async () =>
-      user.click(
-        await within(await ui.aliceRow.find()).findByRole('button', {
-          name: 'users.manage_user.alice.merveille',
-        })
-      )
-    );
+    await act(async () => user.click(await ui.aliceUpdateButton.find()));
     await user.click(
       await within(ui.aliceRow.get()).findByRole('button', { name: 'users.deactivate' })
     );
@@ -388,13 +371,7 @@ describe('in non managed mode', () => {
     const currentUser = mockLoggedInUser({ login: 'alice.merveille' });
     renderUsersApp([], currentUser);
 
-    await act(async () =>
-      user.click(
-        await within(await ui.aliceRow.find()).findByRole('button', {
-          name: 'users.manage_user.alice.merveille',
-        })
-      )
-    );
+    await act(async () => user.click(await ui.aliceUpdateButton.find()));
     await user.click(
       await within(ui.aliceRow.get()).findByRole('button', { name: 'my_profile.password.title' })
     );
@@ -511,8 +488,8 @@ describe('in manage mode', () => {
       await user.click(await ui.localFilter.find());
     });
 
-    expect(ui.aliceRowWithLocalBadge.get()).toBeInTheDocument();
     expect(ui.bobRow.query()).not.toBeInTheDocument();
+    expect(ui.aliceRowWithLocalBadge.get()).toBeInTheDocument();
   });
 
   it('should be able to change tokens of a user', async () => {
@@ -586,12 +563,11 @@ describe('in manage mode', () => {
       authenticationHandler.addProvisioningTask({
         status: TaskStatuses.Failed,
         executedAt: '2022-02-03T11:45:35+0200',
-        errorMessage: "T'es mauvais Jacques",
+        errorMessage: 'Error Message',
       });
       renderUsersApp([Feature.GithubProvisioning]);
       await act(async () => expect(await ui.githubProvisioningAlert.find()).toBeInTheDocument());
-      expect(screen.queryByText("T'es mauvais Jacques")).not.toBeInTheDocument();
-
+      expect(screen.queryByText('Error Message')).not.toBeInTheDocument();
       expect(ui.githubProvisioningSuccess.query()).not.toBeInTheDocument();
     });
 
@@ -603,11 +579,11 @@ describe('in manage mode', () => {
       authenticationHandler.addProvisioningTask({
         status: TaskStatuses.Failed,
         executedAt: '2022-02-03T11:45:35+0200',
-        errorMessage: "T'es mauvais Jacques",
+        errorMessage: 'Error Message',
       });
       renderUsersApp([Feature.GithubProvisioning]);
       await act(async () => expect(await ui.githubProvisioningAlert.find()).toBeInTheDocument());
-      expect(screen.queryByText("T'es mauvais Jacques")).not.toBeInTheDocument();
+      expect(screen.queryByText('Error Message')).not.toBeInTheDocument();
       expect(ui.githubProvisioningSuccess.query()).not.toBeInTheDocument();
       expect(ui.githubProvisioningInProgress.query()).not.toBeInTheDocument();
     });
@@ -621,6 +597,57 @@ it('should render external identity Providers', async () => {
   expect(await ui.denisRow.find()).toHaveTextContent(/test2: UnknownExternalProvider/);
 });
 
+it('accessibility', async () => {
+  userHandler.setIsManaged(false);
+  const user = userEvent.setup();
+  renderUsersApp();
+
+  // user list page should be accessible
+  expect(await ui.aliceRow.find()).toBeInTheDocument();
+  await expect(document.body).toHaveNoA11yViolations();
+
+  // user creation dialog should be accessible
+  await user.click(await ui.createUserButton.find());
+  expect(await ui.dialogCreateUser.find()).toBeInTheDocument();
+  await expect(ui.dialogCreateUser.get()).toHaveNoA11yViolations();
+  await user.click(ui.cancelButton.get());
+
+  // users group membership dialog should be accessible
+  user.click(await ui.aliceUpdateGroupButton.find());
+  expect(await ui.dialogGroups.find()).toBeInTheDocument();
+  await expect(await ui.dialogGroups.find()).toHaveNoA11yViolations();
+  await act(async () => {
+    await user.click(ui.doneButton.get());
+  });
+
+  // user update dialog should be accessible
+  await user.click(await ui.aliceUpdateButton.find());
+  await user.click(await ui.aliceRow.byRole('button', { name: 'update_details' }).find());
+  expect(await ui.dialogUpdateUser.find()).toBeInTheDocument();
+  await expect(await ui.dialogUpdateUser.find()).toHaveNoA11yViolations();
+  await user.click(ui.cancelButton.get());
+
+  // user tokens dialog should be accessible
+  user.click(
+    await ui.aliceRow
+      .byRole('button', {
+        name: 'users.update_tokens_for_x.Alice Merveille',
+      })
+      .find()
+  );
+  expect(await ui.dialogTokens.find()).toBeInTheDocument();
+  await expect(await ui.dialogTokens.find()).toHaveNoA11yViolations();
+  await user.click(ui.doneButton.get());
+
+  // user password dialog should be accessible
+  await user.click(await ui.aliceUpdateButton.find());
+  await user.click(
+    await ui.aliceRow.byRole('button', { name: 'my_profile.password.title' }).find()
+  );
+  expect(await ui.dialogPasswords.find()).toBeInTheDocument();
+  await expect(await ui.dialogPasswords.find()).toHaveNoA11yViolations();
+});
+
 function renderUsersApp(featureList: Feature[] = [], currentUser?: CurrentUser) {
   // eslint-disable-next-line testing-library/no-unnecessary-act
   renderApp('admin/users', <UsersApp />, {
index 5611cb2de2c26aa402d60a45e512e5a272d6f024..d13ff24e7fbbb052133b167b3e84d4eada4784b4 100644 (file)
@@ -19,8 +19,8 @@
  */
 import * as React from 'react';
 import { changePassword } from '../../../api/users';
-import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
 import Modal from '../../../components/controls/Modal';
+import { ResetButtonLink, SubmitButton } from '../../../components/controls/buttons';
 import { Alert } from '../../../components/ui/Alert';
 import MandatoryFieldMarker from '../../../components/ui/MandatoryFieldMarker';
 import MandatoryFieldsExplanation from '../../../components/ui/MandatoryFieldsExplanation';
@@ -118,7 +118,7 @@ export default class PasswordForm extends React.PureComponent<Props, State> {
                   <MandatoryFieldMarker />
                 </label>
                 {/* keep this fake field to hack browser autofill */}
-                <input className="hidden" name="old-password-fake" type="password" />
+                <input className="hidden" aria-hidden name="old-password-fake" type="password" />
                 <input
                   id="old-user-password"
                   name="old-password"
@@ -135,7 +135,7 @@ export default class PasswordForm extends React.PureComponent<Props, State> {
                 <MandatoryFieldMarker />
               </label>
               {/* keep this fake field to hack browser autofill */}
-              <input className="hidden" name="password-fake" type="password" />
+              <input className="hidden" aria-hidden name="password-fake" type="password" />
               <input
                 id="user-password"
                 name="password"
@@ -151,7 +151,7 @@ export default class PasswordForm extends React.PureComponent<Props, State> {
                 <MandatoryFieldMarker />
               </label>
               {/* keep this fake field to hack browser autofill */}
-              <input className="hidden" name="confirm-password-fake" type="password" />
+              <input className="hidden" aria-hidden name="confirm-password-fake" type="password" />
               <input
                 id="confirm-user-password"
                 name="confirm-password"
index 1ee3753ad9e8cb3f4eb60504b5b34f04bfca9698..517fea4d2dad6b5464c9598143f1cdbd6fab007c 100644 (file)
@@ -60,6 +60,8 @@ export default function HelpTooltip(props: Props) {
             <HelpIcon
               fill={colors.gray60}
               size={size}
+              role="img"
+              aria-label={isInteractive ? translate('tooltip_is_interactive') : translate('help')}
               description={
                 isInteractive ? (
                   <>
index 34f670866ac198597ebb101116c9b4c7c0e9df88..f57bb80bd3ac7ca8121904bf8c2453b714e6cea8 100644 (file)
@@ -33,12 +33,14 @@ exports[`should render properly: default 1`] = `
       data-testid="help-tooltip-activator"
     >
       <HelpIcon
+        aria-label="help"
         description={
           <div
             className="my-overlay"
           />
         }
         fill="#888"
+        role="img"
         size={12}
       />
     </span>
index 5e02dc790fe3fde74d949aa370e04535ef429964..6bd8195d7ab79d1ff3a4f60156c809a4c7c0683a 100644 (file)
@@ -20,7 +20,7 @@
 import { uniqueId } from 'lodash';
 import * as React from 'react';
 
-export interface IconProps extends React.AriaAttributes {
+export interface IconProps extends React.HTMLAttributes<SVGSVGElement> {
   className?: string;
   fill?: string;
   size?: number;
@@ -28,7 +28,7 @@ export interface IconProps extends React.AriaAttributes {
   description?: React.ReactNode;
 }
 
-interface Props extends React.AriaAttributes {
+interface Props extends React.HTMLAttributes<SVGSVGElement> {
   children: React.ReactNode;
   className?: string;
   size?: number;
@@ -72,7 +72,6 @@ export default function Icon({
       width={width}
       xmlnsXlink="http://www.w3.org/1999/xlink"
       xmlSpace="preserve"
-      role="img"
       aria-describedby={description && !hidden ? id : undefined}
       {...iconProps}
     >
index bb617d3683c1f6bbf7d6c6e7f5569cf1bff76497..8ce955805714caf264934b21623479dd7acd601b 100644 (file)
@@ -1,3 +1,3 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`should render correctly 1`] = `"<div><svg height="16" style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 1.41421;" version="1.1" viewBox="0 0 16 16" width="16" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" role="img"><path d="test-path"></path></svg></div>"`;
+exports[`should render correctly 1`] = `"<div><svg height="16" style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 1.41421;" version="1.1" viewBox="0 0 16 16" width="16" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve"><path d="test-path"></path></svg></div>"`;
index 3f1c5a7849485228ff3ebd1e6dc8ff32fe3b45d6..ed55cd5b070345631f3e5cd1d9ad48ea45a3f1ca 100644 (file)
@@ -86,7 +86,6 @@ exports[`should render banner alert with correct css 1`] = `
     >
       <svg
         height="16"
-        role="img"
         space="preserve"
         style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421"
         version="1.1"
index 6fcf255c824cd20d65b2eb139e52460f6d8223f9..3915e50c17d0e9522ccde8dbf0190e9a2940c945 100644 (file)
@@ -21,5 +21,6 @@
 declare namespace jest {
   interface Matchers<R> {
     toHaveATooltipWithContent(content: string): Promise<CustomMatcherResult>;
+    toHaveNoA11yViolations(): Promise<CustomMatcherResult>;
   }
 }
index db1cfd1d1366fed64f4ce4f2cae1054f1897a606..17a4c72095dc18ebe1320437f177d1b83e4bcdd7 100644 (file)
@@ -4230,6 +4230,16 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/jest-axe@npm:3.5.5":
+  version: 3.5.5
+  resolution: "@types/jest-axe@npm:3.5.5"
+  dependencies:
+    "@types/jest": "*"
+    axe-core: ^3.5.5
+  checksum: 535038968034fe80fb466dcd5939ea5d9e9adb3ef00852ded3e41c62536c05137eb30bcbfd608142d2bc571d65c20b8e3563181674fb48594c2662d340bb4da5
+  languageName: node
+  linkType: hard
+
 "@types/jest@npm:*":
   version: 27.4.0
   resolution: "@types/jest@npm:27.4.0"
@@ -4852,6 +4862,7 @@ __metadata:
     "@types/dompurify": 3.0.2
     "@types/enzyme": 3.10.13
     "@types/jest": 29.5.2
+    "@types/jest-axe": 3.5.5
     "@types/lodash": 4.14.195
     "@types/node": 18.16.18
     "@types/react": 17.0.39
@@ -4898,6 +4909,7 @@ __metadata:
     fs-extra: 11.1.1
     http-proxy: 1.18.1
     jest: 29.5.0
+    jest-axe: 7.0.1
     jest-environment-jsdom: 29.5.0
     jest-junit: 16.0.0
     jsdom: 21.1.1
@@ -5335,6 +5347,20 @@ __metadata:
   languageName: node
   linkType: hard
 
+"axe-core@npm:4.5.1":
+  version: 4.5.1
+  resolution: "axe-core@npm:4.5.1"
+  checksum: db90c6b41483e9c3452393933072fe0b8c2221e6d9b96ae0e6a03a4ce1e4c35bec539c92e9b6fcd63c4acf6678ad3c3ca7f5ab1d884210d157867cc54acd4f6a
+  languageName: node
+  linkType: hard
+
+"axe-core@npm:^3.5.5":
+  version: 3.5.6
+  resolution: "axe-core@npm:3.5.6"
+  checksum: 000777d2b6bf1f390beb1fb4b8714ed9127797c021c345b032db0c144e07320dbbe8cb0bcb7688b90b79cfbd3cdc1f27a4dc857804e3c61d7e0defb34deeb830
+  languageName: node
+  linkType: hard
+
 "axe-core@npm:^4.6.2":
   version: 4.6.3
   resolution: "axe-core@npm:4.6.3"
@@ -9048,6 +9074,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jest-axe@npm:7.0.1":
+  version: 7.0.1
+  resolution: "jest-axe@npm:7.0.1"
+  dependencies:
+    axe-core: 4.5.1
+    chalk: 4.1.2
+    jest-matcher-utils: 29.2.2
+    lodash.merge: 4.6.2
+  checksum: 3c9b0b8669f6fe5d143ee74aa0414831ea75bbe9e38551aa82352d9102e2827952a14cb3a6782fbd847a0febf6da3461424d1a2eb77256159719890f502cb01f
+  languageName: node
+  linkType: hard
+
 "jest-changed-files@npm:^29.5.0":
   version: 29.5.0
   resolution: "jest-changed-files@npm:29.5.0"
@@ -9163,6 +9201,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jest-diff@npm:^29.2.1, jest-diff@npm:^29.5.0":
+  version: 29.5.0
+  resolution: "jest-diff@npm:29.5.0"
+  dependencies:
+    chalk: ^4.0.0
+    diff-sequences: ^29.4.3
+    jest-get-type: ^29.4.3
+    pretty-format: ^29.5.0
+  checksum: dfd0f4a299b5d127779c76b40106c37854c89c3e0785098c717d52822d6620d227f6234c3a9291df204d619e799e3654159213bf93220f79c8e92a55475a3d39
+  languageName: node
+  linkType: hard
+
 "jest-diff@npm:^29.3.1":
   version: 29.3.1
   resolution: "jest-diff@npm:29.3.1"
@@ -9175,18 +9225,6 @@ __metadata:
   languageName: node
   linkType: hard
 
-"jest-diff@npm:^29.5.0":
-  version: 29.5.0
-  resolution: "jest-diff@npm:29.5.0"
-  dependencies:
-    chalk: ^4.0.0
-    diff-sequences: ^29.4.3
-    jest-get-type: ^29.4.3
-    pretty-format: ^29.5.0
-  checksum: dfd0f4a299b5d127779c76b40106c37854c89c3e0785098c717d52822d6620d227f6234c3a9291df204d619e799e3654159213bf93220f79c8e92a55475a3d39
-  languageName: node
-  linkType: hard
-
 "jest-docblock@npm:^29.4.3":
   version: 29.4.3
   resolution: "jest-docblock@npm:29.4.3"
@@ -9310,6 +9348,18 @@ __metadata:
   languageName: node
   linkType: hard
 
+"jest-matcher-utils@npm:29.2.2":
+  version: 29.2.2
+  resolution: "jest-matcher-utils@npm:29.2.2"
+  dependencies:
+    chalk: ^4.0.0
+    jest-diff: ^29.2.1
+    jest-get-type: ^29.2.0
+    pretty-format: ^29.2.1
+  checksum: 97ef2638ab826c25f84bfedea231cef091820ae0876ba316922da81145e950d2b9d2057d3645813b5ee880bb975ed4f22e228dda5d0d26a20715e575b675357d
+  languageName: node
+  linkType: hard
+
 "jest-matcher-utils@npm:^29.3.1":
   version: 29.3.1
   resolution: "jest-matcher-utils@npm:29.3.1"
@@ -10004,7 +10054,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"lodash.merge@npm:^4.6.2":
+"lodash.merge@npm:4.6.2, lodash.merge@npm:^4.6.2":
   version: 4.6.2
   resolution: "lodash.merge@npm:4.6.2"
   checksum: ad580b4bdbb7ca1f7abf7e1bce63a9a0b98e370cf40194b03380a46b4ed799c9573029599caebc1b14e3f24b111aef72b96674a56cfa105e0f5ac70546cdc005
@@ -11085,7 +11135,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"pretty-format@npm:^29.5.0":
+"pretty-format@npm:^29.2.1, pretty-format@npm:^29.5.0":
   version: 29.5.0
   resolution: "pretty-format@npm:29.5.0"
   dependencies: