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()
+ })
+ })
+})