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 {