Barazo default frontend
barazo.forum
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})