import { fireEvent, render, screen } from '@testing-library/react' import React from 'react' import Actions from './index' // ========================================== // Mock External Dependencies // ========================================== // Mock next/navigation - useParams returns datasetId const mockDatasetId = 'test-dataset-id' jest.mock('next/navigation', () => ({ useParams: () => ({ datasetId: mockDatasetId }), })) // Mock next/link to capture href jest.mock('next/link', () => { return ({ children, href, replace }: { children: React.ReactNode; href: string; replace?: boolean }) => ( {children} ) }) // ========================================== // Test Suite // ========================================== describe('Actions', () => { // Default mock for required props const defaultProps = { handleNextStep: jest.fn(), } beforeEach(() => { jest.clearAllMocks() }) // ========================================== // Rendering Tests // ========================================== describe('Rendering', () => { // Tests basic rendering functionality it('should render without crashing', () => { // Arrange & Act render() // Assert expect(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i })).toBeInTheDocument() }) it('should render cancel button with correct link', () => { // Arrange & Act render() // Assert const cancelLink = screen.getByRole('link') expect(cancelLink).toHaveAttribute('href', `/datasets/${mockDatasetId}/documents`) expect(cancelLink).toHaveAttribute('data-replace', 'true') }) it('should render next step button with arrow icon', () => { // Arrange & Act render() // Assert const nextButton = screen.getByRole('button', { name: /datasetCreation.stepOne.button/i }) expect(nextButton).toBeInTheDocument() expect(nextButton.querySelector('svg')).toBeInTheDocument() }) it('should render cancel button with correct translation key', () => { // Arrange & Act render() // Assert expect(screen.getByText('common.operation.cancel')).toBeInTheDocument() }) it('should not render select all section by default', () => { // Arrange & Act render() // Assert expect(screen.queryByText('common.operation.selectAll')).not.toBeInTheDocument() }) }) // ========================================== // Props Testing // ========================================== describe('Props', () => { // Tests for prop variations and defaults describe('disabled prop', () => { it('should not disable next step button when disabled is false', () => { // Arrange & Act render() // Assert const nextButton = screen.getByRole('button', { name: /datasetCreation.stepOne.button/i }) expect(nextButton).not.toBeDisabled() }) it('should disable next step button when disabled is true', () => { // Arrange & Act render() // Assert const nextButton = screen.getByRole('button', { name: /datasetCreation.stepOne.button/i }) expect(nextButton).toBeDisabled() }) it('should not disable next step button when disabled is undefined', () => { // Arrange & Act render() // Assert const nextButton = screen.getByRole('button', { name: /datasetCreation.stepOne.button/i }) expect(nextButton).not.toBeDisabled() }) }) describe('showSelect prop', () => { it('should show select all section when showSelect is true', () => { // Arrange & Act render() // Assert expect(screen.getByText('common.operation.selectAll')).toBeInTheDocument() }) it('should hide select all section when showSelect is false', () => { // Arrange & Act render() // Assert expect(screen.queryByText('common.operation.selectAll')).not.toBeInTheDocument() }) it('should hide select all section when showSelect defaults to false', () => { // Arrange & Act render() // Assert expect(screen.queryByText('common.operation.selectAll')).not.toBeInTheDocument() }) }) describe('tip prop', () => { it('should show tip when showSelect is true and tip is provided', () => { // Arrange const tip = 'This is a helpful tip' // Act render() // Assert expect(screen.getByText(tip)).toBeInTheDocument() expect(screen.getByTitle(tip)).toBeInTheDocument() }) it('should not show tip when showSelect is false even if tip is provided', () => { // Arrange const tip = 'This is a helpful tip' // Act render() // Assert expect(screen.queryByText(tip)).not.toBeInTheDocument() }) it('should not show tip when tip is empty string', () => { // Arrange & Act render() // Assert const tipElements = screen.queryAllByTitle('') // Empty tip should not render a tip element expect(tipElements.length).toBe(0) }) it('should use empty string as default tip value', () => { // Arrange & Act render() // Assert - tip container should not exist when tip defaults to empty string const tipContainer = document.querySelector('.text-text-tertiary.truncate') expect(tipContainer).not.toBeInTheDocument() }) }) }) // ========================================== // Event Handlers Testing // ========================================== describe('User Interactions', () => { // Tests for event handlers it('should call handleNextStep when next button is clicked', () => { // Arrange const handleNextStep = jest.fn() render() // Act fireEvent.click(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i })) // Assert expect(handleNextStep).toHaveBeenCalledTimes(1) }) it('should not call handleNextStep when next button is disabled and clicked', () => { // Arrange const handleNextStep = jest.fn() render() // Act fireEvent.click(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i })) // Assert expect(handleNextStep).not.toHaveBeenCalled() }) it('should call onSelectAll when checkbox is clicked', () => { // Arrange const onSelectAll = jest.fn() render( , ) // Act - find the checkbox container and click it const selectAllLabel = screen.getByText('common.operation.selectAll') const checkboxContainer = selectAllLabel.closest('.flex.shrink-0.items-center') const checkbox = checkboxContainer?.querySelector('[class*="cursor-pointer"]') if (checkbox) fireEvent.click(checkbox) // Assert expect(onSelectAll).toHaveBeenCalledTimes(1) }) }) // ========================================== // Memoization Logic Testing // ========================================== describe('Memoization Logic', () => { // Tests for useMemo hooks (indeterminate and checked) describe('indeterminate calculation', () => { it('should return false when showSelect is false', () => { // Arrange & Act render( , ) // Assert - checkbox not rendered, so can't check indeterminate directly expect(screen.queryByText('common.operation.selectAll')).not.toBeInTheDocument() }) it('should return false when selectedOptions is undefined', () => { // Arrange & Act const { container } = render( , ) // Assert - checkbox should not be indeterminate const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) it('should return false when totalOptions is undefined', () => { // Arrange & Act const { container } = render( , ) // Assert - checkbox should exist const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) it('should return true when some options are selected (0 < selectedOptions < totalOptions)', () => { // Arrange & Act const { container } = render( , ) // Assert - checkbox should render in indeterminate state // The checkbox component renders IndeterminateIcon when indeterminate and not checked const selectAllContainer = container.querySelector('.flex.shrink-0.items-center') expect(selectAllContainer).toBeInTheDocument() }) it('should return false when no options are selected (selectedOptions === 0)', () => { // Arrange & Act const { container } = render( , ) // Assert - checkbox should be unchecked and not indeterminate const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) it('should return false when all options are selected (selectedOptions === totalOptions)', () => { // Arrange & Act const { container } = render( , ) // Assert - checkbox should be checked, not indeterminate const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) }) describe('checked calculation', () => { it('should return false when showSelect is false', () => { // Arrange & Act render( , ) // Assert - checkbox not rendered expect(screen.queryByText('common.operation.selectAll')).not.toBeInTheDocument() }) it('should return false when selectedOptions is undefined', () => { // Arrange & Act const { container } = render( , ) // Assert const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) it('should return false when totalOptions is undefined', () => { // Arrange & Act const { container } = render( , ) // Assert const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) it('should return true when all options are selected (selectedOptions === totalOptions)', () => { // Arrange & Act const { container } = render( , ) // Assert - checkbox should show checked state (RiCheckLine icon) const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) it('should return false when selectedOptions is 0', () => { // Arrange & Act const { container } = render( , ) // Assert - checkbox should be unchecked const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) it('should return false when not all options are selected', () => { // Arrange & Act const { container } = render( , ) // Assert - checkbox should be indeterminate, not checked const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) }) }) // ========================================== // Component Memoization Testing // ========================================== describe('Component Memoization', () => { // Tests for React.memo behavior it('should be wrapped with React.memo', () => { // Assert - verify component has memo wrapper expect(Actions.$$typeof).toBe(Symbol.for('react.memo')) }) it('should not re-render when props are the same', () => { // Arrange const handleNextStep = jest.fn() const props = { handleNextStep, disabled: false, showSelect: true, totalOptions: 5, selectedOptions: 3, onSelectAll: jest.fn(), tip: 'Test tip', } // Act const { rerender } = render() // Re-render with same props rerender() // Assert - component renders correctly after rerender expect(screen.getByText('common.operation.selectAll')).toBeInTheDocument() expect(screen.getByText('Test tip')).toBeInTheDocument() }) it('should re-render when props change', () => { // Arrange const handleNextStep = jest.fn() const initialProps = { handleNextStep, disabled: false, showSelect: true, totalOptions: 5, selectedOptions: 0, onSelectAll: jest.fn(), tip: 'Initial tip', } // Act const { rerender } = render() expect(screen.getByText('Initial tip')).toBeInTheDocument() // Rerender with different props rerender() // Assert expect(screen.getByText('Updated tip')).toBeInTheDocument() expect(screen.queryByText('Initial tip')).not.toBeInTheDocument() }) }) // ========================================== // Edge Cases Testing // ========================================== describe('Edge Cases', () => { // Tests for boundary conditions and unusual inputs it('should handle totalOptions of 0', () => { // Arrange & Act const { container } = render( , ) // Assert - should render checkbox const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) it('should handle very large totalOptions', () => { // Arrange & Act const { container } = render( , ) // Assert const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) it('should handle very long tip text', () => { // Arrange const longTip = 'A'.repeat(500) // Act render( , ) // Assert - tip should render with truncate class const tipElement = screen.getByTitle(longTip) expect(tipElement).toHaveClass('truncate') }) it('should handle tip with special characters', () => { // Arrange const specialTip = ' & "quotes" \'apostrophes\'' // Act render( , ) // Assert - special characters should be rendered safely expect(screen.getByText(specialTip)).toBeInTheDocument() }) it('should handle tip with unicode characters', () => { // Arrange const unicodeTip = '选中 5 个文件,共 10MB 🚀' // Act render( , ) // Assert expect(screen.getByText(unicodeTip)).toBeInTheDocument() }) it('should handle selectedOptions greater than totalOptions', () => { // This is an edge case that shouldn't happen but should be handled gracefully // Arrange & Act const { container } = render( , ) // Assert - should still render const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) it('should handle negative selectedOptions', () => { // Arrange & Act const { container } = render( , ) // Assert - should still render (though this is an invalid state) const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() }) it('should handle onSelectAll being undefined when showSelect is true', () => { // Arrange & Act const { container } = render( , ) // Assert - should render checkbox const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() // Click should not throw if (checkbox) expect(() => fireEvent.click(checkbox)).not.toThrow() }) it('should handle empty datasetId from params', () => { // This test verifies the link is constructed even with empty datasetId // Arrange & Act render() // Assert - link should still be present with the mocked datasetId const cancelLink = screen.getByRole('link') expect(cancelLink).toHaveAttribute('href', '/datasets/test-dataset-id/documents') }) }) // ========================================== // All Prop Combinations Testing // ========================================== describe('Prop Combinations', () => { // Tests for various combinations of props it('should handle disabled=true with showSelect=false', () => { // Arrange & Act render() // Assert const nextButton = screen.getByRole('button', { name: /datasetCreation.stepOne.button/i }) expect(nextButton).toBeDisabled() expect(screen.queryByText('common.operation.selectAll')).not.toBeInTheDocument() }) it('should handle disabled=true with showSelect=true', () => { // Arrange & Act render( , ) // Assert const nextButton = screen.getByRole('button', { name: /datasetCreation.stepOne.button/i }) expect(nextButton).toBeDisabled() expect(screen.getByText('common.operation.selectAll')).toBeInTheDocument() }) it('should render complete component with all props provided', () => { // Arrange const allProps = { disabled: false, handleNextStep: jest.fn(), showSelect: true, totalOptions: 10, selectedOptions: 5, onSelectAll: jest.fn(), tip: 'All props provided', } // Act render() // Assert expect(screen.getByText('common.operation.selectAll')).toBeInTheDocument() expect(screen.getByText('All props provided')).toBeInTheDocument() expect(screen.getByText('common.operation.cancel')).toBeInTheDocument() expect(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i })).not.toBeDisabled() }) it('should render minimal component with only required props', () => { // Arrange & Act render() // Assert expect(screen.queryByText('common.operation.selectAll')).not.toBeInTheDocument() expect(screen.getByText('common.operation.cancel')).toBeInTheDocument() expect(screen.getByRole('button', { name: /datasetCreation.stepOne.button/i })).not.toBeDisabled() }) }) // ========================================== // Selection State Variations Testing // ========================================== describe('Selection State Variations', () => { // Tests for different selection states const selectionStates = [ { totalOptions: 10, selectedOptions: 0, expectedState: 'unchecked' }, { totalOptions: 10, selectedOptions: 5, expectedState: 'indeterminate' }, { totalOptions: 10, selectedOptions: 10, expectedState: 'checked' }, { totalOptions: 1, selectedOptions: 0, expectedState: 'unchecked' }, { totalOptions: 1, selectedOptions: 1, expectedState: 'checked' }, { totalOptions: 100, selectedOptions: 1, expectedState: 'indeterminate' }, { totalOptions: 100, selectedOptions: 99, expectedState: 'indeterminate' }, ] it.each(selectionStates)( 'should render with $expectedState state when totalOptions=$totalOptions and selectedOptions=$selectedOptions', ({ totalOptions, selectedOptions }) => { // Arrange & Act const { container } = render( , ) // Assert - component should render without errors const checkbox = container.querySelector('[class*="cursor-pointer"]') expect(checkbox).toBeInTheDocument() expect(screen.getByText('common.operation.selectAll')).toBeInTheDocument() }, ) }) // ========================================== // Layout Structure Testing // ========================================== describe('Layout', () => { // Tests for correct layout structure it('should have correct container structure', () => { // Arrange & Act const { container } = render() // Assert const mainContainer = container.querySelector('.flex.items-center.gap-x-2.overflow-hidden') expect(mainContainer).toBeInTheDocument() }) it('should have correct button container structure', () => { // Arrange & Act const { container } = render() // Assert - buttons should be in a flex container const buttonContainer = container.querySelector('.flex.grow.items-center.justify-end.gap-x-2') expect(buttonContainer).toBeInTheDocument() }) it('should position select all section before buttons when showSelect is true', () => { // Arrange & Act const { container } = render( , ) // Assert - select all section should exist const selectAllSection = container.querySelector('.flex.shrink-0.items-center') expect(selectAllSection).toBeInTheDocument() }) }) })