import type { TopBarProps } from './index' import { render, screen } from '@testing-library/react' import { TopBar } from './index' // Mock next/link to capture href values vi.mock('next/link', () => ({ default: ({ children, href, replace, className }: { children: React.ReactNode, href: string, replace?: boolean, className?: string }) => ( {children} ), })) // Helper to render TopBar with default props const renderTopBar = (props: Partial = {}) => { const defaultProps: TopBarProps = { activeIndex: 0, ...props, } return { ...render(), props: defaultProps, } } // ============================================================================ // TopBar Component Tests // ============================================================================ describe('TopBar', () => { beforeEach(() => { vi.clearAllMocks() }) // -------------------------------------------------------------------------- // Rendering Tests - Verify component renders properly // -------------------------------------------------------------------------- describe('Rendering', () => { it('should render without crashing', () => { // Arrange & Act renderTopBar() // Assert expect(screen.getByTestId('back-link')).toBeInTheDocument() }) it('should render back link with arrow icon', () => { // Arrange & Act const { container } = renderTopBar() // Assert const backLink = screen.getByTestId('back-link') expect(backLink).toBeInTheDocument() // Check for the arrow icon (svg element) const arrowIcon = container.querySelector('svg') expect(arrowIcon).toBeInTheDocument() }) it('should render fallback route text', () => { // Arrange & Act renderTopBar() // Assert expect(screen.getByText('datasetCreation.steps.header.fallbackRoute')).toBeInTheDocument() }) it('should render Stepper component with 3 steps', () => { // Arrange & Act renderTopBar({ activeIndex: 0 }) // Assert - Check for step translations expect(screen.getByText('datasetCreation.steps.one')).toBeInTheDocument() expect(screen.getByText('datasetCreation.steps.two')).toBeInTheDocument() expect(screen.getByText('datasetCreation.steps.three')).toBeInTheDocument() }) it('should apply default container classes', () => { // Arrange & Act const { container } = renderTopBar() // Assert const wrapper = container.firstChild as HTMLElement expect(wrapper).toHaveClass('relative') expect(wrapper).toHaveClass('flex') expect(wrapper).toHaveClass('h-[52px]') expect(wrapper).toHaveClass('shrink-0') expect(wrapper).toHaveClass('items-center') expect(wrapper).toHaveClass('justify-between') expect(wrapper).toHaveClass('border-b') expect(wrapper).toHaveClass('border-b-divider-subtle') }) }) // -------------------------------------------------------------------------- // Props Testing - Test all prop variations // -------------------------------------------------------------------------- describe('Props', () => { describe('className prop', () => { it('should apply custom className when provided', () => { // Arrange & Act const { container } = renderTopBar({ className: 'custom-class' }) // Assert const wrapper = container.firstChild as HTMLElement expect(wrapper).toHaveClass('custom-class') }) it('should merge custom className with default classes', () => { // Arrange & Act const { container } = renderTopBar({ className: 'my-custom-class another-class' }) // Assert const wrapper = container.firstChild as HTMLElement expect(wrapper).toHaveClass('relative') expect(wrapper).toHaveClass('flex') expect(wrapper).toHaveClass('my-custom-class') expect(wrapper).toHaveClass('another-class') }) it('should render correctly without className', () => { // Arrange & Act const { container } = renderTopBar({ className: undefined }) // Assert const wrapper = container.firstChild as HTMLElement expect(wrapper).toHaveClass('relative') expect(wrapper).toHaveClass('flex') }) it('should handle empty string className', () => { // Arrange & Act const { container } = renderTopBar({ className: '' }) // Assert const wrapper = container.firstChild as HTMLElement expect(wrapper).toHaveClass('relative') }) }) describe('datasetId prop', () => { it('should set fallback route to /datasets when datasetId is undefined', () => { // Arrange & Act renderTopBar({ datasetId: undefined }) // Assert const backLink = screen.getByTestId('back-link') expect(backLink).toHaveAttribute('href', '/datasets') }) it('should set fallback route to /datasets/:id/documents when datasetId is provided', () => { // Arrange & Act renderTopBar({ datasetId: 'dataset-123' }) // Assert const backLink = screen.getByTestId('back-link') expect(backLink).toHaveAttribute('href', '/datasets/dataset-123/documents') }) it('should handle various datasetId formats', () => { // Arrange & Act renderTopBar({ datasetId: 'abc-def-ghi-123' }) // Assert const backLink = screen.getByTestId('back-link') expect(backLink).toHaveAttribute('href', '/datasets/abc-def-ghi-123/documents') }) it('should handle empty string datasetId', () => { // Arrange & Act renderTopBar({ datasetId: '' }) // Assert - Empty string is falsy, so fallback to /datasets const backLink = screen.getByTestId('back-link') expect(backLink).toHaveAttribute('href', '/datasets') }) }) describe('activeIndex prop', () => { it('should pass activeIndex to Stepper component (index 0)', () => { // Arrange & Act const { container } = renderTopBar({ activeIndex: 0 }) // Assert - First step should be active (has specific styling) const steps = container.querySelectorAll('[class*="system-2xs-semibold-uppercase"]') expect(steps.length).toBeGreaterThan(0) }) it('should pass activeIndex to Stepper component (index 1)', () => { // Arrange & Act renderTopBar({ activeIndex: 1 }) // Assert - Stepper is rendered with correct props expect(screen.getByText('datasetCreation.steps.one')).toBeInTheDocument() expect(screen.getByText('datasetCreation.steps.two')).toBeInTheDocument() }) it('should pass activeIndex to Stepper component (index 2)', () => { // Arrange & Act renderTopBar({ activeIndex: 2 }) // Assert expect(screen.getByText('datasetCreation.steps.three')).toBeInTheDocument() }) it('should handle edge case activeIndex of -1', () => { // Arrange & Act const { container } = renderTopBar({ activeIndex: -1 }) // Assert - Component should render without crashing expect(container.firstChild).toBeInTheDocument() }) it('should handle edge case activeIndex beyond steps length', () => { // Arrange & Act const { container } = renderTopBar({ activeIndex: 10 }) // Assert - Component should render without crashing expect(container.firstChild).toBeInTheDocument() }) }) }) // -------------------------------------------------------------------------- // Memoization Tests - Test useMemo logic and dependencies // -------------------------------------------------------------------------- describe('Memoization Logic', () => { it('should compute fallbackRoute based on datasetId', () => { // Arrange & Act - With datasetId const { rerender } = render() // Assert expect(screen.getByTestId('back-link')).toHaveAttribute('href', '/datasets/test-id/documents') // Act - Rerender with different datasetId rerender() // Assert - Route should update expect(screen.getByTestId('back-link')).toHaveAttribute('href', '/datasets/new-id/documents') }) it('should update fallbackRoute when datasetId changes from undefined to defined', () => { // Arrange const { rerender } = render() expect(screen.getByTestId('back-link')).toHaveAttribute('href', '/datasets') // Act rerender() // Assert expect(screen.getByTestId('back-link')).toHaveAttribute('href', '/datasets/new-dataset/documents') }) it('should update fallbackRoute when datasetId changes from defined to undefined', () => { // Arrange const { rerender } = render() expect(screen.getByTestId('back-link')).toHaveAttribute('href', '/datasets/existing-id/documents') // Act rerender() // Assert expect(screen.getByTestId('back-link')).toHaveAttribute('href', '/datasets') }) it('should not change fallbackRoute when activeIndex changes but datasetId stays same', () => { // Arrange const { rerender } = render() const initialHref = screen.getByTestId('back-link').getAttribute('href') // Act rerender() // Assert - href should remain the same expect(screen.getByTestId('back-link')).toHaveAttribute('href', initialHref) }) it('should not change fallbackRoute when className changes but datasetId stays same', () => { // Arrange const { rerender } = render() const initialHref = screen.getByTestId('back-link').getAttribute('href') // Act rerender() // Assert - href should remain the same expect(screen.getByTestId('back-link')).toHaveAttribute('href', initialHref) }) }) // -------------------------------------------------------------------------- // Link Component Tests // -------------------------------------------------------------------------- describe('Link Component', () => { it('should render Link with replace prop', () => { // Arrange & Act renderTopBar() // Assert const backLink = screen.getByTestId('back-link') expect(backLink).toHaveAttribute('data-replace', 'true') }) it('should render Link with correct classes', () => { // Arrange & Act renderTopBar() // Assert const backLink = screen.getByTestId('back-link') expect(backLink).toHaveClass('inline-flex') expect(backLink).toHaveClass('h-12') expect(backLink).toHaveClass('items-center') expect(backLink).toHaveClass('justify-start') expect(backLink).toHaveClass('gap-1') expect(backLink).toHaveClass('py-2') expect(backLink).toHaveClass('pl-2') expect(backLink).toHaveClass('pr-6') }) }) // -------------------------------------------------------------------------- // STEP_T_MAP Tests - Verify step translations // -------------------------------------------------------------------------- describe('STEP_T_MAP Translations', () => { it('should render step one translation', () => { // Arrange & Act renderTopBar({ activeIndex: 0 }) // Assert expect(screen.getByText('datasetCreation.steps.one')).toBeInTheDocument() }) it('should render step two translation', () => { // Arrange & Act renderTopBar({ activeIndex: 1 }) // Assert expect(screen.getByText('datasetCreation.steps.two')).toBeInTheDocument() }) it('should render step three translation', () => { // Arrange & Act renderTopBar({ activeIndex: 2 }) // Assert expect(screen.getByText('datasetCreation.steps.three')).toBeInTheDocument() }) it('should render all three step translations', () => { // Arrange & Act renderTopBar({ activeIndex: 0 }) // Assert expect(screen.getByText('datasetCreation.steps.one')).toBeInTheDocument() expect(screen.getByText('datasetCreation.steps.two')).toBeInTheDocument() expect(screen.getByText('datasetCreation.steps.three')).toBeInTheDocument() }) }) // -------------------------------------------------------------------------- // Edge Cases and Error Handling Tests // -------------------------------------------------------------------------- describe('Edge Cases', () => { it('should handle special characters in datasetId', () => { // Arrange & Act renderTopBar({ datasetId: 'dataset-with-special_chars.123' }) // Assert const backLink = screen.getByTestId('back-link') expect(backLink).toHaveAttribute('href', '/datasets/dataset-with-special_chars.123/documents') }) it('should handle very long datasetId', () => { // Arrange const longId = 'a'.repeat(100) // Act renderTopBar({ datasetId: longId }) // Assert const backLink = screen.getByTestId('back-link') expect(backLink).toHaveAttribute('href', `/datasets/${longId}/documents`) }) it('should handle UUID format datasetId', () => { // Arrange const uuid = '550e8400-e29b-41d4-a716-446655440000' // Act renderTopBar({ datasetId: uuid }) // Assert const backLink = screen.getByTestId('back-link') expect(backLink).toHaveAttribute('href', `/datasets/${uuid}/documents`) }) it('should handle whitespace in className', () => { // Arrange & Act const { container } = renderTopBar({ className: ' spaced-class ' }) // Assert - classNames utility handles whitespace const wrapper = container.firstChild as HTMLElement expect(wrapper).toBeInTheDocument() }) it('should render correctly with all props provided', () => { // Arrange & Act const { container } = renderTopBar({ className: 'custom-class', datasetId: 'full-props-id', activeIndex: 2, }) // Assert const wrapper = container.firstChild as HTMLElement expect(wrapper).toHaveClass('custom-class') expect(screen.getByTestId('back-link')).toHaveAttribute('href', '/datasets/full-props-id/documents') }) it('should render correctly with minimal props (only activeIndex)', () => { // Arrange & Act const { container } = renderTopBar({ activeIndex: 0 }) // Assert expect(container.firstChild).toBeInTheDocument() expect(screen.getByTestId('back-link')).toHaveAttribute('href', '/datasets') }) }) // -------------------------------------------------------------------------- // Stepper Integration Tests // -------------------------------------------------------------------------- describe('Stepper Integration', () => { it('should pass steps array with correct structure to Stepper', () => { // Arrange & Act renderTopBar({ activeIndex: 0 }) // Assert - All step names should be rendered const stepOne = screen.getByText('datasetCreation.steps.one') const stepTwo = screen.getByText('datasetCreation.steps.two') const stepThree = screen.getByText('datasetCreation.steps.three') expect(stepOne).toBeInTheDocument() expect(stepTwo).toBeInTheDocument() expect(stepThree).toBeInTheDocument() }) it('should render Stepper in centered position', () => { // Arrange & Act const { container } = renderTopBar({ activeIndex: 0 }) // Assert - Check for centered positioning classes const centeredContainer = container.querySelector('.absolute.left-1\\/2.top-1\\/2.-translate-x-1\\/2.-translate-y-1\\/2') expect(centeredContainer).toBeInTheDocument() }) it('should render step dividers between steps', () => { // Arrange & Act const { container } = renderTopBar({ activeIndex: 0 }) // Assert - Check for dividers (h-px w-4 bg-divider-deep) const dividers = container.querySelectorAll('.h-px.w-4.bg-divider-deep') expect(dividers.length).toBe(2) // 2 dividers between 3 steps }) }) // -------------------------------------------------------------------------- // Accessibility Tests // -------------------------------------------------------------------------- describe('Accessibility', () => { it('should have accessible back link', () => { // Arrange & Act renderTopBar() // Assert const backLink = screen.getByTestId('back-link') expect(backLink).toBeInTheDocument() // Link should have visible text expect(screen.getByText('datasetCreation.steps.header.fallbackRoute')).toBeInTheDocument() }) it('should have visible arrow icon in back link', () => { // Arrange & Act const { container } = renderTopBar() // Assert - Arrow icon should be visible const arrowIcon = container.querySelector('svg') expect(arrowIcon).toBeInTheDocument() expect(arrowIcon).toHaveClass('text-text-primary') }) }) // -------------------------------------------------------------------------- // Re-render Tests // -------------------------------------------------------------------------- describe('Re-render Behavior', () => { it('should update activeIndex on re-render', () => { // Arrange const { rerender, container } = render() // Initial check expect(container.firstChild).toBeInTheDocument() // Act - Update activeIndex rerender() // Assert - Component should still render expect(container.firstChild).toBeInTheDocument() }) it('should update className on re-render', () => { // Arrange const { rerender, container } = render() const wrapper = container.firstChild as HTMLElement expect(wrapper).toHaveClass('initial-class') // Act rerender() // Assert expect(wrapper).toHaveClass('updated-class') expect(wrapper).not.toHaveClass('initial-class') }) it('should handle multiple rapid re-renders', () => { // Arrange const { rerender, container } = render() // Act - Multiple rapid re-renders rerender() rerender() rerender() rerender() // Assert - Component should be stable expect(container.firstChild).toBeInTheDocument() const wrapper = container.firstChild as HTMLElement expect(wrapper).toHaveClass('new-class') expect(screen.getByTestId('back-link')).toHaveAttribute('href', '/datasets/another-id/documents') }) }) })