]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-16703 [891721] Label does not convey purpose of control
authorWouter Admiraal <wouter.admiraal@sonarsource.com>
Thu, 28 Jul 2022 10:27:22 +0000 (12:27 +0200)
committersonartech <sonartech@sonarsource.com>
Fri, 29 Jul 2022 20:03:15 +0000 (20:03 +0000)
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/MetaKey.tsx
server/sonar-web/src/main/js/app/components/nav/component/projectInformation/meta/__tests__/MetaKey-test.tsx
server/sonar-web/src/main/js/apps/account/__tests__/Account-it.tsx
server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/clipboard-test.tsx.snap [deleted file]
server/sonar-web/src/main/js/components/controls/__tests__/clipboard-test.tsx
server/sonar-web/src/main/js/components/controls/clipboard.tsx
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index b7e197a83de7064c53f76f9b1e5965680ed2b131..9457e903dfdc3ed8adb75cfcba37025e6d767632 100644 (file)
@@ -38,7 +38,11 @@ export default function MetaKey({ componentKey, qualifier }: MetaKeyProps) {
           type="text"
           value={componentKey}
         />
-        <ClipboardButton className="little-spacer-left" copyValue={componentKey} />
+        <ClipboardButton
+          aria-label={translate('overview.project_key.click_to_copy')}
+          className="little-spacer-left"
+          copyValue={componentKey}
+        />
       </div>
     </>
   );
