Barazo default frontend barazo.forum
at main 154 lines 4.9 kB view raw
1/** 2 * Tests for NewTopicButton component. 3 */ 4 5import { describe, it, expect, vi } from 'vitest' 6import { render, screen } from '@testing-library/react' 7import { axe } from 'vitest-axe' 8import { NewTopicButton } from './new-topic-button' 9 10// Mock next/link to render a plain anchor 11vi.mock('next/link', () => ({ 12 default: ({ 13 children, 14 href, 15 ...props 16 }: { children: React.ReactNode; href: string } & Record<string, unknown>) => ( 17 <a href={href} {...props}> 18 {children} 19 </a> 20 ), 21})) 22 23// Default: authenticated user 24const mockUseAuth = vi.fn(() => ({ 25 isAuthenticated: true, 26 isLoading: false, 27 user: { did: 'did:plc:test', handle: 'test.bsky.social' } as Record<string, unknown> | null, 28})) 29 30vi.mock('@/hooks/use-auth', () => ({ 31 useAuth: () => mockUseAuth(), 32})) 33 34describe('NewTopicButton', () => { 35 describe('header variant', () => { 36 it('renders a link to /new', () => { 37 render(<NewTopicButton variant="header" />) 38 const link = screen.getByRole('link', { name: /new discussion/i }) 39 expect(link).toHaveAttribute('href', '/new') 40 }) 41 42 it('displays "New Discussion" text', () => { 43 render(<NewTopicButton variant="header" />) 44 expect(screen.getByText('New Discussion')).toBeInTheDocument() 45 }) 46 47 it('renders an icon', () => { 48 const { container } = render(<NewTopicButton variant="header" />) 49 const svg = container.querySelector('svg') 50 expect(svg).toBeInTheDocument() 51 expect(svg).toHaveAttribute('aria-hidden', 'true') 52 }) 53 54 it('passes axe accessibility check', async () => { 55 const { container } = render(<NewTopicButton variant="header" />) 56 const results = await axe(container) 57 expect(results).toHaveNoViolations() 58 }) 59 }) 60 61 describe('category variant', () => { 62 it('renders a link with category query param', () => { 63 render(<NewTopicButton variant="category" categorySlug="general" categoryName="General" />) 64 const link = screen.getByRole('link', { name: /new in general/i }) 65 expect(link).toHaveAttribute('href', '/new?category=general') 66 }) 67 68 it('displays category name in button text', () => { 69 render( 70 <NewTopicButton 71 variant="category" 72 categorySlug="help-support" 73 categoryName="Help & Support" 74 /> 75 ) 76 expect(screen.getByText('New in Help & Support')).toBeInTheDocument() 77 }) 78 79 it('encodes category slug in URL', () => { 80 render( 81 <NewTopicButton 82 variant="category" 83 categorySlug="help & support" 84 categoryName="Help & Support" 85 /> 86 ) 87 const link = screen.getByRole('link') 88 expect(link).toHaveAttribute('href', '/new?category=help%20%26%20support') 89 }) 90 91 it('falls back to header variant when categorySlug is missing', () => { 92 render(<NewTopicButton variant="category" categoryName="General" />) 93 const link = screen.getByRole('link', { name: /new discussion/i }) 94 expect(link).toHaveAttribute('href', '/new') 95 }) 96 97 it('falls back to header variant when categoryName is missing', () => { 98 render(<NewTopicButton variant="category" categorySlug="general" />) 99 const link = screen.getByRole('link', { name: /new discussion/i }) 100 expect(link).toHaveAttribute('href', '/new') 101 }) 102 103 it('passes axe accessibility check', async () => { 104 const { container } = render( 105 <NewTopicButton variant="category" categorySlug="general" categoryName="General" /> 106 ) 107 const results = await axe(container) 108 expect(results).toHaveNoViolations() 109 }) 110 }) 111 112 describe('auth state', () => { 113 it('returns null when not authenticated', () => { 114 mockUseAuth.mockReturnValueOnce({ 115 isAuthenticated: false, 116 isLoading: false, 117 user: null, 118 }) 119 const { container } = render(<NewTopicButton variant="header" />) 120 expect(container.innerHTML).toBe('') 121 }) 122 123 it('returns null while auth is loading', () => { 124 mockUseAuth.mockReturnValueOnce({ 125 isAuthenticated: false, 126 isLoading: true, 127 user: null, 128 }) 129 const { container } = render(<NewTopicButton variant="header" />) 130 expect(container.innerHTML).toBe('') 131 }) 132 }) 133 134 describe('className prop', () => { 135 it('applies custom className to header variant', () => { 136 render(<NewTopicButton variant="header" className="ml-4" />) 137 const link = screen.getByRole('link') 138 expect(link.className).toContain('ml-4') 139 }) 140 141 it('applies custom className to category variant', () => { 142 render( 143 <NewTopicButton 144 variant="category" 145 categorySlug="general" 146 categoryName="General" 147 className="mt-2" 148 /> 149 ) 150 const link = screen.getByRole('link') 151 expect(link.className).toContain('mt-2') 152 }) 153 }) 154})