Adds tests and storybook to CopyToClipboard component (#13359)

diff --git a/superset-frontend/.storybook/preview.jsx b/superset-frontend/.storybook/preview.jsx
index e20ff6c..94fcfd7 100644
--- a/superset-frontend/.storybook/preview.jsx
+++ b/superset-frontend/.storybook/preview.jsx
@@ -21,18 +21,34 @@
 import { jsxDecorator } from 'storybook-addon-jsx';
 import { addParameters } from '@storybook/react';
 import { withPaddings } from 'storybook-addon-paddings';
-
 import { supersetTheme, ThemeProvider } from '@superset-ui/core';
+import { combineReducers, createStore, applyMiddleware, compose } from 'redux';
+import thunk from 'redux-thunk';
+import { Provider } from 'react-redux';
+import reducerIndex from 'spec/helpers/reducerIndex';
 
-import '../src/theme.ts';
+import 'src/theme.ts';
 import './storybook.css';
 
+const store = createStore(
+  combineReducers(reducerIndex),
+  {},
+  compose(applyMiddleware(thunk)),
+);
+
 const themeDecorator = Story => (
   <ThemeProvider theme={supersetTheme}>{<Story />}</ThemeProvider>
 );
 
+const providerDecorator = Story => (
+  <Provider store={store}>
+    <Story />
+  </Provider>
+);
+
 addDecorator(jsxDecorator);
 addDecorator(themeDecorator);
+addDecorator(providerDecorator);
 addDecorator(withPaddings);
 
 addParameters({
diff --git a/superset-frontend/spec/javascripts/components/CopyToClipboard_spec.jsx b/superset-frontend/spec/javascripts/components/CopyToClipboard_spec.jsx
deleted file mode 100644
index 9f27ac6..0000000
--- a/superset-frontend/spec/javascripts/components/CopyToClipboard_spec.jsx
+++ /dev/null
@@ -1,33 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-import React from 'react';
-
-import CopyToClipboard from 'src/components/CopyToClipboard';
-
-describe('CopyToClipboard', () => {
-  const defaultProps = {
-    text: 'some text to copy',
-  };
-
-  it('renders', () => {
-    expect(React.isValidElement(<CopyToClipboard {...defaultProps} />)).toBe(
-      true,
-    );
-  });
-});
diff --git a/superset-frontend/src/common/components/Collapse/Collapse.stories.tsx b/superset-frontend/src/common/components/Collapse/Collapse.stories.tsx
index f6636a8..169d3bc 100644
--- a/superset-frontend/src/common/components/Collapse/Collapse.stories.tsx
+++ b/superset-frontend/src/common/components/Collapse/Collapse.stories.tsx
@@ -62,10 +62,10 @@
 InteractiveCollapse.story = {
   parameters: {
     actions: {
-      disabled: true,
+      disable: true,
     },
     knobs: {
-      disabled: true,
+      disable: true,
     },
   },
 };
diff --git a/superset-frontend/src/common/components/Tooltip/Tooltip.stories.tsx b/superset-frontend/src/common/components/Tooltip/Tooltip.stories.tsx
index f1040d7..f4c1302 100644
--- a/superset-frontend/src/common/components/Tooltip/Tooltip.stories.tsx
+++ b/superset-frontend/src/common/components/Tooltip/Tooltip.stories.tsx
@@ -52,7 +52,7 @@
 InteractiveTooltip.story = {
   parameters: {
     knobs: {
-      disabled: true,
+      disable: true,
     },
   },
 };
diff --git a/superset-frontend/src/components/Alert/Alert.stories.tsx b/superset-frontend/src/components/Alert/Alert.stories.tsx
index 4b4f86ea..da02aae 100644
--- a/superset-frontend/src/components/Alert/Alert.stories.tsx
+++ b/superset-frontend/src/components/Alert/Alert.stories.tsx
@@ -63,13 +63,13 @@
 AlertGallery.story = {
   parameters: {
     actions: {
-      disabled: true,
+      disable: true,
     },
     controls: {
-      disabled: true,
+      disable: true,
     },
     knobs: {
-      disabled: true,
+      disable: true,
     },
   },
 };
@@ -94,7 +94,7 @@
 InteractiveAlert.story = {
   parameters: {
     knobs: {
-      disabled: true,
+      disable: true,
     },
   },
 };
diff --git a/superset-frontend/src/components/AsyncAceEditor/AsyncAceEditor.stories.tsx b/superset-frontend/src/components/AsyncAceEditor/AsyncAceEditor.stories.tsx
index 6587121..6269ba9 100644
--- a/superset-frontend/src/components/AsyncAceEditor/AsyncAceEditor.stories.tsx
+++ b/superset-frontend/src/components/AsyncAceEditor/AsyncAceEditor.stories.tsx
@@ -81,7 +81,7 @@
 AsyncAceEditor.args = {
   defaultTabSize: 2,
   width: '100%',
-  height: 500,
+  height: '500px',
   value: `{"text": "Simple text"}`,
 };
 
@@ -99,10 +99,10 @@
 AsyncAceEditor.story = {
   parameters: {
     actions: {
-      disabled: true,
+      disable: true,
     },
     knobs: {
-      disabled: true,
+      disable: true,
     },
   },
 };
diff --git a/superset-frontend/src/components/Button/Button.stories.tsx b/superset-frontend/src/components/Button/Button.stories.tsx
index b5a683b..9a1853c 100644
--- a/superset-frontend/src/components/Button/Button.stories.tsx
+++ b/superset-frontend/src/components/Button/Button.stories.tsx
@@ -98,13 +98,13 @@
 ButtonGallery.story = {
   parameters: {
     actions: {
-      disabled: true,
+      disable: true,
     },
     controls: {
-      disabled: true,
+      disable: true,
     },
     knobs: {
-      disabled: true,
+      disable: true,
     },
   },
 };
@@ -117,7 +117,7 @@
 InteractiveButton.story = {
   parameters: {
     knobs: {
-      disabled: true,
+      disable: true,
     },
   },
 };
diff --git a/superset-frontend/src/components/ButtonGroup/ButtonGroup.stories.tsx b/superset-frontend/src/components/ButtonGroup/ButtonGroup.stories.tsx
index 8c87864..e5b0b94 100644
--- a/superset-frontend/src/components/ButtonGroup/ButtonGroup.stories.tsx
+++ b/superset-frontend/src/components/ButtonGroup/ButtonGroup.stories.tsx
@@ -61,10 +61,10 @@
 InteractiveButtonGroup.story = {
   parameters: {
     actions: {
-      disabled: true,
+      disable: true,
     },
     knobs: {
-      disabled: true,
+      disable: true,
     },
   },
 };
diff --git a/superset-frontend/src/components/CopyToClipboard/CopyToClipboard.stories.tsx b/superset-frontend/src/components/CopyToClipboard/CopyToClipboard.stories.tsx
new file mode 100644
index 0000000..e932b45
--- /dev/null
+++ b/superset-frontend/src/components/CopyToClipboard/CopyToClipboard.stories.tsx
@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import Button from 'src/components/Button';
+import Icon from 'src/components/Icon';
+import ToastPresenter from 'src/messageToasts/containers/ToastPresenter';
+import CopyToClipboard from '.';
+
+export default {
+  title: 'CopyToClipboard',
+  component: CopyToClipboard,
+};
+
+export const InteractiveCopyToClipboard = ({ copyNode, ...rest }: any) => {
+  let node = <Button>Copy</Button>;
+  if (copyNode === 'Icon') {
+    node = <Icon name="copy" />;
+  } else if (copyNode === 'Text') {
+    node = <span role="button">Copy</span>;
+  }
+  return (
+    <>
+      <CopyToClipboard copyNode={node} {...rest} />
+      <ToastPresenter />
+    </>
+  );
+};
+
+InteractiveCopyToClipboard.args = {
+  shouldShowText: true,
+  text: 'http://superset.apache.org/',
+  wrapped: true,
+  tooltipText: 'Copy to clipboard',
+};
+
+InteractiveCopyToClipboard.argTypes = {
+  onCopyEnd: { action: 'onCopyEnd' },
+  copyNode: {
+    defaultValue: 'Button',
+    control: { type: 'radio', options: ['Button', 'Icon', 'Text'] },
+  },
+};
+
+InteractiveCopyToClipboard.story = {
+  parameters: {
+    knobs: {
+      disable: true,
+    },
+  },
+};
diff --git a/superset-frontend/src/components/CopyToClipboard/CopyToClipboard.test.tsx b/superset-frontend/src/components/CopyToClipboard/CopyToClipboard.test.tsx
new file mode 100644
index 0000000..81103a8
--- /dev/null
+++ b/superset-frontend/src/components/CopyToClipboard/CopyToClipboard.test.tsx
@@ -0,0 +1,76 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+import React from 'react';
+import { render, screen, waitFor } from 'spec/helpers/testing-library';
+import userEvent from '@testing-library/user-event';
+import CopyToClipboard from '.';
+
+test('renders with default props', () => {
+  const text = 'Text';
+  render(<CopyToClipboard text={text} />, { useRedux: true });
+  expect(screen.getByText(text)).toBeInTheDocument();
+  expect(screen.getByText('Copy')).toBeInTheDocument();
+});
+
+test('renders with custom copy node', () => {
+  const copyNode = <a href="/">Custom node</a>;
+  render(<CopyToClipboard copyNode={copyNode} />, { useRedux: true });
+  expect(screen.getByRole('link')).toBeInTheDocument();
+});
+
+test('renders without text showing', () => {
+  const text = 'Text';
+  render(<CopyToClipboard text={text} shouldShowText={false} />, {
+    useRedux: true,
+  });
+  expect(screen.queryByText(text)).not.toBeInTheDocument();
+});
+
+test('getText on copy', async () => {
+  const getText = jest.fn(() => 'Text');
+  render(<CopyToClipboard getText={getText} />, { useRedux: true });
+  userEvent.click(screen.getByText('Copy'));
+  await waitFor(() => expect(getText).toHaveBeenCalled());
+});
+
+test('renders tooltip on hover', async () => {
+  const tooltipText = 'Tooltip';
+  render(<CopyToClipboard tooltipText={tooltipText} />, { useRedux: true });
+  userEvent.hover(screen.getByText('Copy'));
+  const tooltip = await screen.findByRole('tooltip');
+  expect(tooltip).toBeInTheDocument();
+  expect(tooltip).toHaveTextContent(tooltipText);
+});
+
+test('triggers onCopyEnd', async () => {
+  const onCopyEnd = jest.fn();
+  render(<CopyToClipboard onCopyEnd={onCopyEnd} />, {
+    useRedux: true,
+  });
+  userEvent.click(screen.getByText('Copy'));
+  await waitFor(() => expect(onCopyEnd).toHaveBeenCalled());
+});
+
+test('renders unwrapped', () => {
+  const text = 'Text';
+  render(<CopyToClipboard text={text} wrapped={false} />, {
+    useRedux: true,
+  });
+  expect(screen.queryByText(text)).not.toBeInTheDocument();
+});
diff --git a/superset-frontend/src/components/CopyToClipboard.jsx b/superset-frontend/src/components/CopyToClipboard/index.jsx
similarity index 97%
rename from superset-frontend/src/components/CopyToClipboard.jsx
rename to superset-frontend/src/components/CopyToClipboard/index.jsx
index bded61f..bff6116 100644
--- a/superset-frontend/src/components/CopyToClipboard.jsx
+++ b/superset-frontend/src/components/CopyToClipboard/index.jsx
@@ -102,7 +102,7 @@
 
   renderLink() {
     return (
-      <span>
+      <span css={{ display: 'inline-flex', alignItems: 'center' }}>
         {this.props.shouldShowText && this.props.text && (
           <span className="m-r-5" data-test="short-url">
             {this.props.text}
diff --git a/superset-frontend/src/components/Loading/Loading.stories.tsx b/superset-frontend/src/components/Loading/Loading.stories.tsx
index b0d8bf1..60ef378 100644
--- a/superset-frontend/src/components/Loading/Loading.stories.tsx
+++ b/superset-frontend/src/components/Loading/Loading.stories.tsx
@@ -49,13 +49,13 @@
 LoadingGallery.story = {
   parameters: {
     actions: {
-      disabled: true,
+      disable: true,
     },
     controls: {
-      disabled: true,
+      disable: true,
     },
     knobs: {
-      disabled: true,
+      disable: true,
     },
   },
 };
@@ -65,7 +65,7 @@
 InteractiveLoading.story = {
   parameters: {
     knobs: {
-      disabled: true,
+      disable: true,
     },
   },
 };