Barazo default frontend barazo.forum
at main 198 lines 6.0 kB view raw
1/** 2 * Tests for admin layout component. 3 */ 4 5import { describe, it, expect, vi } from 'vitest' 6import { render, screen } from '@testing-library/react' 7import userEvent from '@testing-library/user-event' 8import { axe } from 'vitest-axe' 9import { AdminLayout } from './admin-layout' 10 11vi.mock('next/navigation', () => ({ 12 useRouter: () => ({ push: vi.fn() }), 13 usePathname: () => '/admin', 14})) 15 16vi.mock('next/link', () => ({ 17 default: ({ 18 children, 19 href, 20 ...props 21 }: { children: React.ReactNode; href: string } & Record<string, unknown>) => ( 22 <a href={href} {...props}> 23 {children} 24 </a> 25 ), 26})) 27 28vi.mock('next/image', () => ({ 29 default: (props: Record<string, unknown>) => { 30 // eslint-disable-next-line @next/next/no-img-element, jsx-a11y/alt-text 31 return <img {...props} /> 32 }, 33})) 34 35describe('AdminLayout', () => { 36 it('renders sidebar navigation', () => { 37 render( 38 <AdminLayout> 39 <p>Admin content</p> 40 </AdminLayout> 41 ) 42 expect(screen.getByRole('navigation', { name: /admin/i })).toBeInTheDocument() 43 }) 44 45 it('renders main content area', () => { 46 render( 47 <AdminLayout> 48 <p>Admin content</p> 49 </AdminLayout> 50 ) 51 expect(screen.getByText('Admin content')).toBeInTheDocument() 52 }) 53 54 it('renders all navigation links', () => { 55 render( 56 <AdminLayout> 57 <p>Content</p> 58 </AdminLayout> 59 ) 60 expect(screen.getByRole('link', { name: /dashboard/i })).toBeInTheDocument() 61 expect(screen.getByRole('link', { name: /categories/i })).toBeInTheDocument() 62 expect(screen.getByRole('link', { name: /moderation/i })).toBeInTheDocument() 63 expect(screen.getByRole('link', { name: /settings/i })).toBeInTheDocument() 64 expect(screen.getByRole('link', { name: /content ratings/i })).toBeInTheDocument() 65 expect(screen.getByRole('link', { name: /users/i })).toBeInTheDocument() 66 expect(screen.getByRole('link', { name: /plugins/i })).toBeInTheDocument() 67 }) 68 69 it('highlights current page link', () => { 70 render( 71 <AdminLayout> 72 <p>Content</p> 73 </AdminLayout> 74 ) 75 const dashboardLink = screen.getByRole('link', { name: /dashboard/i }) 76 expect(dashboardLink).toHaveAttribute('aria-current', 'page') 77 }) 78 79 it('renders back to forum link', () => { 80 render( 81 <AdminLayout> 82 <p>Content</p> 83 </AdminLayout> 84 ) 85 expect(screen.getByRole('link', { name: /back to forum/i })).toHaveAttribute('href', '/') 86 }) 87 88 it('renders main landmark', () => { 89 render( 90 <AdminLayout> 91 <p>Content</p> 92 </AdminLayout> 93 ) 94 expect(screen.getByRole('main')).toBeInTheDocument() 95 }) 96 97 it('passes axe accessibility check', async () => { 98 const { container } = render( 99 <AdminLayout> 100 <h1>Admin Page</h1> 101 <p>Content</p> 102 </AdminLayout> 103 ) 104 const results = await axe(container) 105 expect(results).toHaveNoViolations() 106 }) 107 108 describe('mobile sidebar drawer', () => { 109 it('renders a menu button for opening the mobile sidebar', () => { 110 render( 111 <AdminLayout> 112 <p>Content</p> 113 </AdminLayout> 114 ) 115 expect(screen.getByRole('button', { name: /open admin menu/i })).toBeInTheDocument() 116 }) 117 118 it('opens the drawer when the menu button is clicked', async () => { 119 const user = userEvent.setup() 120 render( 121 <AdminLayout> 122 <p>Content</p> 123 </AdminLayout> 124 ) 125 126 const menuButton = screen.getByRole('button', { name: /open admin menu/i }) 127 await user.click(menuButton) 128 129 // The drawer should show an accessible dialog with the navigation 130 expect(screen.getByRole('dialog', { name: /admin menu/i })).toBeInTheDocument() 131 }) 132 133 it('closes the drawer when a nav link is clicked', async () => { 134 const user = userEvent.setup() 135 render( 136 <AdminLayout> 137 <p>Content</p> 138 </AdminLayout> 139 ) 140 141 await user.click(screen.getByRole('button', { name: /open admin menu/i })) 142 expect(screen.getByRole('dialog', { name: /admin menu/i })).toBeInTheDocument() 143 144 // Click a nav link inside the drawer 145 const drawerNav = screen.getByRole('dialog', { name: /admin menu/i }) 146 const categoriesLink = drawerNav.querySelector('a[href="/admin/categories"]') 147 expect(categoriesLink).not.toBeNull() 148 await user.click(categoriesLink!) 149 150 expect(screen.queryByRole('dialog', { name: /admin menu/i })).not.toBeInTheDocument() 151 }) 152 153 it('closes the drawer when the close button is clicked', async () => { 154 const user = userEvent.setup() 155 render( 156 <AdminLayout> 157 <p>Content</p> 158 </AdminLayout> 159 ) 160 161 await user.click(screen.getByRole('button', { name: /open admin menu/i })) 162 expect(screen.getByRole('dialog', { name: /admin menu/i })).toBeInTheDocument() 163 164 await user.click(screen.getByRole('button', { name: /close admin menu/i })) 165 expect(screen.queryByRole('dialog', { name: /admin menu/i })).not.toBeInTheDocument() 166 }) 167 168 it('renders back to forum link in the mobile drawer', async () => { 169 const user = userEvent.setup() 170 render( 171 <AdminLayout> 172 <p>Content</p> 173 </AdminLayout> 174 ) 175 176 await user.click(screen.getByRole('button', { name: /open admin menu/i })) 177 const drawer = screen.getByRole('dialog', { name: /admin menu/i }) 178 const backLink = drawer.querySelector('a[href="/"]') 179 expect(backLink).not.toBeNull() 180 }) 181 182 it('passes axe accessibility check with drawer open', async () => { 183 const user = userEvent.setup() 184 const { container } = render( 185 <AdminLayout> 186 <h1>Admin Page</h1> 187 <p>Content</p> 188 </AdminLayout> 189 ) 190 191 await user.click(screen.getByRole('button', { name: /open admin menu/i })) 192 expect(screen.getByRole('dialog', { name: /admin menu/i })).toBeInTheDocument() 193 194 const results = await axe(container) 195 expect(results).toHaveNoViolations() 196 }) 197 }) 198})