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