diff --git a/superset-frontend/spec/javascripts/components/TableView/TableView_spec.tsx b/superset-frontend/spec/javascripts/components/TableView/TableView_spec.tsx
deleted file mode 100644
index ee9b1ae69e6..00000000000
--- a/superset-frontend/spec/javascripts/components/TableView/TableView_spec.tsx
+++ /dev/null
@@ -1,143 +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 { mount } from 'enzyme';
-import { supersetTheme, ThemeProvider } from '@superset-ui/core';
-import Pagination from 'src/components/Pagination';
-import TableView from '../../../../src/components/TableView';
-import { TableViewProps } from '../../../../src/components/TableView/TableView';
-
-const mockedProps: TableViewProps = {
- columns: [
- {
- accessor: 'id',
- Header: 'ID',
- sortable: true,
- },
- {
- accessor: 'age',
- Header: 'Age',
- },
- {
- accessor: 'name',
- Header: 'Name',
- },
- ],
- data: [
- { id: 1, age: 20, name: 'Emily' },
- { id: 2, age: 10, name: 'Kate' },
- { id: 3, age: 40, name: 'Anna' },
- { id: 4, age: 30, name: 'Jane' },
- ],
- pageSize: 1,
-};
-
-const factory = (props = mockedProps) =>
- mount(, {
- wrappingComponent: ThemeProvider,
- wrappingComponentProps: { theme: supersetTheme },
- });
-
-describe('TableView', () => {
- it('render a table, columns and rows', () => {
- const pageSize = 10;
- const wrapper = factory({ ...mockedProps, pageSize });
- expect(wrapper.find('table')).toExist();
- expect(wrapper.find('table th')).toHaveLength(mockedProps.columns.length);
- expect(wrapper.find('table tbody tr')).toHaveLength(
- Math.min(mockedProps.data.length, pageSize),
- );
- });
-
- it('renders pagination controls', () => {
- const wrapper = factory();
- expect(wrapper.find(Pagination)).toExist();
- expect(wrapper.find(Pagination.Prev)).toExist();
- expect(wrapper.find(Pagination.Item)).toExist();
- expect(wrapper.find(Pagination.Next)).toExist();
- });
-
- it("doesn't render pagination when pagination is disabled", () => {
- const wrapper = factory({ ...mockedProps, withPagination: false });
- expect(wrapper.find(Pagination)).not.toExist();
- });
-
- it("doesn't render pagination when fewer rows than page size", () => {
- const pageSize = 999;
- expect(pageSize).toBeGreaterThan(mockedProps.data.length);
-
- const wrapper = factory({ ...mockedProps, pageSize });
- expect(wrapper.find(Pagination)).not.toExist();
- });
-
- it('changes page when button is clicked', () => {
- const pageSize = 3;
- const dataLength = mockedProps.data.length;
-
- expect(dataLength).toBeGreaterThan(pageSize);
- const wrapper = factory({ ...mockedProps, pageSize });
-
- expect(wrapper.find('table tbody tr')).toHaveLength(pageSize);
-
- wrapper.find('NEXT_PAGE_LINK span').simulate('click');
- expect(wrapper.find('table tbody tr')).toHaveLength(
- Math.min(dataLength - pageSize, pageSize),
- );
- });
-
- it('sorts by age when header cell is clicked', () => {
- const wrapper = factory();
- expect(wrapper.find('table tbody td Cell').at(1).props().value).toEqual(20);
-
- // sort ascending
- wrapper.find('table thead th').at(1).simulate('click');
- expect(wrapper.find('table tbody td Cell').at(1).props().value).toEqual(10);
-
- // sort descending
- wrapper.find('table thead th').at(1).simulate('click');
- expect(wrapper.find('table tbody td Cell').at(1).props().value).toEqual(40);
-
- // no sort
- wrapper.find('table thead th').at(1).simulate('click');
- expect(wrapper.find('table tbody td Cell').at(1).props().value).toEqual(20);
- });
-
- it('sorts by data when initialSortBy is passed', () => {
- let wrapper = factory();
- expect(wrapper.find('table tbody td Cell').at(2).props().value).toEqual(
- 'Emily',
- );
-
- wrapper = factory({
- ...mockedProps,
- initialSortBy: [{ id: 'name', desc: true }],
- });
- expect(wrapper.find('table tbody td Cell').at(2).props().value).toEqual(
- 'Kate',
- );
-
- wrapper = factory({
- ...mockedProps,
- initialSortBy: [{ id: 'name', desc: false }],
- });
- expect(wrapper.find('table tbody td Cell').at(2).props().value).toEqual(
- 'Anna',
- );
- });
-});
diff --git a/superset-frontend/src/components/TableView/TableView.stories.tsx b/superset-frontend/src/components/TableView/TableView.stories.tsx
new file mode 100644
index 00000000000..e13ef1bf09e
--- /dev/null
+++ b/superset-frontend/src/components/TableView/TableView.stories.tsx
@@ -0,0 +1,85 @@
+/**
+ * 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 TableView, { TableViewProps, EmptyWrapperType } from '.';
+
+export default {
+ title: 'TableView',
+ component: TableView,
+};
+
+export const InteractiveTableView = (args: TableViewProps) => (
+
+);
+
+InteractiveTableView.args = {
+ columns: [
+ {
+ accessor: 'id',
+ Header: 'ID',
+ sortable: true,
+ },
+ {
+ accessor: 'age',
+ Header: 'Age',
+ },
+ {
+ accessor: 'name',
+ Header: 'Name',
+ },
+ ],
+ data: [
+ { id: 123, age: 27, name: 'Emily' },
+ { id: 321, age: 10, name: 'Kate' },
+ ],
+ initialSortBy: [{ id: 'name', desc: true }],
+ noDataText: 'No data here',
+ pageSize: 1,
+ showRowCount: true,
+ withPagination: true,
+};
+
+InteractiveTableView.argTypes = {
+ emptyWrapperType: {
+ control: {
+ type: 'select',
+ options: [EmptyWrapperType.Default, EmptyWrapperType.Small],
+ },
+ },
+ pageSize: {
+ control: {
+ type: 'number',
+ min: 1,
+ },
+ },
+ initialPageIndex: {
+ control: {
+ type: 'number',
+ min: 0,
+ },
+ },
+};
+
+InteractiveTableView.story = {
+ parameters: {
+ knobs: {
+ disable: true,
+ },
+ },
+};
diff --git a/superset-frontend/src/components/TableView/TableView.test.tsx b/superset-frontend/src/components/TableView/TableView.test.tsx
new file mode 100644
index 00000000000..355eb3f117b
--- /dev/null
+++ b/superset-frontend/src/components/TableView/TableView.test.tsx
@@ -0,0 +1,193 @@
+/**
+ * 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 userEvent from '@testing-library/user-event';
+import TableView, { TableViewProps } from '.';
+
+const mockedProps: TableViewProps = {
+ columns: [
+ {
+ accessor: 'id',
+ Header: 'ID',
+ sortable: true,
+ },
+ {
+ accessor: 'age',
+ Header: 'Age',
+ },
+ {
+ accessor: 'name',
+ Header: 'Name',
+ },
+ ],
+ data: [
+ { id: 123, age: 27, name: 'Emily' },
+ { id: 321, age: 10, name: 'Kate' },
+ ],
+ pageSize: 1,
+};
+
+test('should render', () => {
+ const { container } = render();
+ expect(container).toBeInTheDocument();
+});
+
+test('should render a table', () => {
+ render();
+ expect(screen.getByRole('table')).toBeInTheDocument();
+});
+
+test('should render the headers', () => {
+ render();
+ expect(screen.getAllByRole('columnheader')).toHaveLength(3);
+ expect(screen.getByText('ID')).toBeInTheDocument();
+ expect(screen.getByText('Age')).toBeInTheDocument();
+ expect(screen.getByText('Name')).toBeInTheDocument();
+});
+
+test('should render the rows', () => {
+ render();
+ expect(screen.getAllByRole('row')).toHaveLength(2);
+});
+
+test('should render the cells', () => {
+ render();
+ expect(screen.getAllByRole('cell')).toHaveLength(3);
+ expect(screen.getByText('123')).toBeInTheDocument();
+ expect(screen.getByText('27')).toBeInTheDocument();
+ expect(screen.getByText('Emily')).toBeInTheDocument();
+});
+
+test('should render the pagination', () => {
+ render();
+ expect(screen.getByRole('navigation')).toBeInTheDocument();
+ expect(screen.getAllByRole('button')).toHaveLength(4);
+ expect(screen.getByText('«')).toBeInTheDocument();
+ expect(screen.getByText('»')).toBeInTheDocument();
+});
+
+test('should show the row count by default', () => {
+ render();
+ expect(screen.getByText('1-1 of 2')).toBeInTheDocument();
+});
+
+test('should NOT show the row count', () => {
+ const noRowCountProps = {
+ ...mockedProps,
+ showRowCount: false,
+ };
+ render();
+ expect(screen.queryByText('1-1 of 2')).not.toBeInTheDocument();
+});
+
+test('should NOT render the pagination when disabled', () => {
+ const withoutPaginationProps = {
+ ...mockedProps,
+ withPagination: false,
+ };
+ render();
+ expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
+});
+
+test('should NOT render the pagination when fewer rows than page size', () => {
+ const withoutPaginationProps = {
+ ...mockedProps,
+ pageSize: 3,
+ };
+ render();
+ expect(screen.queryByRole('navigation')).not.toBeInTheDocument();
+});
+
+test('should change page when « and » buttons are clicked', () => {
+ render();
+ const nextBtn = screen.getByText('»');
+ const prevBtn = screen.getByText('«');
+
+ userEvent.click(nextBtn);
+ expect(screen.getAllByRole('cell')).toHaveLength(3);
+ expect(screen.getByText('321')).toBeInTheDocument();
+ expect(screen.getByText('10')).toBeInTheDocument();
+ expect(screen.getByText('Kate')).toBeInTheDocument();
+ expect(screen.queryByText('Emily')).not.toBeInTheDocument();
+
+ userEvent.click(prevBtn);
+ expect(screen.getAllByRole('cell')).toHaveLength(3);
+ expect(screen.getByText('123')).toBeInTheDocument();
+ expect(screen.getByText('27')).toBeInTheDocument();
+ expect(screen.getByText('Emily')).toBeInTheDocument();
+ expect(screen.queryByText('Kate')).not.toBeInTheDocument();
+});
+
+test('should sort by age', () => {
+ render();
+ const ageSort = screen.getAllByRole('columnheader')[1];
+
+ userEvent.click(ageSort);
+ expect(screen.getAllByRole('cell')[1]).toHaveTextContent('10');
+
+ userEvent.click(ageSort);
+ expect(screen.getAllByRole('cell')[1]).toHaveTextContent('27');
+});
+
+test('should sort by initialSortBy DESC', () => {
+ const initialSortByDescProps = {
+ ...mockedProps,
+ initialSortBy: [{ id: 'name', desc: true }],
+ };
+ render();
+
+ expect(screen.getByText('Kate')).toBeInTheDocument();
+ expect(screen.queryByText('Emily')).not.toBeInTheDocument();
+});
+
+test('should sort by initialSortBy ASC', () => {
+ const initialSortByAscProps = {
+ ...mockedProps,
+ initialSortBy: [{ id: 'name', desc: false }],
+ };
+ render();
+
+ expect(screen.getByText('Emily')).toBeInTheDocument();
+ expect(screen.queryByText('Kate')).not.toBeInTheDocument();
+});
+
+test('should show empty', () => {
+ const noDataProps = {
+ ...mockedProps,
+ data: [],
+ noDataText: 'No data here',
+ };
+ render();
+
+ expect(screen.getByText('No data here')).toBeInTheDocument();
+});
+
+test('should render the right page', () => {
+ const pageIndexProps = {
+ ...mockedProps,
+ initialPageIndex: 1,
+ };
+ render();
+
+ expect(screen.getByText('321')).toBeInTheDocument();
+ expect(screen.getByText('10')).toBeInTheDocument();
+ expect(screen.getByText('Kate')).toBeInTheDocument();
+ expect(screen.queryByText('Emily')).not.toBeInTheDocument();
+});
diff --git a/superset-frontend/src/components/TableView/TableView.tsx b/superset-frontend/src/components/TableView/TableView.tsx
index fc8854a4d26..bc3517f5bd9 100644
--- a/superset-frontend/src/components/TableView/TableView.tsx
+++ b/superset-frontend/src/components/TableView/TableView.tsx
@@ -26,8 +26,8 @@ import { SortColumns } from './types';
const DEFAULT_PAGE_SIZE = 10;
export enum EmptyWrapperType {
- Default,
- Small,
+ Default = 'Default',
+ Small = 'Small',
}
export interface TableViewProps {