ed43399b24
- Install @types/bun and @types/jsdom for proper type definitions - Fix mock function typing in test files - Create jest-dom.d.ts to properly extend Bun's Matchers interface - Remove MockedFunction imports (not supported in Bun's types) - Cast global.fetch and useSession mocks to any where needed - Fix mock.module return types to match expected signatures This resolves TypeScript errors that were preventing proper code completion and type checking in IDEs.
200 lines
5.4 KiB
TypeScript
200 lines
5.4 KiB
TypeScript
/**
|
|
* Epic 1: Authentication & User Management
|
|
* Component Test: Navigation
|
|
*
|
|
* Tests the navigation component for proper display based on session state
|
|
*/
|
|
|
|
import { describe, it, expect, mock, beforeEach, afterEach } from 'bun:test'
|
|
import { render, screen, waitFor } from '@testing-library/react'
|
|
import Navigation from '@/components/Navigation'
|
|
|
|
// Mock next/link
|
|
mock.module('next/link', () => ({
|
|
default: ({ children, href }: { children: React.ReactNode; href: string }) => (
|
|
<a href={href}>{children}</a>
|
|
),
|
|
}))
|
|
|
|
// Mock the SessionProvider
|
|
mock.module('@/components/SessionProvider', () => ({
|
|
useSession: mock(() => ({ session: null, loading: false, refreshSession: mock(() => {}) })),
|
|
}))
|
|
|
|
// Mock the auth-client
|
|
mock.module('@/lib/auth-client', () => ({
|
|
authClient: {
|
|
signOut: mock(() => {}),
|
|
},
|
|
}))
|
|
|
|
// Mock fetch for role API call
|
|
global.fetch = mock(async () => new Response()) as any
|
|
|
|
import { useSession as useSessionOriginal } from '@/components/SessionProvider'
|
|
const useSession = useSessionOriginal as any
|
|
|
|
describe('Epic 1: Navigation Component', () => {
|
|
beforeEach(() => {
|
|
// Don't clear all mocks as it might affect bun-setup.ts
|
|
// Set up default fetch mock
|
|
(global.fetch as any).mockImplementation?.(async (url: any) => {
|
|
if (url?.toString().includes('/api/users/')) {
|
|
return new Response(JSON.stringify({ role: 'player' }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
})
|
|
}
|
|
return new Response(JSON.stringify({}), { status: 200 })
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
// Don't clear mocks - let them persist for other test files
|
|
// Navigation relies on module mocks set up at file level
|
|
})
|
|
|
|
it('renders the logo and basic links when not logged in', () => {
|
|
(useSession).mockReturnValue({
|
|
session: null,
|
|
loading: false,
|
|
refreshSession: mock(() => {}),
|
|
})
|
|
|
|
render(<Navigation />)
|
|
|
|
expect(screen.getByText('EuchreCamp')).toBeInTheDocument()
|
|
expect(screen.getByText('Rankings')).toBeInTheDocument()
|
|
expect(screen.getByText('Sign in')).toBeInTheDocument()
|
|
expect(screen.getByText('Sign up')).toBeInTheDocument()
|
|
})
|
|
|
|
it('shows user menu when logged in', async () => {
|
|
(useSession).mockReturnValue({
|
|
session: {
|
|
user: {
|
|
id: 'user-123',
|
|
email: 'test@example.com',
|
|
name: 'Test User',
|
|
role: 'player'
|
|
},
|
|
session: { token: 'abc123' },
|
|
},
|
|
loading: false,
|
|
refreshSession: mock(() => {}),
|
|
})
|
|
|
|
render(<Navigation />)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Test User')).toBeInTheDocument()
|
|
})
|
|
|
|
expect(screen.getByText('Sign out')).toBeInTheDocument()
|
|
expect(screen.getByText('Tournaments')).toBeInTheDocument()
|
|
})
|
|
|
|
it('shows Tournaments link when logged in', async () => {
|
|
(useSession).mockReturnValue({
|
|
session: {
|
|
user: {
|
|
id: 'user-123',
|
|
email: 'test@example.com',
|
|
name: 'Test User',
|
|
role: 'player'
|
|
},
|
|
session: { token: 'abc123' },
|
|
},
|
|
loading: false,
|
|
refreshSession: mock(() => {}),
|
|
})
|
|
|
|
render(<Navigation />)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Tournaments')).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
it('shows admin link for club_admin role', async () => {
|
|
(useSession).mockReturnValue({
|
|
session: {
|
|
user: {
|
|
id: 'admin-123',
|
|
email: 'admin@example.com',
|
|
name: 'Admin User',
|
|
role: 'club_admin',
|
|
},
|
|
session: { token: 'abc123' },
|
|
},
|
|
loading: false,
|
|
refreshSession: mock(() => {}),
|
|
});
|
|
|
|
// Mock fetch to return club_admin role
|
|
(global.fetch as any).mockImplementation(async (url: any) => {
|
|
if (url?.toString().includes('/api/users/admin-123/role')) {
|
|
return new Response(JSON.stringify({ role: 'club_admin' }), {
|
|
status: 200,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
})
|
|
}
|
|
return new Response(JSON.stringify({}), { status: 200 })
|
|
})
|
|
|
|
render(<Navigation />)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Admin')).toBeInTheDocument()
|
|
}, { timeout: 3000 })
|
|
expect(screen.getByText('Tournaments')).toBeInTheDocument()
|
|
})
|
|
|
|
it('hides admin link for non-admin users', async () => {
|
|
(useSession).mockReturnValue({
|
|
session: {
|
|
user: {
|
|
id: 'player-123',
|
|
email: 'player@example.com',
|
|
name: 'Player User',
|
|
role: 'player',
|
|
},
|
|
session: { token: 'abc123' },
|
|
},
|
|
loading: false,
|
|
refreshSession: mock(() => {}),
|
|
});
|
|
|
|
// Mock fetch to return player role (already set in beforeEach, but explicit here)
|
|
(global.fetch as any).mockImplementation(async (url: any) => {
|
|
if (url?.toString().includes('/api/users/')) {
|
|
return {
|
|
json: () => Promise.resolve({ role: 'player' }),
|
|
} as Response
|
|
}
|
|
return {
|
|
json: () => Promise.resolve({}),
|
|
} as Response
|
|
})
|
|
|
|
render(<Navigation />)
|
|
|
|
await waitFor(() => {
|
|
expect(screen.getByText('Tournaments')).toBeInTheDocument()
|
|
})
|
|
expect(screen.queryByText('Admin')).not.toBeInTheDocument()
|
|
})
|
|
|
|
it('shows loading state', () => {
|
|
(useSession).mockReturnValue({
|
|
session: null,
|
|
loading: true,
|
|
refreshSession: mock(() => {}),
|
|
})
|
|
|
|
render(<Navigation />)
|
|
|
|
expect(screen.getByText('Loading...')).toBeInTheDocument()
|
|
})
|
|
})
|