/** * Tests for NavigationContext - navigation-context.tsx * * Tests navigation state transitions, custom navigation providers, * and Link component integration. */ import { describe, it, expect, vi } from 'vitest' import { render, screen, fireEvent } from '@testing-library/react' import { NavigationProvider, useNavigation, type NavigationContextValue, } from '../../context/navigation-context' import type { NavigationProvider as NavigationProviderInterface, LinkProps } from '../../types' import type { ComponentType } from 'react' // Helper component to consume navigation context function NavigationConsumer({ onNavigation }: { onNavigation: (nav: NavigationContextValue) => void }) { const nav = useNavigation() onNavigation(nav) return (
{nav.getCurrentPath()} Test Link
) } function createMockNavigationProvider( overrides: Partial = {} ): NavigationProviderInterface { const MockLink: ComponentType = ({ href, children, className }) => ( {children} ) return { navigate: vi.fn(), getCurrentPath: vi.fn().mockReturnValue('/'), LinkComponent: MockLink, ...overrides, } } describe('NavigationContext', () => { describe('NavigationProvider', () => { it('should provide navigation functions to children', () => { const mockProvider = createMockNavigationProvider({ getCurrentPath: vi.fn().mockReturnValue('/home'), }) let navValue: NavigationContextValue | undefined render( { navValue = nav }} /> ) expect(navValue).toBeDefined() expect(navValue!.navigate).toBeDefined() expect(navValue!.getCurrentPath).toBeDefined() expect(navValue!.Link).toBeDefined() expect(screen.getByTestId('current-path').textContent).toBe('/home') }) it('should call navigate with path and options', () => { const mockNavigate = vi.fn() const mockProvider = createMockNavigationProvider({ navigate: mockNavigate }) render( {}} /> ) // Click navigate button fireEvent.click(screen.getByTestId('navigate-btn')) expect(mockNavigate).toHaveBeenCalledWith('/dashboard') // Click navigate with replace fireEvent.click(screen.getByTestId('navigate-replace-btn')) expect(mockNavigate).toHaveBeenCalledWith('/settings', { replace: true }) }) it('should render custom Link component', () => { const CustomLink: ComponentType = ({ href, children, className }) => ( Custom: {children} ) const mockProvider = createMockNavigationProvider({ LinkComponent: CustomLink }) render( {}} /> ) const link = screen.getByTestId('custom-link') expect(link).toBeDefined() expect(link.getAttribute('href')).toBe('/test') expect(link.className).toContain('test-link') expect(link.textContent).toBe('Custom: Test Link') }) it('should use default navigation provider when none provided', () => { // Mock window.location const originalLocation = window.location const mockLocation = { ...originalLocation, pathname: '/default-path', href: '/default-path', } Object.defineProperty(window, 'location', { value: mockLocation, writable: true, }) render( {}} /> ) expect(screen.getByTestId('current-path').textContent).toBe('/default-path') // Restore Object.defineProperty(window, 'location', { value: originalLocation, writable: true, }) }) it('should provide default Link component that renders anchor tags', () => { render( {}} /> ) // Default Link renders as tag const links = screen.getAllByRole('link') const testLink = links.find(link => link.textContent === 'Test Link') expect(testLink).toBeDefined() expect(testLink?.getAttribute('href')).toBe('/test') }) }) describe('useNavigation', () => { it('should throw error when used outside NavigationProvider', () => { const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {}) expect(() => { render( {}} />) }).toThrow('useNavigation must be used within a NavigationProvider') consoleError.mockRestore() }) }) describe('navigation state transitions', () => { it('should track path changes through getCurrentPath', () => { let currentPath = '/initial' const mockProvider = createMockNavigationProvider({ getCurrentPath: vi.fn(() => currentPath), navigate: vi.fn((to: string) => { currentPath = to }), }) const { rerender } = render( {}} /> ) expect(screen.getByTestId('current-path').textContent).toBe('/initial') // Simulate navigation fireEvent.click(screen.getByTestId('navigate-btn')) // Re-render to see updated path rerender( {}} /> ) expect(screen.getByTestId('current-path').textContent).toBe('/dashboard') }) it('should pass state to navigate when provided', () => { const mockNavigate = vi.fn() const mockProvider = createMockNavigationProvider({ navigate: mockNavigate }) function StateNavigator() { const nav = useNavigation() return ( ) } render( ) fireEvent.click(screen.getByTestId('nav-with-state')) expect(mockNavigate).toHaveBeenCalledWith('/page', { replace: false, state: { from: 'home' } }) }) }) describe('Link component integration', () => { it('should render Link with all props passed through', () => { const MockLink: ComponentType = ({ href, children, className }) => ( {children} ) const mockProvider = createMockNavigationProvider({ LinkComponent: MockLink }) function LinkRenderer() { const { Link } = useNavigation() return ( About Us ) } render( ) const link = screen.getByRole('link') expect(link.getAttribute('href')).toBe('/about') expect(link.className).toContain('nav-link') expect(link.textContent).toBe('About Us') }) it('should support nested children in Link', () => { const mockProvider = createMockNavigationProvider() function NestedLinkRenderer() { const { Link } = useNavigation() return ( * Profile ) } render( ) expect(screen.getByTestId('icon')).toBeDefined() expect(screen.getByTestId('text')).toBeDefined() }) }) })