diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCases/TestCases.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCases/TestCases.component.tsx index 3c8a3016338..0ed8c75b968 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCases/TestCases.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestCases/TestCases.component.tsx @@ -12,10 +12,12 @@ */ import { Col, Row } from 'antd'; import { AxiosError } from 'axios'; +import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder'; import Searchbar from 'components/common/searchbar/Searchbar'; import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider'; import DataQualityTab from 'components/ProfilerDashboard/component/DataQualityTab'; import { INITIAL_PAGING_VALUE, PAGE_SIZE } from 'constants/constants'; +import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum'; import { SearchIndex } from 'enums/search.enum'; import { TestCase } from 'generated/tests/testCase'; import { Paging } from 'generated/type/paging'; @@ -181,11 +183,20 @@ export const TestCases = () => { fetchTestCases(); } } + } else { + setIsLoading(false); } }, [tab, searchValue, testCasePermission]); + if (!testCasePermission?.ViewAll && !testCasePermission?.ViewBasic) { + return ; + } + return ( - + ({ + usePermissionProvider: jest.fn().mockImplementation(() => ({ + permissions: { + testCase: testCasePermission, + }, + })), +})); +jest.mock('rest/testAPI', () => { + return { + ...jest.requireActual('rest/testAPI'), + getListTestCase: jest + .fn() + .mockImplementation(() => + Promise.resolve({ data: [], paging: { total: 0 } }) + ), + getTestCaseById: jest.fn().mockImplementation(() => Promise.resolve()), + }; +}); +jest.mock('rest/searchAPI', () => { + return { + ...jest.requireActual('rest/searchAPI'), + searchQuery: jest + .fn() + .mockImplementation(() => + Promise.resolve({ hits: { hits: [], total: { value: 0 } } }) + ), + }; +}); +jest.mock('react-router-dom', () => { + return { + ...jest.requireActual('react-router-dom'), + useParams: jest.fn().mockImplementation(() => mockUseParam), + useHistory: jest.fn().mockImplementation(() => mockUseHistory), + useLocation: jest.fn().mockImplementation(() => mockLocation), + }; +}); +jest.mock('../SummaryPannel/SummaryPanel.component', () => { + return { + SummaryPanel: jest + .fn() + .mockImplementation(() =>
SummaryPanel.component
), + }; +}); +jest.mock('components/common/next-previous/NextPrevious', () => { + return jest.fn().mockImplementation(() =>
NextPrevious.component
); +}); +jest.mock('components/common/searchbar/Searchbar', () => { + return jest.fn().mockImplementation(() =>
Searchbar.component
); +}); +jest.mock('components/ProfilerDashboard/component/DataQualityTab', () => { + return jest + .fn() + .mockImplementation(() =>
DataQualityTab.component
); +}); +jest.mock('components/common/error-with-placeholder/ErrorPlaceHolder', () => { + return jest + .fn() + .mockImplementation(({ type }) => ( +
+ ErrorPlaceHolder.component +
+ )); +}); + +describe('TestCases component', () => { + it('component should render', async () => { + render(); + + expect( + await screen.findByTestId('test-case-container') + ).toBeInTheDocument(); + expect(await screen.findByText('Searchbar.component')).toBeInTheDocument(); + expect( + await screen.findByText('SummaryPanel.component') + ).toBeInTheDocument(); + expect( + await screen.findByText('DataQualityTab.component') + ).toBeInTheDocument(); + }); + + it('on page load getListTestCase API should call', async () => { + const mockGetListTestCase = getListTestCase as jest.Mock; + + render(); + + expect(mockGetListTestCase).toHaveBeenCalledWith({ + fields: 'testDefinition,testCaseResult,testSuite', + }); + }); + + it('should call searchQuery api, if there is search term in URL', async () => { + const mockSearchQuery = searchQuery as jest.Mock; + mockLocation.search = '?searchValue=sale'; + + render(); + + expect(mockSearchQuery).toHaveBeenCalledWith({ + fetchSource: false, + pageNumber: 1, + pageSize: 10, + query: 'sale', + searchIndex: 'test_case_search_index', + }); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuites/TestSuites.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuites/TestSuites.component.tsx index 923544a260d..17cfe2131c8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuites/TestSuites.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuites/TestSuites.component.tsx @@ -13,6 +13,7 @@ import { Button, Col, Row, Table } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import { AxiosError } from 'axios'; +import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder'; import FilterTablePlaceHolder from 'components/common/error-with-placeholder/FilterTablePlaceHolder'; import NextPrevious from 'components/common/next-previous/NextPrevious'; import { OwnerLabel } from 'components/common/OwnerLabel/OwnerLabel.component'; @@ -26,6 +27,7 @@ import { ROUTES, } from 'constants/constants'; import { PROGRESS_BAR_COLOR } from 'constants/TestSuite.constant'; +import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum'; import { EntityTabs } from 'enums/entity.enum'; import { TestSummary } from 'generated/entity/data/table'; import { TestSuite } from 'generated/tests/testSuite'; @@ -159,17 +161,28 @@ export const TestSuites = () => { useEffect(() => { if (testSuitePermission?.ViewAll || testSuitePermission?.ViewBasic) { fetchTestSuites(); + } else { + setIsLoading(false); } }, [tab, testSuitePermission]); + if (!testSuitePermission?.ViewAll && !testSuitePermission?.ViewBasic) { + return ; + } + return ( - + {tab === DataQualityPageTabs.TEST_SUITES && testSuitePermission?.Create && ( - + diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuites/TestSuites.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuites/TestSuites.test.tsx new file mode 100644 index 00000000000..2b48724f67d --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuites/TestSuites.test.tsx @@ -0,0 +1,153 @@ +/* + * Copyright 2023 Collate. + * Licensed 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 { render, screen } from '@testing-library/react'; +import { DataQualityPageTabs } from 'pages/DataQuality/DataQualityPage.interface'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { getListTestSuites } from 'rest/testAPI'; +import { TestSuites } from './TestSuites.component'; + +const testSuitePermission = { + Create: true, + Delete: true, + ViewAll: true, + EditAll: true, + EditDescription: true, + EditDisplayName: true, + EditCustomFields: true, +}; +const mockUseParam = { tab: DataQualityPageTabs.TABLES } as { + tab?: DataQualityPageTabs; +}; + +jest.mock('components/PermissionProvider/PermissionProvider', () => ({ + usePermissionProvider: jest.fn().mockImplementation(() => ({ + permissions: { + testSuite: testSuitePermission, + }, + })), +})); +jest.mock('rest/testAPI', () => { + return { + ...jest.requireActual('rest/testAPI'), + getListTestSuites: jest + .fn() + .mockImplementation(() => + Promise.resolve({ data: [], paging: { total: 0 } }) + ), + }; +}); +jest.mock('react-router-dom', () => { + return { + ...jest.requireActual('react-router-dom'), + useParams: jest.fn().mockImplementation(() => mockUseParam), + }; +}); +jest.mock('../SummaryPannel/SummaryPanel.component', () => { + return { + SummaryPanel: jest + .fn() + .mockImplementation(() =>
SummaryPanel.component
), + }; +}); +jest.mock('components/common/next-previous/NextPrevious', () => { + return jest.fn().mockImplementation(() =>
NextPrevious.component
); +}); +jest.mock('components/common/error-with-placeholder/ErrorPlaceHolder', () => { + return jest + .fn() + .mockImplementation(({ type }) => ( +
+ ErrorPlaceHolder.component +
+ )); +}); + +describe('TestSuites component', () => { + it('component should render', async () => { + render(); + const tableHeader = await screen.findAllByRole('columnheader'); + const labels = tableHeader.map((header) => header.textContent); + + expect(tableHeader).toHaveLength(4); + expect(labels).toStrictEqual([ + 'label.name', + 'label.test-plural', + 'label.success %', + 'label.owner', + ]); + expect(await screen.findByTestId('test-suite-table')).toBeInTheDocument(); + expect( + await screen.findByText('SummaryPanel.component') + ).toBeInTheDocument(); + }); + + it('should send testSuiteType executable in api, if active tab is tables', async () => { + const mockGetListTestSuites = getListTestSuites as jest.Mock; + + render(); + + expect( + await screen.findByTestId('test-suite-container') + ).toBeInTheDocument(); + expect(mockGetListTestSuites).toHaveBeenCalledWith({ + fields: 'owner,summary', + testSuiteType: 'executable', + }); + }); + + it('pagination should visible if total is grater than 10', async () => { + (getListTestSuites as jest.Mock).mockImplementationOnce(() => + Promise.resolve({ data: [], paging: { total: 15 } }) + ); + + render(); + + expect( + await screen.findByText('NextPrevious.component') + ).toBeInTheDocument(); + }); + + // TestSuite type test + it('add test suite button should be visible, if type is testSuite', async () => { + mockUseParam.tab = DataQualityPageTabs.TEST_SUITES; + render(, { wrapper: MemoryRouter }); + + expect(await screen.findByTestId('add-test-suite-btn')).toBeInTheDocument(); + }); + + it('should send testSuiteType logical in api, if active tab is tables', async () => { + mockUseParam.tab = DataQualityPageTabs.TEST_SUITES; + const mockGetListTestSuites = getListTestSuites as jest.Mock; + + render(, { wrapper: MemoryRouter }); + + expect( + await screen.findByTestId('test-suite-container') + ).toBeInTheDocument(); + expect(mockGetListTestSuites).toHaveBeenCalledWith({ + fields: 'owner,summary', + testSuiteType: 'logical', + }); + }); + + it('should render no data placeholder, if there is no permission', async () => { + mockUseParam.tab = DataQualityPageTabs.TEST_SUITES; + testSuitePermission.ViewAll = false; + render(, { wrapper: MemoryRouter }); + + expect( + await screen.findByTestId('error-placeholder-type-PERMISSION') + ).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DataQuality/DataQualityPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DataQuality/DataQualityPage.test.tsx new file mode 100644 index 00000000000..4c8e16f22f8 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DataQuality/DataQualityPage.test.tsx @@ -0,0 +1,98 @@ +/* + * Copyright 2023 Collate. + * Licensed 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 { act, fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { getDataQualityPagePath } from 'utils/RouterUtils'; +import DataQualityPage from './DataQualityPage'; +import { DataQualityPageTabs } from './DataQualityPage.interface'; + +const mockUseParam = { tab: DataQualityPageTabs.TABLES } as { + tab?: DataQualityPageTabs; +}; +const mockUseHistory = { + push: jest.fn(), +}; + +// mock components +jest.mock('components/containers/PageLayoutV1', () => { + return jest.fn().mockImplementation(({ children }) =>
{children}
); +}); +jest.mock('components/DataQuality/TestSuites/TestSuites.component', () => { + return { + TestSuites: jest + .fn() + .mockImplementation(() =>
TestSuites.component
), + }; +}); +jest.mock('components/DataQuality/TestCases/TestCases.component', () => { + return { + TestCases: jest + .fn() + .mockImplementation(() =>
TestCases.component
), + }; +}); +jest.mock('react-router-dom', () => { + return { + useParams: jest.fn().mockImplementation(() => mockUseParam), + useHistory: jest.fn().mockImplementation(() => mockUseHistory), + }; +}); + +describe('DataQualityPage', () => { + it('component should render', async () => { + render(); + + expect(await screen.findByTestId('page-title')).toBeInTheDocument(); + expect(await screen.findByTestId('page-sub-title')).toBeInTheDocument(); + expect(await screen.findByTestId('tabs')).toBeInTheDocument(); + expect(await screen.findByText('TestSuites.component')).toBeInTheDocument(); + }); + + it('should render 3 tabs', async () => { + render(); + + const tabs = await screen.findAllByRole('tab'); + + expect(tabs).toHaveLength(3); + }); + + it('should change the tab, onClick of tab', async () => { + render(); + + const tabs = await screen.findAllByRole('tab'); + + expect(await screen.findByText('TestSuites.component')).toBeInTheDocument(); + + await act(async () => { + fireEvent.click(tabs[1]); + }); + + expect(mockUseHistory.push).toHaveBeenCalledWith( + getDataQualityPagePath(DataQualityPageTabs.TEST_CASES) + ); + }); + + it('should render tables tab by default', async () => { + mockUseParam.tab = undefined; + render(); + + expect(await screen.findByText('TestSuites.component')).toBeInTheDocument(); + }); + + it('should render testCase tab, if active tab is testCase', async () => { + mockUseParam.tab = DataQualityPageTabs.TEST_CASES; + render(); + + expect(await screen.findByText('TestCases.component')).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DataQuality/DataQualityPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DataQuality/DataQualityPage.tsx index a1c65824e1b..4b9da18c675 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DataQuality/DataQualityPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DataQuality/DataQualityPage.tsx @@ -58,10 +58,15 @@ const DataQualityPage = () => { - + {t('label.data-quality')} - + {t('message.page-sub-header-for-data-quality')}