]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16731 SONAR-16885 [891615] [892423]
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Thu, 4 Aug 2022 14:36:31 +0000 (16:36 +0200)
committersonartech <sonartech@sonarsource.com>
Mon, 8 Aug 2022 20:03:03 +0000 (20:03 +0000)
* [891615] Keyboard focus is lost or misplaced due to user interaction or content update
* [892423] Keyboard focus is lost or misplaced due to user interaction or content update

12 files changed:
server/sonar-web/src/main/js/apps/coding-rules/__tests__/CodingRules-it.ts
server/sonar-web/src/main/js/apps/component-measures/style.css
server/sonar-web/src/main/js/apps/issues/__tests__/IssueApp-it.tsx
server/sonar-web/src/main/js/apps/projects/filters/Filter.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/Filter-test.tsx
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/Filter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/LanguagesFilter-test.tsx.snap
server/sonar-web/src/main/js/apps/projects/filters/__tests__/__snapshots__/TagsFilter-test.tsx.snap
server/sonar-web/src/main/js/components/facet/FacetHeader.tsx
server/sonar-web/src/main/js/components/facet/FacetItem.tsx
server/sonar-web/src/main/js/components/facet/__tests__/Facet-it.tsx
server/sonar-web/src/main/js/components/search-navigator.css

index 81ad6fa1d73219d20cd242f13d860bc4773b4d5a..71914d5b4421113504a3c7cc0a444285b88bf3a6 100644 (file)
@@ -239,7 +239,7 @@ it('should have all type facet', async () => {
       'issue.type.VULNERABILITY',
       'issue.type.CODE_SMELL',
       'issue.type.SECURITY_HOTSPOT'
-    ].forEach(name => expect(screen.getByRole('link', { name })).toBeInTheDocument());
+    ].forEach(name => expect(screen.getByRole('button', { name })).toBeInTheDocument());
   });
 });
 
