/** * 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: 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('') }) }) })