diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/AddTestSuitePipeline.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/AddTestSuitePipeline.test.tsx
index 9822479c589..456433d491e 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/AddTestSuitePipeline.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddDataQualityTest/components/AddTestSuitePipeline.test.tsx
@@ -12,45 +12,45 @@
*/
import { act, fireEvent, render, screen } from '@testing-library/react';
import { Form } from 'antd';
+import React from 'react';
+import { TestCase } from '../../../../generated/tests/testCase';
+import { TestSuite } from '../../../../generated/tests/testSuite';
import { AddTestSuitePipelineProps } from '../AddDataQualityTest.interface';
import AddTestSuitePipeline from './AddTestSuitePipeline';
const mockNavigate = jest.fn();
+const mockUseCustomLocation = jest.fn();
+const mockUseFqn = jest.fn();
+const mockScheduleInterval = jest.fn();
+
+jest.mock('react-router-dom', () => ({
+ useNavigate: () => mockNavigate,
+}));
+
+jest.mock('../../../../hooks/useCustomLocation/useCustomLocation', () =>
+ jest.fn().mockImplementation(() => mockUseCustomLocation())
+);
-jest.mock('../../../../hooks/useCustomLocation/useCustomLocation', () => {
- return jest.fn().mockImplementation(() => ({
- search: `?testSuiteId=test-suite-id`,
- }));
-});
jest.mock('../../../../hooks/useFqn', () => ({
- useFqn: jest.fn().mockReturnValue({ fqn: 'test-suite-fqn' }),
+ useFqn: jest.fn().mockImplementation(() => mockUseFqn()),
}));
+
jest.mock('../../AddTestCaseList/AddTestCaseList.component', () => ({
- AddTestCaseList: jest
- .fn()
- .mockImplementation(() =>
AddTestCaseList.component
),
+ AddTestCaseList: () => AddTestCaseList.component
,
}));
+
jest.mock(
'../../../Settings/Services/AddIngestion/Steps/ScheduleInterval',
- () =>
- jest
- .fn()
- .mockImplementation(({ children, topChildren, onDeploy, onBack }) => (
-
- ScheduleInterval
- {topChildren}
- {children}
-
submit
-
cancel
-
- ))
+ () => jest.fn().mockImplementation((props) => mockScheduleInterval(props))
);
-jest.mock('react-router-dom', () => ({
- useNavigate: jest.fn().mockImplementation(() => mockNavigate),
-}));
jest.mock('../../../../utils/SchedularUtils', () => ({
- getRaiseOnErrorFormField: jest.fn().mockReturnValue({}),
+ getRaiseOnErrorFormField: () => ({
+ name: 'raiseOnError',
+ label: 'Raise On Error',
+ type: 'switch',
+ required: false,
+ }),
}));
const mockProps: AddTestSuitePipelineProps = {
@@ -59,6 +59,25 @@ const mockProps: AddTestSuitePipelineProps = {
};
describe('AddTestSuitePipeline', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockUseCustomLocation.mockReturnValue({
+ search: '?testSuiteId=test-suite-id',
+ });
+ mockUseFqn.mockReturnValue({ ingestionFQN: '' });
+ mockScheduleInterval.mockImplementation(
+ ({ children, topChildren, onDeploy, onBack }) => (
+
+ ScheduleInterval
+ {topChildren}
+ {children}
+
submit
+
cancel
+
+ )
+ );
+ });
+
it('renders form fields', () => {
render(
);
- // Assert that the form fields are rendered
expect(screen.getByTestId('pipeline-name')).toBeInTheDocument();
expect(screen.getByTestId('select-all-test-cases')).toBeInTheDocument();
expect(screen.getByText('submit')).toBeInTheDocument();
@@ -90,7 +108,6 @@ describe('AddTestSuitePipeline', () => {
fireEvent.click(screen.getByText('submit'));
});
- // Assert that onSubmit is called with the correct values
expect(mockProps.onSubmit).toHaveBeenCalled();
});
@@ -130,7 +147,7 @@ describe('AddTestSuitePipeline', () => {
onClick={() =>
onFormChange('', {
forms: {
- ['schedular-form']: {
+ 'schedular-form': {
getFieldValue: jest.fn().mockImplementation(() => true),
setFieldsValue: jest.fn(),
},
@@ -147,15 +164,483 @@ describe('AddTestSuitePipeline', () => {
);
- // Assert that AddTestCaseList.component is now visible
expect(screen.getByText('AddTestCaseList.component')).toBeInTheDocument();
- // Click on the select-all-test-cases switch
await act(async () => {
fireEvent.click(screen.getByTestId('select-all-test-cases'));
});
- // Assert that AddTestCaseList.component is not initially visible
expect(screen.queryByText('AddTestCaseList.component')).toBeNull();
});
+
+ describe('raiseOnError functionality', () => {
+ it('includes raiseOnError field in form submission', async () => {
+ const mockOnSubmit = jest.fn();
+ render(
+
+ );
+
+ await act(async () => {
+ fireEvent.click(screen.getByText('submit'));
+ });
+
+ expect(mockOnSubmit).toHaveBeenCalledWith(
+ expect.objectContaining({
+ raiseOnError: undefined,
+ })
+ );
+ });
+
+ it('passes raiseOnError value from form to onSubmit', async () => {
+ const mockOnSubmit = jest.fn();
+ const initialData = {
+ raiseOnError: true,
+ selectAllTestCases: true,
+ };
+
+ mockScheduleInterval.mockImplementationOnce(
+ ({
+ children,
+ onDeploy,
+ }: {
+ children: React.ReactNode;
+ onDeploy: (values: unknown) => void;
+ }) => (
+
+ {children}
+
+ onDeploy({
+ raiseOnError: true,
+ selectAllTestCases: true,
+ })
+ }>
+ submit
+
+
+ )
+ );
+
+ render(
+
+ );
+
+ await act(async () => {
+ fireEvent.click(screen.getByText('submit'));
+ });
+
+ expect(mockOnSubmit).toHaveBeenCalledWith(
+ expect.objectContaining({
+ raiseOnError: true,
+ })
+ );
+ });
+ });
+
+ describe('testCase mapping logic', () => {
+ it('maps TestCase objects to string names', async () => {
+ const mockOnSubmit = jest.fn();
+ const testCaseObject: TestCase = {
+ name: 'test-case-object',
+ id: '123',
+ fullyQualifiedName: 'test.case.object',
+ } as TestCase;
+
+ mockScheduleInterval.mockImplementationOnce(
+ ({
+ children,
+ onDeploy,
+ }: {
+ children: React.ReactNode;
+ onDeploy: (values: unknown) => void;
+ }) => (
+
+ {children}
+
+ onDeploy({
+ testCases: [testCaseObject, 'test-case-string'],
+ })
+ }>
+ submit
+
+
+ )
+ );
+
+ render(
+
+ );
+
+ await act(async () => {
+ fireEvent.click(screen.getByText('submit'));
+ });
+
+ expect(mockOnSubmit).toHaveBeenCalledWith(
+ expect.objectContaining({
+ testCases: ['test-case-object', 'test-case-string'],
+ })
+ );
+ });
+
+ it('handles undefined testCases array', async () => {
+ const mockOnSubmit = jest.fn();
+
+ mockScheduleInterval.mockImplementationOnce(
+ ({
+ children,
+ onDeploy,
+ }: {
+ children: React.ReactNode;
+ onDeploy: (values: unknown) => void;
+ }) => (
+
+ {children}
+
+ onDeploy({
+ testCases: undefined,
+ selectAllTestCases: true,
+ })
+ }>
+ submit
+
+
+ )
+ );
+
+ render(
+
+ );
+
+ await act(async () => {
+ fireEvent.click(screen.getByText('submit'));
+ });
+
+ expect(mockOnSubmit).toHaveBeenCalledWith(
+ expect.objectContaining({
+ testCases: undefined,
+ selectAllTestCases: true,
+ })
+ );
+ });
+
+ it('handles mixed array of TestCase objects and strings', async () => {
+ const mockOnSubmit = jest.fn();
+ const testCase1: TestCase = {
+ name: 'test-case-1',
+ id: '1',
+ fullyQualifiedName: 'test.case.1',
+ } as TestCase;
+ const testCase2: TestCase = {
+ name: 'test-case-2',
+ id: '2',
+ fullyQualifiedName: 'test.case.2',
+ } as TestCase;
+
+ mockScheduleInterval.mockImplementationOnce(
+ ({
+ children,
+ onDeploy,
+ }: {
+ children: React.ReactNode;
+ onDeploy: (values: unknown) => void;
+ }) => (
+
+ {children}
+
+ onDeploy({
+ testCases: [testCase1, 'string-test', testCase2],
+ })
+ }>
+ submit
+
+
+ )
+ );
+
+ render(
+
+ );
+
+ await act(async () => {
+ fireEvent.click(screen.getByText('submit'));
+ });
+
+ expect(mockOnSubmit).toHaveBeenCalledWith(
+ expect.objectContaining({
+ testCases: ['test-case-1', 'string-test', 'test-case-2'],
+ })
+ );
+ });
+ });
+
+ describe('testSuiteId extraction', () => {
+ it('uses testSuiteId from testSuite prop when available', () => {
+ const testSuite = { id: 'prop-test-suite-id' } as TestSuite;
+
+ render(
+
+ );
+
+ expect(screen.getByText('AddTestCaseList.component')).toBeInTheDocument();
+ });
+
+ it('extracts testSuiteId from URL search params when testSuite prop is not provided', () => {
+ mockUseCustomLocation.mockReturnValueOnce({
+ search: '?testSuiteId=url-test-suite-id',
+ });
+
+ render(
+
+ );
+
+ expect(screen.getByText('AddTestCaseList.component')).toBeInTheDocument();
+ });
+
+ it('handles URL search params without question mark', () => {
+ mockUseCustomLocation.mockReturnValueOnce({
+ search: 'testSuiteId=no-question-mark-id',
+ });
+
+ render(
+
+ );
+
+ expect(screen.getByText('AddTestCaseList.component')).toBeInTheDocument();
+ });
+
+ it('prioritizes testSuite prop over URL params', () => {
+ mockUseCustomLocation.mockReturnValueOnce({
+ search: '?testSuiteId=url-id',
+ });
+
+ const testSuite = { id: 'prop-id' } as TestSuite;
+
+ render(
+
+ );
+
+ expect(screen.getByText('AddTestCaseList.component')).toBeInTheDocument();
+ });
+ });
+
+ describe('Form state management', () => {
+ it('clears testCases field when selectAllTestCases is enabled', async () => {
+ const mockSetFieldsValue = jest.fn();
+ const mockGetFieldValue = jest.fn().mockReturnValue(true);
+
+ jest.spyOn(Form, 'Provider').mockImplementation(
+ jest.fn().mockImplementation(({ onFormChange, children }) => (
+
+ {children}
+
+
+ ))
+ );
+
+ render(
+
+ );
+
+ await act(async () => {
+ fireEvent.click(screen.getByTestId('trigger-form-change'));
+ });
+
+ expect(mockGetFieldValue).toHaveBeenCalledWith('selectAllTestCases');
+ expect(mockSetFieldsValue).toHaveBeenCalledWith({ testCases: undefined });
+ });
+
+ it('does not clear testCases when selectAllTestCases is false', async () => {
+ const mockSetFieldsValue = jest.fn();
+ const mockGetFieldValue = jest.fn().mockReturnValue(false);
+
+ jest.spyOn(Form, 'Provider').mockImplementation(
+ jest.fn().mockImplementation(({ onFormChange, children }) => (
+
+ {children}
+
+
+ ))
+ );
+
+ render(
+
+ );
+
+ await act(async () => {
+ fireEvent.click(screen.getByTestId('trigger-form-change'));
+ });
+
+ expect(mockGetFieldValue).toHaveBeenCalledWith('selectAllTestCases');
+ expect(mockSetFieldsValue).not.toHaveBeenCalled();
+ });
+
+ it('updates selectAllTestCases state when form changes', async () => {
+ const { rerender } = render(
+
+ );
+
+ expect(screen.getByText('AddTestCaseList.component')).toBeInTheDocument();
+
+ const propsWithInitialData = {
+ ...mockProps,
+ initialData: { selectAllTestCases: true },
+ };
+
+ rerender(
+
+ );
+
+ await act(async () => {
+ // Form state should reflect the initial data
+ });
+ });
+ });
+
+ describe('Edit mode behavior', () => {
+ it('displays Save button in edit mode', () => {
+ mockUseFqn.mockReturnValueOnce({ ingestionFQN: 'test-ingestion-fqn' });
+
+ render(
+
+ );
+
+ expect(screen.getByText('ScheduleInterval')).toBeInTheDocument();
+ });
+
+ it('displays Create button when not in edit mode', () => {
+ mockUseFqn.mockReturnValueOnce({ ingestionFQN: '' });
+
+ render(
+
+ );
+
+ expect(screen.getByText('ScheduleInterval')).toBeInTheDocument();
+ });
+ });
+
+ describe('Form submission with all fields', () => {
+ it('submits form with all populated fields', async () => {
+ const mockOnSubmit = jest.fn();
+ const initialData = {
+ name: 'Test Pipeline',
+ cron: '0 0 * * *',
+ enableDebugLog: true,
+ selectAllTestCases: false,
+ raiseOnError: true,
+ };
+
+ mockScheduleInterval.mockImplementationOnce(
+ ({
+ children,
+ onDeploy,
+ }: {
+ children: React.ReactNode;
+ onDeploy: (values: unknown) => void;
+ }) => (
+
+ {children}
+
+ onDeploy({
+ ...initialData,
+ testCases: ['test-1', 'test-2'],
+ })
+ }>
+ submit
+
+
+ )
+ );
+
+ render(
+
+ );
+
+ await act(async () => {
+ fireEvent.click(screen.getByText('submit'));
+ });
+
+ expect(mockOnSubmit).toHaveBeenCalledWith({
+ name: 'Test Pipeline',
+ cron: '0 0 * * *',
+ enableDebugLog: true,
+ selectAllTestCases: false,
+ testCases: ['test-1', 'test-2'],
+ raiseOnError: true,
+ });
+ });
+ });
});
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddTestCaseList/AddTestCaseList.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddTestCaseList/AddTestCaseList.component.test.tsx
index f00e3f07507..862781eaa47 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddTestCaseList/AddTestCaseList.component.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddTestCaseList/AddTestCaseList.component.test.tsx
@@ -11,14 +11,16 @@
* limitations under the License.
*/
import {
- act,
fireEvent,
queryByAttribute,
render,
screen,
waitFor,
} from '@testing-library/react';
-import { EntityReference } from '../../../generated/tests/testCase';
+import { act } from 'react';
+import { MemoryRouter } from 'react-router-dom';
+import { EntityReference, TestCase } from '../../../generated/tests/testCase';
+import { getListTestCaseBySearch } from '../../../rest/testAPI';
import { AddTestCaseList } from './AddTestCaseList.component';
import { AddTestCaseModalProps } from './AddTestCaseList.interface';
@@ -31,7 +33,15 @@ jest.mock('../../common/Loader/Loader', () => {
});
jest.mock('../../common/SearchBarComponent/SearchBar.component', () => {
- return jest.fn().mockImplementation(() => Search Bar Mock
);
+ return jest.fn().mockImplementation(({ onSearch, searchValue }) => (
+
+ onSearch(e.target.value)}
+ />
+
+ ));
});
jest.mock('../../../utils/StringsUtils', () => {
return {
@@ -60,12 +70,7 @@ jest.mock('../../../utils/CommonUtils', () => {
});
jest.mock('../../../rest/testAPI', () => {
return {
- getListTestCaseBySearch: jest.fn().mockResolvedValue({
- data: [],
- paging: {
- total: 0,
- },
- }),
+ getListTestCaseBySearch: jest.fn(),
};
});
@@ -85,23 +90,116 @@ const mockProps: AddTestCaseModalProps = {
};
jest.mock('../../../utils/RouterUtils', () => ({
- getEntityDetailsPath: jest.fn(),
+ getEntityDetailsPath: jest.fn().mockReturnValue('/path/to/entity'),
}));
+const mockGetListTestCaseBySearch =
+ getListTestCaseBySearch as jest.MockedFunction<
+ typeof getListTestCaseBySearch
+ >;
+
+const mockTestCases: TestCase[] = [
+ {
+ id: 'test-case-1',
+ name: 'test_case_1',
+ displayName: 'Test Case 1',
+ entityLink: '<#E::table::sample.table>',
+ testDefinition: {
+ id: 'test-def-1',
+ name: 'table_column_count_to_equal',
+ displayName: 'Table Column Count To Equal',
+ },
+ } as TestCase,
+ {
+ id: 'test-case-2',
+ name: 'test_case_2',
+ displayName: 'Test Case 2',
+ entityLink: '<#E::table::sample.table::columns::id>',
+ testDefinition: {
+ id: 'test-def-2',
+ name: 'column_values_to_be_unique',
+ displayName: 'Column Values To Be Unique',
+ },
+ } as TestCase,
+ {
+ id: 'test-case-3',
+ name: 'test_case_3',
+ displayName: 'Test Case 3',
+ entityLink: '<#E::table::another.table>',
+ testDefinition: {
+ id: 'test-def-3',
+ name: 'table_row_count_to_be_between',
+ displayName: 'Table Row Count To Be Between',
+ },
+ } as TestCase,
+];
+
+const renderWithRouter = (props: AddTestCaseModalProps) => {
+ return render(
+
+
+
+ );
+};
+
describe('AddTestCaseList', () => {
- it('renders the component', async () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ mockGetListTestCaseBySearch.mockResolvedValue({
+ data: [],
+ paging: {
+ total: 0,
+ },
+ });
+ });
+
+ it('renders the component with initial state', async () => {
await act(async () => {
- render();
+ renderWithRouter(mockProps);
});
- expect(screen.getByText('Search Bar Mock')).toBeInTheDocument();
+ expect(screen.getByTestId('search-bar')).toBeInTheDocument();
expect(screen.getByTestId('cancel')).toBeInTheDocument();
expect(screen.getByTestId('submit')).toBeInTheDocument();
+ expect(mockGetListTestCaseBySearch).toHaveBeenCalledWith({
+ q: '*',
+ limit: 25,
+ offset: 0,
+ });
+ });
+
+ it('renders empty state when no test cases are found', async () => {
+ await act(async () => {
+ renderWithRouter(mockProps);
+ });
+
+ await waitFor(() => {
+ expect(screen.getByText('Error Placeholder Mock')).toBeInTheDocument();
+ });
+ });
+
+ it('renders test cases when data is available', async () => {
+ mockGetListTestCaseBySearch.mockResolvedValue({
+ data: mockTestCases,
+ paging: {
+ total: 3,
+ },
+ });
+
+ await act(async () => {
+ renderWithRouter(mockProps);
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId('test_case_1')).toBeInTheDocument();
+ expect(screen.getByTestId('test_case_2')).toBeInTheDocument();
+ expect(screen.getByTestId('test_case_3')).toBeInTheDocument();
+ });
});
it('calls onCancel when cancel button is clicked', async () => {
await act(async () => {
- render();
+ renderWithRouter(mockProps);
});
fireEvent.click(screen.getByTestId('cancel'));
@@ -110,7 +208,7 @@ describe('AddTestCaseList', () => {
it('calls onSubmit when submit button is clicked', async () => {
await act(async () => {
- render();
+ renderWithRouter(mockProps);
});
const submitBtn = screen.getByTestId('submit');
fireEvent.click(submitBtn);
@@ -125,10 +223,483 @@ describe('AddTestCaseList', () => {
it('does not render submit and cancel buttons when showButton is false', async () => {
await act(async () => {
- render();
+ renderWithRouter({ ...mockProps, showButton: false });
});
expect(screen.queryByTestId('cancel')).toBeNull();
expect(screen.queryByTestId('submit')).toBeNull();
});
+
+ describe('Search functionality', () => {
+ it('triggers search when search term is entered', async () => {
+ await act(async () => {
+ renderWithRouter(mockProps);
+ });
+
+ const searchBar = screen.getByTestId('search-bar');
+
+ await act(async () => {
+ fireEvent.change(searchBar, { target: { value: 'test_search' } });
+ });
+
+ await waitFor(() => {
+ expect(mockGetListTestCaseBySearch).toHaveBeenCalledWith({
+ q: 'test_search',
+ limit: 25,
+ offset: 0,
+ });
+ });
+ });
+
+ it('applies filters when provided', async () => {
+ const filters = 'testSuiteFullyQualifiedName:sample.test.suite';
+
+ await act(async () => {
+ renderWithRouter({ ...mockProps, filters });
+ });
+
+ await waitFor(() => {
+ expect(mockGetListTestCaseBySearch).toHaveBeenCalledWith({
+ q: `* && ${filters}`,
+ limit: 25,
+ offset: 0,
+ });
+ });
+ });
+
+ it('combines search term with filters', async () => {
+ const filters = 'testSuiteFullyQualifiedName:sample.test.suite';
+
+ await act(async () => {
+ renderWithRouter({ ...mockProps, filters });
+ });
+
+ const searchBar = screen.getByTestId('search-bar');
+
+ await act(async () => {
+ fireEvent.change(searchBar, { target: { value: 'column_test' } });
+ });
+
+ await waitFor(() => {
+ expect(mockGetListTestCaseBySearch).toHaveBeenCalledWith({
+ q: `column_test && ${filters}`,
+ limit: 25,
+ offset: 0,
+ });
+ });
+ });
+
+ it('passes testCaseParams to API call', async () => {
+ const testCaseParams = {
+ testSuiteId: 'test-suite-123',
+ includeFields: ['testDefinition', 'testSuite'],
+ };
+
+ await act(async () => {
+ render(
+
+ );
+ });
+
+ await waitFor(() => {
+ expect(mockGetListTestCaseBySearch).toHaveBeenCalledWith({
+ q: '*',
+ limit: 25,
+ offset: 0,
+ ...testCaseParams,
+ });
+ });
+ });
+ });
+
+ describe('Test case selection', () => {
+ beforeEach(() => {
+ mockGetListTestCaseBySearch.mockResolvedValue({
+ data: mockTestCases,
+ paging: {
+ total: 3,
+ },
+ });
+ });
+
+ it('selects a test case when clicked', async () => {
+ const onChange = jest.fn();
+
+ await act(async () => {
+ renderWithRouter({ ...mockProps, onChange });
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId('test_case_1')).toBeInTheDocument();
+ });
+
+ const testCaseCard = screen
+ .getByTestId('test_case_1')
+ .closest('.cursor-pointer');
+
+ expect(testCaseCard).not.toBeNull();
+
+ await act(async () => {
+ fireEvent.click(testCaseCard as Element);
+ });
+
+ await waitFor(() => {
+ expect(onChange).toHaveBeenCalledWith([mockTestCases[0]]);
+ });
+
+ const checkbox = screen.getByTestId('checkbox-test_case_1');
+
+ expect(checkbox).toHaveProperty('checked', true);
+ });
+
+ it('deselects a test case when clicked again', async () => {
+ const onChange = jest.fn();
+
+ await act(async () => {
+ renderWithRouter({ ...mockProps, onChange });
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId('test_case_1')).toBeInTheDocument();
+ });
+
+ const testCaseCard = screen
+ .getByTestId('test_case_1')
+ .closest('.cursor-pointer');
+
+ expect(testCaseCard).not.toBeNull();
+
+ await act(async () => {
+ fireEvent.click(testCaseCard as Element);
+ });
+
+ await act(async () => {
+ fireEvent.click(testCaseCard as Element);
+ });
+
+ await waitFor(() => {
+ expect(onChange).toHaveBeenLastCalledWith([]);
+ });
+
+ const checkbox = screen.getByTestId('checkbox-test_case_1');
+
+ expect(checkbox).toHaveProperty('checked', false);
+ });
+
+ it('handles multiple test case selections', async () => {
+ const onChange = jest.fn();
+
+ await act(async () => {
+ renderWithRouter({ ...mockProps, onChange });
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId('test_case_1')).toBeInTheDocument();
+ });
+
+ const testCaseCard1 = screen
+ .getByTestId('test_case_1')
+ .closest('.cursor-pointer');
+ const testCaseCard2 = screen
+ .getByTestId('test_case_2')
+ .closest('.cursor-pointer');
+
+ expect(testCaseCard1).not.toBeNull();
+ expect(testCaseCard2).not.toBeNull();
+
+ await act(async () => {
+ fireEvent.click(testCaseCard1 as Element);
+ });
+
+ await act(async () => {
+ fireEvent.click(testCaseCard2 as Element);
+ });
+
+ await waitFor(() => {
+ expect(onChange).toHaveBeenLastCalledWith([
+ mockTestCases[0],
+ mockTestCases[1],
+ ]);
+ });
+ });
+
+ it('pre-selects test cases when selectedTest prop is provided', async () => {
+ mockGetListTestCaseBySearch.mockResolvedValue({
+ data: [mockTestCases[0], mockTestCases[1]],
+ paging: {
+ total: 2,
+ },
+ });
+
+ await act(async () => {
+ renderWithRouter({
+ ...mockProps,
+ selectedTest: ['test_case_1', 'test_case_2'],
+ });
+ });
+
+ await waitFor(() => {
+ const checkbox1 = screen.getByTestId('checkbox-test_case_1');
+ const checkbox2 = screen.getByTestId('checkbox-test_case_2');
+
+ expect(checkbox1).toHaveProperty('checked', true);
+ expect(checkbox2).toHaveProperty('checked', true);
+ });
+ });
+
+ it('handles test cases without id gracefully', async () => {
+ const testCasesWithoutId = [{ ...mockTestCases[0], id: undefined }];
+
+ mockGetListTestCaseBySearch.mockResolvedValue({
+ data: testCasesWithoutId,
+ paging: {
+ total: 1,
+ },
+ });
+
+ const onChange = jest.fn();
+
+ await act(async () => {
+ renderWithRouter({ ...mockProps, onChange });
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId('test_case_1')).toBeInTheDocument();
+ });
+
+ const testCaseCard = screen
+ .getByTestId('test_case_1')
+ .closest('.cursor-pointer');
+
+ expect(testCaseCard).not.toBeNull();
+
+ await act(async () => {
+ fireEvent.click(testCaseCard as Element);
+ });
+
+ expect(onChange).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('Pagination and virtual list', () => {
+ it('fetches data with correct pagination parameters', async () => {
+ mockGetListTestCaseBySearch.mockResolvedValue({
+ data: mockTestCases.slice(0, 2),
+ paging: {
+ total: 3,
+ },
+ });
+
+ renderWithRouter(mockProps);
+
+ await waitFor(() => {
+ expect(mockGetListTestCaseBySearch).toHaveBeenCalledWith({
+ q: '*',
+ limit: 25,
+ offset: 0,
+ });
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId('test_case_1')).toBeInTheDocument();
+ expect(screen.getByTestId('test_case_2')).toBeInTheDocument();
+ });
+ });
+
+ it('maintains search term with API calls', async () => {
+ mockGetListTestCaseBySearch.mockResolvedValue({
+ data: [mockTestCases[0]],
+ paging: {
+ total: 2,
+ },
+ });
+
+ renderWithRouter(mockProps);
+
+ const searchBar = screen.getByTestId('search-bar');
+
+ await act(async () => {
+ fireEvent.change(searchBar, { target: { value: 'specific_test' } });
+ });
+
+ await waitFor(() => {
+ expect(mockGetListTestCaseBySearch).toHaveBeenCalledWith({
+ q: 'specific_test',
+ limit: 25,
+ offset: 0,
+ });
+ });
+ });
+
+ it('uses virtual list for performance optimization', async () => {
+ mockGetListTestCaseBySearch.mockResolvedValue({
+ data: mockTestCases,
+ paging: {
+ total: 100,
+ },
+ });
+
+ const { container } = renderWithRouter(mockProps);
+
+ await waitFor(() => {
+ expect(screen.getByTestId('test_case_1')).toBeInTheDocument();
+ });
+
+ const virtualList = container.querySelector('.rc-virtual-list-holder');
+
+ expect(virtualList).toBeInTheDocument();
+ });
+ });
+
+ describe('Submit functionality', () => {
+ it('submits selected test cases', async () => {
+ mockGetListTestCaseBySearch.mockResolvedValue({
+ data: mockTestCases,
+ paging: {
+ total: 3,
+ },
+ });
+
+ const onSubmit = jest.fn();
+
+ await act(async () => {
+ renderWithRouter({ ...mockProps, onSubmit });
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId('test_case_1')).toBeInTheDocument();
+ });
+
+ const testCaseCard1 = screen
+ .getByTestId('test_case_1')
+ .closest('.cursor-pointer');
+ const testCaseCard2 = screen
+ .getByTestId('test_case_2')
+ .closest('.cursor-pointer');
+
+ expect(testCaseCard1).not.toBeNull();
+ expect(testCaseCard2).not.toBeNull();
+
+ await act(async () => {
+ fireEvent.click(testCaseCard1 as Element);
+ fireEvent.click(testCaseCard2 as Element);
+ });
+
+ const submitBtn = screen.getByTestId('submit');
+
+ await act(async () => {
+ fireEvent.click(submitBtn);
+ });
+
+ await waitFor(() => {
+ expect(onSubmit).toHaveBeenCalledWith([
+ mockTestCases[0],
+ mockTestCases[1],
+ ]);
+ });
+ });
+
+ it('handles async submit operations', async () => {
+ mockGetListTestCaseBySearch.mockResolvedValue({
+ data: mockTestCases,
+ paging: {
+ total: 3,
+ },
+ });
+
+ const onSubmit = jest
+ .fn()
+ .mockImplementation(
+ () => new Promise((resolve) => setTimeout(resolve, 100))
+ );
+
+ await act(async () => {
+ renderWithRouter({ ...mockProps, onSubmit });
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId('test_case_1')).toBeInTheDocument();
+ });
+
+ const submitBtn = screen.getByTestId('submit');
+
+ await act(async () => {
+ fireEvent.click(submitBtn);
+ });
+
+ await waitFor(() => {
+ const loader = queryByAttribute('aria-label', submitBtn, 'loading');
+
+ expect(loader).toBeInTheDocument();
+ });
+
+ await waitFor(() => {
+ expect(onSubmit).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('Column test cases', () => {
+ it('displays column information for column test cases', async () => {
+ const columnTestCase: TestCase = {
+ id: 'column-test',
+ name: 'column_test',
+ displayName: 'Column Test',
+ entityLink: '<#E::table::sample.table::columns::user_id>',
+ testDefinition: {
+ id: 'test-def',
+ name: 'column_values_to_be_unique',
+ displayName: 'Column Values To Be Unique',
+ },
+ } as TestCase;
+
+ mockGetListTestCaseBySearch.mockResolvedValue({
+ data: [columnTestCase],
+ paging: {
+ total: 1,
+ },
+ });
+
+ await act(async () => {
+ renderWithRouter(mockProps);
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId('column_test')).toBeInTheDocument();
+ });
+
+ expect(screen.getByText('label.column:')).toBeInTheDocument();
+ });
+
+ it('does not display column information for table test cases', async () => {
+ const tableTestCase: TestCase = {
+ id: 'table-test',
+ name: 'table_test',
+ displayName: 'Table Test',
+ entityLink: '<#E::table::sample.table>',
+ testDefinition: {
+ id: 'test-def',
+ name: 'table_row_count_to_be_between',
+ displayName: 'Table Row Count To Be Between',
+ },
+ } as TestCase;
+
+ mockGetListTestCaseBySearch.mockResolvedValue({
+ data: [tableTestCase],
+ paging: {
+ total: 1,
+ },
+ });
+
+ await act(async () => {
+ renderWithRouter(mockProps);
+ });
+
+ await waitFor(() => {
+ expect(screen.getByTestId('table_test')).toBeInTheDocument();
+ });
+
+ expect(screen.queryByText('label.column:')).not.toBeInTheDocument();
+ });
+ });
});
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddTestCaseList/AddTestCaseList.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddTestCaseList/AddTestCaseList.component.tsx
index b1ff5ead48b..3d78fb0cade 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddTestCaseList/AddTestCaseList.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/DataQuality/AddTestCaseList/AddTestCaseList.component.tsx
@@ -70,7 +70,9 @@ export const AddTestCaseList = ({
setIsLoading(true);
const testCaseResponse = await getListTestCaseBySearch({
- q: filters ? `${searchText} && ${filters}` : searchText,
+ q: filters
+ ? `${searchText || WILD_CARD_CHAR} && ${filters}`
+ : searchText,
limit: PAGE_SIZE_MEDIUM,
offset: (page - 1) * PAGE_SIZE_MEDIUM,
...(testCaseParams ?? {}),