From 8bfb2f854ee4e7383342ae7d362dfaa4fd468137 Mon Sep 17 00:00:00 2001 From: Kamil Gabryjelski Date: Tue, 1 Mar 2022 17:33:09 +0100 Subject: [PATCH] chore(native-filters): Add unit tests for filter cards (#18967) * chore(native-filters): Add unit tests for filter cards * Fix test (cherry picked from commit ec746c2a1066f2fd8d6e809397d8172cc336b833) --- .../FilterCard/FilterCard.test.tsx | 307 ++++++++++++++++++ .../nativeFilters/FilterCard/ScopeRow.tsx | 11 +- 2 files changed, 312 insertions(+), 6 deletions(-) create mode 100644 superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCard.test.tsx diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCard.test.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCard.test.tsx new file mode 100644 index 0000000000000..5a0663032d3cf --- /dev/null +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/FilterCard.test.tsx @@ -0,0 +1,307 @@ +/** + * 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 * as reactRedux from 'react-redux'; +import { Filter, NativeFilterType } from '@superset-ui/core'; +import userEvent from '@testing-library/user-event'; +import { render, screen } from 'spec/helpers/testing-library'; +import { DASHBOARD_ROOT_ID } from 'src/dashboard/util/constants'; +import { SET_DIRECT_PATH } from 'src/dashboard/actions/dashboardState'; +import { FilterCardContent } from './FilterCardContent'; + +const baseInitialState = { + nativeFilters: { + filters: { + 'NATIVE_FILTER-1': { + id: 'NATIVE_FILTER-1', + controlValues: {}, + name: 'Native filter 1', + filterType: 'filter_select', + targets: [ + { + datasetId: 1, + column: { + name: 'gender', + }, + }, + ], + defaultDataMask: {}, + cascadeParentIds: [], + scope: { + rootPath: [DASHBOARD_ROOT_ID], + excluded: [], + }, + type: NativeFilterType.NATIVE_FILTER, + description: '', + }, + 'NATIVE_FILTER-2': { + id: 'NATIVE_FILTER-2', + controlValues: {}, + name: 'Native filter 2', + filterType: 'filter_select', + targets: [ + { + datasetId: 1, + column: { + name: 'gender', + }, + }, + ], + defaultDataMask: {}, + cascadeParentIds: [], + scope: { + rootPath: [DASHBOARD_ROOT_ID], + excluded: [], + }, + type: NativeFilterType.NATIVE_FILTER, + description: '', + }, + }, + }, + charts: { + '1': { + id: 1, + }, + '2': { + id: 2, + }, + '3': { + id: 3, + }, + }, + dashboardLayout: { + past: [], + future: [], + present: { + ROOT_ID: { + children: ['TABS-1'], + id: 'ROOT_ID', + type: 'ROOT', + }, + + 'TABS-1': { + children: ['TAB-1', 'TAB-2'], + id: 'TABS-1', + meta: {}, + parents: ['ROOT_ID'], + type: 'TABS', + }, + 'TAB-1': { + children: [], + id: 'TAB-1', + meta: { + defaultText: 'Tab title', + placeholder: 'Tab title', + text: 'Tab 1', + }, + parents: ['ROOT_ID', 'TABS-1'], + type: 'TAB', + }, + 'TAB-2': { + children: [], + id: 'TAB-2', + meta: { + defaultText: 'Tab title', + placeholder: 'Tab title', + text: 'Tab 2', + }, + parents: ['ROOT_ID', 'TABS-1'], + type: 'TAB', + }, + 'CHART-1': { + children: [], + id: 'CHART-1', + meta: { + chartId: 1, + sliceName: 'Test chart', + }, + parents: ['ROOT_ID', 'TABS-1', 'TAB-1'], + type: 'CHART', + }, + 'CHART-2': { + children: [], + id: 'CHART-2', + meta: { + chartId: 2, + sliceName: 'Test chart 2', + }, + parents: ['ROOT_ID', 'TABS-1', 'TAB-1'], + type: 'CHART', + }, + 'CHART-3': { + children: [], + id: 'CHART-3', + meta: { + chartId: 3, + sliceName: 'Test chart 3', + }, + parents: ['ROOT_ID', 'TABS-1', 'TAB-1'], + type: 'CHART', + }, + 'CHART-4': { + children: [], + id: 'CHART-4', + meta: { + chartId: 4, + sliceName: 'Test chart 4', + }, + parents: ['ROOT_ID', 'TABS-1', 'TAB-2'], + type: 'CHART', + }, + }, + }, +}; +const baseFilter: Filter = { + id: 'NATIVE_FILTER-1', + controlValues: {}, + name: 'Native filter 1', + filterType: 'filter_select', + targets: [ + { + datasetId: 1, + column: { + name: 'gender', + }, + }, + ], + defaultDataMask: {}, + cascadeParentIds: [], + scope: { + rootPath: [DASHBOARD_ROOT_ID], + excluded: [], + }, + type: NativeFilterType.NATIVE_FILTER, + description: '', +}; + +jest.mock('@superset-ui/core', () => ({ + // @ts-ignore + ...jest.requireActual('@superset-ui/core'), + getChartMetadataRegistry: () => ({ + get: (type: string) => { + if (type === 'filter_select') { + return { name: 'Select filter' }; + } + return undefined; + }, + }), +})); + +// extract text from embedded html tags +// source: https://polvara.me/posts/five-things-you-didnt-know-about-testing-library +const getTextInHTMLTags = + (target: string | RegExp) => (content: string, node: Element) => { + const hasText = (node: Element) => node.textContent === target; + const nodeHasText = hasText(node); + const childrenDontHaveText = Array.from(node.children).every( + child => !hasText(child), + ); + + return nodeHasText && childrenDontHaveText; + }; + +const renderContent = (filter = baseFilter, initialState = baseInitialState) => + render(, { + useRedux: true, + initialState, + }); + +describe('Filter Card', () => { + it('Basic', () => { + renderContent(); + expect(screen.getByText('Native filter 1')).toBeVisible(); + expect(screen.getByLabelText('filter-small')).toBeVisible(); + + expect(screen.getByText('Filter type')).toBeVisible(); + expect(screen.getByText('Select filter')).toBeVisible(); + + expect(screen.getByText('Scope')).toBeVisible(); + expect(screen.getByText('All charts')).toBeVisible(); + + expect(screen.queryByText('Dependencies')).not.toBeInTheDocument(); + }); + + describe('Scope', () => { + it('Scope with excluded', () => { + const filter = { + ...baseFilter, + scope: { rootPath: [DASHBOARD_ROOT_ID], excluded: [1, 4] }, + }; + renderContent(filter); + expect(screen.getByText('Scope')).toBeVisible(); + expect(screen.getByText('Test chart 2')).toBeVisible(); + expect( + screen.getByText(getTextInHTMLTags('Test chart 2, Test chart 3')), + ).toBeVisible(); + }); + + it('Scope with top level tab as root', () => { + const filter = { + ...baseFilter, + scope: { rootPath: ['TAB-1', 'TAB-2'], excluded: [1, 2] }, + }; + renderContent(filter); + expect(screen.getByText('Scope')).toBeVisible(); + expect( + screen.getByText(getTextInHTMLTags('Tab 2, Test chart 3')), + ).toBeVisible(); + }); + + it('Empty scope', () => { + const filter = { + ...baseFilter, + scope: { rootPath: [], excluded: [1, 2, 3, 4] }, + }; + renderContent(filter); + expect(screen.getByText('Scope')).toBeVisible(); + expect(screen.getByText('None')).toBeVisible(); + }); + }); + + describe('Dependencies', () => { + it('Has dependency', () => { + const filter = { + ...baseFilter, + cascadeParentIds: ['NATIVE_FILTER-2'], + }; + renderContent(filter); + expect(screen.getByText('Dependent on')).toBeVisible(); + expect(screen.getByText('Native filter 2')).toBeVisible(); + }); + + it('Focus filter on dependency click', () => { + const useDispatchMock = jest.spyOn(reactRedux, 'useDispatch'); + const dummyDispatch = jest.fn(); + useDispatchMock.mockReturnValue(dummyDispatch); + + const filter = { + ...baseFilter, + cascadeParentIds: ['NATIVE_FILTER-2'], + }; + renderContent(filter); + + userEvent.click(screen.getByText('Native filter 2')); + expect(dummyDispatch).toHaveBeenCalledWith({ + type: SET_DIRECT_PATH, + path: ['NATIVE_FILTER-2'], + }); + }); + }); +}); diff --git a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/ScopeRow.tsx b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/ScopeRow.tsx index 8f005874d2347..f7ede30692bd4 100644 --- a/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/ScopeRow.tsx +++ b/superset-frontend/src/dashboard/components/nativeFilters/FilterCard/ScopeRow.tsx @@ -47,17 +47,16 @@ export const ScopeRow = React.memo(({ filter }: FilterCardRowProps) => { [elementsTruncated, scope], ); - if (!Array.isArray(scope) || scope.length === 0) { - return null; - } return ( {t('Scope')} - {scope.map((element, index) => ( - {index === 0 ? element : `, ${element}`} - ))} + {scope + ? scope.map((element, index) => ( + {index === 0 ? element : `, ${element}`} + )) + : t('None')} {hasHiddenElements > 0 && ( +{elementsTruncated}