/**
* Tests for AppContext - app-context.tsx
*
* Tests app configuration, navigation state, and user context.
*/
import { describe, it, expect, vi } from 'vitest'
import { render, screen } from '@testing-library/react'
import { AppProvider, useApp, type AppContextValue } from '../../context/app-context'
import type { AppConfig, NavGroup, UserIdentity } from '../../types'
import { Home, Settings, Users } from 'lucide-react'
// Helper component to consume app context
function AppConsumer({ onApp }: { onApp: (app: AppContextValue) => void }) {
const app = useApp()
onApp(app)
return (
{app.config.name}
{app.config.description ?? 'none'}
{app.config.basePath ?? 'none'}
{app.navigation.length}
{app.user?.fullName ?? 'none'}
{app.isLoading?.toString() ?? 'undefined'}
)
}
describe('AppContext', () => {
describe('AppProvider', () => {
it('should provide app config to children', () => {
const config: AppConfig = {
name: 'Admin Dashboard',
description: 'Manage your application',
basePath: '/admin',
}
let appValue: AppContextValue | undefined
render(
{ appValue = app }} />
)
expect(appValue).toBeDefined()
expect(screen.getByTestId('app-name').textContent).toBe('Admin Dashboard')
expect(screen.getByTestId('app-description').textContent).toBe('Manage your application')
expect(screen.getByTestId('app-base-path').textContent).toBe('/admin')
})
it('should provide empty navigation by default', () => {
const config: AppConfig = { name: 'Test App' }
render(
{}} />
)
expect(screen.getByTestId('nav-count').textContent).toBe('0')
})
it('should provide navigation groups', () => {
const config: AppConfig = { name: 'Test App' }
const navigation: NavGroup[] = [
{
label: 'Main',
items: [
{ title: 'Dashboard', url: '/dashboard', icon: Home },
{ title: 'Users', url: '/users', icon: Users },
],
},
{
label: 'Settings',
items: [
{ title: 'Settings', url: '/settings', icon: Settings },
],
},
]
let appValue: AppContextValue | undefined
render(
{ appValue = app }} />
)
expect(screen.getByTestId('nav-count').textContent).toBe('2')
expect(appValue!.navigation[0].label).toBe('Main')
expect(appValue!.navigation[0].items.length).toBe(2)
expect(appValue!.navigation[1].label).toBe('Settings')
})
it('should provide user identity when authenticated', () => {
const config: AppConfig = { name: 'Test App' }
const user: UserIdentity = {
id: 'user-123',
fullName: 'John Doe',
email: 'john@example.com',
avatar: 'https://example.com/avatar.jpg',
}
let appValue: AppContextValue | undefined
render(
{ appValue = app }} />
)
expect(screen.getByTestId('user-name').textContent).toBe('John Doe')
expect(appValue!.user?.id).toBe('user-123')
expect(appValue!.user?.email).toBe('john@example.com')
expect(appValue!.user?.avatar).toBe('https://example.com/avatar.jpg')
})
it('should indicate no user when not authenticated', () => {
const config: AppConfig = { name: 'Test App' }
render(
{}} />
)
expect(screen.getByTestId('user-name').textContent).toBe('none')
})
it('should provide loading state', () => {
const config: AppConfig = { name: 'Test App' }
render(
{}} />
)
expect(screen.getByTestId('is-loading').textContent).toBe('true')
})
it('should indicate not loading by default', () => {
const config: AppConfig = { name: 'Test App' }
render(
{}} />
)
expect(screen.getByTestId('is-loading').textContent).toBe('undefined')
})
it('should handle config with logo', () => {
const config: AppConfig = {
name: 'Test App',
logo:
,
}
let appValue: AppContextValue | undefined
render(
{ appValue = app }} />
)
expect(appValue!.config.logo).toBeDefined()
})
it('should support nested navigation items', () => {
const config: AppConfig = { name: 'Test App' }
const navigation: NavGroup[] = [
{
label: 'Main',
items: [
{
title: 'Dashboard',
url: '/dashboard',
items: [
{ title: 'Overview', url: '/dashboard/overview' },
{ title: 'Analytics', url: '/dashboard/analytics' },
],
},
],
},
]
let appValue: AppContextValue | undefined
render(
{ appValue = app }} />
)
expect(appValue!.navigation[0].items[0].items?.length).toBe(2)
expect(appValue!.navigation[0].items[0].items?.[0].title).toBe('Overview')
})
it('should support active state on nav items', () => {
const config: AppConfig = { name: 'Test App' }
const navigation: NavGroup[] = [
{
label: 'Main',
items: [
{ title: 'Dashboard', url: '/dashboard', isActive: true },
{ title: 'Users', url: '/users', isActive: false },
],
},
]
let appValue: AppContextValue | undefined
render(
{ appValue = app }} />
)
expect(appValue!.navigation[0].items[0].isActive).toBe(true)
expect(appValue!.navigation[0].items[1].isActive).toBe(false)
})
})
describe('useApp', () => {
it('should throw error when used outside AppProvider', () => {
const consoleError = vi.spyOn(console, 'error').mockImplementation(() => {})
expect(() => {
render( {}} />)
}).toThrow('useApp must be used within an AppProvider')
consoleError.mockRestore()
})
})
describe('user identity edge cases', () => {
it('should handle user with minimal fields', () => {
const config: AppConfig = { name: 'Test App' }
const user: UserIdentity = { id: 'user-456' }
let appValue: AppContextValue | undefined
render(
{ appValue = app }} />
)
expect(appValue!.user?.id).toBe('user-456')
expect(appValue!.user?.fullName).toBeUndefined()
expect(appValue!.user?.email).toBeUndefined()
})
it('should handle user with custom properties', () => {
const config: AppConfig = { name: 'Test App' }
const user: UserIdentity = {
id: 'user-789',
fullName: 'Jane Doe',
role: 'admin',
department: 'Engineering',
}
let appValue: AppContextValue | undefined
render(
{ appValue = app }} />
)
expect(appValue!.user?.role).toBe('admin')
expect(appValue!.user?.department).toBe('Engineering')
})
})
describe('config edge cases', () => {
it('should handle config with only required fields', () => {
const config: AppConfig = { name: 'Minimal App' }
render(
{}} />
)
expect(screen.getByTestId('app-name').textContent).toBe('Minimal App')
expect(screen.getByTestId('app-description').textContent).toBe('none')
expect(screen.getByTestId('app-base-path').textContent).toBe('none')
})
it('should handle config with empty base path', () => {
const config: AppConfig = { name: 'Test App', basePath: '' }
let appValue: AppContextValue | undefined
render(
{ appValue = app }} />
)
expect(appValue!.config.basePath).toBe('')
})
})
})