index d081a6d7c7c5f4151a51ec967db39678984c681c..dee344e4a88e40c64d7c9425d3cd232fe8aaa610 100644 (file)
@@ -28,7 +28,9 @@ it('should render correctly', () => {
   expect(
     screen.getByLabelText(`overview.project_key.${ComponentQualifier.Project}`)
   ).toBeInTheDocument();
-  expect(screen.getByRole('button', { name: 'copy_to_clipboard' })).toBeInTheDocument();
+  expect(
+    screen.getByRole('button', { name: 'overview.project_key.click_to_copy' })
+  ).toBeInTheDocument();
 });
 
 function renderMetaKey(props: Partial<MetaKeyProps> = {}) {
index 236dafea32f668924ed91e22d6f80dd3d4affc09..32ca9eabc17281e0599ffc855f5841b8f9e959b3 100644 (file)
@@ -284,7 +284,7 @@ describe('security page', () => {
       expect(
         await screen.findByText(`users.tokens.new_token_created.${newTokenName}`)
       ).toBeInTheDocument();
-      expect(screen.getByRole('button', { name: 'copy' })).toBeInTheDocument();
+      expect(screen.getByRole('button', { name: 'copy_to_clipboard' })).toBeInTheDocument();
 
       const lastTokenCreated = tokenMock.getTokens().pop();
       expect(lastTokenCreated).toBeDefined();
diff --git a/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/clipboard-test.tsx.snap b/server/sonar-web/src/main/js/components/controls/__tests__/__snapshots__/clipboard-test.tsx.snap
deleted file mode 100644 (file)
index de2081c..0000000
+++ /dev/null
@@ -1,52 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`ClipboardBase should display correctly 1`] = `
-<Button>
-  copy
-</Button>
-`;
-
-exports[`ClipboardButton should display correctly 1`] = `
-<Tooltip
-  overlay="copied_action"
-  visible={false}
->
-  <Button
-    className="no-select"
-    data-clipboard-text="foo"
-    innerRef={[Function]}
-  >
-    <CopyIcon
-      className="little-spacer-right"
-    />
-    copy
-  </Button>
-</Tooltip>
-`;
-
-exports[`ClipboardButton should render a custom label if provided 1`] = `
-<Tooltip
-  overlay="copied_action"
-  visible={false}
->
-  <Button
-    className="no-select"
-    data-clipboard-text="foo"
-    innerRef={[Function]}
-  >
-    Foo Bar
-  </Button>
-</Tooltip>
-`;
-
-exports[`ClipboardIconButton should display correctly 1`] = `
-<ButtonIcon
-  aria-label="copy_to_clipboard"
-  className="no-select"
-  data-clipboard-text="foo"
-  innerRef={[Function]}
-  tooltip="copy_to_clipboard"
->
-  <CopyIcon />
-</ButtonIcon>
-`;
index bf2c040aa2177f4f5839998372e28c27c4021da0..075406a6a4448d349995d94afbb3cbb06c5c6bcf 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { mount, shallow } from 'enzyme';
+import { screen } from '@testing-library/react';
 import * as React from 'react';
-import { Button } from '../buttons';
-import { ClipboardBase, ClipboardButton, ClipboardIconButton } from '../clipboard';
-
-const constructor = jest.fn();
-const destroy = jest.fn();
-const on = jest.fn();
-
-jest.mock(
-  'clipboard',
-  () =>
-    function(...args: any) {
-      constructor(...args);
-      return {
-        destroy,
-        on
-      };
-    }
-);
+import { renderComponent } from '../../../helpers/testReactTestingUtils';
+import {
+  ClipboardBase,
+  ClipboardButton,
+  ClipboardButtonProps,
+  ClipboardIconButton,
+  ClipboardIconButtonProps
+} from '../clipboard';
 
 beforeAll(() => {
   jest.useFakeTimers();
@@ -49,61 +39,69 @@ afterAll(() => {
 
 describe('ClipboardBase', () => {
   it('should display correctly', () => {
-    const children = jest.fn().mockReturnValue(<Button>copy</Button>);
-    const wrapper = shallowRender(children);
-    const instance = wrapper.instance();
-    expect(wrapper).toMatchSnapshot();
-    instance.handleSuccessCopy();
-    expect(children).toBeCalledWith({ copySuccess: true, setCopyButton: instance.setCopyButton });
-    jest.runAllTimers();
-    expect(children).toBeCalledWith({ copySuccess: false, setCopyButton: instance.setCopyButton });
+    renderClipboardBase();
+    expect(screen.getByText('click to copy')).toBeInTheDocument();
   });
 
   it('should allow its content to be copied', () => {
-    const wrapper = mountRender(({ setCopyButton }) => (
-      <Button innerRef={setCopyButton}>click</Button>
-    ));
-    const button = wrapper.find('button').getDOMNode();
-    const instance = wrapper.instance();
+    renderClipboardBase();
+    const button = screen.getByRole('button');
+    button.click();
 
-    expect(constructor).toBeCalledWith(button);
-    expect(on).toBeCalledWith('success', instance.handleSuccessCopy);
+    expect(screen.getByText('copied')).toBeInTheDocument();
 
-    jest.clearAllMocks();
+    jest.runAllTimers();
 
-    wrapper.unmount();
-    expect(destroy).toBeCalled();
+    expect(screen.getByText('click to copy')).toBeInTheDocument();
   });
 
-  function shallowRender(children?: ClipboardBase['props']['children']) {
-    return shallow<ClipboardBase>(<ClipboardBase>{children || (() => null)}</ClipboardBase>);
-  }
-
-  function mountRender(children?: ClipboardBase['props']['children']) {
-    return mount<ClipboardBase>(<ClipboardBase>{children || (() => null)}</ClipboardBase>);
+  function renderClipboardBase(props: Partial<ClipboardBase['props']> = {}) {
+    return renderComponent(
+      <ClipboardBase {...props}>
+        {({ setCopyButton, copySuccess }) => (
+          <span data-clipboard-text="foo" ref={setCopyButton} role="button">
+            {copySuccess ? 'copied' : 'click to copy'}
+          </span>
+        )}
+      </ClipboardBase>
+    );
   }
 });
 
 describe('ClipboardButton', () => {
   it('should display correctly', () => {
-    expect(shallowRender()).toMatchSnapshot();
+    renderClipboardButton();
+    expect(screen.getByRole('button', { name: 'copy_to_clipboard' })).toBeInTheDocument();
   });
 
   it('should render a custom label if provided', () => {
-    expect(shallowRender('Foo Bar')).toMatchSnapshot();
+    renderClipboardButton({ children: 'custom label' });
+    expect(screen.getByRole('button', { name: 'copy_to_clipboard' })).toBeInTheDocument();
+    expect(screen.getByText('custom label')).toBeInTheDocument();
+  });
+
+  it('should render a custom aria-label if provided', () => {
+    renderClipboardButton({ 'aria-label': 'custom label' });
+    expect(screen.getByRole('button', { name: 'custom label' })).toBeInTheDocument();
   });
 
-  function shallowRender(children?: React.ReactNode) {
-    return shallow(<ClipboardButton copyValue="foo">{children}</ClipboardButton>).dive();
+  function renderClipboardButton(props: Partial<ClipboardButtonProps> = {}) {
+    return renderComponent(<ClipboardButton copyValue="foo" {...props} />);
   }
 });
 
 describe('ClipboardIconButton', () => {
   it('should display correctly', () => {
-    expect(shallowRender()).toMatchSnapshot();
+    renderClipboardIconButton();
+    expect(screen.getByRole('button', { name: 'copy_to_clipboard' })).toBeInTheDocument();
+  });
+
+  it('should render a custom aria-label if provided', () => {
+    renderClipboardIconButton({ 'aria-label': 'custom label' });
+    expect(screen.getByRole('button', { name: 'custom label' })).toBeInTheDocument();
   });
 
-  function shallowRender() {
-    return shallow(<ClipboardIconButton copyValue="foo" />).dive();
+  function renderClipboardIconButton(props: Partial<ClipboardIconButtonProps> = {}) {
+    return renderComponent(<ClipboardIconButton copyValue="foo" {...props} />);
   }
 });
index 97d29745cea73aeed5b26d5be9a13d0cb01f3bd0..c72336dc7fa8c44082d9428fdc6657240e33931b 100644 (file)
@@ -32,13 +32,14 @@ export interface State {
 interface RenderProps {
   setCopyButton: (node: HTMLElement | null) => void;
   copySuccess: boolean;
+  role: string;
 }
 
-interface BaseProps {
+interface Props {
   children: (props: RenderProps) => React.ReactNode;
 }
 
-export class ClipboardBase extends React.PureComponent<BaseProps, State> {
+export class ClipboardBase extends React.PureComponent<Props, State> {
   private clipboard?: Clipboard;
   private copyButton?: HTMLElement | null;
   mounted = false;
@@ -87,18 +88,25 @@ export class ClipboardBase extends React.PureComponent<BaseProps, State> {
   render() {
     return this.props.children({
       setCopyButton: this.setCopyButton,
-      copySuccess: this.state.copySuccess
+      copySuccess: this.state.copySuccess,
+      role: 'button'
     });
   }
 }
 
-interface ButtonProps {
+export interface ClipboardButtonProps {
+  'aria-label'?: string;
   className?: string;
   copyValue: string;
   children?: React.ReactNode;
 }
 
-export function ClipboardButton({ className, children, copyValue }: ButtonProps) {
+export function ClipboardButton({
+  className,
+  children,
+  copyValue,
+  'aria-label': ariaLabel
+}: ClipboardButtonProps) {
   return (
     <ClipboardBase>
       {({ setCopyButton, copySuccess }) => (
@@ -106,7 +114,8 @@ export function ClipboardButton({ className, children, copyValue }: ButtonProps)
           <Button
             className={classNames('no-select', className)}
             data-clipboard-text={copyValue}
-            innerRef={setCopyButton}>
+            innerRef={setCopyButton}
+            aria-label={ariaLabel ?? translate('copy_to_clipboard')}>
             {children || (
               <>
                 <CopyIcon className="little-spacer-right" />
@@ -120,13 +129,13 @@ export function ClipboardButton({ className, children, copyValue }: ButtonProps)
   );
 }
 
-interface IconButtonProps {
+export interface ClipboardIconButtonProps {
   'aria-label'?: string;
   className?: string;
   copyValue: string;
 }
 
-export function ClipboardIconButton(props: IconButtonProps) {
+export function ClipboardIconButton(props: ClipboardIconButtonProps) {
   const { className, copyValue } = props;
   return (
     <ClipboardBase>
index 34094e1bfa85e0af94fc1700c39acbee42dd970a..4ca50b4a48c40d74a096595c3d5ae45835ed132d 100644 (file)
@@ -3118,6 +3118,7 @@ overview.project_activity.click_to_see=Click to see project activity
 overview.external_links=External Links
 overview.project_key.APP=Application Key
 overview.project_key.TRK=Project Key
+overview.project_key.click_to_copy=Click to copy the key to your clipboard
 overview.activity=Activity
 overview.recent_activity=Recent Activity
 overview.measures=Measures