Barazo default frontend
barazo.forum
1/**
2 * Tests for admin moderation page.
3 */
4
5import { describe, it, expect, vi } from 'vitest'
6import { render, screen, waitFor } from '@testing-library/react'
7import userEvent from '@testing-library/user-event'
8import { axe } from 'vitest-axe'
9import AdminModerationPage from './page'
10
11vi.mock('next/navigation', () => ({
12 useRouter: () => ({ push: vi.fn() }),
13 usePathname: () => '/admin/moderation',
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
35vi.mock('@/hooks/use-auth', () => {
36 const mockAuth = {
37 user: {
38 did: 'did:plc:user-jay-001',
39 handle: 'jay.bsky.team',
40 displayName: 'Jay',
41 avatarUrl: null,
42 },
43 isAuthenticated: true,
44 isLoading: false,
45 getAccessToken: () => 'mock-access-token',
46 login: vi.fn(),
47 logout: vi.fn(),
48 setSessionFromCallback: vi.fn(),
49 authFetch: vi.fn(),
50 }
51 return { useAuth: () => mockAuth }
52})
53
54describe('AdminModerationPage', () => {
55 it('renders moderation heading', () => {
56 render(<AdminModerationPage />)
57 expect(screen.getByRole('heading', { name: /moderation/i })).toBeInTheDocument()
58 })
59
60 it('renders tab navigation', () => {
61 render(<AdminModerationPage />)
62 expect(screen.getByRole('tablist')).toBeInTheDocument()
63 expect(screen.getByRole('tab', { name: /reports/i })).toBeInTheDocument()
64 expect(screen.getByRole('tab', { name: /first post/i })).toBeInTheDocument()
65 expect(screen.getByRole('tab', { name: /action log/i })).toBeInTheDocument()
66 expect(screen.getByRole('tab', { name: /reported users/i })).toBeInTheDocument()
67 expect(screen.getByRole('tab', { name: /thresholds/i })).toBeInTheDocument()
68 })
69
70 it('shows reports queue by default', async () => {
71 render(<AdminModerationPage />)
72 await waitFor(() => {
73 expect(screen.getAllByText(/misleading/i).length).toBeGreaterThan(0)
74 })
75 })
76
77 it('highlights potentially illegal reports', async () => {
78 render(<AdminModerationPage />)
79 await waitFor(() => {
80 expect(screen.getAllByText(/potentially illegal/i).length).toBeGreaterThan(0)
81 })
82 })
83
84 it('shows resolve actions on reports', async () => {
85 render(<AdminModerationPage />)
86 await waitFor(() => {
87 expect(screen.getAllByRole('button', { name: /dismiss/i }).length).toBeGreaterThan(0)
88 })
89 })
90
91 it('switches to first post queue tab', async () => {
92 const user = userEvent.setup()
93 render(<AdminModerationPage />)
94 const firstPostTab = screen.getByRole('tab', { name: /first post/i })
95 await user.click(firstPostTab)
96 await waitFor(() => {
97 expect(screen.getByText(/newbie\.bsky\.social/i)).toBeInTheDocument()
98 })
99 })
100
101 it('shows account age for first post queue items', async () => {
102 const user = userEvent.setup()
103 render(<AdminModerationPage />)
104 await user.click(screen.getByRole('tab', { name: /first post/i }))
105 await waitFor(() => {
106 expect(screen.getByText(/2 days/i)).toBeInTheDocument()
107 })
108 })
109
110 it('shows cross-community count for first post items', async () => {
111 const user = userEvent.setup()
112 render(<AdminModerationPage />)
113 await user.click(screen.getByRole('tab', { name: /first post/i }))
114 await waitFor(() => {
115 expect(screen.getByText(/active in 3 other communities/i)).toBeInTheDocument()
116 })
117 })
118
119 it('switches to action log tab', async () => {
120 const user = userEvent.setup()
121 render(<AdminModerationPage />)
122 await user.click(screen.getByRole('tab', { name: /action log/i }))
123 await waitFor(() => {
124 expect(screen.getByText(/pinned/i)).toBeInTheDocument()
125 })
126 })
127
128 it('switches to reported users tab', async () => {
129 const user = userEvent.setup()
130 render(<AdminModerationPage />)
131 await user.click(screen.getByRole('tab', { name: /reported users/i }))
132 await waitFor(() => {
133 expect(screen.getByText(/robin\.bsky\.team/i)).toBeInTheDocument()
134 })
135 })
136
137 it('shows cross-community ban warning for reported users', async () => {
138 const user = userEvent.setup()
139 render(<AdminModerationPage />)
140 await user.click(screen.getByRole('tab', { name: /reported users/i }))
141 await waitFor(() => {
142 expect(screen.getByText(/banned from 2 other communities/i)).toBeInTheDocument()
143 })
144 })
145
146 it('switches to thresholds tab', async () => {
147 const user = userEvent.setup()
148 render(<AdminModerationPage />)
149 await user.click(screen.getByRole('tab', { name: /thresholds/i }))
150 await waitFor(() => {
151 expect(screen.getByLabelText(/auto-block/i)).toBeInTheDocument()
152 })
153 })
154
155 it('shows batch action controls in first post queue', async () => {
156 const user = userEvent.setup()
157 render(<AdminModerationPage />)
158 await user.click(screen.getByRole('tab', { name: /first post/i }))
159 await waitFor(() => {
160 expect(screen.getByText(/newbie\.bsky\.social/i)).toBeInTheDocument()
161 })
162 // Select all checkbox
163 const selectAll = screen.getByRole('checkbox', { name: /select all/i })
164 expect(selectAll).toBeInTheDocument()
165 // Individual checkboxes for each item
166 const itemCheckboxes = screen.getAllByRole('checkbox').filter((cb) => cb !== selectAll)
167 expect(itemCheckboxes.length).toBe(2)
168 })
169
170 it('shows batch approve/reject buttons when items are selected', async () => {
171 const user = userEvent.setup()
172 render(<AdminModerationPage />)
173 await user.click(screen.getByRole('tab', { name: /first post/i }))
174 await waitFor(() => {
175 expect(screen.getByText(/newbie\.bsky\.social/i)).toBeInTheDocument()
176 })
177 // Batch buttons should not be visible when nothing is selected
178 expect(screen.queryByRole('button', { name: /approve selected/i })).not.toBeInTheDocument()
179 // Select all items
180 await user.click(screen.getByRole('checkbox', { name: /select all/i }))
181 // Batch buttons should now be visible
182 expect(screen.getByRole('button', { name: /approve selected/i })).toBeInTheDocument()
183 expect(screen.getByRole('button', { name: /reject selected/i })).toBeInTheDocument()
184 })
185
186 it('shows cross-community ban warning in first post queue', async () => {
187 const user = userEvent.setup()
188 render(<AdminModerationPage />)
189 await user.click(screen.getByRole('tab', { name: /first post/i }))
190 await waitFor(() => {
191 expect(screen.getByText(/banned from 1 other community/i)).toBeInTheDocument()
192 })
193 })
194
195 it('passes axe accessibility check', async () => {
196 const { container } = render(<AdminModerationPage />)
197 await waitFor(() => {
198 expect(screen.getAllByText(/misleading/i).length).toBeGreaterThan(0)
199 })
200 const results = await axe(container)
201 expect(results).toHaveNoViolations()
202 })
203})