diff --git a/superset-frontend/spec/fixtures/mockChartQueries.js b/superset-frontend/spec/fixtures/mockChartQueries.js index d25267392a0db..0175df981a306 100644 --- a/superset-frontend/spec/fixtures/mockChartQueries.js +++ b/superset-frontend/spec/fixtures/mockChartQueries.js @@ -36,6 +36,7 @@ export default { datasource: datasourceId, viz_type: 'pie', slice_id: sliceId, + slice_name: 'Genders', granularity_sqla: null, time_grain_sqla: null, since: '100 years ago', diff --git a/superset-frontend/spec/fixtures/mockState.js b/superset-frontend/spec/fixtures/mockState.js index e492217e72b6d..629ed775b4121 100644 --- a/superset-frontend/spec/fixtures/mockState.js +++ b/superset-frontend/spec/fixtures/mockState.js @@ -35,6 +35,11 @@ export default { sliceEntities: sliceEntitiesForChart, charts: chartQueries, nativeFilters: nativeFiltersInfo, + common: { + conf: { + SAMPLES_ROW_LIMIT: 10, + }, + }, dataMask: mockDataMaskInfo, dashboardInfo, dashboardFilters: emptyFilters, diff --git a/superset-frontend/src/components/Chart/DrillDetailModal.test.tsx b/superset-frontend/src/components/Chart/DrillDetailModal.test.tsx new file mode 100644 index 0000000000000..20f319ce4f979 --- /dev/null +++ b/superset-frontend/src/components/Chart/DrillDetailModal.test.tsx @@ -0,0 +1,110 @@ +/** + * 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, waitFor } from 'spec/helpers/testing-library'; +import { getMockStoreWithNativeFilters } from 'spec/fixtures/mockStore'; +import chartQueries, { sliceId } from 'spec/fixtures/mockChartQueries'; +import { QueryFormData } from '@superset-ui/core'; +import fetchMock from 'fetch-mock'; +import userEvent from '@testing-library/user-event'; +import DrillDetailModal from './DrillDetailModal'; + +const chart = chartQueries[sliceId]; +const setup = (overrides: Record = {}) => { + const store = getMockStoreWithNativeFilters(); + const props = { + chartId: sliceId, + initialFilters: [], + formData: chart.form_data as unknown as QueryFormData, + ...overrides, + }; + return render(, { + useRedux: true, + useRouter: true, + store, + }); +}; +const waitForRender = (overrides: Record = {}) => + waitFor(() => setup(overrides)); + +fetchMock.post( + 'end:/datasource/samples?force=false&datasource_type=table&datasource_id=7&per_page=50&page=1', + { + result: { + data: [], + colnames: [], + coltypes: [], + }, + }, +); + +const mockHistoryPush = jest.fn(); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useHistory: () => ({ + push: mockHistoryPush, + }), +})); + +test('should render', async () => { + const { container } = await waitForRender(); + expect(container).toBeInTheDocument(); +}); + +test('should render the title', async () => { + await waitForRender(); + expect( + screen.getByText(`Drill to detail: ${chart.form_data.slice_name}`), + ).toBeInTheDocument(); +}); + +test('should render the modal', async () => { + await waitForRender(); + expect(screen.getByRole('dialog')).toBeInTheDocument(); +}); + +test('should not render the modal', async () => { + await waitForRender({ + initialFilters: undefined, + }); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); +}); + +test('should render the button', async () => { + await waitForRender(); + expect( + screen.getByRole('button', { name: 'Edit chart' }), + ).toBeInTheDocument(); + expect(screen.getAllByRole('button', { name: 'Close' })).toHaveLength(2); +}); + +test('should close the modal', async () => { + await waitForRender(); + expect(screen.getByRole('dialog')).toBeInTheDocument(); + userEvent.click(screen.getAllByRole('button', { name: 'Close' })[1]); + expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); +}); + +test('should forward to Explore', async () => { + await waitForRender(); + userEvent.click(screen.getByRole('button', { name: 'Edit chart' })); + expect(mockHistoryPush).toHaveBeenCalledWith( + `/explore/?dashboard_page_id=&slice_id=${sliceId}`, + ); +}); diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.test.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.test.tsx new file mode 100644 index 0000000000000..a65d469fb02d6 --- /dev/null +++ b/superset-frontend/src/dashboard/components/DrillDetailPane/DrillDetailPane.test.tsx @@ -0,0 +1,124 @@ +/** + * 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, waitFor } from 'spec/helpers/testing-library'; +import { getMockStoreWithNativeFilters } from 'spec/fixtures/mockStore'; +import chartQueries, { sliceId } from 'spec/fixtures/mockChartQueries'; +import { QueryFormData, SupersetClient } from '@superset-ui/core'; +import fetchMock from 'fetch-mock'; +import DrillDetailPane from './DrillDetailPane'; + +const chart = chartQueries[sliceId]; +const setup = (overrides: Record = {}) => { + const store = getMockStoreWithNativeFilters(); + const props = { + initialFilters: [], + formData: chart.form_data as unknown as QueryFormData, + ...overrides, + }; + return render(, { + useRedux: true, + store, + }); +}; +const waitForRender = (overrides: Record = {}) => + waitFor(() => setup(overrides)); +const samplesEndpoint = + 'end:/datasource/samples?force=false&datasource_type=table&datasource_id=7&per_page=50&page=1'; +const fetchWithNoData = () => + fetchMock.post(samplesEndpoint, { + result: { + total_count: 0, + data: [], + colnames: [], + coltypes: [], + }, + }); +const fetchWithData = () => + fetchMock.post(samplesEndpoint, { + result: { + total_count: 3, + data: [ + { + year: 1996, + na_sales: 11.27, + eu_sales: 8.89, + }, + { + year: 1989, + na_sales: 23.2, + eu_sales: 2.26, + }, + { + year: 1999, + na_sales: 9, + eu_sales: 6.18, + }, + ], + colnames: ['year', 'na_sales', 'eu_sales'], + coltypes: [0, 0, 0], + }, + }); +const SupersetClientPost = jest.spyOn(SupersetClient, 'post'); + +afterEach(fetchMock.restore); + +test('should render', async () => { + fetchWithNoData(); + const { container } = await waitForRender(); + expect(container).toBeInTheDocument(); +}); + +test('should render the loading component', async () => { + fetchWithData(); + setup(); + await waitFor(() => { + expect(screen.getByRole('status', { name: 'Loading' })).toBeInTheDocument(); + }); +}); + +test('should render the table with results', async () => { + fetchWithData(); + await waitForRender(); + expect(screen.getByRole('table')).toBeInTheDocument(); + expect(screen.getAllByRole('row')).toHaveLength(4); + expect( + screen.getByRole('columnheader', { name: 'year' }), + ).toBeInTheDocument(); + expect( + screen.getByRole('columnheader', { name: 'na_sales' }), + ).toBeInTheDocument(); + expect( + screen.getByRole('columnheader', { name: 'eu_sales' }), + ).toBeInTheDocument(); +}); + +test('should render the "No results" components', async () => { + fetchWithNoData(); + setup(); + expect( + await screen.findByText('No rows were returned for this dataset'), + ).toBeInTheDocument(); +}); + +test('should render the error', async () => { + SupersetClientPost.mockRejectedValue(new Error('Something went wrong')); + await waitForRender(); + expect(screen.getByText('Error: Something went wrong')).toBeInTheDocument(); +}); diff --git a/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.test.tsx b/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.test.tsx new file mode 100644 index 0000000000000..0768d0ec7344d --- /dev/null +++ b/superset-frontend/src/dashboard/components/DrillDetailPane/TableControls.test.tsx @@ -0,0 +1,109 @@ +/** + * 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 TableControls from './TableControls'; + +const setFilters = jest.fn(); +const onReload = jest.fn(); +const setup = (overrides: Record = {}) => { + const props = { + filters: [], + setFilters, + onReload, + loading: false, + totalCount: 0, + ...overrides, + }; + return render(); +}; +test('should render', () => { + const { container } = setup(); + expect(container).toBeInTheDocument(); +}); + +test('should show 0 rows', () => { + setup(); + expect(screen.getByText('0 rows')).toBeInTheDocument(); +}); + +test('should show the correct amount of rows', () => { + setup({ + totalCount: 10, + }); + expect(screen.getByText('10 rows')).toBeInTheDocument(); +}); + +test('should render the reload button', () => { + setup(); + expect(screen.getByRole('button', { name: 'Reload' })).toBeInTheDocument(); +}); + +test('should show the loading indicator', () => { + setup({ + loading: true, + }); + expect(screen.getByText('Loading...')).toBeInTheDocument(); +}); + +test('should call onreload', () => { + setup(); + userEvent.click(screen.getByRole('button', { name: 'Reload' })); + expect(onReload).toHaveBeenCalledTimes(1); +}); + +test('should render with filters', () => { + setup({ + filters: [ + { + col: 'platform', + op: '==', + val: 'GB', + }, + { + col: 'lang', + op: '==', + val: 'IT', + }, + ], + }); + expect(screen.getByText('platform')).toBeInTheDocument(); + expect(screen.getByText('GB')).toBeInTheDocument(); + expect(screen.getByText('lang')).toBeInTheDocument(); + expect(screen.getByText('IT')).toBeInTheDocument(); +}); + +test('should remove the filters on close', () => { + setup({ + filters: [ + { + col: 'platform', + op: '==', + val: 'GB', + }, + ], + }); + expect(screen.getByText('platform')).toBeInTheDocument(); + expect(screen.getByText('GB')).toBeInTheDocument(); + + userEvent.click(screen.getByRole('img', { name: 'close' })); + + expect(setFilters).toHaveBeenCalledWith([]); +}); diff --git a/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx b/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx index 514ff4fc2cdea..455afb60392a3 100644 --- a/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx +++ b/superset-frontend/src/dashboard/components/SliceHeaderControls/SliceHeaderControls.test.tsx @@ -236,3 +236,23 @@ test('Should "Enter fullscreen"', () => { userEvent.click(screen.getByText('Enter fullscreen')); expect(props.handleToggleFullSize).toBeCalledTimes(1); }); + +test('Drill to detail modal is under featureflag', () => { + // @ts-ignore + global.featureFlags = { + [FeatureFlag.DRILL_TO_DETAIL]: false, + }; + const props = createProps(); + renderWrapper(props); + expect(screen.queryByText('Drill to detail')).not.toBeInTheDocument(); +}); + +test('Should show the "Drill to detail"', () => { + // @ts-ignore + global.featureFlags = { + [FeatureFlag.DRILL_TO_DETAIL]: true, + }; + const props = createProps(); + renderWrapper(props); + expect(screen.getByText('Drill to detail')).toBeInTheDocument(); +});