/**
* Tests for ModerationControls component.
*/
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { axe } from 'vitest-axe'
import { ModerationControls } from './moderation-controls'
describe('ModerationControls', () => {
it('renders nothing when user is not a moderator', () => {
render()
expect(screen.queryByRole('group')).not.toBeInTheDocument()
})
it('renders moderation actions for moderators', () => {
render()
expect(screen.getByRole('group', { name: /moderation/i })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /lock/i })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /pin/i })).toBeInTheDocument()
expect(screen.getByRole('button', { name: /delete/i })).toBeInTheDocument()
})
it('shows confirmation dialog before executing destructive action', async () => {
const user = userEvent.setup()
render()
await user.click(screen.getByRole('button', { name: /delete/i }))
expect(screen.getByRole('alertdialog')).toBeInTheDocument()
})
it('executes action after confirmation', async () => {
const user = userEvent.setup()
const onAction = vi.fn()
render()
await user.click(screen.getByRole('button', { name: /delete/i }))
await user.click(screen.getByRole('button', { name: /confirm/i }))
expect(onAction).toHaveBeenCalledWith('delete')
})
it('cancels action when dialog is dismissed', async () => {
const user = userEvent.setup()
const onAction = vi.fn()
render()
await user.click(screen.getByRole('button', { name: /delete/i }))
await user.click(screen.getByRole('button', { name: /cancel/i }))
expect(onAction).not.toHaveBeenCalled()
expect(screen.queryByRole('alertdialog')).not.toBeInTheDocument()
})
it('reflects locked state', () => {
render()
expect(screen.getByRole('button', { name: /unlock/i })).toBeInTheDocument()
})
it('reflects pinned state', () => {
render()
expect(screen.getByRole('button', { name: /unpin/i })).toBeInTheDocument()
})
it('passes axe accessibility check', async () => {
const { container } = render()
const results = await axe(container)
expect(results).toHaveNoViolations()
})
describe('pin scope selector', () => {
it('should show scope selector in pin confirmation dialog', async () => {
const user = userEvent.setup()
render(
)
await user.click(screen.getByRole('button', { name: /pin topic/i }))
expect(screen.getByLabelText('This category')).toBeInTheDocument()
expect(screen.getByLabelText('Forum-wide')).toBeInTheDocument()
})
it('should not show forum-wide option for non-admin moderators', async () => {
const user = userEvent.setup()
render(
)
await user.click(screen.getByRole('button', { name: /pin topic/i }))
expect(screen.getByLabelText('This category')).toBeInTheDocument()
expect(screen.queryByLabelText('Forum-wide')).not.toBeInTheDocument()
})
it('should default to category scope when isAdmin is not provided', async () => {
const user = userEvent.setup()
render()
await user.click(screen.getByRole('button', { name: /pin topic/i }))
expect(screen.getByLabelText('This category')).toBeInTheDocument()
expect(screen.queryByLabelText('Forum-wide')).not.toBeInTheDocument()
})
it('should pass scope option when confirming pin with category scope', async () => {
const user = userEvent.setup()
const onAction = vi.fn()
render(
)
await user.click(screen.getByRole('button', { name: /pin topic/i }))
// Category is selected by default
await user.click(screen.getByRole('button', { name: /confirm/i }))
expect(onAction).toHaveBeenCalledWith('pin', { scope: 'category' })
})
it('should pass scope option when confirming pin with forum-wide scope', async () => {
const user = userEvent.setup()
const onAction = vi.fn()
render(
)
await user.click(screen.getByRole('button', { name: /pin topic/i }))
await user.click(screen.getByLabelText('Forum-wide'))
await user.click(screen.getByRole('button', { name: /confirm/i }))
expect(onAction).toHaveBeenCalledWith('pin', { scope: 'forum' })
})
it('should not show scope selector for unpin action', async () => {
const user = userEvent.setup()
render(
)
await user.click(screen.getByRole('button', { name: /unpin topic/i }))
expect(screen.queryByLabelText('This category')).not.toBeInTheDocument()
expect(screen.queryByLabelText('Forum-wide')).not.toBeInTheDocument()
})
it('should not pass scope option for non-pin actions', async () => {
const user = userEvent.setup()
const onAction = vi.fn()
render()
await user.click(screen.getByRole('button', { name: /delete/i }))
await user.click(screen.getByRole('button', { name: /confirm/i }))
expect(onAction).toHaveBeenCalledWith('delete')
})
it('should reset scope to category when dialog is cancelled and reopened', async () => {
const user = userEvent.setup()
const onAction = vi.fn()
render(
)
// Open, select forum-wide, cancel
await user.click(screen.getByRole('button', { name: /pin topic/i }))
await user.click(screen.getByLabelText('Forum-wide'))
await user.click(screen.getByRole('button', { name: /cancel/i }))
// Reopen -- should default to category
await user.click(screen.getByRole('button', { name: /pin topic/i }))
const categoryRadio = screen.getByLabelText('This category') as HTMLInputElement
expect(categoryRadio.checked).toBe(true)
})
it('should show warning when pinnedCount is 5 or more', async () => {
const user = userEvent.setup()
render(
)
await user.click(screen.getByRole('button', { name: /pin topic/i }))
expect(screen.getByText(/6 pinned topics/)).toBeInTheDocument()
expect(screen.getByRole('status')).toBeInTheDocument()
})
it('should not show warning when pinnedCount is below 5', async () => {
const user = userEvent.setup()
render(
)
await user.click(screen.getByRole('button', { name: /pin topic/i }))
expect(screen.queryByText(/pinned topics/)).not.toBeInTheDocument()
})
it('should not show warning when pinnedCount is not provided', async () => {
const user = userEvent.setup()
render()
await user.click(screen.getByRole('button', { name: /pin topic/i }))
expect(screen.queryByRole('status')).not.toBeInTheDocument()
})
it('passes axe accessibility check with pin scope selector open', async () => {
const user = userEvent.setup()
const { container } = render(
)
await user.click(screen.getByRole('button', { name: /pin topic/i }))
const results = await axe(container)
expect(results).toHaveNoViolations()
})
})
})