import { fireEvent, render, screen, waitFor } from '@testing-library/react' import React from 'react' import ProcessDocuments from './index' import type { BaseConfiguration } from '@/app/components/base/form/form-scenarios/base/types' import { BaseFieldType } from '@/app/components/base/form/form-scenarios/base/types' // ========================================== // Mock External Dependencies // ========================================== // Mock useInputVariables hook let mockIsFetchingParams = false let mockParamsConfig: { variables: unknown[] } | undefined = { variables: [] } jest.mock('./hooks', () => ({ useInputVariables: jest.fn(() => ({ isFetchingParams: mockIsFetchingParams, paramsConfig: mockParamsConfig, })), })) // Mock useConfigurations hook let mockConfigurations: BaseConfiguration[] = [] // Mock useInitialData hook let mockInitialData: Record = {} jest.mock('@/app/components/rag-pipeline/hooks/use-input-fields', () => ({ useInitialData: jest.fn(() => mockInitialData), useConfigurations: jest.fn(() => mockConfigurations), })) // ========================================== // Test Data Factory Functions // ========================================== /** * Creates mock configuration for testing */ const createMockConfiguration = (overrides: Partial = {}): BaseConfiguration => ({ type: BaseFieldType.textInput, variable: 'testVariable', label: 'Test Label', required: false, maxLength: undefined, options: undefined, showConditions: [], placeholder: '', tooltip: '', ...overrides, }) /** * Creates default test props */ const createDefaultProps = (overrides: Partial> = {}) => ({ dataSourceNodeId: 'test-node-id', ref: { current: null } as React.RefObject, isRunning: false, onProcess: jest.fn(), onPreview: jest.fn(), onSubmit: jest.fn(), onBack: jest.fn(), ...overrides, }) // ========================================== // Test Suite // ========================================== describe('ProcessDocuments', () => { beforeEach(() => { jest.clearAllMocks() // Reset mock values mockIsFetchingParams = false mockParamsConfig = { variables: [] } mockInitialData = {} mockConfigurations = [] }) // ========================================== // Rendering Tests // ========================================== describe('Rendering', () => { // Tests basic rendering functionality it('should render without crashing', () => { // Arrange const props = createDefaultProps() // Act render() // Assert - check for Header title from Form component expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() }) it('should render Form and Actions components', () => { // Arrange const props = createDefaultProps() // Act render() // Assert - check for elements from both components expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() expect(screen.getByText('datasetPipeline.operations.dataSource')).toBeInTheDocument() expect(screen.getByText('datasetPipeline.operations.saveAndProcess')).toBeInTheDocument() }) it('should render with correct container structure', () => { // Arrange const props = createDefaultProps() // Act const { container } = render() // Assert const mainContainer = container.querySelector('.flex.flex-col.gap-y-4.pt-4') expect(mainContainer).toBeInTheDocument() }) }) // ========================================== // Props Testing // ========================================== describe('Props', () => { describe('dataSourceNodeId prop', () => { it('should pass dataSourceNodeId to useInputVariables hook', () => { // Arrange const { useInputVariables } = require('./hooks') const props = createDefaultProps({ dataSourceNodeId: 'custom-node-id' }) // Act render() // Assert expect(useInputVariables).toHaveBeenCalledWith('custom-node-id') }) it('should handle empty dataSourceNodeId', () => { // Arrange const props = createDefaultProps({ dataSourceNodeId: '' }) // Act render() // Assert expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() }) }) describe('isRunning prop', () => { it('should disable preview button when isRunning is true', () => { // Arrange const props = createDefaultProps({ isRunning: true }) // Act render() // Assert const previewButton = screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i }) expect(previewButton).toBeDisabled() }) it('should not disable preview button when isRunning is false', () => { // Arrange const props = createDefaultProps({ isRunning: false }) // Act render() // Assert const previewButton = screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i }) expect(previewButton).not.toBeDisabled() }) it('should disable process button in Actions when isRunning is true', () => { // Arrange mockIsFetchingParams = false const props = createDefaultProps({ isRunning: true }) // Act render() // Assert const processButton = screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i }) expect(processButton).toBeDisabled() }) }) describe('ref prop', () => { it('should expose submit method via ref', () => { // Arrange const mockRef = { current: null } as React.MutableRefObject<{ submit: () => void } | null> const props = createDefaultProps({ ref: mockRef }) // Act render() // Assert expect(mockRef.current).not.toBeNull() expect(typeof mockRef.current?.submit).toBe('function') }) }) }) // ========================================== // User Interactions Testing // ========================================== describe('User Interactions', () => { it('should call onProcess when Actions process button is clicked', () => { // Arrange const onProcess = jest.fn() const props = createDefaultProps({ onProcess }) render() // Act fireEvent.click(screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i })) // Assert expect(onProcess).toHaveBeenCalledTimes(1) }) it('should call onBack when Actions back button is clicked', () => { // Arrange const onBack = jest.fn() const props = createDefaultProps({ onBack }) render() // Act fireEvent.click(screen.getByRole('button', { name: /datasetPipeline.operations.dataSource/i })) // Assert expect(onBack).toHaveBeenCalledTimes(1) }) it('should call onPreview when preview button is clicked', () => { // Arrange const onPreview = jest.fn() const props = createDefaultProps({ onPreview }) render() // Act fireEvent.click(screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i })) // Assert expect(onPreview).toHaveBeenCalledTimes(1) }) it('should call onSubmit when form is submitted', async () => { // Arrange const onSubmit = jest.fn() const props = createDefaultProps({ onSubmit }) const { container } = render() // Act const form = container.querySelector('form')! fireEvent.submit(form) // Assert await waitFor(() => { expect(onSubmit).toHaveBeenCalled() }) }) }) // ========================================== // Hook Integration Tests // ========================================== describe('Hook Integration', () => { it('should pass variables from useInputVariables to useInitialData', () => { // Arrange const mockVariables = [{ variable: 'testVar', type: 'text', label: 'Test' }] mockParamsConfig = { variables: mockVariables } const { useInitialData } = require('@/app/components/rag-pipeline/hooks/use-input-fields') const props = createDefaultProps() // Act render() // Assert expect(useInitialData).toHaveBeenCalledWith(mockVariables) }) it('should pass variables from useInputVariables to useConfigurations', () => { // Arrange const mockVariables = [{ variable: 'testVar', type: 'text', label: 'Test' }] mockParamsConfig = { variables: mockVariables } const { useConfigurations } = require('@/app/components/rag-pipeline/hooks/use-input-fields') const props = createDefaultProps() // Act render() // Assert expect(useConfigurations).toHaveBeenCalledWith(mockVariables) }) it('should use empty array when paramsConfig.variables is undefined', () => { // Arrange mockParamsConfig = { variables: undefined as unknown as unknown[] } const { useInitialData, useConfigurations } = require('@/app/components/rag-pipeline/hooks/use-input-fields') const props = createDefaultProps() // Act render() // Assert expect(useInitialData).toHaveBeenCalledWith([]) expect(useConfigurations).toHaveBeenCalledWith([]) }) it('should use empty array when paramsConfig is undefined', () => { // Arrange mockParamsConfig = undefined const { useInitialData, useConfigurations } = require('@/app/components/rag-pipeline/hooks/use-input-fields') const props = createDefaultProps() // Act render() // Assert expect(useInitialData).toHaveBeenCalledWith([]) expect(useConfigurations).toHaveBeenCalledWith([]) }) }) // ========================================== // Actions runDisabled Testing // ========================================== describe('Actions runDisabled', () => { it('should disable process button when isFetchingParams is true', () => { // Arrange mockIsFetchingParams = true const props = createDefaultProps({ isRunning: false }) // Act render() // Assert const processButton = screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i }) expect(processButton).toBeDisabled() }) it('should disable process button when isRunning is true', () => { // Arrange mockIsFetchingParams = false const props = createDefaultProps({ isRunning: true }) // Act render() // Assert const processButton = screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i }) expect(processButton).toBeDisabled() }) it('should enable process button when both isFetchingParams and isRunning are false', () => { // Arrange mockIsFetchingParams = false const props = createDefaultProps({ isRunning: false }) // Act render() // Assert const processButton = screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i }) expect(processButton).not.toBeDisabled() }) it('should disable process button when both isFetchingParams and isRunning are true', () => { // Arrange mockIsFetchingParams = true const props = createDefaultProps({ isRunning: true }) // Act render() // Assert const processButton = screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i }) expect(processButton).toBeDisabled() }) }) // ========================================== // Component Memoization Testing // ========================================== describe('Component Memoization', () => { it('should be wrapped with React.memo', () => { // Assert - verify component has memo wrapper expect(ProcessDocuments.$$typeof).toBe(Symbol.for('react.memo')) }) it('should render correctly after rerender with same props', () => { // Arrange const props = createDefaultProps() // Act const { rerender } = render() rerender() // Assert expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() }) it('should update when dataSourceNodeId prop changes', () => { // Arrange const { useInputVariables } = require('./hooks') const props = createDefaultProps({ dataSourceNodeId: 'node-1' }) // Act const { rerender } = render() expect(useInputVariables).toHaveBeenLastCalledWith('node-1') rerender() // Assert expect(useInputVariables).toHaveBeenLastCalledWith('node-2') }) }) // ========================================== // Edge Cases Testing // ========================================== describe('Edge Cases', () => { it('should handle undefined paramsConfig gracefully', () => { // Arrange mockParamsConfig = undefined const props = createDefaultProps() // Act render() // Assert expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() }) it('should handle empty variables array', () => { // Arrange mockParamsConfig = { variables: [] } mockConfigurations = [] const props = createDefaultProps() // Act render() // Assert expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() }) it('should handle special characters in dataSourceNodeId', () => { // Arrange const { useInputVariables } = require('./hooks') const props = createDefaultProps({ dataSourceNodeId: 'node-id-with-special_chars:123' }) // Act render() // Assert expect(useInputVariables).toHaveBeenCalledWith('node-id-with-special_chars:123') }) it('should handle long dataSourceNodeId', () => { // Arrange const { useInputVariables } = require('./hooks') const longId = 'a'.repeat(1000) const props = createDefaultProps({ dataSourceNodeId: longId }) // Act render() // Assert expect(useInputVariables).toHaveBeenCalledWith(longId) }) it('should handle multiple callbacks without interference', () => { // Arrange const onProcess = jest.fn() const onBack = jest.fn() const onPreview = jest.fn() const props = createDefaultProps({ onProcess, onBack, onPreview }) render() // Act fireEvent.click(screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i })) fireEvent.click(screen.getByRole('button', { name: /datasetPipeline.operations.dataSource/i })) fireEvent.click(screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i })) // Assert expect(onProcess).toHaveBeenCalledTimes(1) expect(onBack).toHaveBeenCalledTimes(1) expect(onPreview).toHaveBeenCalledTimes(1) }) }) // ========================================== // runDisabled Logic Testing (with test.each) // ========================================== describe('runDisabled Logic', () => { const runDisabledTestCases = [ { isFetchingParams: false, isRunning: false, expectedDisabled: false }, { isFetchingParams: false, isRunning: true, expectedDisabled: true }, { isFetchingParams: true, isRunning: false, expectedDisabled: true }, { isFetchingParams: true, isRunning: true, expectedDisabled: true }, ] it.each(runDisabledTestCases)( 'should set process button disabled=$expectedDisabled when isFetchingParams=$isFetchingParams and isRunning=$isRunning', ({ isFetchingParams, isRunning, expectedDisabled }) => { // Arrange mockIsFetchingParams = isFetchingParams const props = createDefaultProps({ isRunning }) // Act render() // Assert const processButton = screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i }) if (expectedDisabled) expect(processButton).toBeDisabled() else expect(processButton).not.toBeDisabled() }, ) }) // ========================================== // Configuration Rendering Tests // ========================================== describe('Configuration Rendering', () => { it('should render configurations as form fields', () => { // Arrange mockConfigurations = [ createMockConfiguration({ variable: 'var1', label: 'Variable 1' }), createMockConfiguration({ variable: 'var2', label: 'Variable 2' }), ] mockInitialData = { var1: '', var2: '' } const props = createDefaultProps() // Act render() // Assert expect(screen.getByText('Variable 1')).toBeInTheDocument() expect(screen.getByText('Variable 2')).toBeInTheDocument() }) it('should handle configurations with different field types', () => { // Arrange mockConfigurations = [ createMockConfiguration({ type: BaseFieldType.textInput, variable: 'textVar', label: 'Text Field' }), createMockConfiguration({ type: BaseFieldType.numberInput, variable: 'numberVar', label: 'Number Field' }), ] mockInitialData = { textVar: '', numberVar: 0 } const props = createDefaultProps() // Act render() // Assert expect(screen.getByText('Text Field')).toBeInTheDocument() expect(screen.getByText('Number Field')).toBeInTheDocument() }) }) // ========================================== // Full Integration Props Testing // ========================================== describe('Full Prop Integration', () => { it('should render correctly with all props provided', () => { // Arrange const mockRef = { current: null } as React.MutableRefObject<{ submit: () => void } | null> mockIsFetchingParams = false mockParamsConfig = { variables: [{ variable: 'testVar', type: 'text', label: 'Test' }] } mockInitialData = { testVar: 'initial value' } mockConfigurations = [createMockConfiguration({ variable: 'testVar', label: 'Test Variable' })] const props = { dataSourceNodeId: 'full-test-node', ref: mockRef, isRunning: false, onProcess: jest.fn(), onPreview: jest.fn(), onSubmit: jest.fn(), onBack: jest.fn(), } // Act render() // Assert expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() expect(screen.getByText('datasetPipeline.operations.dataSource')).toBeInTheDocument() expect(screen.getByText('datasetPipeline.operations.saveAndProcess')).toBeInTheDocument() expect(screen.getByText('Test Variable')).toBeInTheDocument() expect(mockRef.current).not.toBeNull() }) }) })