From 3d2f61ec33dd909eae61d5fda6aac5c1bdd097a1 Mon Sep 17 00:00:00 2001 From: CodingOnStar Date: Tue, 16 Dec 2025 13:46:15 +0800 Subject: [PATCH] test: add unit tests for CreateFromPipeline and Tab components with comprehensive coverage --- .../create-from-dsl-modal/tab/index.spec.tsx | 564 ++++++++++++++++++ .../create-from-pipeline/index.spec.tsx | 439 ++++++++++++++ 2 files changed, 1003 insertions(+) create mode 100644 web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/tab/index.spec.tsx create mode 100644 web/app/components/datasets/create-from-pipeline/index.spec.tsx diff --git a/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/tab/index.spec.tsx b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/tab/index.spec.tsx new file mode 100644 index 0000000000..30a3501634 --- /dev/null +++ b/web/app/components/datasets/create-from-pipeline/create-options/create-from-dsl-modal/tab/index.spec.tsx @@ -0,0 +1,564 @@ +import React from 'react' +import { fireEvent, render, screen } from '@testing-library/react' +import Tab from './index' + +// Define enum locally to avoid importing the whole module +enum CreateFromDSLModalTab { + FROM_FILE = 'from-file', + FROM_URL = 'from-url', +} + +// Mock the create-from-dsl-modal module to export the enum +jest.mock('@/app/components/app/create-from-dsl-modal', () => ({ + CreateFromDSLModalTab: { + FROM_FILE: 'from-file', + FROM_URL: 'from-url', + }, +})) + +// Mock react-i18next +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})) + +describe('Tab', () => { + beforeEach(() => { + jest.clearAllMocks() + }) + + // Tests for basic rendering + describe('Rendering', () => { + it('should render without crashing', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + expect(screen.getByText('app.importFromDSLFile')).toBeInTheDocument() + expect(screen.getByText('app.importFromDSLUrl')).toBeInTheDocument() + }) + + it('should render two tab items', () => { + const setCurrentTab = jest.fn() + const { container } = render( + , + ) + + // Should have 2 clickable tab items + const tabItems = container.querySelectorAll('.cursor-pointer') + expect(tabItems.length).toBe(2) + }) + + it('should render with correct container styling', () => { + const setCurrentTab = jest.fn() + const { container } = render( + , + ) + + const tabContainer = container.firstChild as HTMLElement + expect(tabContainer).toHaveClass('flex') + expect(tabContainer).toHaveClass('h-9') + expect(tabContainer).toHaveClass('items-center') + expect(tabContainer).toHaveClass('gap-x-6') + expect(tabContainer).toHaveClass('border-b') + expect(tabContainer).toHaveClass('border-divider-subtle') + expect(tabContainer).toHaveClass('px-6') + }) + + it('should render tab labels with translation keys', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + expect(screen.getByText('app.importFromDSLFile')).toBeInTheDocument() + expect(screen.getByText('app.importFromDSLUrl')).toBeInTheDocument() + }) + }) + + // Tests for active tab indication + describe('Active Tab Indication', () => { + it('should show FROM_FILE tab as active when currentTab is FROM_FILE', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + // getByText returns the Item element directly (text is inside it) + const fileTab = screen.getByText('app.importFromDSLFile') + const urlTab = screen.getByText('app.importFromDSLUrl') + + // Active tab should have text-text-primary class + expect(fileTab).toHaveClass('text-text-primary') + // Inactive tab should have text-text-tertiary class + expect(urlTab).toHaveClass('text-text-tertiary') + expect(urlTab).not.toHaveClass('text-text-primary') + }) + + it('should show FROM_URL tab as active when currentTab is FROM_URL', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + const fileTab = screen.getByText('app.importFromDSLFile') + const urlTab = screen.getByText('app.importFromDSLUrl') + + // Inactive tab should have text-text-tertiary class + expect(fileTab).toHaveClass('text-text-tertiary') + expect(fileTab).not.toHaveClass('text-text-primary') + // Active tab should have text-text-primary class + expect(urlTab).toHaveClass('text-text-primary') + }) + + it('should render active indicator bar for active tab', () => { + const setCurrentTab = jest.fn() + const { container } = render( + , + ) + + // Active tab should have the indicator bar + const indicatorBars = container.querySelectorAll('.bg-util-colors-blue-brand-blue-brand-600') + expect(indicatorBars.length).toBe(1) + }) + + it('should render active indicator bar for URL tab when active', () => { + const setCurrentTab = jest.fn() + const { container } = render( + , + ) + + // Should have one indicator bar + const indicatorBars = container.querySelectorAll('.bg-util-colors-blue-brand-blue-brand-600') + expect(indicatorBars.length).toBe(1) + + // The indicator should be in the URL tab + const urlTab = screen.getByText('app.importFromDSLUrl') + expect(urlTab.querySelector('.bg-util-colors-blue-brand-blue-brand-600')).toBeInTheDocument() + }) + + it('should not render indicator bar for inactive tab', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + // The URL tab (inactive) should not have an indicator bar + const urlTab = screen.getByText('app.importFromDSLUrl') + expect(urlTab.querySelector('.bg-util-colors-blue-brand-blue-brand-600')).not.toBeInTheDocument() + }) + }) + + // Tests for user interactions + describe('User Interactions', () => { + it('should call setCurrentTab with FROM_FILE when file tab is clicked', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + const fileTab = screen.getByText('app.importFromDSLFile') + fireEvent.click(fileTab) + + expect(setCurrentTab).toHaveBeenCalledTimes(1) + // .bind() passes tab.key as first arg, event as second + expect(setCurrentTab).toHaveBeenCalledWith(CreateFromDSLModalTab.FROM_FILE, expect.anything()) + }) + + it('should call setCurrentTab with FROM_URL when url tab is clicked', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + const urlTab = screen.getByText('app.importFromDSLUrl') + fireEvent.click(urlTab) + + expect(setCurrentTab).toHaveBeenCalledTimes(1) + expect(setCurrentTab).toHaveBeenCalledWith(CreateFromDSLModalTab.FROM_URL, expect.anything()) + }) + + it('should call setCurrentTab when clicking already active tab', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + const fileTab = screen.getByText('app.importFromDSLFile') + fireEvent.click(fileTab) + + // Should still call setCurrentTab even for active tab + expect(setCurrentTab).toHaveBeenCalledTimes(1) + expect(setCurrentTab).toHaveBeenCalledWith(CreateFromDSLModalTab.FROM_FILE, expect.anything()) + }) + + it('should handle multiple tab clicks', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + const fileTab = screen.getByText('app.importFromDSLFile') + const urlTab = screen.getByText('app.importFromDSLUrl') + + fireEvent.click(urlTab) + fireEvent.click(fileTab) + fireEvent.click(urlTab) + + expect(setCurrentTab).toHaveBeenCalledTimes(3) + expect(setCurrentTab).toHaveBeenNthCalledWith(1, CreateFromDSLModalTab.FROM_URL, expect.anything()) + expect(setCurrentTab).toHaveBeenNthCalledWith(2, CreateFromDSLModalTab.FROM_FILE, expect.anything()) + expect(setCurrentTab).toHaveBeenNthCalledWith(3, CreateFromDSLModalTab.FROM_URL, expect.anything()) + }) + }) + + // Tests for props variations + describe('Props Variations', () => { + it('should handle FROM_FILE as currentTab prop', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + const fileTab = screen.getByText('app.importFromDSLFile') + expect(fileTab).toHaveClass('text-text-primary') + }) + + it('should handle FROM_URL as currentTab prop', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + const urlTab = screen.getByText('app.importFromDSLUrl') + expect(urlTab).toHaveClass('text-text-primary') + }) + + it('should work with different setCurrentTab callback functions', () => { + const setCurrentTab1 = jest.fn() + const { rerender } = render( + , + ) + + fireEvent.click(screen.getByText('app.importFromDSLUrl')) + expect(setCurrentTab1).toHaveBeenCalledWith(CreateFromDSLModalTab.FROM_URL, expect.anything()) + + const setCurrentTab2 = jest.fn() + rerender( + , + ) + + fireEvent.click(screen.getByText('app.importFromDSLUrl')) + expect(setCurrentTab2).toHaveBeenCalledWith(CreateFromDSLModalTab.FROM_URL, expect.anything()) + }) + }) + + // Tests for edge cases + describe('Edge Cases', () => { + it('should handle component mounting without errors', () => { + const setCurrentTab = jest.fn() + expect(() => + render( + , + ), + ).not.toThrow() + }) + + it('should handle component unmounting without errors', () => { + const setCurrentTab = jest.fn() + const { unmount } = render( + , + ) + + expect(() => unmount()).not.toThrow() + }) + + it('should handle currentTab prop change', () => { + const setCurrentTab = jest.fn() + const { rerender } = render( + , + ) + + // Initially FROM_FILE is active + let fileTab = screen.getByText('app.importFromDSLFile') + expect(fileTab).toHaveClass('text-text-primary') + + // Change to FROM_URL + rerender( + , + ) + + // Now FROM_URL should be active + const urlTab = screen.getByText('app.importFromDSLUrl') + fileTab = screen.getByText('app.importFromDSLFile') + expect(urlTab).toHaveClass('text-text-primary') + expect(fileTab).not.toHaveClass('text-text-primary') + }) + + it('should handle multiple rerenders', () => { + const setCurrentTab = jest.fn() + const { rerender } = render( + , + ) + + rerender( + , + ) + + rerender( + , + ) + + const fileTab = screen.getByText('app.importFromDSLFile') + expect(fileTab).toHaveClass('text-text-primary') + }) + + it('should maintain DOM structure after multiple interactions', () => { + const setCurrentTab = jest.fn() + const { container } = render( + , + ) + + const initialTabCount = container.querySelectorAll('.cursor-pointer').length + + // Multiple clicks + fireEvent.click(screen.getByText('app.importFromDSLUrl')) + fireEvent.click(screen.getByText('app.importFromDSLFile')) + + const afterClicksTabCount = container.querySelectorAll('.cursor-pointer').length + expect(afterClicksTabCount).toBe(initialTabCount) + }) + }) + + // Tests for Item component integration + describe('Item Component Integration', () => { + it('should render Item components with correct cursor style', () => { + const setCurrentTab = jest.fn() + const { container } = render( + , + ) + + const tabItems = container.querySelectorAll('.cursor-pointer') + expect(tabItems.length).toBe(2) + }) + + it('should pass correct isActive prop to Item components', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + const fileTab = screen.getByText('app.importFromDSLFile') + const urlTab = screen.getByText('app.importFromDSLUrl') + + // File tab should be active + expect(fileTab).toHaveClass('text-text-primary') + // URL tab should be inactive + expect(urlTab).not.toHaveClass('text-text-primary') + }) + + it('should pass correct label to Item components', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + expect(screen.getByText('app.importFromDSLFile')).toBeInTheDocument() + expect(screen.getByText('app.importFromDSLUrl')).toBeInTheDocument() + }) + + it('should pass correct onClick handler to Item components', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + const fileTab = screen.getByText('app.importFromDSLFile') + const urlTab = screen.getByText('app.importFromDSLUrl') + + fireEvent.click(fileTab) + fireEvent.click(urlTab) + + expect(setCurrentTab).toHaveBeenCalledTimes(2) + expect(setCurrentTab).toHaveBeenNthCalledWith(1, CreateFromDSLModalTab.FROM_FILE, expect.anything()) + expect(setCurrentTab).toHaveBeenNthCalledWith(2, CreateFromDSLModalTab.FROM_URL, expect.anything()) + }) + }) + + // Tests for accessibility + describe('Accessibility', () => { + it('should have clickable elements for each tab', () => { + const setCurrentTab = jest.fn() + const { container } = render( + , + ) + + const clickableElements = container.querySelectorAll('.cursor-pointer') + expect(clickableElements.length).toBe(2) + }) + + it('should have visible text labels for each tab', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + const fileLabel = screen.getByText('app.importFromDSLFile') + const urlLabel = screen.getByText('app.importFromDSLUrl') + + expect(fileLabel).toBeVisible() + expect(urlLabel).toBeVisible() + }) + + it('should visually distinguish active tab from inactive tabs', () => { + const setCurrentTab = jest.fn() + const { container } = render( + , + ) + + // Active tab has indicator bar + const indicatorBars = container.querySelectorAll('.bg-util-colors-blue-brand-blue-brand-600') + expect(indicatorBars.length).toBe(1) + + // Active tab has different text color + const fileTab = screen.getByText('app.importFromDSLFile') + expect(fileTab).toHaveClass('text-text-primary') + }) + }) + + // Tests for component stability + describe('Component Stability', () => { + it('should handle rapid mount/unmount cycles', () => { + const setCurrentTab = jest.fn() + + for (let i = 0; i < 5; i++) { + const { unmount } = render( + , + ) + unmount() + } + + expect(true).toBe(true) + }) + + it('should handle rapid tab switching', () => { + const setCurrentTab = jest.fn() + render( + , + ) + + const fileTab = screen.getByText('app.importFromDSLFile') + const urlTab = screen.getByText('app.importFromDSLUrl') + + // Rapid clicks + for (let i = 0; i < 10; i++) + fireEvent.click(i % 2 === 0 ? urlTab : fileTab) + + expect(setCurrentTab).toHaveBeenCalledTimes(10) + }) + }) +}) diff --git a/web/app/components/datasets/create-from-pipeline/index.spec.tsx b/web/app/components/datasets/create-from-pipeline/index.spec.tsx new file mode 100644 index 0000000000..0d39275beb --- /dev/null +++ b/web/app/components/datasets/create-from-pipeline/index.spec.tsx @@ -0,0 +1,439 @@ +import React from 'react' +import { fireEvent, render, screen } from '@testing-library/react' +import CreateFromPipeline from './index' + +// Mock list component to avoid deep dependency issues +jest.mock('./list', () => ({ + __esModule: true, + default: () =>
List Component
, +})) + +// Mock CreateFromDSLModal to avoid deep dependency chain +jest.mock('./create-options/create-from-dsl-modal', () => ({ + __esModule: true, + default: ({ show, onClose, onSuccess }: { show: boolean; onClose: () => void; onSuccess: () => void }) => ( + show + ? ( +
+ + +
+ ) + : null + ), + CreateFromDSLModalTab: { + FROM_URL: 'from-url', + }, +})) + +// Mock next/navigation +const mockReplace = jest.fn() +const mockPush = jest.fn() +let mockSearchParams = new URLSearchParams() + +jest.mock('next/navigation', () => ({ + useRouter: () => ({ + replace: mockReplace, + push: mockPush, + }), + useSearchParams: () => mockSearchParams, +})) + +// Mock react-i18next +jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), +})) + +// Mock useInvalidDatasetList hook +const mockInvalidDatasetList = jest.fn() +jest.mock('@/service/knowledge/use-dataset', () => ({ + useInvalidDatasetList: () => mockInvalidDatasetList, +})) + +describe('CreateFromPipeline', () => { + beforeEach(() => { + jest.clearAllMocks() + mockSearchParams = new URLSearchParams() + }) + + // Tests for basic rendering + describe('Rendering', () => { + it('should render without crashing', () => { + const { container } = render() + + const mainContainer = container.firstChild as HTMLElement + expect(mainContainer).toBeInTheDocument() + }) + + it('should render the main container with correct className', () => { + const { container } = render() + + const mainContainer = container.firstChild as HTMLElement + expect(mainContainer).toHaveClass('relative') + expect(mainContainer).toHaveClass('flex') + expect(mainContainer).toHaveClass('h-[calc(100vh-56px)]') + expect(mainContainer).toHaveClass('flex-col') + expect(mainContainer).toHaveClass('overflow-hidden') + expect(mainContainer).toHaveClass('rounded-t-2xl') + expect(mainContainer).toHaveClass('border-t') + expect(mainContainer).toHaveClass('border-effects-highlight') + expect(mainContainer).toHaveClass('bg-background-default-subtle') + }) + + it('should render Header component with back to knowledge text', () => { + render() + + expect(screen.getByText('datasetPipeline.creation.backToKnowledge')).toBeInTheDocument() + }) + + it('should render List component', () => { + render() + + expect(screen.getByTestId('list')).toBeInTheDocument() + }) + + it('should render Footer component with import DSL button', () => { + render() + + expect(screen.getByText('datasetPipeline.creation.importDSL')).toBeInTheDocument() + }) + + it('should render Effect component with blur effect', () => { + const { container } = render() + + const effectElement = container.querySelector('.blur-\\[80px\\]') + expect(effectElement).toBeInTheDocument() + }) + + it('should render Effect component with correct positioning classes', () => { + const { container } = render() + + const effectElement = container.querySelector('.left-8.top-\\[-34px\\].opacity-20') + expect(effectElement).toBeInTheDocument() + }) + }) + + // Tests for Header component integration + describe('Header Component Integration', () => { + it('should render header with navigation link', () => { + render() + + const link = screen.getByRole('link') + expect(link).toHaveAttribute('href', '/datasets') + }) + + it('should render back button inside header', () => { + render() + + const button = screen.getByRole('button', { name: '' }) + expect(button).toBeInTheDocument() + expect(button).toHaveClass('rounded-full') + }) + + it('should render header with correct styling', () => { + const { container } = render() + + const headerElement = container.querySelector('.px-16.pb-2.pt-5') + expect(headerElement).toBeInTheDocument() + }) + }) + + // Tests for Footer component integration + describe('Footer Component Integration', () => { + it('should render footer with import DSL button', () => { + render() + + const importButton = screen.getByText('datasetPipeline.creation.importDSL') + expect(importButton).toBeInTheDocument() + }) + + it('should render footer at bottom with correct positioning classes', () => { + const { container } = render() + + const footer = container.querySelector('.absolute.bottom-0.left-0.right-0') + expect(footer).toBeInTheDocument() + }) + + it('should render footer with backdrop blur', () => { + const { container } = render() + + const footer = container.querySelector('.backdrop-blur-\\[6px\\]') + expect(footer).toBeInTheDocument() + }) + + it('should render divider in footer', () => { + const { container } = render() + + // Divider renders with w-8 class + const divider = container.querySelector('.w-8') + expect(divider).toBeInTheDocument() + }) + + it('should open import modal when import DSL button is clicked', () => { + render() + + const importButton = screen.getByText('datasetPipeline.creation.importDSL') + fireEvent.click(importButton) + + expect(screen.getByTestId('dsl-modal')).toBeInTheDocument() + }) + + it('should not show import modal initially', () => { + render() + + expect(screen.queryByTestId('dsl-modal')).not.toBeInTheDocument() + }) + }) + + // Tests for Effect component integration + describe('Effect Component Integration', () => { + it('should render Effect with blur effect', () => { + const { container } = render() + + const effectElement = container.querySelector('.blur-\\[80px\\]') + expect(effectElement).toBeInTheDocument() + }) + + it('should render Effect with absolute positioning', () => { + const { container } = render() + + const effectElement = container.querySelector('.absolute.size-\\[112px\\].rounded-full') + expect(effectElement).toBeInTheDocument() + }) + + it('should render Effect with brand color', () => { + const { container } = render() + + const effectElement = container.querySelector('.bg-util-colors-blue-brand-blue-brand-500') + expect(effectElement).toBeInTheDocument() + }) + + it('should render Effect with custom opacity', () => { + const { container } = render() + + const effectElement = container.querySelector('.opacity-20') + expect(effectElement).toBeInTheDocument() + }) + }) + + // Tests for layout structure + describe('Layout Structure', () => { + it('should render children in correct order', () => { + const { container } = render() + + const mainContainer = container.firstChild as HTMLElement + const children = mainContainer.children + + // Should have 4 children: Effect, Header, List, Footer + expect(children.length).toBe(4) + }) + + it('should have flex column layout', () => { + const { container } = render() + + const mainContainer = container.firstChild as HTMLElement + expect(mainContainer).toHaveClass('flex-col') + }) + + it('should have overflow hidden on main container', () => { + const { container } = render() + + const mainContainer = container.firstChild as HTMLElement + expect(mainContainer).toHaveClass('overflow-hidden') + }) + + it('should have correct height calculation', () => { + const { container } = render() + + const mainContainer = container.firstChild as HTMLElement + expect(mainContainer).toHaveClass('h-[calc(100vh-56px)]') + }) + }) + + // Tests for styling + describe('Styling', () => { + it('should have border styling on main container', () => { + const { container } = render() + + const mainContainer = container.firstChild as HTMLElement + expect(mainContainer).toHaveClass('border-t') + expect(mainContainer).toHaveClass('border-effects-highlight') + }) + + it('should have rounded top corners', () => { + const { container } = render() + + const mainContainer = container.firstChild as HTMLElement + expect(mainContainer).toHaveClass('rounded-t-2xl') + }) + + it('should have subtle background color', () => { + const { container } = render() + + const mainContainer = container.firstChild as HTMLElement + expect(mainContainer).toHaveClass('bg-background-default-subtle') + }) + + it('should have relative positioning for child absolute positioning', () => { + const { container } = render() + + const mainContainer = container.firstChild as HTMLElement + expect(mainContainer).toHaveClass('relative') + }) + }) + + // Tests for edge cases + describe('Edge Cases', () => { + it('should handle component mounting without errors', () => { + expect(() => render()).not.toThrow() + }) + + it('should handle component unmounting without errors', () => { + const { unmount } = render() + + expect(() => unmount()).not.toThrow() + }) + + it('should handle multiple renders without issues', () => { + const { rerender } = render() + + rerender() + rerender() + rerender() + + expect(screen.getByText('datasetPipeline.creation.backToKnowledge')).toBeInTheDocument() + }) + + it('should maintain consistent DOM structure across rerenders', () => { + const { container, rerender } = render() + + const initialChildCount = (container.firstChild as HTMLElement)?.children.length + + rerender() + + const afterRerenderChildCount = (container.firstChild as HTMLElement)?.children.length + expect(afterRerenderChildCount).toBe(initialChildCount) + }) + + it('should handle remoteInstallUrl search param', () => { + mockSearchParams = new URLSearchParams('remoteInstallUrl=https://example.com/dsl.yaml') + + render() + + // Should render without crashing when remoteInstallUrl is present + expect(screen.getByText('datasetPipeline.creation.backToKnowledge')).toBeInTheDocument() + }) + }) + + // Tests for accessibility + describe('Accessibility', () => { + it('should have accessible link for navigation', () => { + render() + + const link = screen.getByRole('link') + expect(link).toBeInTheDocument() + expect(link).toHaveAttribute('href', '/datasets') + }) + + it('should have accessible buttons', () => { + render() + + const buttons = screen.getAllByRole('button') + expect(buttons.length).toBeGreaterThanOrEqual(2) // back button and import DSL button + }) + + it('should use semantic structure for content', () => { + const { container } = render() + + const mainContainer = container.firstChild as HTMLElement + expect(mainContainer.tagName).toBe('DIV') + }) + }) + + // Tests for component stability + describe('Component Stability', () => { + it('should not cause memory leaks on unmount', () => { + const { unmount } = render() + + unmount() + + expect(true).toBe(true) + }) + + it('should handle rapid mount/unmount cycles', () => { + for (let i = 0; i < 5; i++) { + const { unmount } = render() + unmount() + } + + expect(true).toBe(true) + }) + }) + + // Tests for user interactions + describe('User Interactions', () => { + it('should toggle import modal when clicking import DSL button', () => { + render() + + // Initially modal is not shown + expect(screen.queryByTestId('dsl-modal')).not.toBeInTheDocument() + + // Click import DSL button + const importButton = screen.getByText('datasetPipeline.creation.importDSL') + fireEvent.click(importButton) + + // Modal should be shown + expect(screen.getByTestId('dsl-modal')).toBeInTheDocument() + }) + + it('should close modal when close button is clicked', () => { + render() + + // Open modal + const importButton = screen.getByText('datasetPipeline.creation.importDSL') + fireEvent.click(importButton) + expect(screen.getByTestId('dsl-modal')).toBeInTheDocument() + + // Click close button + const closeButton = screen.getByTestId('dsl-modal-close') + fireEvent.click(closeButton) + + // Modal should be hidden + expect(screen.queryByTestId('dsl-modal')).not.toBeInTheDocument() + }) + + it('should close modal and redirect when close button is clicked with remoteInstallUrl', () => { + mockSearchParams = new URLSearchParams('remoteInstallUrl=https://example.com/dsl.yaml') + + render() + + // Open modal + const importButton = screen.getByText('datasetPipeline.creation.importDSL') + fireEvent.click(importButton) + + // Click close button + const closeButton = screen.getByTestId('dsl-modal-close') + fireEvent.click(closeButton) + + // Should call replace to remove the URL param + expect(mockReplace).toHaveBeenCalledWith('/datasets/create-from-pipeline') + }) + + it('should call invalidDatasetList when import is successful', () => { + render() + + // Open modal + const importButton = screen.getByText('datasetPipeline.creation.importDSL') + fireEvent.click(importButton) + + // Click success button + const successButton = screen.getByTestId('dsl-modal-success') + fireEvent.click(successButton) + + // Should call invalidDatasetList + expect(mockInvalidDatasetList).toHaveBeenCalled() + }) + }) +})