/** * Tests for NewTopicButton component. */ import { describe, it, expect, vi } from 'vitest' import { render, screen } from '@testing-library/react' import { axe } from 'vitest-axe' import { NewTopicButton } from './new-topic-button' // Mock next/link to render a plain anchor vi.mock('next/link', () => ({ default: ({ children, href, ...props }: { children: React.ReactNode; href: string } & Record) => ( {children} ), })) // Default: authenticated user const mockUseAuth = vi.fn(() => ({ isAuthenticated: true, isLoading: false, user: { did: 'did:plc:test', handle: 'test.bsky.social' } as Record | null, })) vi.mock('@/hooks/use-auth', () => ({ useAuth: () => mockUseAuth(), })) describe('NewTopicButton', () => { describe('header variant', () => { it('renders a link to /new', () => { render() const link = screen.getByRole('link', { name: /new discussion/i }) expect(link).toHaveAttribute('href', '/new') }) it('displays "New Discussion" text', () => { render() expect(screen.getByText('New Discussion')).toBeInTheDocument() }) it('renders an icon', () => { const { container } = render() const svg = container.querySelector('svg') expect(svg).toBeInTheDocument() expect(svg).toHaveAttribute('aria-hidden', 'true') }) it('passes axe accessibility check', async () => { const { container } = render() const results = await axe(container) expect(results).toHaveNoViolations() }) }) describe('category variant', () => { it('renders a link with category query param', () => { render() const link = screen.getByRole('link', { name: /new in general/i }) expect(link).toHaveAttribute('href', '/new?category=general') }) it('displays category name in button text', () => { render( ) expect(screen.getByText('New in Help & Support')).toBeInTheDocument() }) it('encodes category slug in URL', () => { render( ) const link = screen.getByRole('link') expect(link).toHaveAttribute('href', '/new?category=help%20%26%20support') }) it('falls back to header variant when categorySlug is missing', () => { render() const link = screen.getByRole('link', { name: /new discussion/i }) expect(link).toHaveAttribute('href', '/new') }) it('falls back to header variant when categoryName is missing', () => { render() const link = screen.getByRole('link', { name: /new discussion/i }) expect(link).toHaveAttribute('href', '/new') }) it('passes axe accessibility check', async () => { const { container } = render( ) const results = await axe(container) expect(results).toHaveNoViolations() }) }) describe('auth state', () => { it('returns null when not authenticated', () => { mockUseAuth.mockReturnValueOnce({ isAuthenticated: false, isLoading: false, user: null, }) const { container } = render() expect(container.innerHTML).toBe('') }) it('returns null while auth is loading', () => { mockUseAuth.mockReturnValueOnce({ isAuthenticated: false, isLoading: true, user: null, }) const { container } = render() expect(container.innerHTML).toBe('') }) }) describe('className prop', () => { it('applies custom className to header variant', () => { render() const link = screen.getByRole('link') expect(link.className).toContain('ml-4') }) it('applies custom className to category variant', () => { render( ) const link = screen.getByRole('link') expect(link.className).toContain('mt-2') }) }) })