diff --git a/superset-frontend/src/explore/components/DataTableControl/CopyButton.test.tsx b/superset-frontend/src/explore/components/DataTableControl/CopyButton.test.tsx
new file mode 100644
index 0000000000000..37b5677bd6050
--- /dev/null
+++ b/superset-frontend/src/explore/components/DataTableControl/CopyButton.test.tsx
@@ -0,0 +1,28 @@
+/**
+ * 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 } from 'spec/helpers/testing-library';
+
+import { CopyButton } from '.';
+
+test('Render a button', () => {
+ render(btn);
+ expect(screen.getByRole('button')).toBeInTheDocument();
+ expect(screen.getByRole('button')).toHaveClass('superset-button');
+});
diff --git a/superset-frontend/src/explore/components/DataTableControl/CopyToClipboardButton.test.tsx b/superset-frontend/src/explore/components/DataTableControl/CopyToClipboardButton.test.tsx
new file mode 100644
index 0000000000000..2ce91590b9890
--- /dev/null
+++ b/superset-frontend/src/explore/components/DataTableControl/CopyToClipboardButton.test.tsx
@@ -0,0 +1,41 @@
+/**
+ * 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 { CopyToClipboardButton } from '.';
+
+test('Render a button', () => {
+ render(, {
+ useRedux: true,
+ });
+ expect(screen.getByRole('button')).toBeInTheDocument();
+});
+
+test('Should copy to clipboard', () => {
+ document.execCommand = jest.fn();
+
+ render(, {
+ useRedux: true,
+ });
+
+ expect(document.execCommand).toHaveBeenCalledTimes(0);
+ userEvent.click(screen.getByRole('button'));
+ expect(document.execCommand).toHaveBeenCalledWith('copy');
+});
diff --git a/superset-frontend/src/explore/components/DataTableControl/FilterInput.test.tsx b/superset-frontend/src/explore/components/DataTableControl/FilterInput.test.tsx
new file mode 100644
index 0000000000000..fb77c21d7a779
--- /dev/null
+++ b/superset-frontend/src/explore/components/DataTableControl/FilterInput.test.tsx
@@ -0,0 +1,37 @@
+/**
+ * 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 { FilterInput } from '.';
+
+jest.mock('lodash/debounce', () => ({
+ __esModule: true,
+ default: (fuc: Function) => fuc,
+}));
+
+test('Render a FilterInput', async () => {
+ const onChangeHandler = jest.fn();
+ render();
+
+ expect(onChangeHandler).toBeCalledTimes(0);
+ userEvent.type(screen.getByRole('textbox'), 'test');
+
+ expect(onChangeHandler).toBeCalledTimes(4);
+});
diff --git a/superset-frontend/src/explore/components/DataTableControl/RowCount.test.tsx b/superset-frontend/src/explore/components/DataTableControl/RowCount.test.tsx
new file mode 100644
index 0000000000000..be3f0e980c2b8
--- /dev/null
+++ b/superset-frontend/src/explore/components/DataTableControl/RowCount.test.tsx
@@ -0,0 +1,31 @@
+/**
+ * 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 } from 'spec/helpers/testing-library';
+import { RowCount } from '.';
+
+test('Render a RowCount', () => {
+ render();
+ expect(screen.getByText('3 rows retrieved')).toBeInTheDocument();
+});
+
+test('Render a RowCount on loading', () => {
+ render();
+ expect(screen.getByText('Loading...')).toBeInTheDocument();
+});
diff --git a/superset-frontend/src/explore/components/DataTableControl.tsx b/superset-frontend/src/explore/components/DataTableControl/index.tsx
similarity index 98%
rename from superset-frontend/src/explore/components/DataTableControl.tsx
rename to superset-frontend/src/explore/components/DataTableControl/index.tsx
index 359dc7ecd96c9..934ebe72add76 100644
--- a/superset-frontend/src/explore/components/DataTableControl.tsx
+++ b/superset-frontend/src/explore/components/DataTableControl/index.tsx
@@ -33,7 +33,7 @@ import {
prepareCopyToClipboardTabularData,
} from 'src/utils/common';
import CopyToClipboard from 'src/components/CopyToClipboard';
-import RowCountLabel from './RowCountLabel';
+import RowCountLabel from 'src/explore/components/RowCountLabel';
export const CopyButton = styled(Button)`
font-size: ${({ theme }) => theme.typography.sizes.s}px;
diff --git a/superset-frontend/src/explore/components/DataTableControl/useFilteredTableData.test.ts b/superset-frontend/src/explore/components/DataTableControl/useFilteredTableData.test.ts
new file mode 100644
index 0000000000000..f321aee362f08
--- /dev/null
+++ b/superset-frontend/src/explore/components/DataTableControl/useFilteredTableData.test.ts
@@ -0,0 +1,57 @@
+/**
+ * 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 { renderHook } from '@testing-library/react-hooks';
+import { useFilteredTableData } from '.';
+
+const data = [
+ { col01: 'some', col02: 'data' },
+ { col01: 'any', col02: 'data' },
+ { col01: 'some', col02: 'thing' },
+ { col01: 'any', col02: 'things' },
+];
+
+test('Empty filter', () => {
+ const hook = renderHook(() => useFilteredTableData('', data));
+ expect(hook.result.current).toEqual(data);
+});
+
+test('Filter by the word "data"', () => {
+ const hook = renderHook(() => useFilteredTableData('data', data));
+ expect(hook.result.current).toEqual([
+ { col01: 'some', col02: 'data' },
+ { col01: 'any', col02: 'data' },
+ ]);
+});
+
+test('Filter by the word "thing"', () => {
+ const hook = renderHook(() => useFilteredTableData('thing', data));
+ expect(hook.result.current).toEqual([
+ { col01: 'some', col02: 'thing' },
+ { col01: 'any', col02: 'things' },
+ ]);
+});
+
+test('Filter by the word "any"', () => {
+ const hook = renderHook(() => useFilteredTableData('any', data));
+ expect(hook.result.current).toEqual([
+ { col01: 'any', col02: 'data' },
+ { col01: 'any', col02: 'things' },
+ ]);
+});
diff --git a/superset-frontend/src/explore/components/DataTableControl/useTableColumns.test.ts b/superset-frontend/src/explore/components/DataTableControl/useTableColumns.test.ts
new file mode 100644
index 0000000000000..b950dbdf60ad0
--- /dev/null
+++ b/superset-frontend/src/explore/components/DataTableControl/useTableColumns.test.ts
@@ -0,0 +1,64 @@
+/**
+ * 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 { renderHook } from '@testing-library/react-hooks';
+import { useTableColumns } from '.';
+
+const data = [
+ { col01: 'some', col02: 'data' },
+ { col01: 'any', col02: 'data' },
+ { col01: 'some', col02: 'thing' },
+ { col01: 'any', col02: 'things', col03: 'secret' },
+];
+
+test('useTableColumns with no options', () => {
+ const hook = renderHook(() => useTableColumns(data));
+ expect(hook.result.current).toEqual([
+ { Cell: expect.any(Function), Header: 'col01', accessor: 'col01' },
+ { Cell: expect.any(Function), Header: 'col02', accessor: 'col02' },
+ ]);
+});
+
+test('use only the first record columns', () => {
+ const hook = renderHook(() => useTableColumns(data));
+ expect(hook.result.current).toEqual([
+ { Cell: expect.any(Function), Header: 'col01', accessor: 'col01' },
+ { Cell: expect.any(Function), Header: 'col02', accessor: 'col02' },
+ ]);
+
+ const hook2 = renderHook(() => useTableColumns([data[3], data[0]]));
+ expect(hook2.result.current).toEqual([
+ { Cell: expect.any(Function), Header: 'col01', accessor: 'col01' },
+ { Cell: expect.any(Function), Header: 'col02', accessor: 'col02' },
+ { Cell: expect.any(Function), Header: 'col03', accessor: 'col03' },
+ ]);
+});
+
+test('useTableColumns with options', () => {
+ const hook = renderHook(() => useTableColumns(data, { col01: { id: 'ID' } }));
+ expect(hook.result.current).toEqual([
+ {
+ Cell: expect.any(Function),
+ Header: 'col01',
+ accessor: 'col01',
+ id: 'ID',
+ },
+ { Cell: expect.any(Function), Header: 'col02', accessor: 'col02' },
+ ]);
+});