diff --git a/superset-frontend/spec/javascripts/components/ModalTrigger_spec.jsx b/superset-frontend/spec/javascripts/components/ModalTrigger_spec.jsx
deleted file mode 100644
index ca8981bfb..000000000
--- a/superset-frontend/spec/javascripts/components/ModalTrigger_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 ModalTrigger from 'src/components/ModalTrigger';
-
-describe('ModalTrigger', () => {
- const defaultProps = {
- triggerNode: ,
- modalTitle: 'My Modal Title',
- modalBody:
Modal Body
,
- };
-
- it('is a valid element', () => {
- expect(React.isValidElement()).toBe(true);
- });
-});
diff --git a/superset-frontend/src/components/ModalTrigger/ModalTrigger.stories.tsx b/superset-frontend/src/components/ModalTrigger/ModalTrigger.stories.tsx
new file mode 100644
index 000000000..b5507f155
--- /dev/null
+++ b/superset-frontend/src/components/ModalTrigger/ModalTrigger.stories.tsx
@@ -0,0 +1,56 @@
+/**
+ * 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 ModalTrigger from '.';
+
+interface IModalTriggerProps {
+ triggerNode: React.ReactNode;
+ dialogClassName?: string;
+ modalTitle?: React.ReactNode;
+ modalBody?: React.ReactNode;
+ modalFooter?: React.ReactNode;
+ beforeOpen?: () => void;
+ onExit?: () => void;
+ isButton?: boolean;
+ className?: string;
+ tooltip?: string;
+ width?: string;
+ maxWidth?: string;
+ responsive?: boolean;
+}
+
+export default {
+ title: 'ModalTrigger',
+ component: ModalTrigger,
+};
+
+export const InteractiveModalTrigger = (args: IModalTriggerProps) => (
+ Click me} {...args} />
+);
+
+InteractiveModalTrigger.args = {
+ isButton: true,
+ modalTitle: 'I am a modal title',
+ modalBody: 'I am a modal body',
+ modalFooter: 'I am a modal footer',
+ tooltip: 'I am a tooltip',
+ width: '600px',
+ maxWidth: '1000px',
+ responsive: true,
+};
diff --git a/superset-frontend/src/components/ModalTrigger/ModalTrigger.test.tsx b/superset-frontend/src/components/ModalTrigger/ModalTrigger.test.tsx
new file mode 100644
index 000000000..a0dc8d4b6
--- /dev/null
+++ b/superset-frontend/src/components/ModalTrigger/ModalTrigger.test.tsx
@@ -0,0 +1,91 @@
+/**
+ * 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 { supersetTheme } from '@superset-ui/core';
+import ModalTrigger from '.';
+
+const mockedProps = {
+ triggerNode: Trigger,
+};
+
+test('should render', () => {
+ const { container } = render();
+ expect(container).toBeInTheDocument();
+});
+
+test('should render a button', () => {
+ render();
+ expect(screen.getByRole('button')).toBeInTheDocument();
+});
+
+test('should render a span element by default', () => {
+ render();
+ expect(screen.getByTestId('span-modal-trigger')).toBeInTheDocument();
+});
+
+test('should render a button element when specified', () => {
+ const btnProps = {
+ ...mockedProps,
+ isButton: true,
+ };
+ render();
+ expect(screen.getByTestId('btn-modal-trigger')).toBeInTheDocument();
+});
+
+test('should render triggerNode', () => {
+ render();
+ expect(screen.getByText('Trigger')).toBeInTheDocument();
+});
+
+test('should render a tooltip on hover', async () => {
+ const tooltipProps = {
+ ...mockedProps,
+ isButton: true,
+ tooltip: 'I am a tooltip',
+ };
+ render();
+
+ userEvent.hover(screen.getByRole('button'));
+ await waitFor(() => expect(screen.getByRole('tooltip')).toBeInTheDocument());
+});
+
+test('should not render a modal before click', () => {
+ render();
+ expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
+});
+
+test('should render a modal after click', () => {
+ render();
+ userEvent.click(screen.getByRole('button'));
+ expect(screen.getByRole('dialog')).toBeInTheDocument();
+});
+
+test('renders with theme', () => {
+ const btnProps = {
+ ...mockedProps,
+ isButton: true,
+ };
+ render();
+ const button = screen.getByRole('button');
+ expect(button.firstChild).toHaveStyle(`
+ fontSize: ${supersetTheme.typography.sizes.s};
+ `);
+});
diff --git a/superset-frontend/src/components/ModalTrigger.jsx b/superset-frontend/src/components/ModalTrigger/index.jsx
similarity index 94%
rename from superset-frontend/src/components/ModalTrigger.jsx
rename to superset-frontend/src/components/ModalTrigger/index.jsx
index 3999e7fa4..882142fca 100644
--- a/superset-frontend/src/components/ModalTrigger.jsx
+++ b/superset-frontend/src/components/ModalTrigger/index.jsx
@@ -91,6 +91,7 @@ export default class ModalTrigger extends React.Component {
<>