mirror of
https://github.com/YFGaia/dify-plus.git
synced 2026-06-14 20:41:21 +08:00
test: add unit tests for RagPipeline components (#30429)
Co-authored-by: CodingOnStar <hanxujiang@dify.ai>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,971 @@
|
||||
import type { PanelProps } from '@/app/components/workflow/panel'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import * as React from 'react'
|
||||
import RagPipelinePanel from './index'
|
||||
|
||||
// ============================================================================
|
||||
// Mock External Dependencies
|
||||
// ============================================================================
|
||||
|
||||
// Type definitions for dynamic module
|
||||
type DynamicModule = {
|
||||
default?: React.ComponentType<Record<string, unknown>>
|
||||
}
|
||||
|
||||
type PromiseOrModule = Promise<DynamicModule> | DynamicModule
|
||||
|
||||
// Mock next/dynamic to return synchronous components immediately
|
||||
vi.mock('next/dynamic', () => ({
|
||||
default: (loader: () => PromiseOrModule, _options?: Record<string, unknown>) => {
|
||||
let Component: React.ComponentType<Record<string, unknown>> | null = null
|
||||
|
||||
// Try to resolve the loader synchronously for mocked modules
|
||||
try {
|
||||
const result = loader() as PromiseOrModule
|
||||
if (result && typeof (result as Promise<DynamicModule>).then === 'function') {
|
||||
// For async modules, we need to handle them specially
|
||||
// This will work with vi.mock since mocks resolve synchronously
|
||||
(result as Promise<DynamicModule>).then((mod: DynamicModule) => {
|
||||
Component = (mod.default || mod) as React.ComponentType<Record<string, unknown>>
|
||||
})
|
||||
}
|
||||
else if (result) {
|
||||
Component = ((result as DynamicModule).default || result) as React.ComponentType<Record<string, unknown>>
|
||||
}
|
||||
}
|
||||
catch {
|
||||
// If the module can't be resolved, Component stays null
|
||||
}
|
||||
|
||||
// Return a simple wrapper that renders the component or null
|
||||
const DynamicComponent = React.forwardRef((props: Record<string, unknown>, ref: React.Ref<unknown>) => {
|
||||
// For mocked modules, Component should already be set
|
||||
if (Component)
|
||||
return <Component {...props} ref={ref} />
|
||||
|
||||
return null
|
||||
})
|
||||
|
||||
DynamicComponent.displayName = 'DynamicComponent'
|
||||
return DynamicComponent
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock workflow store
|
||||
let mockHistoryWorkflowData: Record<string, unknown> | null = null
|
||||
let mockShowDebugAndPreviewPanel = false
|
||||
let mockShowGlobalVariablePanel = false
|
||||
let mockShowInputFieldPanel = false
|
||||
let mockShowInputFieldPreviewPanel = false
|
||||
let mockInputFieldEditPanelProps: Record<string, unknown> | null = null
|
||||
let mockPipelineId = 'test-pipeline-123'
|
||||
|
||||
type MockStoreState = {
|
||||
historyWorkflowData: Record<string, unknown> | null
|
||||
showDebugAndPreviewPanel: boolean
|
||||
showGlobalVariablePanel: boolean
|
||||
showInputFieldPanel: boolean
|
||||
showInputFieldPreviewPanel: boolean
|
||||
inputFieldEditPanelProps: Record<string, unknown> | null
|
||||
pipelineId: string
|
||||
}
|
||||
|
||||
vi.mock('@/app/components/workflow/store', () => ({
|
||||
useStore: (selector: (state: MockStoreState) => unknown) => {
|
||||
const state: MockStoreState = {
|
||||
historyWorkflowData: mockHistoryWorkflowData,
|
||||
showDebugAndPreviewPanel: mockShowDebugAndPreviewPanel,
|
||||
showGlobalVariablePanel: mockShowGlobalVariablePanel,
|
||||
showInputFieldPanel: mockShowInputFieldPanel,
|
||||
showInputFieldPreviewPanel: mockShowInputFieldPreviewPanel,
|
||||
inputFieldEditPanelProps: mockInputFieldEditPanelProps,
|
||||
pipelineId: mockPipelineId,
|
||||
}
|
||||
return selector(state)
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock Panel component to capture props and render children
|
||||
let capturedPanelProps: PanelProps | null = null
|
||||
vi.mock('@/app/components/workflow/panel', () => ({
|
||||
default: (props: PanelProps) => {
|
||||
capturedPanelProps = props
|
||||
return (
|
||||
<div data-testid="workflow-panel">
|
||||
<div data-testid="panel-left">{props.components?.left}</div>
|
||||
<div data-testid="panel-right">{props.components?.right}</div>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock Record component
|
||||
vi.mock('@/app/components/workflow/panel/record', () => ({
|
||||
default: () => <div data-testid="record-panel">Record Panel</div>,
|
||||
}))
|
||||
|
||||
// Mock TestRunPanel component
|
||||
vi.mock('@/app/components/rag-pipeline/components/panel/test-run', () => ({
|
||||
default: () => <div data-testid="test-run-panel">Test Run Panel</div>,
|
||||
}))
|
||||
|
||||
// Mock InputFieldPanel component
|
||||
vi.mock('./input-field', () => ({
|
||||
default: () => <div data-testid="input-field-panel">Input Field Panel</div>,
|
||||
}))
|
||||
|
||||
// Mock InputFieldEditorPanel component
|
||||
const mockInputFieldEditorProps = vi.fn()
|
||||
vi.mock('./input-field/editor', () => ({
|
||||
default: (props: Record<string, unknown>) => {
|
||||
mockInputFieldEditorProps(props)
|
||||
return <div data-testid="input-field-editor-panel">Input Field Editor Panel</div>
|
||||
},
|
||||
}))
|
||||
|
||||
// Mock PreviewPanel component
|
||||
vi.mock('./input-field/preview', () => ({
|
||||
default: () => <div data-testid="preview-panel">Preview Panel</div>,
|
||||
}))
|
||||
|
||||
// Mock GlobalVariablePanel component
|
||||
vi.mock('@/app/components/workflow/panel/global-variable-panel', () => ({
|
||||
default: () => <div data-testid="global-variable-panel">Global Variable Panel</div>,
|
||||
}))
|
||||
|
||||
// ============================================================================
|
||||
// Helper Functions
|
||||
// ============================================================================
|
||||
|
||||
type SetupMockOptions = {
|
||||
historyWorkflowData?: Record<string, unknown> | null
|
||||
showDebugAndPreviewPanel?: boolean
|
||||
showGlobalVariablePanel?: boolean
|
||||
showInputFieldPanel?: boolean
|
||||
showInputFieldPreviewPanel?: boolean
|
||||
inputFieldEditPanelProps?: Record<string, unknown> | null
|
||||
pipelineId?: string
|
||||
}
|
||||
|
||||
const setupMocks = (options?: SetupMockOptions) => {
|
||||
mockHistoryWorkflowData = options?.historyWorkflowData ?? null
|
||||
mockShowDebugAndPreviewPanel = options?.showDebugAndPreviewPanel ?? false
|
||||
mockShowGlobalVariablePanel = options?.showGlobalVariablePanel ?? false
|
||||
mockShowInputFieldPanel = options?.showInputFieldPanel ?? false
|
||||
mockShowInputFieldPreviewPanel = options?.showInputFieldPreviewPanel ?? false
|
||||
mockInputFieldEditPanelProps = options?.inputFieldEditPanelProps ?? null
|
||||
mockPipelineId = options?.pipelineId ?? 'test-pipeline-123'
|
||||
capturedPanelProps = null
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// RagPipelinePanel Component Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('RagPipelinePanel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Rendering Tests
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', async () => {
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('workflow-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should render Panel component with correct structure', async () => {
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('panel-left')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('panel-right')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should pass versionHistoryPanelProps to Panel', async () => {
|
||||
// Arrange
|
||||
setupMocks({ pipelineId: 'my-pipeline-456' })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(capturedPanelProps?.versionHistoryPanelProps).toBeDefined()
|
||||
expect(capturedPanelProps?.versionHistoryPanelProps?.getVersionListUrl).toBe(
|
||||
'/rag/pipelines/my-pipeline-456/workflows',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Memoization Tests - versionHistoryPanelProps
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Memoization - versionHistoryPanelProps', () => {
|
||||
it('should compute correct getVersionListUrl based on pipelineId', async () => {
|
||||
// Arrange
|
||||
setupMocks({ pipelineId: 'pipeline-abc' })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(capturedPanelProps?.versionHistoryPanelProps?.getVersionListUrl).toBe(
|
||||
'/rag/pipelines/pipeline-abc/workflows',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('should compute correct deleteVersionUrl function', async () => {
|
||||
// Arrange
|
||||
setupMocks({ pipelineId: 'pipeline-xyz' })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
const deleteUrl = capturedPanelProps?.versionHistoryPanelProps?.deleteVersionUrl?.('version-1')
|
||||
expect(deleteUrl).toBe('/rag/pipelines/pipeline-xyz/workflows/version-1')
|
||||
})
|
||||
})
|
||||
|
||||
it('should compute correct updateVersionUrl function', async () => {
|
||||
// Arrange
|
||||
setupMocks({ pipelineId: 'pipeline-def' })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
const updateUrl = capturedPanelProps?.versionHistoryPanelProps?.updateVersionUrl?.('version-2')
|
||||
expect(updateUrl).toBe('/rag/pipelines/pipeline-def/workflows/version-2')
|
||||
})
|
||||
})
|
||||
|
||||
it('should set latestVersionId to empty string', async () => {
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(capturedPanelProps?.versionHistoryPanelProps?.latestVersionId).toBe('')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Memoization Tests - panelProps
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Memoization - panelProps', () => {
|
||||
it('should pass components.left to Panel', async () => {
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(capturedPanelProps?.components?.left).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
it('should pass components.right to Panel', async () => {
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(capturedPanelProps?.components?.right).toBeDefined()
|
||||
})
|
||||
})
|
||||
|
||||
it('should pass versionHistoryPanelProps to panelProps', async () => {
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(capturedPanelProps?.versionHistoryPanelProps).toBeDefined()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Component Memoization Tests (React.memo)
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Component Memoization', () => {
|
||||
it('should be wrapped with React.memo', async () => {
|
||||
// The component should not break when re-rendered
|
||||
const { rerender } = render(<RagPipelinePanel />)
|
||||
|
||||
// Act - rerender without prop changes
|
||||
rerender(<RagPipelinePanel />)
|
||||
|
||||
// Assert - component should still render correctly
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('workflow-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// RagPipelinePanelOnRight Component Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('RagPipelinePanelOnRight', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Conditional Rendering - Record Panel
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Record Panel Conditional Rendering', () => {
|
||||
it('should render Record panel when historyWorkflowData exists', async () => {
|
||||
// Arrange
|
||||
setupMocks({ historyWorkflowData: { id: 'history-1' } })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('record-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not render Record panel when historyWorkflowData is null', async () => {
|
||||
// Arrange
|
||||
setupMocks({ historyWorkflowData: null })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('record-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not render Record panel when historyWorkflowData is undefined', async () => {
|
||||
// Arrange
|
||||
setupMocks({ historyWorkflowData: undefined })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('record-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Conditional Rendering - TestRun Panel
|
||||
// -------------------------------------------------------------------------
|
||||
describe('TestRun Panel Conditional Rendering', () => {
|
||||
it('should render TestRun panel when showDebugAndPreviewPanel is true', async () => {
|
||||
// Arrange
|
||||
setupMocks({ showDebugAndPreviewPanel: true })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('test-run-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not render TestRun panel when showDebugAndPreviewPanel is false', async () => {
|
||||
// Arrange
|
||||
setupMocks({ showDebugAndPreviewPanel: false })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('test-run-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Conditional Rendering - GlobalVariable Panel
|
||||
// -------------------------------------------------------------------------
|
||||
describe('GlobalVariable Panel Conditional Rendering', () => {
|
||||
it('should render GlobalVariable panel when showGlobalVariablePanel is true', async () => {
|
||||
// Arrange
|
||||
setupMocks({ showGlobalVariablePanel: true })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('global-variable-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not render GlobalVariable panel when showGlobalVariablePanel is false', async () => {
|
||||
// Arrange
|
||||
setupMocks({ showGlobalVariablePanel: false })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('global-variable-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Multiple Panels Rendering
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Multiple Panels Rendering', () => {
|
||||
it('should render all right panels when all conditions are true', async () => {
|
||||
// Arrange
|
||||
setupMocks({
|
||||
historyWorkflowData: { id: 'history-1' },
|
||||
showDebugAndPreviewPanel: true,
|
||||
showGlobalVariablePanel: true,
|
||||
})
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('record-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('test-run-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('global-variable-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should render no right panels when all conditions are false', async () => {
|
||||
// Arrange
|
||||
setupMocks({
|
||||
historyWorkflowData: null,
|
||||
showDebugAndPreviewPanel: false,
|
||||
showGlobalVariablePanel: false,
|
||||
})
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('record-panel')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('test-run-panel')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('global-variable-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should render only Record and TestRun panels', async () => {
|
||||
// Arrange
|
||||
setupMocks({
|
||||
historyWorkflowData: { id: 'history-1' },
|
||||
showDebugAndPreviewPanel: true,
|
||||
showGlobalVariablePanel: false,
|
||||
})
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('record-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('test-run-panel')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('global-variable-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// RagPipelinePanelOnLeft Component Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('RagPipelinePanelOnLeft', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Conditional Rendering - Preview Panel
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Preview Panel Conditional Rendering', () => {
|
||||
it('should render Preview panel when showInputFieldPreviewPanel is true', async () => {
|
||||
// Arrange
|
||||
setupMocks({ showInputFieldPreviewPanel: true })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('preview-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not render Preview panel when showInputFieldPreviewPanel is false', async () => {
|
||||
// Arrange
|
||||
setupMocks({ showInputFieldPreviewPanel: false })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('preview-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Conditional Rendering - InputFieldEditor Panel
|
||||
// -------------------------------------------------------------------------
|
||||
describe('InputFieldEditor Panel Conditional Rendering', () => {
|
||||
it('should render InputFieldEditor panel when inputFieldEditPanelProps is provided', async () => {
|
||||
// Arrange
|
||||
const editProps = {
|
||||
onClose: vi.fn(),
|
||||
onSubmit: vi.fn(),
|
||||
initialData: { variable: 'test' },
|
||||
}
|
||||
setupMocks({ inputFieldEditPanelProps: editProps })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('input-field-editor-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not render InputFieldEditor panel when inputFieldEditPanelProps is null', async () => {
|
||||
// Arrange
|
||||
setupMocks({ inputFieldEditPanelProps: null })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('input-field-editor-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should pass props to InputFieldEditor panel', async () => {
|
||||
// Arrange
|
||||
const editProps = {
|
||||
onClose: vi.fn(),
|
||||
onSubmit: vi.fn(),
|
||||
initialData: { variable: 'test_var', label: 'Test Label' },
|
||||
}
|
||||
setupMocks({ inputFieldEditPanelProps: editProps })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(mockInputFieldEditorProps).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
onClose: editProps.onClose,
|
||||
onSubmit: editProps.onSubmit,
|
||||
initialData: editProps.initialData,
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Conditional Rendering - InputField Panel
|
||||
// -------------------------------------------------------------------------
|
||||
describe('InputField Panel Conditional Rendering', () => {
|
||||
it('should render InputField panel when showInputFieldPanel is true', async () => {
|
||||
// Arrange
|
||||
setupMocks({ showInputFieldPanel: true })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('input-field-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should not render InputField panel when showInputFieldPanel is false', async () => {
|
||||
// Arrange
|
||||
setupMocks({ showInputFieldPanel: false })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('input-field-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Multiple Panels Rendering
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Multiple Left Panels Rendering', () => {
|
||||
it('should render all left panels when all conditions are true', async () => {
|
||||
// Arrange
|
||||
setupMocks({
|
||||
showInputFieldPreviewPanel: true,
|
||||
inputFieldEditPanelProps: { onClose: vi.fn(), onSubmit: vi.fn() },
|
||||
showInputFieldPanel: true,
|
||||
})
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('preview-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('input-field-editor-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('input-field-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should render no left panels when all conditions are false', async () => {
|
||||
// Arrange
|
||||
setupMocks({
|
||||
showInputFieldPreviewPanel: false,
|
||||
inputFieldEditPanelProps: null,
|
||||
showInputFieldPanel: false,
|
||||
})
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('preview-panel')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('input-field-editor-panel')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('input-field-panel')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should render only Preview and InputField panels', async () => {
|
||||
// Arrange
|
||||
setupMocks({
|
||||
showInputFieldPreviewPanel: true,
|
||||
inputFieldEditPanelProps: null,
|
||||
showInputFieldPanel: true,
|
||||
})
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('preview-panel')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('input-field-editor-panel')).not.toBeInTheDocument()
|
||||
expect(screen.getByTestId('input-field-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Edge Cases Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Edge Cases', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Empty/Undefined Values
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Empty/Undefined Values', () => {
|
||||
it('should handle empty pipelineId gracefully', async () => {
|
||||
// Arrange
|
||||
setupMocks({ pipelineId: '' })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(capturedPanelProps?.versionHistoryPanelProps?.getVersionListUrl).toBe(
|
||||
'/rag/pipelines//workflows',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle special characters in pipelineId', async () => {
|
||||
// Arrange
|
||||
setupMocks({ pipelineId: 'pipeline-with-special_chars.123' })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(capturedPanelProps?.versionHistoryPanelProps?.getVersionListUrl).toBe(
|
||||
'/rag/pipelines/pipeline-with-special_chars.123/workflows',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Props Spreading Tests
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Props Spreading', () => {
|
||||
it('should correctly spread inputFieldEditPanelProps to editor component', async () => {
|
||||
// Arrange
|
||||
const customProps = {
|
||||
onClose: vi.fn(),
|
||||
onSubmit: vi.fn(),
|
||||
initialData: {
|
||||
variable: 'custom_var',
|
||||
label: 'Custom Label',
|
||||
type: 'text',
|
||||
},
|
||||
extraProp: 'extra-value',
|
||||
}
|
||||
setupMocks({ inputFieldEditPanelProps: customProps })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(mockInputFieldEditorProps).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
extraProp: 'extra-value',
|
||||
}),
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// State Combinations
|
||||
// -------------------------------------------------------------------------
|
||||
describe('State Combinations', () => {
|
||||
it('should handle all panels visible simultaneously', async () => {
|
||||
// Arrange
|
||||
setupMocks({
|
||||
historyWorkflowData: { id: 'h1' },
|
||||
showDebugAndPreviewPanel: true,
|
||||
showGlobalVariablePanel: true,
|
||||
showInputFieldPreviewPanel: true,
|
||||
inputFieldEditPanelProps: { onClose: vi.fn(), onSubmit: vi.fn() },
|
||||
showInputFieldPanel: true,
|
||||
})
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert - All panels should be visible
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('record-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('test-run-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('global-variable-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('preview-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('input-field-editor-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('input-field-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// URL Generator Functions Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('URL Generator Functions', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
it('should return consistent URLs for same versionId', async () => {
|
||||
// Arrange
|
||||
setupMocks({ pipelineId: 'stable-pipeline' })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
const deleteUrl1 = capturedPanelProps?.versionHistoryPanelProps?.deleteVersionUrl?.('version-x')
|
||||
const deleteUrl2 = capturedPanelProps?.versionHistoryPanelProps?.deleteVersionUrl?.('version-x')
|
||||
expect(deleteUrl1).toBe(deleteUrl2)
|
||||
})
|
||||
})
|
||||
|
||||
it('should return different URLs for different versionIds', async () => {
|
||||
// Arrange
|
||||
setupMocks({ pipelineId: 'stable-pipeline' })
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
const deleteUrl1 = capturedPanelProps?.versionHistoryPanelProps?.deleteVersionUrl?.('version-1')
|
||||
const deleteUrl2 = capturedPanelProps?.versionHistoryPanelProps?.deleteVersionUrl?.('version-2')
|
||||
expect(deleteUrl1).not.toBe(deleteUrl2)
|
||||
expect(deleteUrl1).toBe('/rag/pipelines/stable-pipeline/workflows/version-1')
|
||||
expect(deleteUrl2).toBe('/rag/pipelines/stable-pipeline/workflows/version-2')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Type Safety Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Type Safety', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
it('should pass correct PanelProps structure', async () => {
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert - Check structure matches PanelProps
|
||||
await waitFor(() => {
|
||||
expect(capturedPanelProps).toHaveProperty('components')
|
||||
expect(capturedPanelProps).toHaveProperty('versionHistoryPanelProps')
|
||||
expect(capturedPanelProps?.components).toHaveProperty('left')
|
||||
expect(capturedPanelProps?.components).toHaveProperty('right')
|
||||
})
|
||||
})
|
||||
|
||||
it('should pass correct versionHistoryPanelProps structure', async () => {
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(capturedPanelProps?.versionHistoryPanelProps).toHaveProperty('getVersionListUrl')
|
||||
expect(capturedPanelProps?.versionHistoryPanelProps).toHaveProperty('deleteVersionUrl')
|
||||
expect(capturedPanelProps?.versionHistoryPanelProps).toHaveProperty('updateVersionUrl')
|
||||
expect(capturedPanelProps?.versionHistoryPanelProps).toHaveProperty('latestVersionId')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Performance Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Performance', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
it('should handle multiple rerenders without issues', async () => {
|
||||
// Arrange
|
||||
const { rerender } = render(<RagPipelinePanel />)
|
||||
|
||||
// Act - Multiple rerenders
|
||||
for (let i = 0; i < 10; i++)
|
||||
rerender(<RagPipelinePanel />)
|
||||
|
||||
// Assert - Component should still work
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('workflow-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Integration Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Integration Tests', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
it('should pass correct components to Panel', async () => {
|
||||
// Arrange
|
||||
setupMocks({
|
||||
historyWorkflowData: { id: 'h1' },
|
||||
showInputFieldPanel: true,
|
||||
})
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert
|
||||
await waitFor(() => {
|
||||
expect(capturedPanelProps?.components?.left).toBeDefined()
|
||||
expect(capturedPanelProps?.components?.right).toBeDefined()
|
||||
|
||||
// Check that the components are React elements
|
||||
expect(React.isValidElement(capturedPanelProps?.components?.left)).toBe(true)
|
||||
expect(React.isValidElement(capturedPanelProps?.components?.right)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('should correctly consume all store selectors', async () => {
|
||||
// Arrange
|
||||
setupMocks({
|
||||
historyWorkflowData: { id: 'test-history' },
|
||||
showDebugAndPreviewPanel: true,
|
||||
showGlobalVariablePanel: true,
|
||||
showInputFieldPanel: true,
|
||||
showInputFieldPreviewPanel: true,
|
||||
inputFieldEditPanelProps: { onClose: vi.fn(), onSubmit: vi.fn() },
|
||||
pipelineId: 'integration-test-pipeline',
|
||||
})
|
||||
|
||||
// Act
|
||||
render(<RagPipelinePanel />)
|
||||
|
||||
// Assert - All store-dependent rendering should work
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('record-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('test-run-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('global-variable-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('input-field-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('preview-panel')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('input-field-editor-panel')).toBeInTheDocument()
|
||||
expect(capturedPanelProps?.versionHistoryPanelProps?.getVersionListUrl).toBe(
|
||||
'/rag/pipelines/integration-test-pipeline/workflows',
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
+1744
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -49,6 +49,7 @@ const InputFieldEditorPanel = ({
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
data-testid="input-field-editor-close-btn"
|
||||
className="absolute right-2.5 top-2.5 flex size-8 items-center justify-center"
|
||||
onClick={onClose}
|
||||
>
|
||||
|
||||
+2557
File diff suppressed because it is too large
Load Diff
@@ -53,6 +53,7 @@ const FieldList = ({
|
||||
{LabelRightContent}
|
||||
</div>
|
||||
<ActionButton
|
||||
data-testid="field-list-add-btn"
|
||||
onClick={() => handleOpenInputFieldEditor()}
|
||||
disabled={readonly}
|
||||
className={cn(readonly && 'cursor-not-allowed')}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,937 @@
|
||||
import type { WorkflowRunningData } from '@/app/components/workflow/types'
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import { WorkflowRunningStatus } from '@/app/components/workflow/types'
|
||||
import { ChunkingMode } from '@/models/datasets'
|
||||
|
||||
import Header from './header'
|
||||
// Import components after mocks
|
||||
import TestRunPanel from './index'
|
||||
|
||||
// ============================================================================
|
||||
// Mocks
|
||||
// ============================================================================
|
||||
|
||||
// Mock workflow store
|
||||
const mockIsPreparingDataSource = vi.fn(() => true)
|
||||
const mockSetIsPreparingDataSource = vi.fn()
|
||||
const mockWorkflowRunningData = vi.fn<() => WorkflowRunningData | undefined>(() => undefined)
|
||||
const mockPipelineId = 'test-pipeline-id'
|
||||
|
||||
vi.mock('@/app/components/workflow/store', () => ({
|
||||
useStore: (selector: (state: Record<string, unknown>) => unknown) => {
|
||||
const state = {
|
||||
isPreparingDataSource: mockIsPreparingDataSource(),
|
||||
workflowRunningData: mockWorkflowRunningData(),
|
||||
pipelineId: mockPipelineId,
|
||||
}
|
||||
return selector(state)
|
||||
},
|
||||
useWorkflowStore: () => ({
|
||||
getState: () => ({
|
||||
isPreparingDataSource: mockIsPreparingDataSource(),
|
||||
setIsPreparingDataSource: mockSetIsPreparingDataSource,
|
||||
}),
|
||||
}),
|
||||
}))
|
||||
|
||||
// Mock workflow interactions
|
||||
const mockHandleCancelDebugAndPreviewPanel = vi.fn()
|
||||
vi.mock('@/app/components/workflow/hooks', () => ({
|
||||
useWorkflowInteractions: () => ({
|
||||
handleCancelDebugAndPreviewPanel: mockHandleCancelDebugAndPreviewPanel,
|
||||
}),
|
||||
useWorkflowRun: () => ({
|
||||
handleRun: vi.fn(),
|
||||
}),
|
||||
useToolIcon: () => 'mock-tool-icon',
|
||||
}))
|
||||
|
||||
// Mock data source provider
|
||||
vi.mock('@/app/components/datasets/documents/create-from-pipeline/data-source/store/provider', () => ({
|
||||
default: ({ children }: { children: React.ReactNode }) => <div data-testid="data-source-provider">{children}</div>,
|
||||
}))
|
||||
|
||||
// Mock Preparation component
|
||||
vi.mock('./preparation', () => ({
|
||||
default: () => <div data-testid="preparation-component">Preparation</div>,
|
||||
}))
|
||||
|
||||
// Mock Result component (for TestRunPanel tests only)
|
||||
vi.mock('./result', () => ({
|
||||
default: () => <div data-testid="result-component">Result</div>,
|
||||
}))
|
||||
|
||||
// Mock ResultPanel from workflow
|
||||
vi.mock('@/app/components/workflow/run/result-panel', () => ({
|
||||
default: (props: Record<string, unknown>) => (
|
||||
<div data-testid="result-panel">
|
||||
ResultPanel -
|
||||
{' '}
|
||||
{props.status as string}
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock TracingPanel from workflow
|
||||
vi.mock('@/app/components/workflow/run/tracing-panel', () => ({
|
||||
default: (props: { list: unknown[] }) => (
|
||||
<div data-testid="tracing-panel">
|
||||
TracingPanel -
|
||||
{' '}
|
||||
{props.list?.length ?? 0}
|
||||
{' '}
|
||||
items
|
||||
</div>
|
||||
),
|
||||
}))
|
||||
|
||||
// Mock Loading component
|
||||
vi.mock('@/app/components/base/loading', () => ({
|
||||
default: () => <div data-testid="loading">Loading...</div>,
|
||||
}))
|
||||
|
||||
// Mock config
|
||||
vi.mock('@/config', () => ({
|
||||
RAG_PIPELINE_PREVIEW_CHUNK_NUM: 5,
|
||||
}))
|
||||
|
||||
// ============================================================================
|
||||
// Test Data Factories
|
||||
// ============================================================================
|
||||
|
||||
const createMockWorkflowRunningData = (overrides: Partial<WorkflowRunningData> = {}): WorkflowRunningData => ({
|
||||
result: {
|
||||
status: WorkflowRunningStatus.Succeeded,
|
||||
outputs: '{"test": "output"}',
|
||||
outputs_truncated: false,
|
||||
inputs: '{"test": "input"}',
|
||||
inputs_truncated: false,
|
||||
process_data_truncated: false,
|
||||
error: undefined,
|
||||
elapsed_time: 1000,
|
||||
total_tokens: 100,
|
||||
created_at: Date.now(),
|
||||
created_by: 'Test User',
|
||||
total_steps: 5,
|
||||
exceptions_count: 0,
|
||||
},
|
||||
tracing: [],
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const createMockGeneralOutputs = (chunkContents: string[] = ['chunk1', 'chunk2']) => ({
|
||||
chunk_structure: ChunkingMode.text,
|
||||
preview: chunkContents.map(content => ({ content })),
|
||||
})
|
||||
|
||||
const createMockParentChildOutputs = (parentMode: 'paragraph' | 'full-doc' = 'paragraph') => ({
|
||||
chunk_structure: ChunkingMode.parentChild,
|
||||
parent_mode: parentMode,
|
||||
preview: [
|
||||
{ content: 'parent1', child_chunks: ['child1', 'child2'] },
|
||||
{ content: 'parent2', child_chunks: ['child3', 'child4'] },
|
||||
],
|
||||
})
|
||||
|
||||
const createMockQAOutputs = () => ({
|
||||
chunk_structure: ChunkingMode.qa,
|
||||
qa_preview: [
|
||||
{ question: 'Q1', answer: 'A1' },
|
||||
{ question: 'Q2', answer: 'A2' },
|
||||
],
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// TestRunPanel Component Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('TestRunPanel', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockIsPreparingDataSource.mockReturnValue(true)
|
||||
mockWorkflowRunningData.mockReturnValue(undefined)
|
||||
})
|
||||
|
||||
// Basic rendering tests
|
||||
describe('Rendering', () => {
|
||||
it('should render with correct container styles', () => {
|
||||
const { container } = render(<TestRunPanel />)
|
||||
const panelDiv = container.firstChild as HTMLElement
|
||||
|
||||
expect(panelDiv).toHaveClass('relative', 'flex', 'h-full', 'w-[480px]', 'flex-col')
|
||||
})
|
||||
|
||||
it('should render Header component', () => {
|
||||
render(<TestRunPanel />)
|
||||
|
||||
expect(screen.getByText('datasetPipeline.testRun.title')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Conditional rendering based on isPreparingDataSource
|
||||
describe('Conditional Content Rendering', () => {
|
||||
it('should render Preparation inside DataSourceProvider when isPreparingDataSource is true', () => {
|
||||
mockIsPreparingDataSource.mockReturnValue(true)
|
||||
|
||||
render(<TestRunPanel />)
|
||||
|
||||
expect(screen.getByTestId('data-source-provider')).toBeInTheDocument()
|
||||
expect(screen.getByTestId('preparation-component')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('result-component')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render Result when isPreparingDataSource is false', () => {
|
||||
mockIsPreparingDataSource.mockReturnValue(false)
|
||||
|
||||
render(<TestRunPanel />)
|
||||
|
||||
expect(screen.getByTestId('result-component')).toBeInTheDocument()
|
||||
expect(screen.queryByTestId('data-source-provider')).not.toBeInTheDocument()
|
||||
expect(screen.queryByTestId('preparation-component')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Header Component Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Header', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockIsPreparingDataSource.mockReturnValue(true)
|
||||
})
|
||||
|
||||
// Rendering tests
|
||||
describe('Rendering', () => {
|
||||
it('should render title with correct translation key', () => {
|
||||
render(<Header />)
|
||||
|
||||
expect(screen.getByText('datasetPipeline.testRun.title')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render close button', () => {
|
||||
render(<Header />)
|
||||
|
||||
const closeButton = screen.getByRole('button')
|
||||
expect(closeButton).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should have correct layout classes', () => {
|
||||
const { container } = render(<Header />)
|
||||
const headerDiv = container.firstChild as HTMLElement
|
||||
|
||||
expect(headerDiv).toHaveClass('flex', 'items-center', 'gap-x-2', 'pl-4', 'pr-3', 'pt-4')
|
||||
})
|
||||
})
|
||||
|
||||
// Close button interactions
|
||||
describe('Close Button Interaction', () => {
|
||||
it('should call setIsPreparingDataSource(false) and handleCancelDebugAndPreviewPanel when clicked and isPreparingDataSource is true', () => {
|
||||
mockIsPreparingDataSource.mockReturnValue(true)
|
||||
|
||||
render(<Header />)
|
||||
|
||||
const closeButton = screen.getByRole('button')
|
||||
fireEvent.click(closeButton)
|
||||
|
||||
expect(mockSetIsPreparingDataSource).toHaveBeenCalledWith(false)
|
||||
expect(mockHandleCancelDebugAndPreviewPanel).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should only call handleCancelDebugAndPreviewPanel when isPreparingDataSource is false', () => {
|
||||
mockIsPreparingDataSource.mockReturnValue(false)
|
||||
|
||||
render(<Header />)
|
||||
|
||||
const closeButton = screen.getByRole('button')
|
||||
fireEvent.click(closeButton)
|
||||
|
||||
expect(mockSetIsPreparingDataSource).not.toHaveBeenCalled()
|
||||
expect(mockHandleCancelDebugAndPreviewPanel).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Result Component Tests (Real Implementation)
|
||||
// ============================================================================
|
||||
|
||||
// Unmock Result for these tests
|
||||
vi.doUnmock('./result')
|
||||
|
||||
describe('Result', () => {
|
||||
// Dynamically import Result to get real implementation
|
||||
let Result: typeof import('./result').default
|
||||
|
||||
beforeAll(async () => {
|
||||
const resultModule = await import('./result')
|
||||
Result = resultModule.default
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
mockWorkflowRunningData.mockReturnValue(undefined)
|
||||
})
|
||||
|
||||
// Rendering tests
|
||||
describe('Rendering', () => {
|
||||
it('should render with RESULT tab active by default', async () => {
|
||||
render(<Result />)
|
||||
|
||||
await waitFor(() => {
|
||||
const resultTab = screen.getByRole('button', { name: /runLog\.result/i })
|
||||
expect(resultTab).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
|
||||
})
|
||||
})
|
||||
|
||||
it('should render all three tabs', () => {
|
||||
render(<Result />)
|
||||
|
||||
expect(screen.getByRole('button', { name: /runLog\.result/i })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: /runLog\.detail/i })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: /runLog\.tracing/i })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Tab switching tests
|
||||
describe('Tab Switching', () => {
|
||||
it('should switch to DETAIL tab when clicked', async () => {
|
||||
mockWorkflowRunningData.mockReturnValue(createMockWorkflowRunningData())
|
||||
render(<Result />)
|
||||
|
||||
const detailTab = screen.getByRole('button', { name: /runLog\.detail/i })
|
||||
fireEvent.click(detailTab)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('result-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should switch to TRACING tab when clicked', async () => {
|
||||
mockWorkflowRunningData.mockReturnValue(createMockWorkflowRunningData({ tracing: [{ id: '1' }] as unknown as WorkflowRunningData['tracing'] }))
|
||||
render(<Result />)
|
||||
|
||||
const tracingTab = screen.getByRole('button', { name: /runLog\.tracing/i })
|
||||
fireEvent.click(tracingTab)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('tracing-panel')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// Loading states
|
||||
describe('Loading States', () => {
|
||||
it('should show loading in DETAIL tab when no result data', async () => {
|
||||
mockWorkflowRunningData.mockReturnValue({
|
||||
result: undefined as unknown as WorkflowRunningData['result'],
|
||||
tracing: [],
|
||||
})
|
||||
render(<Result />)
|
||||
|
||||
const detailTab = screen.getByRole('button', { name: /runLog\.detail/i })
|
||||
fireEvent.click(detailTab)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
it('should show loading in TRACING tab when no tracing data', async () => {
|
||||
mockWorkflowRunningData.mockReturnValue(createMockWorkflowRunningData({ tracing: [] }))
|
||||
render(<Result />)
|
||||
|
||||
const tracingTab = screen.getByRole('button', { name: /runLog\.tracing/i })
|
||||
fireEvent.click(tracingTab)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('loading')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// ResultPreview Component Tests
|
||||
// ============================================================================
|
||||
|
||||
// We need to import ResultPreview directly
|
||||
vi.doUnmock('./result/result-preview')
|
||||
|
||||
describe('ResultPreview', () => {
|
||||
let ResultPreview: typeof import('./result/result-preview').default
|
||||
|
||||
beforeAll(async () => {
|
||||
const previewModule = await import('./result/result-preview')
|
||||
ResultPreview = previewModule.default
|
||||
})
|
||||
|
||||
const mockOnSwitchToDetail = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// Loading state
|
||||
describe('Loading State', () => {
|
||||
it('should show loading spinner when isRunning is true and no outputs', () => {
|
||||
render(
|
||||
<ResultPreview
|
||||
isRunning={true}
|
||||
outputs={undefined}
|
||||
error={undefined}
|
||||
onSwitchToDetail={mockOnSwitchToDetail}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('pipeline.result.resultPreview.loading')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not show loading when outputs are available', () => {
|
||||
render(
|
||||
<ResultPreview
|
||||
isRunning={true}
|
||||
outputs={createMockGeneralOutputs()}
|
||||
error={undefined}
|
||||
onSwitchToDetail={mockOnSwitchToDetail}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByText('pipeline.result.resultPreview.loading')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Error state
|
||||
describe('Error State', () => {
|
||||
it('should show error message when not running and has error', () => {
|
||||
render(
|
||||
<ResultPreview
|
||||
isRunning={false}
|
||||
outputs={undefined}
|
||||
error="Test error message"
|
||||
onSwitchToDetail={mockOnSwitchToDetail}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('pipeline.result.resultPreview.error')).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'pipeline.result.resultPreview.viewDetails' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should call onSwitchToDetail when View Details button is clicked', () => {
|
||||
render(
|
||||
<ResultPreview
|
||||
isRunning={false}
|
||||
outputs={undefined}
|
||||
error="Test error message"
|
||||
onSwitchToDetail={mockOnSwitchToDetail}
|
||||
/>,
|
||||
)
|
||||
|
||||
const viewDetailsButton = screen.getByRole('button', { name: 'pipeline.result.resultPreview.viewDetails' })
|
||||
fireEvent.click(viewDetailsButton)
|
||||
|
||||
expect(mockOnSwitchToDetail).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should not show error when still running', () => {
|
||||
render(
|
||||
<ResultPreview
|
||||
isRunning={true}
|
||||
outputs={undefined}
|
||||
error="Test error message"
|
||||
onSwitchToDetail={mockOnSwitchToDetail}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByText('pipeline.result.resultPreview.error')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Success state with outputs
|
||||
describe('Success State with Outputs', () => {
|
||||
it('should render chunk content when outputs are available', () => {
|
||||
render(
|
||||
<ResultPreview
|
||||
isRunning={false}
|
||||
outputs={createMockGeneralOutputs(['test chunk content'])}
|
||||
error={undefined}
|
||||
onSwitchToDetail={mockOnSwitchToDetail}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Check that chunk content is rendered (the real ChunkCardList renders the content)
|
||||
expect(screen.getByText('test chunk content')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render multiple chunks when provided', () => {
|
||||
render(
|
||||
<ResultPreview
|
||||
isRunning={false}
|
||||
outputs={createMockGeneralOutputs(['chunk one', 'chunk two'])}
|
||||
error={undefined}
|
||||
onSwitchToDetail={mockOnSwitchToDetail}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText('chunk one')).toBeInTheDocument()
|
||||
expect(screen.getByText('chunk two')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should show footer tip', () => {
|
||||
render(
|
||||
<ResultPreview
|
||||
isRunning={false}
|
||||
outputs={createMockGeneralOutputs()}
|
||||
error={undefined}
|
||||
onSwitchToDetail={mockOnSwitchToDetail}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByText(/pipeline\.result\.resultPreview\.footerTip/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Edge cases
|
||||
describe('Edge Cases', () => {
|
||||
it('should handle empty outputs gracefully', () => {
|
||||
render(
|
||||
<ResultPreview
|
||||
isRunning={false}
|
||||
outputs={null}
|
||||
error={undefined}
|
||||
onSwitchToDetail={mockOnSwitchToDetail}
|
||||
/>,
|
||||
)
|
||||
|
||||
// Should not crash and should not show chunk card list
|
||||
expect(screen.queryByTestId('chunk-card-list')).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should handle undefined outputs', () => {
|
||||
render(
|
||||
<ResultPreview
|
||||
isRunning={false}
|
||||
outputs={undefined}
|
||||
error={undefined}
|
||||
onSwitchToDetail={mockOnSwitchToDetail}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.queryByTestId('chunk-card-list')).not.toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Tabs Component Tests
|
||||
// ============================================================================
|
||||
|
||||
vi.doUnmock('./result/tabs')
|
||||
|
||||
describe('Tabs', () => {
|
||||
let Tabs: typeof import('./result/tabs').default
|
||||
|
||||
beforeAll(async () => {
|
||||
const tabsModule = await import('./result/tabs')
|
||||
Tabs = tabsModule.default
|
||||
})
|
||||
|
||||
const mockSwitchTab = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// Rendering tests
|
||||
describe('Rendering', () => {
|
||||
it('should render all three tabs', () => {
|
||||
render(
|
||||
<Tabs
|
||||
currentTab="RESULT"
|
||||
workflowRunningData={createMockWorkflowRunningData()}
|
||||
switchTab={mockSwitchTab}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: /runLog\.result/i })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: /runLog\.detail/i })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: /runLog\.tracing/i })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Active tab styling
|
||||
describe('Active Tab Styling', () => {
|
||||
it('should highlight RESULT tab when currentTab is RESULT', () => {
|
||||
render(
|
||||
<Tabs
|
||||
currentTab="RESULT"
|
||||
workflowRunningData={createMockWorkflowRunningData()}
|
||||
switchTab={mockSwitchTab}
|
||||
/>,
|
||||
)
|
||||
|
||||
const resultTab = screen.getByRole('button', { name: /runLog\.result/i })
|
||||
expect(resultTab).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
|
||||
})
|
||||
|
||||
it('should highlight DETAIL tab when currentTab is DETAIL', () => {
|
||||
render(
|
||||
<Tabs
|
||||
currentTab="DETAIL"
|
||||
workflowRunningData={createMockWorkflowRunningData()}
|
||||
switchTab={mockSwitchTab}
|
||||
/>,
|
||||
)
|
||||
|
||||
const detailTab = screen.getByRole('button', { name: /runLog\.detail/i })
|
||||
expect(detailTab).toHaveClass('border-util-colors-blue-brand-blue-brand-600')
|
||||
})
|
||||
})
|
||||
|
||||
// Tab click handling
|
||||
describe('Tab Click Handling', () => {
|
||||
it('should call switchTab with RESULT when RESULT tab is clicked', () => {
|
||||
render(
|
||||
<Tabs
|
||||
currentTab="DETAIL"
|
||||
workflowRunningData={createMockWorkflowRunningData()}
|
||||
switchTab={mockSwitchTab}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /runLog\.result/i }))
|
||||
|
||||
expect(mockSwitchTab).toHaveBeenCalledWith('RESULT')
|
||||
})
|
||||
|
||||
it('should call switchTab with DETAIL when DETAIL tab is clicked', () => {
|
||||
render(
|
||||
<Tabs
|
||||
currentTab="RESULT"
|
||||
workflowRunningData={createMockWorkflowRunningData()}
|
||||
switchTab={mockSwitchTab}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /runLog\.detail/i }))
|
||||
|
||||
expect(mockSwitchTab).toHaveBeenCalledWith('DETAIL')
|
||||
})
|
||||
|
||||
it('should call switchTab with TRACING when TRACING tab is clicked', () => {
|
||||
render(
|
||||
<Tabs
|
||||
currentTab="RESULT"
|
||||
workflowRunningData={createMockWorkflowRunningData()}
|
||||
switchTab={mockSwitchTab}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: /runLog\.tracing/i }))
|
||||
|
||||
expect(mockSwitchTab).toHaveBeenCalledWith('TRACING')
|
||||
})
|
||||
})
|
||||
|
||||
// Disabled state when no data
|
||||
describe('Disabled State', () => {
|
||||
it('should disable tabs when workflowRunningData is undefined', () => {
|
||||
render(
|
||||
<Tabs
|
||||
currentTab="RESULT"
|
||||
workflowRunningData={undefined}
|
||||
switchTab={mockSwitchTab}
|
||||
/>,
|
||||
)
|
||||
|
||||
const resultTab = screen.getByRole('button', { name: /runLog\.result/i })
|
||||
expect(resultTab).toBeDisabled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Tab Component Tests
|
||||
// ============================================================================
|
||||
|
||||
vi.doUnmock('./result/tabs/tab')
|
||||
|
||||
describe('Tab', () => {
|
||||
let Tab: typeof import('./result/tabs/tab').default
|
||||
|
||||
beforeAll(async () => {
|
||||
const tabModule = await import('./result/tabs/tab')
|
||||
Tab = tabModule.default
|
||||
})
|
||||
|
||||
const mockOnClick = vi.fn()
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// Rendering tests
|
||||
describe('Rendering', () => {
|
||||
it('should render tab with label', () => {
|
||||
render(
|
||||
<Tab
|
||||
isActive={false}
|
||||
label="Test Tab"
|
||||
value="TEST"
|
||||
workflowRunningData={createMockWorkflowRunningData()}
|
||||
onClick={mockOnClick}
|
||||
/>,
|
||||
)
|
||||
|
||||
expect(screen.getByRole('button', { name: 'Test Tab' })).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
// Active state styling
|
||||
describe('Active State', () => {
|
||||
it('should have active styles when isActive is true', () => {
|
||||
render(
|
||||
<Tab
|
||||
isActive={true}
|
||||
label="Active Tab"
|
||||
value="TEST"
|
||||
workflowRunningData={createMockWorkflowRunningData()}
|
||||
onClick={mockOnClick}
|
||||
/>,
|
||||
)
|
||||
|
||||
const tab = screen.getByRole('button')
|
||||
expect(tab).toHaveClass('border-util-colors-blue-brand-blue-brand-600', 'text-text-primary')
|
||||
})
|
||||
|
||||
it('should have inactive styles when isActive is false', () => {
|
||||
render(
|
||||
<Tab
|
||||
isActive={false}
|
||||
label="Inactive Tab"
|
||||
value="TEST"
|
||||
workflowRunningData={createMockWorkflowRunningData()}
|
||||
onClick={mockOnClick}
|
||||
/>,
|
||||
)
|
||||
|
||||
const tab = screen.getByRole('button')
|
||||
expect(tab).toHaveClass('border-transparent', 'text-text-tertiary')
|
||||
})
|
||||
})
|
||||
|
||||
// Click handling
|
||||
describe('Click Handling', () => {
|
||||
it('should call onClick with value when clicked', () => {
|
||||
render(
|
||||
<Tab
|
||||
isActive={false}
|
||||
label="Test Tab"
|
||||
value="MY_VALUE"
|
||||
workflowRunningData={createMockWorkflowRunningData()}
|
||||
onClick={mockOnClick}
|
||||
/>,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
expect(mockOnClick).toHaveBeenCalledWith('MY_VALUE')
|
||||
})
|
||||
|
||||
it('should not call onClick when disabled (no workflowRunningData)', () => {
|
||||
render(
|
||||
<Tab
|
||||
isActive={false}
|
||||
label="Test Tab"
|
||||
value="MY_VALUE"
|
||||
workflowRunningData={undefined}
|
||||
onClick={mockOnClick}
|
||||
/>,
|
||||
)
|
||||
|
||||
const tab = screen.getByRole('button')
|
||||
fireEvent.click(tab)
|
||||
|
||||
// The click handler is still called, but button is disabled
|
||||
expect(tab).toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
// Disabled state
|
||||
describe('Disabled State', () => {
|
||||
it('should be disabled when workflowRunningData is undefined', () => {
|
||||
render(
|
||||
<Tab
|
||||
isActive={false}
|
||||
label="Test Tab"
|
||||
value="TEST"
|
||||
workflowRunningData={undefined}
|
||||
onClick={mockOnClick}
|
||||
/>,
|
||||
)
|
||||
|
||||
const tab = screen.getByRole('button')
|
||||
expect(tab).toBeDisabled()
|
||||
expect(tab).toHaveClass('opacity-30')
|
||||
})
|
||||
|
||||
it('should not be disabled when workflowRunningData is provided', () => {
|
||||
render(
|
||||
<Tab
|
||||
isActive={false}
|
||||
label="Test Tab"
|
||||
value="TEST"
|
||||
workflowRunningData={createMockWorkflowRunningData()}
|
||||
onClick={mockOnClick}
|
||||
/>,
|
||||
)
|
||||
|
||||
const tab = screen.getByRole('button')
|
||||
expect(tab).not.toBeDisabled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// formatPreviewChunks Utility Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('formatPreviewChunks', () => {
|
||||
let formatPreviewChunks: typeof import('./result/result-preview/utils').formatPreviewChunks
|
||||
|
||||
beforeAll(async () => {
|
||||
const utilsModule = await import('./result/result-preview/utils')
|
||||
formatPreviewChunks = utilsModule.formatPreviewChunks
|
||||
})
|
||||
|
||||
// Edge cases
|
||||
describe('Edge Cases', () => {
|
||||
it('should return undefined for null outputs', () => {
|
||||
expect(formatPreviewChunks(null)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for undefined outputs', () => {
|
||||
expect(formatPreviewChunks(undefined)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('should return undefined for unknown chunk structure', () => {
|
||||
const outputs = {
|
||||
chunk_structure: 'unknown_mode',
|
||||
preview: [],
|
||||
}
|
||||
expect(formatPreviewChunks(outputs)).toBeUndefined()
|
||||
})
|
||||
})
|
||||
|
||||
// General (text) chunks
|
||||
describe('General Chunks (ChunkingMode.text)', () => {
|
||||
it('should format general chunks correctly', () => {
|
||||
const outputs = createMockGeneralOutputs(['content1', 'content2', 'content3'])
|
||||
const result = formatPreviewChunks(outputs)
|
||||
|
||||
expect(result).toEqual(['content1', 'content2', 'content3'])
|
||||
})
|
||||
|
||||
it('should limit to RAG_PIPELINE_PREVIEW_CHUNK_NUM chunks', () => {
|
||||
const manyChunks = Array.from({ length: 10 }, (_, i) => `chunk${i}`)
|
||||
const outputs = createMockGeneralOutputs(manyChunks)
|
||||
const result = formatPreviewChunks(outputs) as string[]
|
||||
|
||||
// RAG_PIPELINE_PREVIEW_CHUNK_NUM is mocked to 5
|
||||
expect(result).toHaveLength(5)
|
||||
expect(result).toEqual(['chunk0', 'chunk1', 'chunk2', 'chunk3', 'chunk4'])
|
||||
})
|
||||
|
||||
it('should handle empty preview array', () => {
|
||||
const outputs = createMockGeneralOutputs([])
|
||||
const result = formatPreviewChunks(outputs)
|
||||
|
||||
expect(result).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
// Parent-child chunks
|
||||
describe('Parent-Child Chunks (ChunkingMode.parentChild)', () => {
|
||||
it('should format paragraph mode parent-child chunks correctly', () => {
|
||||
const outputs = createMockParentChildOutputs('paragraph')
|
||||
const result = formatPreviewChunks(outputs)
|
||||
|
||||
expect(result).toEqual({
|
||||
parent_child_chunks: [
|
||||
{ parent_content: 'parent1', child_contents: ['child1', 'child2'], parent_mode: 'paragraph' },
|
||||
{ parent_content: 'parent2', child_contents: ['child3', 'child4'], parent_mode: 'paragraph' },
|
||||
],
|
||||
parent_mode: 'paragraph',
|
||||
})
|
||||
})
|
||||
|
||||
it('should format full-doc mode parent-child chunks and limit child chunks', () => {
|
||||
const outputs = {
|
||||
chunk_structure: ChunkingMode.parentChild,
|
||||
parent_mode: 'full-doc' as const,
|
||||
preview: [
|
||||
{
|
||||
content: 'full-doc-parent',
|
||||
child_chunks: Array.from({ length: 10 }, (_, i) => `child${i}`),
|
||||
},
|
||||
],
|
||||
}
|
||||
const result = formatPreviewChunks(outputs)
|
||||
|
||||
expect(result).toEqual({
|
||||
parent_child_chunks: [
|
||||
{
|
||||
parent_content: 'full-doc-parent',
|
||||
child_contents: ['child0', 'child1', 'child2', 'child3', 'child4'], // Limited to 5
|
||||
parent_mode: 'full-doc',
|
||||
},
|
||||
],
|
||||
parent_mode: 'full-doc',
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// QA chunks
|
||||
describe('QA Chunks (ChunkingMode.qa)', () => {
|
||||
it('should format QA chunks correctly', () => {
|
||||
const outputs = createMockQAOutputs()
|
||||
const result = formatPreviewChunks(outputs)
|
||||
|
||||
expect(result).toEqual({
|
||||
qa_chunks: [
|
||||
{ question: 'Q1', answer: 'A1' },
|
||||
{ question: 'Q2', answer: 'A2' },
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
it('should limit QA chunks to RAG_PIPELINE_PREVIEW_CHUNK_NUM', () => {
|
||||
const outputs = {
|
||||
chunk_structure: ChunkingMode.qa,
|
||||
qa_preview: Array.from({ length: 10 }, (_, i) => ({
|
||||
question: `Q${i}`,
|
||||
answer: `A${i}`,
|
||||
})),
|
||||
}
|
||||
const result = formatPreviewChunks(outputs) as { qa_chunks: Array<{ question: string, answer: string }> }
|
||||
|
||||
expect(result.qa_chunks).toHaveLength(5)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// Types Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Types', () => {
|
||||
describe('TestRunStep Enum', () => {
|
||||
it('should have correct enum values', async () => {
|
||||
const { TestRunStep } = await import('./types')
|
||||
|
||||
expect(TestRunStep.dataSource).toBe('dataSource')
|
||||
expect(TestRunStep.documentProcessing).toBe('documentProcessing')
|
||||
})
|
||||
})
|
||||
})
|
||||
+549
@@ -0,0 +1,549 @@
|
||||
import { fireEvent, render, screen } from '@testing-library/react'
|
||||
import Actions from './index'
|
||||
|
||||
// ============================================================================
|
||||
// Actions Component Tests
|
||||
// ============================================================================
|
||||
|
||||
describe('Actions', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Rendering Tests
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Rendering', () => {
|
||||
it('should render without crashing', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render button with translated text', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert - Translation mock returns key with namespace prefix
|
||||
expect(screen.getByText('datasetCreation.stepOne.button')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should render with correct container structure', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
const { container } = render(<Actions handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert
|
||||
const wrapper = container.firstChild as HTMLElement
|
||||
expect(wrapper.className).toContain('flex')
|
||||
expect(wrapper.className).toContain('justify-end')
|
||||
expect(wrapper.className).toContain('p-4')
|
||||
expect(wrapper.className).toContain('pt-2')
|
||||
})
|
||||
|
||||
it('should render span with px-0.5 class around text', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
const { container } = render(<Actions handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert
|
||||
const span = container.querySelector('span')
|
||||
expect(span).toBeInTheDocument()
|
||||
expect(span?.className).toContain('px-0.5')
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Props Variations Tests
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Props Variations', () => {
|
||||
it('should pass disabled=true to button when disabled prop is true', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions disabled={true} handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
})
|
||||
|
||||
it('should pass disabled=false to button when disabled prop is false', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions disabled={false} handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('button')).not.toBeDisabled()
|
||||
})
|
||||
|
||||
it('should not disable button when disabled prop is undefined', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('button')).not.toBeDisabled()
|
||||
})
|
||||
|
||||
it('should handle disabled switching from true to false', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
const { rerender } = render(
|
||||
<Actions disabled={true} handleNextStep={handleNextStep} />,
|
||||
)
|
||||
|
||||
// Assert - Initially disabled
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
|
||||
// Act - Rerender with disabled=false
|
||||
rerender(<Actions disabled={false} handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert - Now enabled
|
||||
expect(screen.getByRole('button')).not.toBeDisabled()
|
||||
})
|
||||
|
||||
it('should handle disabled switching from false to true', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
const { rerender } = render(
|
||||
<Actions disabled={false} handleNextStep={handleNextStep} />,
|
||||
)
|
||||
|
||||
// Assert - Initially enabled
|
||||
expect(screen.getByRole('button')).not.toBeDisabled()
|
||||
|
||||
// Act - Rerender with disabled=true
|
||||
rerender(<Actions disabled={true} handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert - Now disabled
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
})
|
||||
|
||||
it('should handle undefined disabled becoming true', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
const { rerender } = render(
|
||||
<Actions handleNextStep={handleNextStep} />,
|
||||
)
|
||||
|
||||
// Assert - Initially not disabled (undefined)
|
||||
expect(screen.getByRole('button')).not.toBeDisabled()
|
||||
|
||||
// Act - Rerender with disabled=true
|
||||
rerender(<Actions disabled={true} handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert - Now disabled
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// User Interaction Tests
|
||||
// -------------------------------------------------------------------------
|
||||
describe('User Interactions', () => {
|
||||
it('should call handleNextStep when button is clicked', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions handleNextStep={handleNextStep} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
// Assert
|
||||
expect(handleNextStep).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should call handleNextStep exactly once per click', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions handleNextStep={handleNextStep} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
// Assert
|
||||
expect(handleNextStep).toHaveBeenCalled()
|
||||
expect(handleNextStep.mock.calls).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('should call handleNextStep multiple times on multiple clicks', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions handleNextStep={handleNextStep} />)
|
||||
const button = screen.getByRole('button')
|
||||
fireEvent.click(button)
|
||||
fireEvent.click(button)
|
||||
fireEvent.click(button)
|
||||
|
||||
// Assert
|
||||
expect(handleNextStep).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('should not call handleNextStep when button is disabled and clicked', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions disabled={true} handleNextStep={handleNextStep} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
// Assert - Disabled button should not trigger onClick
|
||||
expect(handleNextStep).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should handle rapid clicks when not disabled', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions handleNextStep={handleNextStep} />)
|
||||
const button = screen.getByRole('button')
|
||||
|
||||
// Simulate rapid clicks
|
||||
for (let i = 0; i < 10; i++)
|
||||
fireEvent.click(button)
|
||||
|
||||
// Assert
|
||||
expect(handleNextStep).toHaveBeenCalledTimes(10)
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Callback Stability Tests
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Callback Stability', () => {
|
||||
it('should use the new handleNextStep when prop changes', () => {
|
||||
// Arrange
|
||||
const handleNextStep1 = vi.fn()
|
||||
const handleNextStep2 = vi.fn()
|
||||
|
||||
// Act
|
||||
const { rerender } = render(
|
||||
<Actions handleNextStep={handleNextStep1} />,
|
||||
)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
rerender(<Actions handleNextStep={handleNextStep2} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
// Assert
|
||||
expect(handleNextStep1).toHaveBeenCalledTimes(1)
|
||||
expect(handleNextStep2).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should maintain functionality after rerender with same props', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
const { rerender } = render(
|
||||
<Actions handleNextStep={handleNextStep} />,
|
||||
)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
rerender(<Actions handleNextStep={handleNextStep} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
// Assert
|
||||
expect(handleNextStep).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('should work correctly when handleNextStep changes multiple times', () => {
|
||||
// Arrange
|
||||
const handleNextStep1 = vi.fn()
|
||||
const handleNextStep2 = vi.fn()
|
||||
const handleNextStep3 = vi.fn()
|
||||
|
||||
// Act
|
||||
const { rerender } = render(
|
||||
<Actions handleNextStep={handleNextStep1} />,
|
||||
)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
rerender(<Actions handleNextStep={handleNextStep2} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
rerender(<Actions handleNextStep={handleNextStep3} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
// Assert
|
||||
expect(handleNextStep1).toHaveBeenCalledTimes(1)
|
||||
expect(handleNextStep2).toHaveBeenCalledTimes(1)
|
||||
expect(handleNextStep3).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Memoization Tests
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Memoization', () => {
|
||||
it('should be wrapped with React.memo', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act - Verify component is memoized by checking display name pattern
|
||||
const { rerender } = render(
|
||||
<Actions handleNextStep={handleNextStep} />,
|
||||
)
|
||||
|
||||
// Rerender with same props should work without issues
|
||||
rerender(<Actions handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert - Component should render correctly after rerender
|
||||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('should not break when props remain the same across rerenders', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
const { rerender } = render(
|
||||
<Actions disabled={false} handleNextStep={handleNextStep} />,
|
||||
)
|
||||
|
||||
// Multiple rerenders with same props
|
||||
for (let i = 0; i < 5; i++) {
|
||||
rerender(<Actions disabled={false} handleNextStep={handleNextStep} />)
|
||||
}
|
||||
|
||||
// Assert - Should still function correctly
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
expect(handleNextStep).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should update correctly when only disabled prop changes', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
const { rerender } = render(
|
||||
<Actions disabled={false} handleNextStep={handleNextStep} />,
|
||||
)
|
||||
|
||||
// Assert - Initially not disabled
|
||||
expect(screen.getByRole('button')).not.toBeDisabled()
|
||||
|
||||
// Act - Change only disabled prop
|
||||
rerender(<Actions disabled={true} handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert - Should reflect the new disabled state
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
})
|
||||
|
||||
it('should update correctly when only handleNextStep prop changes', () => {
|
||||
// Arrange
|
||||
const handleNextStep1 = vi.fn()
|
||||
const handleNextStep2 = vi.fn()
|
||||
|
||||
// Act
|
||||
const { rerender } = render(
|
||||
<Actions disabled={false} handleNextStep={handleNextStep1} />,
|
||||
)
|
||||
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
expect(handleNextStep1).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Act - Change only handleNextStep prop
|
||||
rerender(<Actions disabled={false} handleNextStep={handleNextStep2} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
// Assert - New callback should be used
|
||||
expect(handleNextStep1).toHaveBeenCalledTimes(1)
|
||||
expect(handleNextStep2).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Edge Cases Tests
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Edge Cases', () => {
|
||||
it('should call handleNextStep even if it has side effects', () => {
|
||||
// Arrange
|
||||
let sideEffectValue = 0
|
||||
const handleNextStep = vi.fn(() => {
|
||||
sideEffectValue = 42
|
||||
})
|
||||
|
||||
// Act
|
||||
render(<Actions handleNextStep={handleNextStep} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
// Assert
|
||||
expect(handleNextStep).toHaveBeenCalledTimes(1)
|
||||
expect(sideEffectValue).toBe(42)
|
||||
})
|
||||
|
||||
it('should handle handleNextStep that returns a value', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn(() => 'return value')
|
||||
|
||||
// Act
|
||||
render(<Actions handleNextStep={handleNextStep} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
// Assert
|
||||
expect(handleNextStep).toHaveBeenCalledTimes(1)
|
||||
expect(handleNextStep).toHaveReturnedWith('return value')
|
||||
})
|
||||
|
||||
it('should handle handleNextStep that is async', async () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn().mockResolvedValue(undefined)
|
||||
|
||||
// Act
|
||||
render(<Actions handleNextStep={handleNextStep} />)
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
|
||||
// Assert
|
||||
expect(handleNextStep).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should render correctly with both disabled=true and handleNextStep', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions disabled={true} handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert
|
||||
const button = screen.getByRole('button')
|
||||
expect(button).toBeDisabled()
|
||||
})
|
||||
|
||||
it('should handle component unmount gracefully', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
const { unmount } = render(<Actions handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert - Unmount should not throw
|
||||
expect(() => unmount()).not.toThrow()
|
||||
})
|
||||
|
||||
it('should handle disabled as boolean-like falsy value', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act - Test with explicit false
|
||||
render(<Actions disabled={false} handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('button')).not.toBeDisabled()
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Accessibility Tests
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Accessibility', () => {
|
||||
it('should have button element that can receive focus', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions handleNextStep={handleNextStep} />)
|
||||
const button = screen.getByRole('button')
|
||||
|
||||
// Assert - Button should be focusable (not disabled by default)
|
||||
expect(button).not.toBeDisabled()
|
||||
})
|
||||
|
||||
it('should indicate disabled state correctly', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
render(<Actions disabled={true} handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert
|
||||
expect(screen.getByRole('button')).toHaveAttribute('disabled')
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Integration Tests
|
||||
// -------------------------------------------------------------------------
|
||||
describe('Integration', () => {
|
||||
it('should work in a typical workflow: enable -> click -> disable', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act - Start enabled
|
||||
const { rerender } = render(
|
||||
<Actions disabled={false} handleNextStep={handleNextStep} />,
|
||||
)
|
||||
|
||||
// Assert - Can click when enabled
|
||||
expect(screen.getByRole('button')).not.toBeDisabled()
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
expect(handleNextStep).toHaveBeenCalledTimes(1)
|
||||
|
||||
// Act - Disable after click (simulating loading state)
|
||||
rerender(<Actions disabled={true} handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert - Cannot click when disabled
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
expect(handleNextStep).toHaveBeenCalledTimes(1) // Still 1, not 2
|
||||
|
||||
// Act - Re-enable
|
||||
rerender(<Actions disabled={false} handleNextStep={handleNextStep} />)
|
||||
|
||||
// Assert - Can click again
|
||||
expect(screen.getByRole('button')).not.toBeDisabled()
|
||||
fireEvent.click(screen.getByRole('button'))
|
||||
expect(handleNextStep).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('should maintain consistent rendering across multiple state changes', () => {
|
||||
// Arrange
|
||||
const handleNextStep = vi.fn()
|
||||
|
||||
// Act
|
||||
const { rerender } = render(
|
||||
<Actions disabled={false} handleNextStep={handleNextStep} />,
|
||||
)
|
||||
|
||||
// Toggle disabled state multiple times
|
||||
const states = [true, false, true, false, true]
|
||||
states.forEach((disabled) => {
|
||||
rerender(<Actions disabled={disabled} handleNextStep={handleNextStep} />)
|
||||
if (disabled)
|
||||
expect(screen.getByRole('button')).toBeDisabled()
|
||||
else
|
||||
expect(screen.getByRole('button')).not.toBeDisabled()
|
||||
})
|
||||
|
||||
// Assert - Button should still render correctly
|
||||
expect(screen.getByRole('button')).toBeInTheDocument()
|
||||
expect(screen.getByText('datasetCreation.stepOne.button')).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
})
|
||||
+1829
File diff suppressed because it is too large
Load Diff
+1712
File diff suppressed because it is too large
Load Diff
+2221
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1175
File diff suppressed because it is too large
Load Diff
+1352
File diff suppressed because it is too large
Load Diff
@@ -85,7 +85,11 @@ const PublishAsKnowledgePipelineModal = ({
|
||||
>
|
||||
<div className="title-2xl-semi-bold relative flex items-center p-6 pb-3 pr-14 text-text-primary">
|
||||
{t('common.publishAs', { ns: 'pipeline' })}
|
||||
<div className="absolute right-5 top-5 flex h-8 w-8 cursor-pointer items-center justify-center" onClick={onCancel}>
|
||||
<div
|
||||
data-testid="publish-modal-close-btn"
|
||||
className="absolute right-5 top-5 flex h-8 w-8 cursor-pointer items-center justify-center"
|
||||
onClick={onCancel}
|
||||
>
|
||||
<RiCloseLine className="h-4 w-4 text-text-tertiary" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1348
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user