/** * 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, shallow } from 'enzyme'; import { act } from 'react-dom/test-utils'; import { MenuItem, Pagination } from 'react-bootstrap'; import Select from 'src/components/Select'; import { QueryParamProvider } from 'use-query-params'; import ListView from 'src/components/ListView/ListView'; import ListViewFilters from 'src/components/ListView/Filters'; import ListViewPagination from 'src/components/ListView/Pagination'; import { areArraysShallowEqual } from 'src/reduxUtils'; import { ThemeProvider } from 'emotion-theming'; import { supersetTheme } from '@superset-ui/style'; export function makeMockLocation(query) { const queryStr = encodeURIComponent(query); return { protocol: 'http:', host: 'localhost', pathname: '/', search: queryStr.length ? `?${queryStr}` : '', }; } const mockedProps = { title: 'Data Table', columns: [ { accessor: 'id', Header: 'ID', sortable: true, }, { accessor: 'age', Header: 'Age', }, { accessor: 'name', Header: 'Name', }, ], filters: [ { Header: 'Name', id: 'name', operators: [{ label: 'Starts With', value: 'sw' }], }, ], data: [ { id: 1, name: 'data 1' }, { id: 2, name: 'data 2' }, ], count: 2, pageSize: 1, fetchData: jest.fn(() => []), loading: false, bulkActions: [ { key: 'something', name: 'do something', onSelect: jest.fn() }, ], }; const factory = (props = mockedProps) => mount( , { wrappingComponent: ThemeProvider, wrappingComponentProps: { theme: supersetTheme }, }, ); describe('ListView', () => { const wrapper = factory(); afterEach(() => { mockedProps.fetchData.mockClear(); mockedProps.bulkActions.forEach(ba => { ba.onSelect.mockClear(); }); }); it('calls fetchData on mount', () => { expect(wrapper.find(ListView)).toHaveLength(1); expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { "filters": Array [], "pageIndex": 0, "pageSize": 1, "sortBy": Array [], }, ] `); }); it('calls fetchData on sort', () => { wrapper.find('[data-test="sort-header"]').at(1).simulate('click'); expect(mockedProps.fetchData).toHaveBeenCalled(); expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { "filters": Array [], "pageIndex": 0, "pageSize": 1, "sortBy": Array [ Object { "desc": false, "id": "id", }, ], }, ] `); }); it('calls fetchData on filter', () => { act(() => { wrapper .find('.dropdown-toggle') .children('button') .at(0) .props() .onClick(); wrapper .find(MenuItem) .at(0) .props() .onSelect({ id: 'name', Header: 'name' }); }); wrapper.update(); act(() => { wrapper.find('.filter-inputs input[type="text"]').prop('onChange')({ persist() {}, currentTarget: { value: 'foo' }, }); }); wrapper.update(); act(() => { wrapper.find('[data-test="apply-filters"]').last().prop('onClick')(); }); wrapper.update(); expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { "filters": Array [ Object { "id": "name", "operator": "sw", "value": "foo", }, ], "pageIndex": 0, "pageSize": 1, "sortBy": Array [ Object { "desc": false, "id": "id", }, ], }, ] `); }); it('renders pagination controls', () => { expect(wrapper.find(Pagination).exists()).toBe(true); expect(wrapper.find(Pagination.Prev).exists()).toBe(true); expect(wrapper.find(Pagination.Item).exists()).toBe(true); expect(wrapper.find(Pagination.Next).exists()).toBe(true); }); it('calls fetchData on page change', () => { act(() => { wrapper.find(ListViewPagination).prop('onChange')(2); }); wrapper.update(); expect(mockedProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { "filters": Array [ Object { "id": "name", "operator": "sw", "value": "foo", }, ], "pageIndex": 1, "pageSize": 1, "sortBy": Array [ Object { "desc": false, "id": "id", }, ], }, ] `); }); it('handles bulk actions on 1 row', () => { act(() => { wrapper.find('input[id="0"]').at(0).prop('onChange')({ target: { value: 'on' }, }); wrapper .find('.dropdown-toggle') .children('button') .at(1) .props() .onClick(); }); wrapper.update(); const bulkActionsProps = wrapper.find(MenuItem).last().props(); bulkActionsProps.onSelect(bulkActionsProps.eventKey); expect(mockedProps.bulkActions[0].onSelect.mock.calls[0]) .toMatchInlineSnapshot(` Array [ Array [ Object { "id": 1, "name": "data 1", }, ], ] `); }); it('handles bulk actions on all rows', () => { act(() => { wrapper.find('input[id="header-toggle-all"]').at(0).prop('onChange')({ target: { value: 'on' }, }); wrapper .find('.dropdown-toggle') .children('button') .at(1) .props() .onClick(); }); wrapper.update(); const bulkActionsProps = wrapper.find(MenuItem).last().props(); bulkActionsProps.onSelect(bulkActionsProps.eventKey); expect(mockedProps.bulkActions[0].onSelect.mock.calls[0]) .toMatchInlineSnapshot(` Array [ Array [ Object { "id": 1, "name": "data 1", }, Object { "id": 2, "name": "data 2", }, ], ] `); }); it('Throws an exception if filter missing in columns', () => { expect.assertions(1); const props = { ...mockedProps, filters: [...mockedProps.filters, { id: 'some_column' }], }; try { shallow(, { wrappingComponent: ThemeProvider, wrappingComponentProps: { theme: supersetTheme }, }); } catch (e) { expect(e).toMatchInlineSnapshot( `[ListViewError: Invalid filter config, some_column is not present in columns]`, ); } }); }); describe('ListView with new UI filters', () => { const fetchSelectsMock = jest.fn(() => []); const newFiltersProps = { ...mockedProps, useNewUIFilters: true, filters: [ { Header: 'ID', id: 'id', input: 'select', selects: [{ label: 'foo', value: 'bar' }], operator: 'eq', }, { Header: 'Name', id: 'name', input: 'search', operator: 'ct', }, { Header: 'Age', id: 'age', input: 'select', fetchSelects: fetchSelectsMock, paginate: true, operator: 'eq', }, ], }; const wrapper = factory(newFiltersProps); afterEach(() => { mockedProps.fetchData.mockClear(); mockedProps.bulkActions.forEach(ba => { ba.onSelect.mockClear(); }); }); it('renders UI filters', () => { expect(wrapper.find(ListViewFilters)).toHaveLength(1); }); it('calls fetchData on filter', () => { act(() => { wrapper .find('[data-test="filters-select"]') .first() .props() .onChange({ value: 'bar' }); }); act(() => { wrapper .find('[data-test="filters-search"]') .first() .props() .onChange({ currentTarget: { value: 'something' } }); }); wrapper.update(); act(() => { wrapper.find('[data-test="search-input"]').last().props().onBlur(); }); expect(newFiltersProps.fetchData.mock.calls[0]).toMatchInlineSnapshot(` Array [ Object { "filters": Array [ Object { "id": "id", "operator": "eq", "value": "bar", }, ], "pageIndex": 0, "pageSize": 1, "sortBy": Array [], }, ] `); expect(newFiltersProps.fetchData.mock.calls[1]).toMatchInlineSnapshot(` Array [ Object { "filters": Array [ Object { "id": "id", "operator": "eq", "value": "bar", }, Object { "id": "name", "operator": "ct", "value": "something", }, ], "pageIndex": 0, "pageSize": 1, "sortBy": Array [], }, ] `); }); });