diff --git a/superset-frontend/src/dashboard/components/gridComponents/Tab.test.tsx b/superset-frontend/src/dashboard/components/gridComponents/Tab.test.tsx
new file mode 100644
index 0000000000000..1e99b6496cff7
--- /dev/null
+++ b/superset-frontend/src/dashboard/components/gridComponents/Tab.test.tsx
@@ -0,0 +1,235 @@
+/**
+ * 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 userEvent from '@testing-library/user-event';
+import React from 'react';
+import { render, screen } from 'spec/helpers/testing-library';
+import DashboardComponent from 'src/dashboard/containers/DashboardComponent';
+import EditableTitle from 'src/components/EditableTitle';
+import DragDroppable from 'src/dashboard/components/dnd/DragDroppable';
+
+import Tab from './Tab';
+
+jest.mock('src/dashboard/containers/DashboardComponent', () =>
+ jest.fn(() =>
),
+);
+jest.mock('src/components/EditableTitle', () =>
+ jest.fn(props => (
+
+ {props.title}
+
+ )),
+);
+jest.mock('src/dashboard/components/dnd/DragDroppable', () =>
+ jest.fn(props => {
+ const childProps = props.editMode
+ ? {
+ dragSourceRef: props.dragSourceRef,
+ dropIndicatorProps: props.dropIndicatorProps,
+ }
+ : {};
+ return (
+
+
+ DragDroppable
+
+ {props.children(childProps)}
+
+ );
+ }),
+);
+
+const creteProps = () => ({
+ id: 'TAB-YT6eNksV-',
+ parentId: 'TABS-L-d9eyOE-b',
+ depth: 2,
+ index: 1,
+ renderType: 'RENDER_TAB_CONTENT',
+ availableColumnCount: 12,
+ columnWidth: 120,
+ isFocused: false,
+ component: {
+ children: ['ROW-DR80aHJA2c', 'ROW--BIzjz9F0'],
+ id: 'TAB-YT6eNksV-',
+ meta: { text: '🚀 Aspiring Developers' },
+ parents: ['ROOT_ID', 'GRID_ID', 'TABS-L-d9eyOE-b'],
+ type: 'TAB',
+ },
+ parentComponent: {
+ children: ['TAB-AsMaxdYL_t', 'TAB-YT6eNksV-', 'TAB-l_9I0aNYZ'],
+ id: 'TABS-L-d9eyOE-b',
+ meta: {},
+ parents: ['ROOT_ID', 'GRID_ID'],
+ type: 'TABS',
+ },
+ editMode: false,
+ undoLength: 0,
+ redoLength: 0,
+ filters: {},
+ directPathToChild: ['ROOT_ID', 'GRID_ID', 'TABS-L-d9eyOE-b', 'TAB-YT6eNksV-'],
+ directPathLastUpdated: 1617374760080,
+ dashboardId: 23,
+ focusedFilterScope: null,
+ isComponentVisible: true,
+ onDropOnTab: jest.fn(),
+ handleComponentDrop: jest.fn(),
+ updateComponents: jest.fn(),
+ setDirectPathToChild: jest.fn(),
+});
+
+beforeEach(() => {
+ jest.clearAllMocks();
+});
+
+test('Render tab (no content)', () => {
+ const props = creteProps();
+ props.renderType = 'RENDER_TAB';
+ render( , { useRedux: true, useDnd: true });
+ expect(screen.getByText('🚀 Aspiring Developers')).toBeInTheDocument();
+ expect(EditableTitle).toBeCalledTimes(1);
+ expect(DragDroppable).toBeCalledTimes(1);
+});
+
+test('Render tab (no content) editMode:true', () => {
+ const props = creteProps();
+ props.editMode = true;
+ props.renderType = 'RENDER_TAB';
+ render( , { useRedux: true, useDnd: true });
+ expect(screen.getByText('🚀 Aspiring Developers')).toBeInTheDocument();
+ expect(EditableTitle).toBeCalledTimes(1);
+ expect(DragDroppable).toBeCalledTimes(1);
+});
+
+test('Edit table title', () => {
+ const props = creteProps();
+ props.editMode = true;
+ props.renderType = 'RENDER_TAB';
+ render( , { useRedux: true, useDnd: true });
+
+ expect(EditableTitle).toBeCalledTimes(1);
+ expect(DragDroppable).toBeCalledTimes(1);
+
+ expect(props.updateComponents).not.toBeCalled();
+ userEvent.click(screen.getByText('🚀 Aspiring Developers'));
+ expect(props.updateComponents).toBeCalled();
+});
+
+test('Render tab (with content)', () => {
+ const props = creteProps();
+ props.isFocused = true;
+ render( , { useRedux: true, useDnd: true });
+ expect(DashboardComponent).toBeCalledTimes(2);
+ expect(DashboardComponent).toHaveBeenNthCalledWith(
+ 1,
+ expect.objectContaining({
+ availableColumnCount: 12,
+ columnWidth: 120,
+ depth: 2,
+ id: 'ROW-DR80aHJA2c',
+ index: 0,
+ isComponentVisible: true,
+ onChangeTab: expect.any(Function),
+ onDrop: expect.any(Function),
+ onResize: expect.any(Function),
+ onResizeStart: expect.any(Function),
+ onResizeStop: expect.any(Function),
+ parentId: 'TAB-YT6eNksV-',
+ }),
+ {},
+ );
+ expect(DashboardComponent).toHaveBeenNthCalledWith(
+ 2,
+ expect.objectContaining({
+ availableColumnCount: 12,
+ columnWidth: 120,
+ depth: 2,
+ id: 'ROW--BIzjz9F0',
+ index: 1,
+ isComponentVisible: true,
+ onChangeTab: expect.any(Function),
+ onDrop: expect.any(Function),
+ onResize: expect.any(Function),
+ onResizeStart: expect.any(Function),
+ onResizeStop: expect.any(Function),
+ parentId: 'TAB-YT6eNksV-',
+ }),
+ {},
+ );
+ expect(DragDroppable).toBeCalledTimes(0);
+});
+
+test('Render tab (with content) editMode:true', () => {
+ const props = creteProps();
+ props.isFocused = true;
+ props.editMode = true;
+ render( , { useRedux: true, useDnd: true });
+ expect(DashboardComponent).toBeCalledTimes(2);
+ expect(DashboardComponent).toHaveBeenNthCalledWith(
+ 1,
+ expect.objectContaining({
+ availableColumnCount: 12,
+ columnWidth: 120,
+ depth: 2,
+ id: 'ROW-DR80aHJA2c',
+ index: 0,
+ isComponentVisible: true,
+ onChangeTab: expect.any(Function),
+ onDrop: expect.any(Function),
+ onResize: expect.any(Function),
+ onResizeStart: expect.any(Function),
+ onResizeStop: expect.any(Function),
+ parentId: 'TAB-YT6eNksV-',
+ }),
+ {},
+ );
+ expect(DashboardComponent).toHaveBeenNthCalledWith(
+ 2,
+ expect.objectContaining({
+ availableColumnCount: 12,
+ columnWidth: 120,
+ depth: 2,
+ id: 'ROW--BIzjz9F0',
+ index: 1,
+ isComponentVisible: true,
+ onChangeTab: expect.any(Function),
+ onDrop: expect.any(Function),
+ onResize: expect.any(Function),
+ onResizeStart: expect.any(Function),
+ onResizeStop: expect.any(Function),
+ parentId: 'TAB-YT6eNksV-',
+ }),
+ {},
+ );
+ expect(DragDroppable).toBeCalledTimes(2);
+});
+
+test('Should call "handleDrop" and "handleTopDropTargetDrop"', () => {
+ const props = creteProps();
+ props.isFocused = true;
+ props.editMode = true;
+ render( , { useRedux: true, useDnd: true });
+
+ expect(props.handleComponentDrop).not.toBeCalled();
+ userEvent.click(screen.getAllByRole('button')[0]);
+ expect(props.handleComponentDrop).toBeCalledTimes(1);
+ expect(props.onDropOnTab).not.toBeCalled();
+ userEvent.click(screen.getAllByRole('button')[1]);
+ expect(props.onDropOnTab).toBeCalledTimes(1);
+ expect(props.handleComponentDrop).toBeCalledTimes(2);
+});