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