From 1f0476e00bdc430a50eaad201526353eb56f74d5 Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Fri, 24 Apr 2026 18:14:28 +0530 Subject: [PATCH 1/5] refactor(ui): migrate Test Suites list to ui-core Table Replace Ant Design table columns with @openmetadata/ui-core-components Table, SortDescriptor-based sorting, OwnerLabel for owners, and ButtonGroup for data quality sub-tabs. Update unit tests accordingly. Made-with: Cursor --- .../TestSuiteList/TestSuites.component.tsx | 285 ++++++++-------- .../TestSuiteList/TestSuites.test.tsx | 311 ++++++++++++++++-- 2 files changed, 443 insertions(+), 153 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx index 243942718d20..ba98462b9014 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx @@ -10,17 +10,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { + ButtonGroup, + ButtonGroupItem, + Table, +} from '@openmetadata/ui-core-components'; import { Col, Form, - Radio, - RadioChangeEvent, Row, Select, Space, Typography, } from 'antd'; -import { ColumnsType } from 'antd/lib/table'; import { AxiosError } from 'axios'; import { isEmpty } from 'lodash'; import QueryString from 'qs'; @@ -56,6 +58,7 @@ import { ListTestSuitePramsBySearch, } from '../../../../rest/testAPI'; import { getEntityName } from '../../../../utils/EntityUtils'; +import type { SortDescriptor } from 'react-aria-components'; import { getPopupContainer } from '../../../../utils/formUtils'; import observabilityRouterClassBase from '../../../../utils/ObservabilityRouterClassBase'; import { getPrioritizedViewPermission } from '../../../../utils/PermissionsUtils'; @@ -63,18 +66,18 @@ import { getEntityDetailsPath, getTestSuitePath, } from '../../../../utils/RouterUtils'; -import { ownerTableObject } from '../../../../utils/TableColumn.util'; import { showErrorToast } from '../../../../utils/ToastUtils'; import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder'; import FilterTablePlaceHolder from '../../../common/ErrorWithPlaceholder/FilterTablePlaceHolder'; +import NextPrevious from '../../../common/NextPrevious/NextPrevious'; import { PagingHandlerParams } from '../../../common/NextPrevious/NextPrevious.interface'; +import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component'; import Searchbar from '../../../common/SearchBarComponent/SearchBar.component'; -import Table from '../../../common/Table/Table'; -import { UserTeamSelectableList } from '../../../common/UserTeamSelectableList/UserTeamSelectableList.component'; import { ProfilerTabPath } from '../../../Database/Profiler/ProfilerDashboard/profilerDashboard.interface'; import ProfilerProgressWidget from '../../../Database/Profiler/TableProfiler/ProfilerProgressWidget/ProfilerProgressWidget'; import { TestSuiteSearchParams } from '../../DataQuality.interface'; import PieChartSummaryPanel from '../../SummaryPannel/PieChartSummaryPanel.component'; +import { UserTeamSelectableList } from '../../../common/UserTeamSelectableList/UserTeamSelectableList.component'; import './test-suites.style.less'; export const TestSuites = () => { @@ -109,6 +112,9 @@ export const TestSuites = () => { const { permissions } = usePermissionProvider(); const { testSuite: testSuitePermission } = permissions; const [testSuites, setTestSuites] = useState([]); + const [sortDescriptor, setSortDescriptor] = useState( + {} as SortDescriptor + ); const { currentPage, pageSize, @@ -129,88 +135,35 @@ export const TestSuites = () => { } : undefined; }, [selectedOwner]); - const columns = useMemo(() => { - const data: ColumnsType = [ - { - title: t('label.name'), - dataIndex: 'name', - key: 'name', - width: 600, - sorter: (a, b) => { - if (a.basic) { - // Sort for basic test suites - return ( - a.basicEntityReference?.fullyQualifiedName?.localeCompare( - b.basicEntityReference?.fullyQualifiedName ?? '' - ) ?? 0 - ); - } else { - // Sort for logical test suites - return ( - a.fullyQualifiedName?.localeCompare(b.fullyQualifiedName ?? '') ?? - 0 - ); - } - }, - sortDirections: ['ascend', 'descend'], - render: (name, record) => { - return ( - - {record.basic ? ( - - {record.basicEntityReference?.fullyQualifiedName ?? - record.basicEntityReference?.name} - - ) : ( - - {getEntityName(record)} - - )} - - ); - }, - }, - { - title: t('label.test-plural'), - dataIndex: 'summary', - key: 'tests', - width: 100, - render: (value: TestSummary) => value?.total ?? 0, - }, - { - title: `${t('label.success')} %`, - dataIndex: 'summary', - width: 200, - key: 'success', - render: (value: TestSuite['summary']) => { - const percent = - value?.total && value?.success ? value.success / value.total : 0; - return ( - - ); - }, - }, - ...ownerTableObject(), - ]; + const columnList = useMemo( + () => [ + { id: 'name', name: t('label.name'), allowsSorting: true }, + { id: 'tests', name: t('label.test-plural') }, + { id: 'success', name: `${t('label.success')} %` }, + { id: 'owners', name: t('label.owner-plural') }, + ], + [t] + ); + + const sortedData = useMemo(() => { + if (!sortDescriptor.column || !sortDescriptor.direction) { + return testSuites; + } - return data; - }, []); + return [...testSuites].sort((a, b) => { + const getFqn = (item: TestSuite) => + item.basic + ? (item.basicEntityReference?.fullyQualifiedName ?? '') + : (item.fullyQualifiedName ?? ''); + + const aVal = getFqn(a); + const bVal = getFqn(b); + const cmp = aVal.localeCompare(bVal); + + return sortDescriptor.direction === 'descending' ? -cmp : cmp; + }); + }, [testSuites, sortDescriptor]); const fetchTestSuites = async ( currentPage = INITIAL_PAGING_VALUE, @@ -268,35 +221,74 @@ export const TestSuites = () => { ); }; - const handleSubTabChange = (e: RadioChangeEvent) => { - navigate( - observabilityRouterClassBase.getDataQualityPagePath( - tab, - e.target.value as DataQualitySubTabs - ) + const handleSubTabChange = (keys: Set) => { + const selected = [...keys][0] as DataQualitySubTabs; + if (selected) { + navigate( + observabilityRouterClassBase.getDataQualityPagePath(tab, selected) + ); + } + }; + + const renderNameCell = (record: TestSuite) => { + if (record.basic) { + return ( + + {record.basicEntityReference?.fullyQualifiedName ?? + record.basicEntityReference?.name} + + ); + } + + return ( + + {getEntityName(record)} + + ); + }; + + const renderSuccessCell = (summary: TestSuite['summary']) => { + const percent = + summary?.total && summary?.success ? summary.success / summary.total : 0; + + return ( + ); }; - const customPaginationProps = useMemo( - () => ({ - currentPage, - isLoading, - pageSize, - isNumberBased: true, - paging, - pagingHandler: handleTestSuitesPageChange, - onShowSizeChange: handlePageSizeChange, - showPagination, - }), - [ - currentPage, - isLoading, - pageSize, - paging, - handleTestSuitesPageChange, - handlePageSizeChange, - showPagination, - ] + const renderRow = (record: TestSuite) => ( + + + {renderNameCell(record)} + + + {(record.summary as TestSummary)?.total ?? 0} + + {renderSuccessCell(record.summary)} + + + + ); const noDataPlaceholder = useMemo(() => { @@ -380,18 +372,23 @@ export const TestSuites = () => {
- - + + id={DataQualitySubTabs.TABLE_SUITES}> {t('label.table-suite-plural')} - - + + id={DataQualitySubTabs.BUNDLE_SUITES}> {t('label.bundle-suite-plural')} - - + + {
+ + sortDescriptor={sortDescriptor} + onSortChange={setSortDescriptor}> + + {(col) => ( + + )} + + + isLoading ? <> : noDataPlaceholder + }> + {(record) => renderRow(record as TestSuite)} + +
+ + {showPagination && ( + + )} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.test.tsx index 939d90d93c61..946525544686 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.test.tsx @@ -10,8 +10,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { render, screen } from '@testing-library/react'; -import { MemoryRouter } from 'react-router-dom'; +import { act, fireEvent, render, screen } from '@testing-library/react'; +import { MemoryRouter, useNavigate } from 'react-router-dom'; import { DataQualityPageTabs } from '../../../../pages/DataQuality/DataQualityPage.interface'; import { getListTestSuitesBySearch } from '../../../../rest/testAPI'; import { TestSuites } from './TestSuites.component'; @@ -58,6 +58,167 @@ const mockList = { }, }; +jest.mock('@openmetadata/ui-core-components', () => { + const SortContext = require('react').createContext<{ + sortDescriptor?: { column?: string; direction?: string }; + onSortChange?: (desc: { + column?: string; + direction?: 'ascending' | 'descending'; + }) => void; + }>({}); + + const MockTableHead = ({ + label, + id, + allowsSorting, + }: { + label?: string; + id?: string; + allowsSorting?: boolean; + }) => { + const { onSortChange, sortDescriptor } = + require('react').useContext(SortContext); + const handleClick = () => { + if (!allowsSorting || !onSortChange) { + return; + } + const currentDir = + sortDescriptor?.column === id ? sortDescriptor.direction : undefined; + const newDir = + currentDir === 'ascending' ? 'descending' : 'ascending'; + onSortChange({ column: id, direction: newDir }); + }; + + return ( + + {label} + + ); + }; + + const MockTable = ({ + children, + 'data-testid': testId, + onSortChange, + sortDescriptor, + }: React.PropsWithChildren<{ + 'data-testid'?: string; + onSortChange?: (desc: { + column?: string; + direction?: 'ascending' | 'descending'; + }) => void; + sortDescriptor?: { column?: string; direction?: string }; + [key: string]: unknown; + }>) => ( + + {children}
+
+ ); + + MockTable.Header = ({ + columns, + children, + }: { + columns: unknown[]; + children: (col: unknown) => React.ReactNode; + }) => ( + + {(columns || []).map((col) => children(col))} + + ); + + MockTable.Head = MockTableHead; + + MockTable.Body = ({ + items, + children, + renderEmptyState, + }: { + items?: unknown[]; + children: (item: unknown) => React.ReactNode; + renderEmptyState?: () => React.ReactNode; + dependencies?: unknown[]; + }) => ( + + {items && items.length > 0 + ? items.map((item) => children(item)) + : renderEmptyState?.()} + + ); + + MockTable.Row = ({ + children, + id, + }: React.PropsWithChildren<{ id?: string }>) => ( + {children} + ); + + MockTable.Cell = ({ + children, + className, + }: React.PropsWithChildren<{ className?: string }>) => ( + {children} + ); + + const MockButtonGroup = ({ + children, + onSelectionChange, + selectedKeys, + }: React.PropsWithChildren<{ + onSelectionChange?: (keys: Set) => void; + selectedKeys?: Iterable; + disallowEmptySelection?: boolean; + }>) => ( +
+ {require('react').Children.map(children, (child) => { + if (!require('react').isValidElement(child)) { + return child; + } + + return require('react').cloneElement( + child as React.ReactElement<{ + id?: string; + onClick?: () => void; + }>, + { + onClick: () => { + const id = ( + child as React.ReactElement<{ id?: string }> + ).props.id; + if (id !== undefined) { + onSelectionChange?.(new Set([id])); + } + }, + } + ); + })} +
+ ); + + const MockButtonGroupItem = ({ + children, + id, + 'data-testid': testId, + onClick, + }: React.PropsWithChildren<{ + id?: string; + 'data-testid'?: string; + onClick?: () => void; + }>) => ( + + ); + + return { + ButtonGroup: MockButtonGroup, + ButtonGroupItem: MockButtonGroupItem, + Table: MockTable, + }; +}); + jest.mock('../../../../context/PermissionProvider/PermissionProvider', () => ({ usePermissionProvider: jest.fn().mockImplementation(() => ({ permissions: { @@ -65,6 +226,7 @@ jest.mock('../../../../context/PermissionProvider/PermissionProvider', () => ({ }, })), })); + jest.mock('../../../../rest/testAPI', () => { return { ...jest.requireActual('../../../../rest/testAPI'), @@ -73,9 +235,11 @@ jest.mock('../../../../rest/testAPI', () => { .mockImplementation(() => Promise.resolve(mockList)), }; }); + jest.mock('../../../../hooks/useCustomLocation/useCustomLocation', () => { return jest.fn().mockImplementation(() => ({ ...mockLocation })); }); + jest.mock('react-router-dom', () => { return { ...jest.requireActual('react-router-dom'), @@ -91,9 +255,25 @@ jest.mock('react-router-dom', () => { }), }; }); + jest.mock('../../../common/NextPrevious/NextPrevious', () => { return jest.fn().mockImplementation(() =>
NextPrevious.component
); }); + +jest.mock( + '../../../../utils/ObservabilityRouterClassBase', + () => ({ + __esModule: true, + default: { + getDataQualityPagePath: jest + .fn() + .mockImplementation( + (tab: string, subTab: string) => `/data-quality/${tab}/${subTab}` + ), + }, + }) +); + const mockDataQualityContext = { isTestCaseSummaryLoading: false, testCaseSummary: { @@ -104,6 +284,7 @@ const mockDataQualityContext = { }, activeTab: DataQualityPageTabs.TEST_CASES, }; + jest.mock('../../../../pages/DataQuality/DataQualityProvider', () => { return { useDataQualityProvider: jest @@ -111,6 +292,7 @@ jest.mock('../../../../pages/DataQuality/DataQualityProvider', () => { .mockImplementation(() => mockDataQualityContext), }; }); + jest.mock( '../../../common/UserTeamSelectableList/UserTeamSelectableList.component', () => ({ @@ -119,16 +301,19 @@ jest.mock( .mockImplementation(({ children }) =>
{children}
), }) ); + jest.mock('../../../common/SearchBarComponent/SearchBar.component', () => ({ __esModule: true, default: jest.fn().mockImplementation(() =>
SearchBar.component
), })); + jest.mock('../../SummaryPannel/PieChartSummaryPanel.component', () => ({ __esModule: true, default: jest .fn() .mockImplementation(() =>
SummaryPanel.component
), })); + jest.mock('../../../common/ErrorWithPlaceholder/ErrorPlaceHolder', () => ({ __esModule: true, default: jest @@ -140,20 +325,41 @@ jest.mock('../../../common/ErrorWithPlaceholder/ErrorPlaceHolder', () => ({ )), })); -jest.mock('../../../../utils/TableColumn.util', () => ({ - ownerTableObject: jest.fn().mockReturnValue([ - { - title: 'label.owner-plural', - dataIndex: 'owners', - key: 'owners', - width: 180, - render: () =>
OwnerLabel
, - }, - ]), - descriptionTableObject: jest.fn().mockReturnValue([]), +jest.mock( + '../../../common/ErrorWithPlaceholder/FilterTablePlaceHolder', + () => ({ + __esModule: true, + default: jest + .fn() + .mockImplementation(() => ( +
+ )), + }) +); + +jest.mock('../../../common/OwnerLabel/OwnerLabel.component', () => ({ + OwnerLabel: jest + .fn() + .mockImplementation(() =>
), })); +jest.mock( + '../../../Database/Profiler/TableProfiler/ProfilerProgressWidget/ProfilerProgressWidget', + () => + jest + .fn() + .mockImplementation(() => ( +
+ )) +); + describe('TestSuites component', () => { + beforeEach(() => { + jest.clearAllMocks(); + testSuitePermission.ViewAll = true; + mockLocation.search = ''; + }); + it('component should render', async () => { render(); const tableHeader = await screen.findAllByRole('columnheader'); @@ -200,8 +406,8 @@ describe('TestSuites component', () => { it('filters API call should be made, if owner is selected', async () => { mockLocation.search = '?owner={"id":"84c3e66f-a4a6-42ab-b85c-b578f46d3bca","type":"user","name":"admin","fullyQualifiedName":"admin"}&searchValue=sales'; - testSuitePermission.ViewAll = true; const mockGetListTestSuites = getListTestSuitesBySearch as jest.Mock; + render(, { wrapper: MemoryRouter }); expect(mockGetListTestSuites).toHaveBeenCalledWith({ @@ -217,7 +423,7 @@ describe('TestSuites component', () => { }); }); - it('pagination should visible if total is grater than 15', async () => { + it('pagination should visible if total is greater than 15', async () => { (getListTestSuitesBySearch as jest.Mock).mockImplementationOnce(() => Promise.resolve({ data: [], paging: { total: 16 } }) ); @@ -229,10 +435,10 @@ describe('TestSuites component', () => { ).toBeInTheDocument(); }); - // TestSuite type test - it('should render radio buttons for table and bundle suites', async () => { + it('should render ButtonGroup with table and bundle suite options', async () => { render(, { wrapper: MemoryRouter }); + expect(await screen.findByTestId('button-group')).toBeInTheDocument(); expect( await screen.findByTestId('table-suite-radio-btn') ).toBeInTheDocument(); @@ -241,8 +447,37 @@ describe('TestSuites component', () => { ).toBeInTheDocument(); }); + it('should navigate to bundle-suites path when bundle suite button is clicked', async () => { + render(, { wrapper: MemoryRouter }); + + const mockNavigate = (useNavigate as jest.Mock).mock.results[0].value; + const bundleBtn = await screen.findByTestId('bundle-suite-radio-btn'); + + await act(async () => { + fireEvent.click(bundleBtn); + }); + + expect(mockNavigate).toHaveBeenCalledWith( + '/data-quality/test-cases/bundle-suites' + ); + }); + + it('should navigate to table-suites path when table suite button is clicked', async () => { + render(, { wrapper: MemoryRouter }); + + const mockNavigate = (useNavigate as jest.Mock).mock.results[0].value; + const tableBtn = await screen.findByTestId('table-suite-radio-btn'); + + await act(async () => { + fireEvent.click(tableBtn); + }); + + expect(mockNavigate).toHaveBeenCalledWith( + '/data-quality/test-cases/table-suites' + ); + }); + it('should send testSuiteType basic by default', async () => { - mockLocation.search = ''; const mockGetListTestSuites = getListTestSuitesBySearch as jest.Mock; render(, { wrapper: MemoryRouter }); @@ -264,7 +499,6 @@ describe('TestSuites component', () => { }); it('should render no data placeholder, if there is no permission', async () => { - // Reset permission for this test testSuitePermission.ViewAll = false; render(, { wrapper: MemoryRouter }); @@ -273,4 +507,43 @@ describe('TestSuites component', () => { await screen.findByTestId('error-placeholder-type-PERMISSION') ).toBeInTheDocument(); }); + + it('should render table rows with name, tests, success and owner cells', async () => { + await act(async () => { + render(); + }); + + expect( + await screen.findByTestId('profiler-progress-widget') + ).toBeInTheDocument(); + expect(await screen.findByTestId('owner-label')).toBeInTheDocument(); + + const rows = screen.getAllByRole('row'); + + expect(rows.length).toBeGreaterThan(1); + }); + + it('should render empty placeholder when no test suites are returned', async () => { + (getListTestSuitesBySearch as jest.Mock).mockImplementationOnce(() => + Promise.resolve({ data: [], paging: { total: 0 } }) + ); + + render(); + + expect( + await screen.findByTestId('filter-table-placeholder') + ).toBeInTheDocument(); + }); + + it('should not render pagination when showPagination is false', async () => { + (getListTestSuitesBySearch as jest.Mock).mockImplementationOnce(() => + Promise.resolve({ data: [], paging: { total: 5 } }) + ); + + render(); + + await screen.findByTestId('test-suite-container'); + + expect(screen.queryByText('NextPrevious.component')).not.toBeInTheDocument(); + }); }); From 3002acaca884cc1a50b5668482c32b9f9a403865 Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Mon, 27 Apr 2026 11:54:17 +0530 Subject: [PATCH 2/5] address gitar-bot comments --- .../TestSuiteList/TestSuites.component.tsx | 45 ++++++++---------- .../TestSuiteList/TestSuites.test.tsx | 47 ++++++++----------- 2 files changed, 38 insertions(+), 54 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx index ba98462b9014..ed25b5912e75 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.component.tsx @@ -15,18 +15,12 @@ import { ButtonGroupItem, Table, } from '@openmetadata/ui-core-components'; -import { - Col, - Form, - Row, - Select, - Space, - Typography, -} from 'antd'; +import { Col, Form, Row, Select, Space, Typography } from 'antd'; import { AxiosError } from 'axios'; import { isEmpty } from 'lodash'; import QueryString from 'qs'; import { useCallback, useEffect, useMemo, useState } from 'react'; +import type { SortDescriptor } from 'react-aria-components'; import { useTranslation } from 'react-i18next'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { INITIAL_PAGING_VALUE } from '../../../../constants/constants'; @@ -58,7 +52,6 @@ import { ListTestSuitePramsBySearch, } from '../../../../rest/testAPI'; import { getEntityName } from '../../../../utils/EntityUtils'; -import type { SortDescriptor } from 'react-aria-components'; import { getPopupContainer } from '../../../../utils/formUtils'; import observabilityRouterClassBase from '../../../../utils/ObservabilityRouterClassBase'; import { getPrioritizedViewPermission } from '../../../../utils/PermissionsUtils'; @@ -73,11 +66,11 @@ import NextPrevious from '../../../common/NextPrevious/NextPrevious'; import { PagingHandlerParams } from '../../../common/NextPrevious/NextPrevious.interface'; import { OwnerLabel } from '../../../common/OwnerLabel/OwnerLabel.component'; import Searchbar from '../../../common/SearchBarComponent/SearchBar.component'; +import { UserTeamSelectableList } from '../../../common/UserTeamSelectableList/UserTeamSelectableList.component'; import { ProfilerTabPath } from '../../../Database/Profiler/ProfilerDashboard/profilerDashboard.interface'; import ProfilerProgressWidget from '../../../Database/Profiler/TableProfiler/ProfilerProgressWidget/ProfilerProgressWidget'; import { TestSuiteSearchParams } from '../../DataQuality.interface'; import PieChartSummaryPanel from '../../SummaryPannel/PieChartSummaryPanel.component'; -import { UserTeamSelectableList } from '../../../common/UserTeamSelectableList/UserTeamSelectableList.component'; import './test-suites.style.less'; export const TestSuites = () => { @@ -112,9 +105,9 @@ export const TestSuites = () => { const { permissions } = usePermissionProvider(); const { testSuite: testSuitePermission } = permissions; const [testSuites, setTestSuites] = useState([]); - const [sortDescriptor, setSortDescriptor] = useState( - {} as SortDescriptor - ); + const [sortDescriptor, setSortDescriptor] = useState< + SortDescriptor | undefined + >(); const { currentPage, pageSize, @@ -147,19 +140,19 @@ export const TestSuites = () => { ); const sortedData = useMemo(() => { - if (!sortDescriptor.column || !sortDescriptor.direction) { + if (!sortDescriptor?.column || !sortDescriptor?.direction) { return testSuites; } return [...testSuites].sort((a, b) => { - const getFqn = (item: TestSuite) => - item.basic - ? (item.basicEntityReference?.fullyQualifiedName ?? '') - : (item.fullyQualifiedName ?? ''); - - const aVal = getFqn(a); - const bVal = getFqn(b); - const cmp = aVal.localeCompare(bVal); + let cmp = 0; + if (sortDescriptor.column === 'name') { + const getFqn = (item: TestSuite) => + item.basic + ? item.basicEntityReference?.fullyQualifiedName ?? '' + : item.fullyQualifiedName ?? ''; + cmp = getFqn(a).localeCompare(getFqn(b)); + } return sortDescriptor.direction === 'descending' ? -cmp : cmp; }); @@ -277,7 +270,9 @@ export const TestSuites = () => { {renderNameCell(record)} - {(record.summary as TestSummary)?.total ?? 0} + + {(record.summary as TestSummary)?.total ?? 0} + {renderSuccessCell(record.summary)} @@ -423,9 +418,7 @@ export const TestSuites = () => { - isLoading ? <> : noDataPlaceholder - }> + renderEmptyState={() => (isLoading ? <> : noDataPlaceholder)}> {(record) => renderRow(record as TestSuite)} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.test.tsx index 946525544686..ff09e54c5ec4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/TestSuite/TestSuiteList/TestSuites.test.tsx @@ -84,8 +84,7 @@ jest.mock('@openmetadata/ui-core-components', () => { } const currentDir = sortDescriptor?.column === id ? sortDescriptor.direction : undefined; - const newDir = - currentDir === 'ascending' ? 'descending' : 'ascending'; + const newDir = currentDir === 'ascending' ? 'descending' : 'ascending'; onSortChange({ column: id, direction: newDir }); }; @@ -149,9 +148,7 @@ jest.mock('@openmetadata/ui-core-components', () => { MockTable.Row = ({ children, id, - }: React.PropsWithChildren<{ id?: string }>) => ( - {children} - ); + }: React.PropsWithChildren<{ id?: string }>) => {children}; MockTable.Cell = ({ children, @@ -184,9 +181,8 @@ jest.mock('@openmetadata/ui-core-components', () => { }>, { onClick: () => { - const id = ( - child as React.ReactElement<{ id?: string }> - ).props.id; + const id = (child as React.ReactElement<{ id?: string }>).props + .id; if (id !== undefined) { onSelectionChange?.(new Set([id])); } @@ -260,19 +256,16 @@ jest.mock('../../../common/NextPrevious/NextPrevious', () => { return jest.fn().mockImplementation(() =>
NextPrevious.component
); }); -jest.mock( - '../../../../utils/ObservabilityRouterClassBase', - () => ({ - __esModule: true, - default: { - getDataQualityPagePath: jest - .fn() - .mockImplementation( - (tab: string, subTab: string) => `/data-quality/${tab}/${subTab}` - ), - }, - }) -); +jest.mock('../../../../utils/ObservabilityRouterClassBase', () => ({ + __esModule: true, + default: { + getDataQualityPagePath: jest + .fn() + .mockImplementation( + (tab: string, subTab: string) => `/data-quality/${tab}/${subTab}` + ), + }, +})); const mockDataQualityContext = { isTestCaseSummaryLoading: false, @@ -331,9 +324,7 @@ jest.mock( __esModule: true, default: jest .fn() - .mockImplementation(() => ( -
- )), + .mockImplementation(() =>
), }) ); @@ -348,9 +339,7 @@ jest.mock( () => jest .fn() - .mockImplementation(() => ( -
- )) + .mockImplementation(() =>
) ); describe('TestSuites component', () => { @@ -544,6 +533,8 @@ describe('TestSuites component', () => { await screen.findByTestId('test-suite-container'); - expect(screen.queryByText('NextPrevious.component')).not.toBeInTheDocument(); + expect( + screen.queryByText('NextPrevious.component') + ).not.toBeInTheDocument(); }); }); From 70529869d410a01f17bdf5662d6a17883573f926 Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Tue, 28 Apr 2026 10:36:26 +0530 Subject: [PATCH 3/5] fix failing test --- .../src/main/resources/ui/playwright/utils/dataContracts.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts index 1cf01f2f1cae..6bf4de498ad6 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts @@ -76,10 +76,7 @@ export const validateDataContractInsideBundleTestSuites = async ( response.status() === 200 ); - await page - .locator('.ant-radio-button-wrapper') - .filter({ hasText: 'Bundle Suites' }) - .click(); + await page.getByTestId('bundle-suite-radio-btn').click(); await bundleSuitesResponse; From 8ffc8e469c582404378712cd789bcd71b2bcda4d Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Tue, 5 May 2026 11:44:59 +0530 Subject: [PATCH 4/5] fix failing tests --- .../resources/ui/playwright/e2e/Pages/DataContracts.spec.ts | 2 +- .../src/main/resources/ui/playwright/utils/dataContracts.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts index e5dfdda04cb7..ed4cbce2e9fb 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/e2e/Pages/DataContracts.spec.ts @@ -506,7 +506,7 @@ test.describe('Data Contracts', () => { await expect( page .getByTestId('test-suite-table') - .locator('.ant-table-cell') + .locator('[role="gridcell"]') .filter({ hasText: `Data Contract - ${DATA_CONTRACT_DETAILS.name}`, }) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts index 6bf4de498ad6..bcb1f351e115 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts @@ -153,7 +153,7 @@ export const waitForContractExecutionWithFallback = async ( const suiteNameCell = page .getByTestId('test-suite-table') - .locator('.ant-table-cell') + .locator('[role="gridcell"]') .filter({ hasText: `Data Contract - ${contractName}` }); await expect(suiteNameCell).toBeVisible(); From 3e38769709cb5ad72464a265789aa0e550d7f633 Mon Sep 17 00:00:00 2001 From: Harshit Shah Date: Wed, 6 May 2026 12:10:27 +0530 Subject: [PATCH 5/5] fix failing test --- .../src/main/resources/ui/playwright/utils/dataContracts.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts b/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts index 77d730c02db9..bcb1f351e115 100644 --- a/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts +++ b/openmetadata-ui/src/main/resources/ui/playwright/utils/dataContracts.ts @@ -76,9 +76,7 @@ export const validateDataContractInsideBundleTestSuites = async ( response.status() === 200 ); - await page - .locator('label:has([data-testid="bundle-suite-radio-btn"])') - .click(); + await page.getByTestId('bundle-suite-radio-btn').click(); await bundleSuitesResponse;