import { renderHook } from '@testing-library/react' import { getBasePath, findBestMatchingItem, useActiveNavigation, normalizePath, isActiveHref, } from './useActiveNavigation' describe('getBasePath', () => { describe('basic path extraction', () => { it('should return root path for "/"', () => { expect(getBasePath({ path: '/' })).toBe('/') }) it('should return undefined for empty string', () => { expect(getBasePath({ path: '' })).toBe(undefined) }) it('should return undefined for null/undefined input', () => { expect(getBasePath({ path: null as any })).toBe(undefined) expect(getBasePath({ path: undefined as any })).toBe(undefined) }) it('should extract first segment from single level path', () => { expect(getBasePath({ path: '/feeds' })).toBe('/feeds') }) it('should extract first segment from multi-level path', () => { expect(getBasePath({ path: '/feeds/ethereum' })).toBe('/feeds') }) }) describe('query parameter handling', () => { it('should strip query parameters', () => { expect(getBasePath({ path: '/feeds?param=value' })).toBe('/feeds') }) it('should strip query parameters from multi-level paths', () => { expect(getBasePath({ path: '/feeds/ethereum?param=value' })).toBe( '/feeds', ) }) }) describe('hash fragment handling', () => { it('should strip hash fragments', () => { expect(getBasePath({ path: '/feeds#section' })).toBe('/feeds') }) it('should strip hash fragments from multi-level paths', () => { expect(getBasePath({ path: '/feeds/ethereum#section' })).toBe('/feeds') }) }) describe('combined query and hash handling', () => { it('should strip both query parameters and hash fragments', () => { expect(getBasePath({ path: '/feeds?param=value#section' })).toBe('/feeds') }) it('should handle complex URLs with both query and hash', () => { expect( getBasePath({ path: '/feeds/ethereum?param=value&other=test#section' }), ).toBe('/feeds') }) }) describe('edge cases', () => { it('should handle paths with only query parameters', () => { expect(getBasePath({ path: '/?param=value' })).toBe('/') }) it('should handle paths with only hash fragments', () => { expect(getBasePath({ path: '/#section' })).toBe('/') }) it('should handle paths with only query and hash', () => { expect(getBasePath({ path: '/?param=value#section' })).toBe('/') }) it('should handle malformed paths', () => { expect(getBasePath({ path: '//feeds' })).toBe('/feeds') }) it('should handle paths with special characters', () => { expect(getBasePath({ path: '/feeds/ethereum-mainnet' })).toBe('/feeds') }) it('should handle very long paths', () => { const longPath = '/feeds/ethereum/bitcoin/solana/polygon/avalanche' expect(getBasePath({ path: longPath })).toBe('/feeds') }) }) describe('real-world examples', () => { it('should handle typical Next.js paths', () => { expect(getBasePath({ path: '/dashboard/settings' })).toBe('/dashboard') expect(getBasePath({ path: '/api/users' })).toBe('/api') }) it('should handle typical React Router paths', () => { expect(getBasePath({ path: '/products/123' })).toBe('/products') expect(getBasePath({ path: '/admin/users' })).toBe('/admin') }) it('should handle API-like paths', () => { expect(getBasePath({ path: '/api/feeds/ethereum' })).toBe('/api') }) }) }) describe('findBestMatchingItem', () => { const mockLinks = [ { href: '/billing', name: 'Overview' }, { href: '/billing/consumption', name: 'Consumption' }, { href: '/billing/credits', name: 'Credits' }, ] describe('exact matches', () => { it('should return exact match for billing overview', () => { expect(findBestMatchingItem({ path: '/billing', links: mockLinks })).toBe( '/billing', ) }) it('should return exact match for billing consumption', () => { expect( findBestMatchingItem({ path: '/billing/consumption', links: mockLinks, }), ).toBe('/billing/consumption') }) it('should return exact match for billing credits', () => { expect( findBestMatchingItem({ path: '/billing/credits', links: mockLinks }), ).toBe('/billing/credits') }) }) describe('prefix matches', () => { it('should return most specific match for nested paths', () => { expect( findBestMatchingItem({ path: '/billing/consumption/details', links: mockLinks, }), ).toBe('/billing/consumption') }) it('should return most specific match for deeper nested paths', () => { expect( findBestMatchingItem({ path: '/billing/consumption/monthly/2024', links: mockLinks, }), ).toBe('/billing/consumption') }) it('should return parent match when child does not exist', () => { expect( findBestMatchingItem({ path: '/billing/unknown', links: mockLinks }), ).toBe('/billing') }) }) describe('query parameters and hash handling', () => { it('should handle query parameters', () => { expect( findBestMatchingItem({ path: '/billing/consumption?filter=active', links: mockLinks, }), ).toBe('/billing/consumption') }) it('should handle hash fragments', () => { expect( findBestMatchingItem({ path: '/billing/consumption#section', links: mockLinks, }), ).toBe('/billing/consumption') }) it('should handle both query and hash', () => { expect( findBestMatchingItem({ path: '/billing/consumption?filter=active#section', links: mockLinks, }), ).toBe('/billing/consumption') }) }) describe('edge cases', () => { it('should return undefined for no match', () => { expect(findBestMatchingItem({ path: '/unknown', links: mockLinks })).toBe( undefined, ) }) it('should return undefined for empty links array', () => { expect(findBestMatchingItem({ path: '/billing', links: [] })).toBe( undefined, ) }) it('should return undefined for undefined path', () => { expect(findBestMatchingItem({ path: undefined, links: mockLinks })).toBe( undefined, ) }) it('should return undefined for empty path', () => { expect(findBestMatchingItem({ path: '', links: mockLinks })).toBe( undefined, ) }) }) describe('real-world billing scenario', () => { it('should correctly match billing overview', () => { expect(findBestMatchingItem({ path: '/billing', links: mockLinks })).toBe( '/billing', ) }) it('should correctly match billing consumption', () => { expect( findBestMatchingItem({ path: '/billing/consumption', links: mockLinks, }), ).toBe('/billing/consumption') }) it('should match consumption tab for consumption details', () => { expect( findBestMatchingItem({ path: '/billing/consumption/monthly', links: mockLinks, }), ).toBe('/billing/consumption') }) }) }) describe('isActiveHref', () => { describe('exact matches', () => { it('should return true for exact match', () => { expect(isActiveHref({ currentPath: '/billing', href: '/billing' })).toBe( true, ) }) it('should return true for exact match with query params', () => { expect( isActiveHref({ currentPath: '/billing?param=value', href: '/billing' }), ).toBe(true) }) it('should return true for exact match with hash', () => { expect( isActiveHref({ currentPath: '/billing#section', href: '/billing' }), ).toBe(true) }) }) describe('prefix matches', () => { it('should return true for prefix match', () => { expect( isActiveHref({ currentPath: '/billing/consumption', href: '/billing' }), ).toBe(true) }) it('should return true for deeper prefix match', () => { expect( isActiveHref({ currentPath: '/billing/consumption/details', href: '/billing/consumption', }), ).toBe(true) }) it('should return false for non-matching paths', () => { expect( isActiveHref({ currentPath: '/billing', href: '/billing/consumption' }), ).toBe(false) }) }) describe('edge cases', () => { it('should return false for undefined path', () => { expect(isActiveHref({ currentPath: undefined, href: '/billing' })).toBe( false, ) }) it('should return false for undefined href', () => { expect(isActiveHref({ currentPath: '/billing', href: undefined })).toBe( false, ) }) it('should return false for null path', () => { expect(isActiveHref({ currentPath: null, href: '/billing' })).toBe(false) }) }) }) describe('useActiveNavigation', () => { describe('without links (legacy behavior)', () => { it('should initialize with correct active item', () => { const { result } = renderHook(() => useActiveNavigation('/feeds')) expect(result.current.activeItem).toBe('/feeds') }) it('should initialize with root path for "/"', () => { const { result } = renderHook(() => useActiveNavigation('/')) expect(result.current.activeItem).toBe('/') }) it('should update active item when path changes', () => { const { result, rerender } = renderHook( ({ path }) => useActiveNavigation(path), { initialProps: { path: '/feeds' } }, ) expect(result.current.activeItem).toBe('/feeds') rerender({ path: '/streams' }) expect(result.current.activeItem).toBe('/streams') }) it('should handle path changes with query parameters', () => { const { result, rerender } = renderHook( ({ path }) => useActiveNavigation(path), { initialProps: { path: '/feeds' } }, ) expect(result.current.activeItem).toBe('/feeds') rerender({ path: '/feeds?param=value' }) expect(result.current.activeItem).toBe('/feeds') }) it('should handle path changes with hash fragments', () => { const { result, rerender } = renderHook( ({ path }) => useActiveNavigation(path), { initialProps: { path: '/feeds' } }, ) expect(result.current.activeItem).toBe('/feeds') rerender({ path: '/feeds#section' }) expect(result.current.activeItem).toBe('/feeds') }) it('should handle multi-level path changes', () => { const { result, rerender } = renderHook( ({ path }) => useActiveNavigation(path), { initialProps: { path: '/feeds/ethereum' } }, ) expect(result.current.activeItem).toBe('/feeds') rerender({ path: '/streams/bitcoin' }) expect(result.current.activeItem).toBe('/streams') }) it('should provide setActiveItem function', () => { const { result } = renderHook(() => useActiveNavigation('/feeds')) expect(typeof result.current.setActiveItem).toBe('function') }) it('should handle empty or invalid paths', () => { const { result } = renderHook(() => useActiveNavigation('')) expect(result.current.activeItem).toBe(undefined) const { result: result2 } = renderHook(() => useActiveNavigation(null as any), ) expect(result2.current.activeItem).toBe(undefined) }) it('should handle undefined path (no active item)', () => { const { result } = renderHook(() => useActiveNavigation(undefined)) expect(result.current.activeItem).toBe(undefined) }) }) describe('with links (new behavior)', () => { const mockLinks = [ { href: '/billing', name: 'Overview' }, { href: '/billing/consumption', name: 'Consumption' }, { href: '/billing/credits', name: 'Credits' }, ] it('should match exact billing overview', () => { const { result } = renderHook(() => useActiveNavigation('/billing', mockLinks), ) expect(result.current.activeItem).toBe('/billing') }) it('should match exact billing consumption', () => { const { result } = renderHook(() => useActiveNavigation('/billing/consumption', mockLinks), ) expect(result.current.activeItem).toBe('/billing/consumption') }) it('should match most specific tab for nested paths', () => { const { result } = renderHook(() => useActiveNavigation('/billing/consumption/details', mockLinks), ) expect(result.current.activeItem).toBe('/billing/consumption') }) it('should handle query parameters', () => { const { result } = renderHook(() => useActiveNavigation('/billing/consumption?filter=active', mockLinks), ) expect(result.current.activeItem).toBe('/billing/consumption') }) it('should return undefined for no match', () => { const { result } = renderHook(() => useActiveNavigation('/unknown', mockLinks), ) expect(result.current.activeItem).toBe(undefined) }) it('should update when path changes', () => { const { result, rerender } = renderHook( ({ path }) => useActiveNavigation(path, mockLinks), { initialProps: { path: '/billing' } }, ) expect(result.current.activeItem).toBe('/billing') rerender({ path: '/billing/consumption' }) expect(result.current.activeItem).toBe('/billing/consumption') }) }) describe('real-world billing scenarios', () => { const billingLinks = [ { href: '/billing', name: 'Overview' }, { href: '/billing/consumption', name: 'Consumption' }, { href: '/billing/credits', name: 'Credits' }, ] it('should select Overview tab for /billing', () => { const { result } = renderHook(() => useActiveNavigation('/billing', billingLinks), ) expect(result.current.activeItem).toBe('/billing') }) it('should select Consumption tab for /billing/consumption', () => { const { result } = renderHook(() => useActiveNavigation('/billing/consumption', billingLinks), ) expect(result.current.activeItem).toBe('/billing/consumption') }) it('should select Consumption tab for /billing/consumption/details', () => { const { result } = renderHook(() => useActiveNavigation('/billing/consumption/details', billingLinks), ) expect(result.current.activeItem).toBe('/billing/consumption') }) it('should select Overview tab for /billing/settings (no specific match)', () => { const { result } = renderHook(() => useActiveNavigation('/billing/settings', billingLinks), ) expect(result.current.activeItem).toBe('/billing') }) it('should handle billing with query parameters', () => { const { result } = renderHook(() => useActiveNavigation( '/billing/consumption?filter=monthly', billingLinks, ), ) expect(result.current.activeItem).toBe('/billing/consumption') }) it('should handle billing with hash fragments', () => { const { result } = renderHook(() => useActiveNavigation('/billing/credits#purchase', billingLinks), ) expect(result.current.activeItem).toBe('/billing/credits') }) }) describe('isActive function', () => { it('should return correct active state', () => { const { result } = renderHook(() => useActiveNavigation('/billing')) expect(result.current.isActive('/billing')).toBe(true) expect(result.current.isActive('/billing/consumption')).toBe(false) expect(result.current.isActive('/streams')).toBe(false) }) it('should handle undefined href', () => { const { result } = renderHook(() => useActiveNavigation('/billing')) expect(result.current.isActive(undefined)).toBe(false) }) }) describe('Trailing slash handling', () => { const links = [ { href: '/billing', name: 'Billing' }, { href: '/billing/consumption', name: 'Consumption' }, { href: '/billing/credits', name: 'Credits' }, ] it('should normalize paths with trailing slashes', () => { expect(normalizePath({ path: '/billing/' })).toBe('/billing') expect(normalizePath({ path: '/billing/consumption/' })).toBe( '/billing/consumption', ) expect(normalizePath({ path: '/' })).toBe('/') expect(normalizePath({ path: '/billing?param=value' })).toBe('/billing') expect(normalizePath({ path: '/billing#section' })).toBe('/billing') }) it('should handle exact matches with trailing slashes', () => { expect(isActiveHref({ currentPath: '/billing/', href: '/billing' })).toBe( true, ) expect(isActiveHref({ currentPath: '/billing', href: '/billing/' })).toBe( true, ) expect( isActiveHref({ currentPath: '/billing/', href: '/billing/' }), ).toBe(true) }) it('should handle prefix matches with trailing slashes', () => { expect( isActiveHref({ currentPath: '/billing/consumption/', href: '/billing', }), ).toBe(true) expect( isActiveHref({ currentPath: '/billing/consumption', href: '/billing/', }), ).toBe(true) expect( isActiveHref({ currentPath: '/billing/consumption/', href: '/billing/consumption', }), ).toBe(true) }) it('should work with findBestMatchingItem and trailing slashes', () => { expect(findBestMatchingItem({ path: '/billing/', links })).toBe( '/billing', ) expect( findBestMatchingItem({ path: '/billing/consumption/', links }), ).toBe('/billing/consumption') expect(findBestMatchingItem({ path: '/billing/credits/', links })).toBe( '/billing/credits', ) }) it('should work with useActiveNavigation hook and trailing slashes', () => { const { result } = renderHook(() => useActiveNavigation('/billing/', links), ) expect(result.current.activeItem).toBe('/billing') expect(result.current.isActive('/billing')).toBe(true) expect(result.current.isActive('/billing/')).toBe(true) expect(result.current.isActive('/billing/consumption')).toBe(false) }) it('should handle mixed trailing slash scenarios', () => { const linksWithTrailingSlashes = [ { href: '/billing/', name: 'Billing' }, { href: '/billing/consumption', name: 'Consumption' }, { href: '/billing/credits/', name: 'Credits' }, ] const { result } = renderHook(() => useActiveNavigation('/billing/consumption', linksWithTrailingSlashes), ) expect(result.current.activeItem).toBe('/billing/consumption') expect(result.current.isActive('/billing/')).toBe(true) expect(result.current.isActive('/billing')).toBe(true) expect(result.current.isActive('/billing/consumption')).toBe(true) }) it('should handle root path correctly', () => { expect(normalizePath({ path: '/' })).toBe('/') expect(isActiveHref({ currentPath: '/', href: '/' })).toBe(true) expect(isActiveHref({ currentPath: '/home', href: '/' })).toBe(false) }) it('should handle empty and undefined paths', () => { expect(normalizePath({ path: '' })).toBe(undefined) expect(normalizePath({ path: undefined })).toBe(undefined) expect(isActiveHref({ currentPath: '', href: '/billing' })).toBe(false) expect(isActiveHref({ currentPath: undefined, href: '/billing' })).toBe( false, ) }) }) })