import { fireEvent, render, screen, waitFor } from '@testing-library/react' import React from 'react' import Actions from './actions' import Header from './header' import Form from './form' import type { BaseConfiguration } from '@/app/components/base/form/form-scenarios/base/types' import { BaseFieldType } from '@/app/components/base/form/form-scenarios/base/types' import { z } from 'zod' import Toast from '@/app/components/base/toast' // ========================================== // Spy on Toast.notify for validation tests // ========================================== const toastNotifySpy = jest.spyOn(Toast, 'notify') // ========================================== // 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: 'Enter value', tooltip: '', ...overrides, }) /** * Creates a valid Zod schema for testing */ const createMockSchema = () => { return z.object({ field1: z.string().optional(), }) } /** * Creates a schema that always fails validation */ const createFailingSchema = () => { return { safeParse: () => ({ success: false, error: { issues: [{ path: ['field1'], message: 'is required' }], }, }), } as unknown as z.ZodSchema } // ========================================== // Actions Component Tests // ========================================== describe('Actions', () => { const defaultActionsProps = { onBack: jest.fn(), onProcess: jest.fn(), } beforeEach(() => { jest.clearAllMocks() }) // ========================================== // Rendering Tests // ========================================== describe('Rendering', () => { it('should render without crashing', () => { // Arrange & Act render() // Assert expect(screen.getByText('datasetPipeline.operations.dataSource')).toBeInTheDocument() expect(screen.getByText('datasetPipeline.operations.saveAndProcess')).toBeInTheDocument() }) it('should render back button with arrow icon', () => { // Arrange & Act render() // Assert const backButton = screen.getByRole('button', { name: /datasetPipeline.operations.dataSource/i }) expect(backButton).toBeInTheDocument() expect(backButton.querySelector('svg')).toBeInTheDocument() }) it('should render process button', () => { // Arrange & Act render() // Assert const processButton = screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i }) expect(processButton).toBeInTheDocument() }) it('should have correct container layout', () => { // Arrange & Act const { container } = render() // Assert const mainContainer = container.querySelector('.flex.items-center.justify-between') expect(mainContainer).toBeInTheDocument() }) }) // ========================================== // Props Testing // ========================================== describe('Props', () => { describe('runDisabled prop', () => { it('should not disable process button when runDisabled is false', () => { // Arrange & Act render() // Assert const processButton = screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i }) expect(processButton).not.toBeDisabled() }) it('should disable process button when runDisabled is true', () => { // Arrange & Act render() // Assert const processButton = screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i }) expect(processButton).toBeDisabled() }) it('should not disable process button when runDisabled is undefined', () => { // Arrange & Act render() // Assert const processButton = screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i }) expect(processButton).not.toBeDisabled() }) }) }) // ========================================== // User Interactions Testing // ========================================== describe('User Interactions', () => { it('should call onBack when back button is clicked', () => { // Arrange const onBack = jest.fn() render() // Act fireEvent.click(screen.getByRole('button', { name: /datasetPipeline.operations.dataSource/i })) // Assert expect(onBack).toHaveBeenCalledTimes(1) }) it('should call onProcess when process button is clicked', () => { // Arrange const onProcess = jest.fn() render() // Act fireEvent.click(screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i })) // Assert expect(onProcess).toHaveBeenCalledTimes(1) }) it('should not call onProcess when process button is disabled and clicked', () => { // Arrange const onProcess = jest.fn() render() // Act fireEvent.click(screen.getByRole('button', { name: /datasetPipeline.operations.saveAndProcess/i })) // Assert expect(onProcess).not.toHaveBeenCalled() }) }) // ========================================== // Component Memoization Testing // ========================================== describe('Component Memoization', () => { it('should be wrapped with React.memo', () => { // Assert expect(Actions.$$typeof).toBe(Symbol.for('react.memo')) }) }) }) // ========================================== // Header Component Tests // ========================================== describe('Header', () => { const defaultHeaderProps = { onReset: jest.fn(), resetDisabled: false, previewDisabled: false, } beforeEach(() => { jest.clearAllMocks() }) // ========================================== // Rendering Tests // ========================================== describe('Rendering', () => { it('should render without crashing', () => { // Arrange & Act render(
) // Assert expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() }) it('should render reset button', () => { // Arrange & Act render(
) // Assert expect(screen.getByRole('button', { name: /common.operation.reset/i })).toBeInTheDocument() }) it('should render preview button with icon', () => { // Arrange & Act render(
) // Assert const previewButton = screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i }) expect(previewButton).toBeInTheDocument() expect(previewButton.querySelector('svg')).toBeInTheDocument() }) it('should render title with correct text', () => { // Arrange & Act render(
) // Assert expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() }) it('should have correct container layout', () => { // Arrange & Act const { container } = render(
) // Assert const mainContainer = container.querySelector('.flex.items-center.gap-x-1') expect(mainContainer).toBeInTheDocument() }) }) // ========================================== // Props Testing // ========================================== describe('Props', () => { describe('resetDisabled prop', () => { it('should not disable reset button when resetDisabled is false', () => { // Arrange & Act render(
) // Assert const resetButton = screen.getByRole('button', { name: /common.operation.reset/i }) expect(resetButton).not.toBeDisabled() }) it('should disable reset button when resetDisabled is true', () => { // Arrange & Act render(
) // Assert const resetButton = screen.getByRole('button', { name: /common.operation.reset/i }) expect(resetButton).toBeDisabled() }) }) describe('previewDisabled prop', () => { it('should not disable preview button when previewDisabled is false', () => { // Arrange & Act render(
) // Assert const previewButton = screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i }) expect(previewButton).not.toBeDisabled() }) it('should disable preview button when previewDisabled is true', () => { // Arrange & Act render(
) // Assert const previewButton = screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i }) expect(previewButton).toBeDisabled() }) }) it('should handle onPreview being undefined', () => { // Arrange & Act render(
) // Assert const previewButton = screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i }) expect(previewButton).toBeInTheDocument() // Click should not throw let didThrow = false try { fireEvent.click(previewButton) } catch { didThrow = true } expect(didThrow).toBe(false) }) }) // ========================================== // User Interactions Testing // ========================================== describe('User Interactions', () => { it('should call onReset when reset button is clicked', () => { // Arrange const onReset = jest.fn() render(
) // Act fireEvent.click(screen.getByRole('button', { name: /common.operation.reset/i })) // Assert expect(onReset).toHaveBeenCalledTimes(1) }) it('should not call onReset when reset button is disabled and clicked', () => { // Arrange const onReset = jest.fn() render(
) // Act fireEvent.click(screen.getByRole('button', { name: /common.operation.reset/i })) // Assert expect(onReset).not.toHaveBeenCalled() }) it('should call onPreview when preview button is clicked', () => { // Arrange const onPreview = jest.fn() render(
) // Act fireEvent.click(screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i })) // Assert expect(onPreview).toHaveBeenCalledTimes(1) }) it('should not call onPreview when preview button is disabled and clicked', () => { // Arrange const onPreview = jest.fn() render(
) // Act fireEvent.click(screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i })) // Assert expect(onPreview).not.toHaveBeenCalled() }) }) // ========================================== // Component Memoization Testing // ========================================== describe('Component Memoization', () => { it('should be wrapped with React.memo', () => { // Assert expect(Header.$$typeof).toBe(Symbol.for('react.memo')) }) }) // ========================================== // Edge Cases Testing // ========================================== describe('Edge Cases', () => { it('should handle both buttons disabled', () => { // Arrange & Act render(
) // Assert const resetButton = screen.getByRole('button', { name: /common.operation.reset/i }) const previewButton = screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i }) expect(resetButton).toBeDisabled() expect(previewButton).toBeDisabled() }) it('should handle both buttons enabled', () => { // Arrange & Act render(
) // Assert const resetButton = screen.getByRole('button', { name: /common.operation.reset/i }) const previewButton = screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i }) expect(resetButton).not.toBeDisabled() expect(previewButton).not.toBeDisabled() }) }) }) // ========================================== // Form Component Tests // ========================================== describe('Form', () => { const defaultFormProps = { initialData: { field1: '' }, configurations: [] as BaseConfiguration[], schema: createMockSchema(), onSubmit: jest.fn(), onPreview: jest.fn(), ref: { current: null } as React.RefObject, isRunning: false, } beforeEach(() => { jest.clearAllMocks() toastNotifySpy.mockClear() }) // ========================================== // Rendering Tests // ========================================== describe('Rendering', () => { it('should render without crashing', () => { // Arrange & Act render(
) // Assert expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() }) it('should render form element', () => { // Arrange & Act const { container } = render() // Assert const form = container.querySelector('form') expect(form).toBeInTheDocument() }) it('should render Header component', () => { // Arrange & Act render() // Assert expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() expect(screen.getByRole('button', { name: /common.operation.reset/i })).toBeInTheDocument() expect(screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i })).toBeInTheDocument() }) it('should have correct form structure', () => { // Arrange & Act const { container } = render() // Assert const form = container.querySelector('form.flex.w-full.flex-col') expect(form).toBeInTheDocument() }) }) // ========================================== // Props Testing // ========================================== describe('Props', () => { describe('isRunning prop', () => { it('should disable preview button when isRunning is true', () => { // Arrange & 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 & Act render() // Assert const previewButton = screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i }) expect(previewButton).not.toBeDisabled() }) }) describe('configurations prop', () => { it('should render empty when configurations is empty', () => { // Arrange & Act const { container } = render() // Assert - the fields container should have no field children const fieldsContainer = container.querySelector('.flex.flex-col.gap-3') expect(fieldsContainer?.children.length).toBe(0) }) it('should render all configurations', () => { // Arrange const configurations = [ createMockConfiguration({ variable: 'var1', label: 'Variable 1' }), createMockConfiguration({ variable: 'var2', label: 'Variable 2' }), createMockConfiguration({ variable: 'var3', label: 'Variable 3' }), ] // Act render() // Assert expect(screen.getByText('Variable 1')).toBeInTheDocument() expect(screen.getByText('Variable 2')).toBeInTheDocument() expect(screen.getByText('Variable 3')).toBeInTheDocument() }) }) it('should expose submit method via ref', () => { // Arrange const mockRef = { current: null } as React.MutableRefObject<{ submit: () => void } | null> // Act render() // Assert expect(mockRef.current).not.toBeNull() expect(typeof mockRef.current?.submit).toBe('function') }) }) // ========================================== // Ref Submit Testing // ========================================== describe('Ref Submit', () => { it('should call onSubmit when ref.submit() is called', async () => { // Arrange const onSubmit = jest.fn() const mockRef = { current: null } as React.MutableRefObject<{ submit: () => void } | null> render() // Act - call submit via ref mockRef.current?.submit() // Assert await waitFor(() => { expect(onSubmit).toHaveBeenCalled() }) }) it('should trigger form validation when ref.submit() is called', async () => { // Arrange const failingSchema = createFailingSchema() const mockRef = { current: null } as React.MutableRefObject<{ submit: () => void } | null> render() // Act - call submit via ref mockRef.current?.submit() // Assert - validation error should be shown await waitFor(() => { expect(toastNotifySpy).toHaveBeenCalledWith({ type: 'error', message: '"field1" is required', }) }) }) }) // ========================================== // User Interactions Testing // ========================================== describe('User Interactions', () => { it('should call onPreview when preview button is clicked', () => { // Arrange const onPreview = jest.fn() render() // Act fireEvent.click(screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i })) // Assert expect(onPreview).toHaveBeenCalledTimes(1) }) it('should handle form submission via form element', async () => { // Arrange const onSubmit = jest.fn() const { container } = render() const form = container.querySelector('form')! // Act fireEvent.submit(form) // Assert await waitFor(() => { expect(onSubmit).toHaveBeenCalled() }) }) }) // ========================================== // Form State Testing // ========================================== describe('Form State', () => { it('should disable reset button initially when form is not dirty', () => { // Arrange & Act render() // Assert const resetButton = screen.getByRole('button', { name: /common.operation.reset/i }) expect(resetButton).toBeDisabled() }) it('should enable reset button when form becomes dirty', async () => { // Arrange const configurations = [ createMockConfiguration({ variable: 'field1', label: 'Field 1' }), ] render() // Act - change input to make form dirty const input = screen.getByRole('textbox') fireEvent.change(input, { target: { value: 'new value' } }) // Assert await waitFor(() => { const resetButton = screen.getByRole('button', { name: /common.operation.reset/i }) expect(resetButton).not.toBeDisabled() }) }) it('should reset form to initial values when reset button is clicked', async () => { // Arrange const configurations = [ createMockConfiguration({ variable: 'field1', label: 'Field 1' }), ] const initialData = { field1: 'initial value' } render() // Act - change input to make form dirty const input = screen.getByRole('textbox') fireEvent.change(input, { target: { value: 'new value' } }) // Wait for reset button to be enabled await waitFor(() => { const resetButton = screen.getByRole('button', { name: /common.operation.reset/i }) expect(resetButton).not.toBeDisabled() }) // Click reset button const resetButton = screen.getByRole('button', { name: /common.operation.reset/i }) fireEvent.click(resetButton) // Assert - form should be reset, button should be disabled again await waitFor(() => { expect(resetButton).toBeDisabled() }) }) it('should call form.reset when handleReset is triggered', async () => { // Arrange const configurations = [ createMockConfiguration({ variable: 'field1', label: 'Field 1' }), ] const initialData = { field1: 'original' } render() // Make form dirty const input = screen.getByRole('textbox') fireEvent.change(input, { target: { value: 'modified' } }) // Wait for dirty state await waitFor(() => { expect(screen.getByRole('button', { name: /common.operation.reset/i })).not.toBeDisabled() }) // Act - click reset fireEvent.click(screen.getByRole('button', { name: /common.operation.reset/i })) // Assert - input should be reset to initial value await waitFor(() => { expect(input).toHaveValue('original') }) }) }) // ========================================== // Validation Testing // ========================================== describe('Validation', () => { it('should show toast notification on validation error', async () => { // Arrange const failingSchema = createFailingSchema() const { container } = render() // Act const form = container.querySelector('form')! fireEvent.submit(form) // Assert await waitFor(() => { expect(toastNotifySpy).toHaveBeenCalledWith({ type: 'error', message: '"field1" is required', }) }) }) it('should not call onSubmit when validation fails', async () => { // Arrange const onSubmit = jest.fn() const failingSchema = createFailingSchema() const { container } = render() // Act const form = container.querySelector('form')! fireEvent.submit(form) // Assert - wait a bit and verify onSubmit was not called await waitFor(() => { expect(toastNotifySpy).toHaveBeenCalled() }) expect(onSubmit).not.toHaveBeenCalled() }) it('should call onSubmit when validation passes', async () => { // Arrange const onSubmit = jest.fn() const passingSchema = createMockSchema() const { container } = render() // Act const form = container.querySelector('form')! fireEvent.submit(form) // Assert await waitFor(() => { expect(onSubmit).toHaveBeenCalled() }) }) }) // ========================================== // Edge Cases Testing // ========================================== describe('Edge Cases', () => { it('should handle empty initialData', () => { // Arrange & Act render() // Assert expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() }) it('should handle configurations with different field types', () => { // Arrange const configurations = [ createMockConfiguration({ type: BaseFieldType.textInput, variable: 'text', label: 'Text Field' }), createMockConfiguration({ type: BaseFieldType.numberInput, variable: 'number', label: 'Number Field' }), ] // Act render() // Assert expect(screen.getByText('Text Field')).toBeInTheDocument() expect(screen.getByText('Number Field')).toBeInTheDocument() }) it('should handle null ref', () => { // Arrange & Act render() // Assert expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() }) }) // ========================================== // Configuration Variations Testing // ========================================== describe('Configuration Variations', () => { it('should render configuration with label', () => { // Arrange const configurations = [ createMockConfiguration({ variable: 'field1', label: 'Custom Label' }), ] // Act render() // Assert expect(screen.getByText('Custom Label')).toBeInTheDocument() }) it('should render required configuration', () => { // Arrange const configurations = [ createMockConfiguration({ variable: 'field1', label: 'Required Field', required: true }), ] // Act render() // Assert expect(screen.getByText('Required Field')).toBeInTheDocument() }) }) }) // ========================================== // Integration Tests (Cross-component) // ========================================== describe('Process Documents Components Integration', () => { beforeEach(() => { jest.clearAllMocks() }) describe('Form with Header Integration', () => { const defaultFormProps = { initialData: { field1: '' }, configurations: [] as BaseConfiguration[], schema: createMockSchema(), onSubmit: jest.fn(), onPreview: jest.fn(), ref: { current: null } as React.RefObject, isRunning: false, } it('should render Header within Form', () => { // Arrange & Act render() // Assert expect(screen.getByText('datasetPipeline.addDocuments.stepTwo.chunkSettings')).toBeInTheDocument() expect(screen.getByRole('button', { name: /common.operation.reset/i })).toBeInTheDocument() }) it('should pass isRunning to Header for previewDisabled', () => { // Arrange & Act render() // Assert const previewButton = screen.getByRole('button', { name: /datasetPipeline.addDocuments.stepTwo.previewChunks/i }) expect(previewButton).toBeDisabled() }) }) })