Barazo default frontend
barazo.forum
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})