1import { jest } from '@jest/globals'; 2import { render, screen } from '@testing-library/react'; 3import mockRouter from 'next-router-mock'; 4import { MemoryRouterProvider } from 'next-router-mock/MemoryRouterProvider'; 5import { u as node } from 'unist-builder'; 6import { visit } from 'unist-util-visit'; 7 8import { findActiveRoute, Navigation } from './Navigation'; 9import { NavigationNode } from './types'; 10 11jest.mock('next/router', () => mockRouter); 12 13/** A set of navigation nodes to test with */ 14const nodes: NavigationNode[] = [ 15 node('section', { name: 'Get started' }, [ 16 node('page', { name: 'Introduction', href: '/introduction' }), 17 node('page', { name: 'Create a new app', href: '/introduction/create-new-app' }), 18 node('page', { name: 'Errors and debugging', href: '/introduction/not-that-great-tbh' }), 19 ]), 20 node('section', { name: 'Tutorial' }, [ 21 node('group', { name: 'First steps' }, [ 22 node('page', { name: 'Styling text', href: '/tutorial/first-steps/styling-text' }), 23 node('page', { name: 'Adding an image', href: '/tutorial/first-steps/adding-images' }), 24 node('page', { name: 'Creating a button', href: '/tutorial/creating-button' }), 25 ]), 26 node('group', { name: 'Building apps' }, [ 27 node('page', { name: 'Building for store', href: '/build/eas-build' }), 28 node('page', { name: 'Submitting to store', href: '/build/eas-submit' }), 29 ]), 30 node('group', { name: 'Parallel universe', hidden: true }, [ 31 node('page', { name: 'Create Flutter apps', href: '/parallel-universe/flutter' }), 32 node('page', { name: 'Create websites', href: '/parallel-universe/ionic' }), 33 node('page', { name: 'Create broken apps', href: '/parallel-universe/microsoft-uwp' }), 34 ]), 35 ]), 36]; 37 38describe(Navigation, () => { 39 it('renders pages', () => { 40 const section = getNode(nodes, { name: 'Get started' }); 41 render( 42 <MemoryRouterProvider> 43 <Navigation routes={children(section)} /> 44 </MemoryRouterProvider> 45 ); 46 // Get started -> 47 expect(screen.getByText('Introduction')).toBeInTheDocument(); 48 expect(screen.getByText('Create a new app')).toBeInTheDocument(); 49 expect(screen.getByText('Errors and debugging')).toBeInTheDocument(); 50 }); 51 52 it('renders pages inside groups', () => { 53 const section = getNode(nodes, { name: 'Tutorial' }); 54 render( 55 <MemoryRouterProvider> 56 <Navigation routes={children(section)} /> 57 </MemoryRouterProvider> 58 ); 59 // Tutorial -> 60 expect(screen.getByText('Building apps')).toBeInTheDocument(); 61 // Tutorial -> Building apps -> 62 expect(screen.getByText('Building for store')).toBeInTheDocument(); 63 expect(screen.getByText('Submitting to store')).toBeInTheDocument(); 64 }); 65 66 it('renders pages inside groups inside sections', () => { 67 render( 68 <MemoryRouterProvider> 69 <Navigation routes={nodes} /> 70 </MemoryRouterProvider> 71 ); 72 // Get started -> 73 expect(screen.getByText('Introduction')).toBeInTheDocument(); 74 // Tutorial -> First steps -> 75 expect(screen.getByText('Adding an image')).toBeInTheDocument(); 76 // Tutorial -> Building apps -> 77 expect(screen.getByText('Submitting to store')).toBeInTheDocument(); 78 }); 79}); 80 81describe(findActiveRoute, () => { 82 it('finds active page in list', () => { 83 const section = getNode(nodes, { name: 'Get started' }); 84 expect(findActiveRoute(children(section), '/introduction/create-new-app')).toMatchObject({ 85 page: getNode(section, { name: 'Create a new app' }), 86 group: null, 87 section: null, 88 }); 89 }); 90 91 it('finds active page and group in list', () => { 92 const section = getNode(nodes, { name: 'Tutorial' }); 93 expect(findActiveRoute(children(section), '/build/eas-submit')).toMatchObject({ 94 page: getNode(section, { name: 'Submitting to store' }), 95 group: getNode(section, { name: 'Building apps' }), 96 section: null, 97 }); 98 }); 99 100 it('finds active page, group, and section in list', () => { 101 expect(findActiveRoute(nodes, '/tutorial/first-steps/styling-text')).toMatchObject({ 102 page: getNode(nodes, { name: 'Styling text' }), 103 group: getNode(nodes, { name: 'First steps' }), 104 section: getNode(nodes, { name: 'Tutorial' }), 105 }); 106 }); 107 108 it('skips hidden navigation node', () => { 109 expect(findActiveRoute(nodes, '/parallel-universe/microsoft-uwp')).toMatchObject({ 110 page: null, 111 group: null, 112 section: null, 113 }); 114 }); 115}); 116 117/** Helper function to find the first node that matches the predicate */ 118function getNode( 119 list: NavigationNode | NavigationNode[] | null, 120 predicate: Partial<NavigationNode> | ((node: NavigationNode) => boolean) 121): NavigationNode | null { 122 let result = null; 123 const tree = Array.isArray(list) ? node('root', list) : list || node('root'); 124 visit(tree, predicate as any, node => { 125 result = node; 126 }); 127 return result; 128} 129 130/** Helper function to pull children from the node, if any */ 131function children(node: NavigationNode | null) { 132 switch (node?.type) { 133 case 'section': 134 case 'group': 135 return node.children; 136 default: 137 return []; 138 } 139} 140