test: add unit tests for CreateFromPipeline and Tab components with comprehensive coverage

This commit is contained in:
CodingOnStar
2025-12-16 13:46:15 +08:00
parent 82ead9556c
commit 3d2f61ec33
2 changed files with 1003 additions and 0 deletions

View File

@@ -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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
// 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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
// 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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_URL}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
// 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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_URL}
setCurrentTab={setCurrentTab}
/>,
)
// 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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
// 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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_URL}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_URL}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab1}
/>,
)
fireEvent.click(screen.getByText('app.importFromDSLUrl'))
expect(setCurrentTab1).toHaveBeenCalledWith(CreateFromDSLModalTab.FROM_URL, expect.anything())
const setCurrentTab2 = jest.fn()
rerender(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab2}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
),
).not.toThrow()
})
it('should handle component unmounting without errors', () => {
const setCurrentTab = jest.fn()
const { unmount } = render(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
expect(() => unmount()).not.toThrow()
})
it('should handle currentTab prop change', () => {
const setCurrentTab = jest.fn()
const { rerender } = render(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
// Initially FROM_FILE is active
let fileTab = screen.getByText('app.importFromDSLFile')
expect(fileTab).toHaveClass('text-text-primary')
// Change to FROM_URL
rerender(
<Tab
currentTab={CreateFromDSLModalTab.FROM_URL}
setCurrentTab={setCurrentTab}
/>,
)
// 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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
rerender(
<Tab
currentTab={CreateFromDSLModalTab.FROM_URL}
setCurrentTab={setCurrentTab}
/>,
)
rerender(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
// 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(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
unmount()
}
expect(true).toBe(true)
})
it('should handle rapid tab switching', () => {
const setCurrentTab = jest.fn()
render(
<Tab
currentTab={CreateFromDSLModalTab.FROM_FILE}
setCurrentTab={setCurrentTab}
/>,
)
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)
})
})
})

View File