@@ -249,7 +249,7 @@ it('select the correct quality profile for bulk change base on language search',
   renderCodingRulesApp(mockLoggedInUser());
   const selectQP = handler.allQualityProfile('js')[0];
 
-  await user.click(await screen.findByRole('link', { name: 'JavaScript' }));
+  await user.click(await screen.findByRole('button', { name: 'JavaScript' }));
   await user.click(await screen.findByRole('button', { name: 'bulk_change' }));
   await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' }));
   const dialog = screen.getByRole('dialog', {
@@ -266,7 +266,7 @@ it('no quality profile for bulk cahnge base on language search', async () => {
   handler.setIsAdmin();
   renderCodingRulesApp(mockLoggedInUser());
 
-  await user.click(await screen.findByRole('link', { name: 'C' }));
+  await user.click(await screen.findByRole('button', { name: 'C' }));
   await user.click(await screen.findByRole('button', { name: 'bulk_change' }));
   await user.click(await screen.findByRole('link', { name: 'coding_rules.activate_in…' }));
   const dialog = screen.getByRole('dialog', {
index b4e0cdd5d32db2b638127d8664359c7e024878a8..2e7b5b50693653821abb338b134d7668de406340 100644 (file)
   margin-right: -4px;
 }
 
+button.search-navigator-facet {
+  text-align: start;
+}
+
 .search-navigator-facet .leak-box {
   height: var(--controlHeight);
   line-height: var(--controlHeight);
index 4514bf7353207209c3efbb40fdb1f0d4c477ea20..9e4d644cd42ef7f4c45dbe1fb8fde67f53fd5322 100644 (file)
@@ -158,8 +158,8 @@ it('should be able to navigate to other issue located in the same file', async (
 it('should support OWASP Top 10 version 2021', async () => {
   const user = userEvent.setup();
   renderIssueApp();
-  await user.click(await screen.findByRole('link', { name: 'issues.facet.standards' }));
-  const owaspTop102021 = screen.getByRole('link', { name: 'issues.facet.owaspTop10_2021' });
+  await user.click(await screen.findByRole('button', { name: 'issues.facet.standards' }));
+  const owaspTop102021 = screen.getByRole('button', { name: 'issues.facet.owaspTop10_2021' });
   expect(owaspTop102021).toBeInTheDocument();
 
   await user.click(owaspTop102021);
@@ -168,7 +168,7 @@ it('should support OWASP Top 10 version 2021', async () => {
       const standard = await handler.getStandards();
       /* eslint-disable-next-line testing-library/render-result-naming-convention */
       const linkName = renderOwaspTop102021Category(standard, val);
-      expect(await screen.findByRole('link', { name: linkName })).toBeInTheDocument();
+      expect(await screen.findByRole('button', { name: linkName })).toBeInTheDocument();
     })
   );
 });
@@ -415,7 +415,7 @@ describe('redirects', () => {
     );
 
     expect(
-      await screen.findByRole('link', { name: `issue.type.${IssueType.CodeSmell}` })
+      await screen.findByRole('button', { name: `issue.type.${IssueType.CodeSmell}` })
     ).toBeInTheDocument();
   });
 });
index 1693fd2494f3c7cd107edd9abac0dd6f6690cc12..8ed53ef69e8a2f08dae8a29feac24dea4dcb8145 100644 (file)
@@ -66,25 +66,36 @@ export default class Filter extends React.PureComponent<Props> {
     );
   }
 
-  handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+  getUrlOptionForSingleValue = (option: string) => {
+    return this.isSelected(option) ? null : option;
+  };
+
+  getUrlOptionForMultiValue = (
+    event: React.MouseEvent<HTMLButtonElement>,
+    option: string,
+    value: Option[]
+  ) => {
+    if (event.ctrlKey || event.metaKey) {
+      if (this.isSelected(option)) {
+        return value.length > 1 ? value.filter(val => val !== option).join(',') : null;
+      }
+
+      return value.concat(option).join(',');
+    }
+
+    return this.isSelected(option) && value.length < 2 ? null : option;
+  };
+
+  handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
     event.preventDefault();
-    event.currentTarget.blur();
 
     const { property, value } = this.props;
     const { key: option } = event.currentTarget.dataset;
-    let urlOption;
 
     if (option) {
-      if (Array.isArray(value) && (event.ctrlKey || event.metaKey)) {
-        if (this.isSelected(option)) {
-          urlOption = value.length > 1 ? value.filter(val => val !== option).join(',') : null;
-        } else {
-          urlOption = value.concat(option).join(',');
-        }
-      } else {
-        urlOption =
-          this.isSelected(option) && (!Array.isArray(value) || value.length < 2) ? null : option;
-      }
+      const urlOption = Array.isArray(value)
+        ? this.getUrlOptionForMultiValue(event, option, value)
+        : this.getUrlOptionForSingleValue(option);
 
       this.props.onQueryChange({ [property]: urlOption });
     }
@@ -110,6 +121,7 @@ export default class Filter extends React.PureComponent<Props> {
       'facet',
       'search-navigator-facet',
       'projects-facet',
+      'button-link',
       {
         active: this.isSelected(option),
         'search-navigator-facet-half': this.props.halfWidth
@@ -127,11 +139,12 @@ export default class Filter extends React.PureComponent<Props> {
       option > value;
 
     return (
-      <a
+      <button
         aria-label={this.props.renderAccessibleLabel(option)}
         className={className}
         data-key={option}
-        href="#"
+        type="button"
+        tabIndex={0}
         key={option}
         onClick={this.handleClick}>
         <span className="facet-name">
@@ -143,7 +156,7 @@ export default class Filter extends React.PureComponent<Props> {
             {this.renderOptionBar(facetValue)}
           </span>
         )}
-      </a>
+      </button>
     );
   }
 
index 14b9a2c55e3b587869fe946f40b4165ae096db3b..c2f55eed29f2430a49a630d03a59868ca8e64d25 100644 (file)
@@ -19,6 +19,7 @@
  */
 import { shallow } from 'enzyme';
 import * as React from 'react';
+import { mockEvent } from '../../../../helpers/testUtils';
 import Filter from '../Filter';
 
 it('renders', () => {
@@ -60,6 +61,69 @@ it('renders facet bar chart', () => {
   ).toMatchSnapshot();
 });
 
+it('should handle click when value is single', () => {
+  const onQueryChange = jest.fn();
+  const wrapper = shallowRender({ onQueryChange, value: 'option1' });
+
+  // select
+  wrapper.instance().handleClick(mockEvent({ currentTarget: { dataset: { key: 'option2' } } }));
+  expect(onQueryChange).toBeCalledWith({ foo: 'option2' });
+
+  onQueryChange.mockClear();
+
+  // deselect
+  wrapper.instance().handleClick(mockEvent({ currentTarget: { dataset: { key: 'option1' } } }));
+  expect(onQueryChange).toBeCalledWith({ foo: null });
+});
+
+it('should handle click when value is array', () => {
+  const onQueryChange = jest.fn();
+  const wrapper = shallowRender({ onQueryChange, value: ['option1', 'option2'] });
+
+  // select one
+  wrapper.instance().handleClick(mockEvent({ currentTarget: { dataset: { key: 'option2' } } }));
+  expect(onQueryChange).toBeCalledWith({ foo: 'option2' });
+
+  onQueryChange.mockClear();
+
+  // select other
+  wrapper.instance().handleClick(mockEvent({ currentTarget: { dataset: { key: 'option3' } } }));
+  expect(onQueryChange).toBeCalledWith({ foo: 'option3' });
+
+  onQueryChange.mockClear();
+
+  // select additional
+  wrapper
+    .instance()
+    .handleClick(mockEvent({ ctrlKey: true, currentTarget: { dataset: { key: 'option3' } } }));
+  expect(onQueryChange).toBeCalledWith({ foo: 'option1,option2,option3' });
+
+  onQueryChange.mockClear();
+
+  // deselect one
+  wrapper
+    .instance()
+    .handleClick(mockEvent({ metaKey: true, currentTarget: { dataset: { key: 'option2' } } }));
+  expect(onQueryChange).toBeCalledWith({ foo: 'option1' });
+});
+
+it('should handle click when value is array with one value', () => {
+  const onQueryChange = jest.fn();
+  const wrapper = shallowRender({ onQueryChange, value: ['option1'] });
+
+  // deselect one
+  wrapper
+    .instance()
+    .handleClick(mockEvent({ ctrlKey: true, currentTarget: { dataset: { key: 'option1' } } }));
+  expect(onQueryChange).toBeCalledWith({ foo: null });
+
+  onQueryChange.mockClear();
+
+  // deselect one
+  wrapper.instance().handleClick(mockEvent({ currentTarget: { dataset: { key: 'option1' } } }));
+  expect(onQueryChange).toBeCalledWith({ foo: null });
+});
+
 function shallowRender(overrides: Partial<Filter['props']> = {}) {
   return shallow<Filter>(
     <Filter
index c63c31cecea8df3a6c625e7f8e77e435cc8e35d8..43d1c0ae347f0dd5259147d043efb68a98084873 100644 (file)
@@ -8,51 +8,54 @@ exports[`highlights under 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <a
+    <button
       aria-label="1"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key={1}
-      href="#"
       key="1"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         1
       </span>
-    </a>
+    </button>
     <div
       className="search-navigator-facet-highlight-under-container"
     >
-      <a
+      <button
         aria-label="2"
-        className="facet search-navigator-facet projects-facet"
+        className="facet search-navigator-facet projects-facet button-link"
         data-key={2}
-        href="#"
         key="2"
         onClick={[Function]}
+        tabIndex={0}
+        type="button"
       >
         <span
           className="facet-name"
         >
           2
         </span>
-      </a>
-      <a
+      </button>
+      <button
         aria-label="3"
-        className="facet search-navigator-facet projects-facet"
+        className="facet search-navigator-facet projects-facet button-link"
         data-key={3}
-        href="#"
         key="3"
         onClick={[Function]}
+        tabIndex={0}
+        type="button"
       >
         <span
           className="facet-name"
         >
           3
         </span>
-      </a>
+      </button>
     </div>
   </div>
 </div>
@@ -66,51 +69,54 @@ exports[`hightlights under selected 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <a
+    <button
       aria-label="1"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key={1}
-      href="#"
       key="1"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         1
       </span>
-    </a>
+    </button>
     <div
       className="search-navigator-facet-highlight-under-container"
     >
-      <a
+      <button
         aria-label="2"
-        className="facet search-navigator-facet projects-facet active"
+        className="facet search-navigator-facet projects-facet button-link active"
         data-key={2}
-        href="#"
         key="2"
         onClick={[Function]}
+        tabIndex={0}
+        type="button"
       >
         <span
           className="facet-name"
         >
           2
         </span>
-      </a>
-      <a
+      </button>
+      <button
         aria-label="3"
-        className="facet search-navigator-facet projects-facet"
+        className="facet search-navigator-facet projects-facet button-link"
         data-key={3}
-        href="#"
         key="3"
         onClick={[Function]}
+        tabIndex={0}
+        type="button"
       >
         <span
           className="facet-name"
         >
           3
         </span>
-      </a>
+      </button>
     </div>
   </div>
 </div>
@@ -124,48 +130,51 @@ exports[`renders 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <a
+    <button
       aria-label="1"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key={1}
-      href="#"
       key="1"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         1
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="2"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key={2}
-      href="#"
       key="2"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         2
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="3"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key={3}
-      href="#"
       key="3"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         3
       </span>
-    </a>
+    </button>
   </div>
 </div>
 `;
@@ -178,13 +187,14 @@ exports[`renders facet bar chart 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <a
+    <button
       aria-label="a"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key="a"
-      href="#"
       key="a"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
@@ -208,14 +218,15 @@ exports[`renders facet bar chart 1`] = `
           />
         </div>
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="b"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key="b"
-      href="#"
       key="b"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
@@ -239,14 +250,15 @@ exports[`renders facet bar chart 1`] = `
           />
         </div>
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="c"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key="c"
-      href="#"
       key="c"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
@@ -270,7 +282,7 @@ exports[`renders facet bar chart 1`] = `
           />
         </div>
       </span>
-    </a>
+    </button>
   </div>
 </div>
 `;
@@ -284,48 +296,51 @@ exports[`renders header and footer 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <a
+    <button
       aria-label="1"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key={1}
-      href="#"
       key="1"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         1
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="2"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key={2}
-      href="#"
       key="2"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         2
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="3"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key={3}
-      href="#"
       key="3"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         3
       </span>
-    </a>
+    </button>
   </div>
   <footer />
 </div>
@@ -339,48 +354,51 @@ exports[`renders multiple selected 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <a
+    <button
       aria-label="1"
-      className="facet search-navigator-facet projects-facet active"
+      className="facet search-navigator-facet projects-facet button-link active"
       data-key={1}
-      href="#"
       key="1"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         1
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="2"
-      className="facet search-navigator-facet projects-facet active"
+      className="facet search-navigator-facet projects-facet button-link active"
       data-key={2}
-      href="#"
       key="2"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         2
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="3"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key={3}
-      href="#"
       key="3"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         3
       </span>
-    </a>
+    </button>
   </div>
 </div>
 `;
@@ -406,48 +424,51 @@ exports[`renders selected 1`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <a
+    <button
       aria-label="1"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key={1}
-      href="#"
       key="1"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         1
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="2"
-      className="facet search-navigator-facet projects-facet active"
+      className="facet search-navigator-facet projects-facet button-link active"
       data-key={2}
-      href="#"
       key="2"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         2
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="3"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key={3}
-      href="#"
       key="3"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
       >
         3
       </span>
-    </a>
+    </button>
   </div>
 </div>
 `;
index 4e2ba5f9db31ab8f983b28e4f0e70cddc279a3ff..5909a5b384137eab1898a07852a4a477853c285b 100644 (file)
@@ -75,13 +75,14 @@ exports[`should render the languages facet with the selected languages 2`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <a
+    <button
       aria-label="projects.facets.label_text_x.projects.facets.languages.Java"
-      className="facet search-navigator-facet projects-facet active"
+      className="facet search-navigator-facet projects-facet button-link active"
       data-key="java"
-      href="#"
       key="java"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
@@ -101,14 +102,15 @@ exports[`should render the languages facet with the selected languages 2`] = `
       >
         39
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="projects.facets.label_text_x.projects.facets.languages.C#"
-      className="facet search-navigator-facet projects-facet active"
+      className="facet search-navigator-facet projects-facet button-link active"
       data-key="cs"
-      href="#"
       key="cs"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
@@ -128,14 +130,15 @@ exports[`should render the languages facet with the selected languages 2`] = `
       >
         4
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="projects.facets.label_text_x.projects.facets.languages.JavaScript"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key="js"
-      href="#"
       key="js"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
@@ -155,7 +158,7 @@ exports[`should render the languages facet with the selected languages 2`] = `
       >
         1
       </span>
-    </a>
+    </button>
   </div>
   <SearchableFilterFooter
     onQueryChange={[MockFunction]}
index f9c288868773615fdc9f316d0e65fd345e80132e..f9f5a9b4f3e35a9b71f3040f3a1e85bc03d729fd 100644 (file)
@@ -160,13 +160,14 @@ exports[`should render the tags facet with the selected tags 2`] = `
   <div
     className="search-navigator-facet-list projects-facet-list"
   >
-    <a
+    <button
       aria-label="projects.facets.label_text_x.projects.facets.tags.lang"
-      className="facet search-navigator-facet projects-facet active"
+      className="facet search-navigator-facet projects-facet button-link active"
       data-key="lang"
-      href="#"
       key="lang"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
@@ -180,14 +181,15 @@ exports[`should render the tags facet with the selected tags 2`] = `
       >
         4
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="projects.facets.label_text_x.projects.facets.tags.sonar"
-      className="facet search-navigator-facet projects-facet active"
+      className="facet search-navigator-facet projects-facet button-link active"
       data-key="sonar"
-      href="#"
       key="sonar"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
@@ -201,14 +203,15 @@ exports[`should render the tags facet with the selected tags 2`] = `
       >
         3
       </span>
-    </a>
-    <a
+    </button>
+    <button
       aria-label="projects.facets.label_text_x.projects.facets.tags.csharp"
-      className="facet search-navigator-facet projects-facet"
+      className="facet search-navigator-facet projects-facet button-link"
       data-key="csharp"
-      href="#"
       key="csharp"
       onClick={[Function]}
+      tabIndex={0}
+      type="button"
     >
       <span
         className="facet-name"
@@ -222,7 +225,7 @@ exports[`should render the tags facet with the selected tags 2`] = `
       >
         1
       </span>
-    </a>
+    </button>
   </div>
   <SearchableFilterFooter
     isLoading={false}
index 3cf283955287c2d9621b2c26606968e6351c85ab..febd98b02edad148e5573847f179a526e76b7dec 100644 (file)
@@ -36,7 +36,7 @@ interface Props {
 }
 
 export default class FacetHeader extends React.PureComponent<Props> {
-  handleClick = (event: React.SyntheticEvent<HTMLAnchorElement>) => {
+  handleClick = (event: React.SyntheticEvent<HTMLButtonElement>) => {
     event.preventDefault();
     event.nativeEvent.preventDefault();
     if (this.props.onClick) {
@@ -73,10 +73,15 @@ export default class FacetHeader extends React.PureComponent<Props> {
       <div className="search-navigator-facet-header-wrapper display-flex-center">
         {this.props.onClick ? (
           <span className="search-navigator-facet-header display-flex-center">
-            <a href="#" onClick={this.handleClick} aria-expanded={this.props.open}>
+            <button
+              className="button-link"
+              type="button"
+              onClick={this.handleClick}
+              aria-expanded={this.props.open}
+              tabIndex={0}>
               <OpenCloseIcon className="little-spacer-right" open={this.props.open} />
               {this.props.name}
-            </a>
+            </button>
             {this.renderHelper()}
           </span>
         ) : (
index 5b0c87200e042d67ad341bdb8ccfac93956712c4..1c9cb821718c800698d2767fd11132839f64000f 100644 (file)
@@ -40,9 +40,8 @@ export default class FacetItem extends React.PureComponent<Props> {
     loading: false
   };
 
-  handleClick = (event: React.MouseEvent<HTMLAnchorElement>) => {
+  handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
     event.preventDefault();
-    event.currentTarget.blur();
     this.props.onClick(this.props.value, event.ctrlKey || event.metaKey);
   };
 
@@ -56,7 +55,7 @@ export default class FacetItem extends React.PureComponent<Props> {
 
   render() {
     const { name } = this.props;
-    const className = classNames('search-navigator-facet', this.props.className, {
+    const className = classNames('search-navigator-facet button-link', this.props.className, {
       active: this.props.active,
       'search-navigator-facet-half': this.props.halfWidth
     });
@@ -67,15 +66,16 @@ export default class FacetItem extends React.PureComponent<Props> {
         {this.renderValue()}
       </span>
     ) : (
-      <a
+      <button
         className={className}
         data-facet={this.props.value}
-        href="#"
         onClick={this.handleClick}
-        title={this.props.tooltip}>
+        tabIndex={0}
+        title={this.props.tooltip}
+        type="button">
         <span className="facet-name">{name}</span>
         {this.renderValue()}
-      </a>
+      </button>
     );
   }
 }
index 5888f117619c0c424cc0cdfb0e56de2757958533..1be6b019960415249fec150a5f3c962d9d52453b 100644 (file)
@@ -31,31 +31,31 @@ it('should render and function correctly', () => {
   renderFacet(undefined, undefined, undefined, { onClick: onFacetClick });
 
   // Start closed.
-  let facetHeader = screen.getByRole('link', { name: 'foo', expanded: false });
+  let facetHeader = screen.getByRole('button', { name: 'foo', expanded: false });
   expect(facetHeader).toBeInTheDocument();
   expect(screen.queryByText('Foo/Bar')).not.toBeInTheDocument();
 
   // Expand.
   facetHeader.click();
-  facetHeader = screen.getByRole('link', { name: 'foo', expanded: true });
+  facetHeader = screen.getByRole('button', { name: 'foo', expanded: true });
   expect(facetHeader).toBeInTheDocument();
   expect(screen.getByText('Foo/Bar')).toBeInTheDocument();
 
   // Interact with facets.
-  const facet1 = screen.getByRole('link', { name: 'Foo/Bar 10' });
+  const facet1 = screen.getByRole('button', { name: 'Foo/Bar 10' });
   expect(facet1).toHaveClass('active');
   facet1.click();
   expect(onFacetClick).toBeCalledWith('bar', false);
 
-  const facet2 = screen.getByRole('link', { name: 'Foo/Baz' });
+  const facet2 = screen.getByRole('button', { name: 'Foo/Baz' });
   expect(facet2).not.toHaveClass('active');
 
-  expect(screen.queryByRole('link', { name: 'Foo/Bat' })).not.toBeInTheDocument();
+  expect(screen.queryByRole('button', { name: 'Foo/Bat' })).not.toBeInTheDocument();
   expect(screen.getByText('Foo/Bat')).toBeInTheDocument();
 
   // Collapse again.
   facetHeader.click();
-  expect(screen.getByRole('link', { name: 'foo', expanded: false })).toBeInTheDocument();
+  expect(screen.getByRole('button', { name: 'foo', expanded: false })).toBeInTheDocument();
   expect(screen.queryByText('Foo/Bar')).not.toBeInTheDocument();
 });
 
@@ -75,7 +75,7 @@ it('should correctly render a header with value data', () => {
 
 it('should correctly render a disabled header', () => {
   renderFacet(undefined, { onClick: undefined });
-  expect(screen.queryByRole('link', { name: 'foo' })).not.toBeInTheDocument();
+  expect(screen.queryByRole('button', { name: 'foo' })).not.toBeInTheDocument();
 });
 
 it('should correctly render a facet item list with title', () => {
index c76e7f4ed6ac7c2d9ee62b2a0499481335d43d45..4eee507734da05c8335ffd449776633d6bd6beb4 100644 (file)
   transition: none;
 }
 
-a.search-navigator-facet {
+button.search-navigator-facet {
   opacity: 1;
   cursor: pointer;
 }
 
-a.search-navigator-facet .facet-name {
+button.search-navigator-facet .facet-name {
   color: var(--baseFontColor);
 }
 
-a.search-navigator-facet:hover,
-a.search-navigator-facet:focus,
+button.search-navigator-facet:hover,
+button.search-navigator-facet:focus,
 .search-navigator-facet.active {
   border-color: var(--blue);
 }
@@ -309,20 +309,20 @@ a.search-navigator-facet:focus,
 .search-navigator-facet-header {
   display: block;
   flex-shrink: 0;
-  padding: 8px 0;
+  padding: 8px 1px;
   color: var(--baseFontColor);
   font-weight: 600;
   overflow: hidden;
   white-space: nowrap;
 }
 
-.search-navigator-facet-header > a {
+.search-navigator-facet-header > button {
   border-bottom: none;
   color: var(--baseFontColor);
 }
 
-.search-navigator-facet-header > a:focus,
-.search-navigator-facet-header > a:hover {
+.search-navigator-facet-header > button:focus,
+.search-navigator-facet-header > button:hover {
   color: var(--darkBlue);
 }