/**
* Tests for TopicForm component.
*/
import { describe, it, expect, vi, beforeAll, afterAll, afterEach } from 'vitest'
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { axe } from 'vitest-axe'
import { setupServer } from 'msw/node'
import { handlers } from '@/mocks/handlers'
import { TopicForm } from './topic-form'
vi.mock('@/hooks/use-auth', () => ({
useAuth: () => ({
crossPostScopesGranted: true,
requestCrossPostAuth: vi.fn(),
}),
}))
const server = setupServer(...handlers)
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
describe('TopicForm', () => {
it('renders title input', () => {
render()
expect(screen.getByRole('textbox', { name: 'Title' })).toBeInTheDocument()
})
it('renders category select', () => {
render()
expect(screen.getByRole('combobox', { name: 'Category' })).toBeInTheDocument()
})
it('renders tag input', () => {
render()
expect(screen.getByRole('textbox', { name: /Tags/ })).toBeInTheDocument()
})
it('renders content editor', () => {
render()
expect(screen.getByRole('textbox', { name: 'Content' })).toBeInTheDocument()
})
it('renders cross-post checkboxes', () => {
render()
expect(screen.getByLabelText('Share on Bluesky')).toBeInTheDocument()
expect(screen.getByLabelText('Share on Frontpage')).toBeInTheDocument()
})
it('defaults Bluesky cross-post to checked', () => {
render()
expect(screen.getByLabelText('Share on Bluesky')).toBeChecked()
})
it('defaults Frontpage cross-post to unchecked', () => {
render()
expect(screen.getByLabelText('Share on Frontpage')).not.toBeChecked()
})
it('renders submit button', () => {
render()
expect(screen.getByRole('button', { name: 'Create Topic' })).toBeInTheDocument()
})
it('shows edit mode submit button text', () => {
render(
)
expect(screen.getByRole('button', { name: 'Save Changes' })).toBeInTheDocument()
})
it('validates required title', async () => {
const user = userEvent.setup()
const onSubmit = vi.fn()
render()
await user.click(screen.getByRole('button', { name: 'Create Topic' }))
expect(screen.getByText('Title is required')).toBeInTheDocument()
expect(onSubmit).not.toHaveBeenCalled()
})
it('validates required content', async () => {
const user = userEvent.setup()
const onSubmit = vi.fn()
render()
await user.type(screen.getByRole('textbox', { name: 'Title' }), 'Test Title')
await user.click(screen.getByRole('button', { name: 'Create Topic' }))
expect(screen.getByText('Content is required')).toBeInTheDocument()
expect(onSubmit).not.toHaveBeenCalled()
})
it('validates required category', async () => {
const user = userEvent.setup()
const onSubmit = vi.fn()
render()
await user.type(screen.getByRole('textbox', { name: 'Title' }), 'Test Title')
await user.type(screen.getByRole('textbox', { name: 'Content' }), 'Test content')
await user.click(screen.getByRole('button', { name: 'Create Topic' }))
expect(screen.getByText('Category is required')).toBeInTheDocument()
expect(onSubmit).not.toHaveBeenCalled()
})
it('validates title length', async () => {
const user = userEvent.setup()
const onSubmit = vi.fn()
render()
await user.type(screen.getByRole('textbox', { name: 'Title' }), 'AB')
await user.click(screen.getByRole('button', { name: 'Create Topic' }))
expect(screen.getByText('Title must be at least 3 characters')).toBeInTheDocument()
expect(onSubmit).not.toHaveBeenCalled()
})
it('renders preview tab', async () => {
const user = userEvent.setup()
render()
const previewTab = screen.getByRole('tab', { name: 'Preview' })
expect(previewTab).toBeInTheDocument()
await user.click(previewTab)
expect(screen.getByText('Nothing to preview')).toBeInTheDocument()
})
it('pre-populates form in edit mode', () => {
render(
)
expect(screen.getByRole('textbox', { name: 'Title' })).toHaveValue('Existing Topic')
expect(screen.getByRole('textbox', { name: 'Content' })).toHaveValue('Existing content')
})
it('passes axe accessibility check', async () => {
const { container } = render()
const results = await axe(container)
expect(results).toHaveNoViolations()
})
})