@@ -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: () => <div data-testid="list">List Component</div>,
}))
// 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
? (
<div data-testid="dsl-modal">
<button data-testid="dsl-modal-close" onClick={onClose}>Close</button>
<button data-testid="dsl-modal-success" onClick={onSuccess}>Import Success</button>
</div>
)
: 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(<CreateFromPipeline />)
const mainContainer = container.firstChild as HTMLElement
expect(mainContainer).toBeInTheDocument()
})
it('should render the main container with correct className', () => {
const { container } = render(<CreateFromPipeline />)
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(<CreateFromPipeline />)
expect(screen.getByText('datasetPipeline.creation.backToKnowledge')).toBeInTheDocument()
})
it('should render List component', () => {
render(<CreateFromPipeline />)
expect(screen.getByTestId('list')).toBeInTheDocument()
})
it('should render Footer component with import DSL button', () => {
render(<CreateFromPipeline />)
expect(screen.getByText('datasetPipeline.creation.importDSL')).toBeInTheDocument()
})
it('should render Effect component with blur effect', () => {
const { container } = render(<CreateFromPipeline />)
const effectElement = container.querySelector('.blur-\\[80px\\]')
expect(effectElement).toBeInTheDocument()
})
it('should render Effect component with correct positioning classes', () => {
const { container } = render(<CreateFromPipeline />)
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(<CreateFromPipeline />)
const link = screen.getByRole('link')
expect(link).toHaveAttribute('href', '/datasets')
})
it('should render back button inside header', () => {
render(<CreateFromPipeline />)
const button = screen.getByRole('button', { name: '' })
expect(button).toBeInTheDocument()
expect(button).toHaveClass('rounded-full')
})
it('should render header with correct styling', () => {
const { container } = render(<CreateFromPipeline />)
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(<CreateFromPipeline />)
const importButton = screen.getByText('datasetPipeline.creation.importDSL')
expect(importButton).toBeInTheDocument()
})
it('should render footer at bottom with correct positioning classes', () => {
const { container } = render(<CreateFromPipeline />)
const footer = container.querySelector('.absolute.bottom-0.left-0.right-0')
expect(footer).toBeInTheDocument()
})
it('should render footer with backdrop blur', () => {
const { container } = render(<CreateFromPipeline />)
const footer = container.querySelector('.backdrop-blur-\\[6px\\]')
expect(footer).toBeInTheDocument()
})
it('should render divider in footer', () => {
const { container } = render(<CreateFromPipeline />)
// 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(<CreateFromPipeline />)
const importButton = screen.getByText('datasetPipeline.creation.importDSL')
fireEvent.click(importButton)
expect(screen.getByTestId('dsl-modal')).toBeInTheDocument()
})
it('should not show import modal initially', () => {
render(<CreateFromPipeline />)
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(<CreateFromPipeline />)
const effectElement = container.querySelector('.blur-\\[80px\\]')
expect(effectElement).toBeInTheDocument()
})
it('should render Effect with absolute positioning', () => {
const { container } = render(<CreateFromPipeline />)
const effectElement = container.querySelector('.absolute.size-\\[112px\\].rounded-full')
expect(effectElement).toBeInTheDocument()
})
it('should render Effect with brand color', () => {
const { container } = render(<CreateFromPipeline />)
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(<CreateFromPipeline />)
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(<CreateFromPipeline />)
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(<CreateFromPipeline />)
const mainContainer = container.firstChild as HTMLElement
expect(mainContainer).toHaveClass('flex-col')
})
it('should have overflow hidden on main container', () => {
const { container } = render(<CreateFromPipeline />)
const mainContainer = container.firstChild as HTMLElement
expect(mainContainer).toHaveClass('overflow-hidden')
})
it('should have correct height calculation', () => {
const { container } = render(<CreateFromPipeline />)
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(<CreateFromPipeline />)
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(<CreateFromPipeline />)
const mainContainer = container.firstChild as HTMLElement
expect(mainContainer).toHaveClass('rounded-t-2xl')
})
it('should have subtle background color', () => {
const { container } = render(<CreateFromPipeline />)
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(<CreateFromPipeline />)
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(<CreateFromPipeline />)).not.toThrow()
})
it('should handle component unmounting without errors', () => {
const { unmount } = render(<CreateFromPipeline />)
expect(() => unmount()).not.toThrow()
})
it('should handle multiple renders without issues', () => {
const { rerender } = render(<CreateFromPipeline />)
rerender(<CreateFromPipeline />)
rerender(<CreateFromPipeline />)
rerender(<CreateFromPipeline />)
expect(screen.getByText('datasetPipeline.creation.backToKnowledge')).toBeInTheDocument()
})
it('should maintain consistent DOM structure across rerenders', () => {
const { container, rerender } = render(<CreateFromPipeline />)
const initialChildCount = (container.firstChild as HTMLElement)?.children.length
rerender(<CreateFromPipeline />)
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(<CreateFromPipeline />)
// 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(<CreateFromPipeline />)
const link = screen.getByRole('link')
expect(link).toBeInTheDocument()
expect(link).toHaveAttribute('href', '/datasets')
})
it('should have accessible buttons', () => {
render(<CreateFromPipeline />)
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(<CreateFromPipeline />)
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(<CreateFromPipeline />)
unmount()
expect(true).toBe(true)
})
it('should handle rapid mount/unmount cycles', () => {
for (let i = 0; i < 5; i++) {
const { unmount } = render(<CreateFromPipeline />)
unmount()
}
expect(true).toBe(true)
})
})
// Tests for user interactions
describe('User Interactions', () => {
it('should toggle import modal when clicking import DSL button', () => {
render(<CreateFromPipeline />)
// 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(<CreateFromPipeline />)
// 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(<CreateFromPipeline />)
// 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(<CreateFromPipeline />)
// 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()
})
})
})