130 lines
3.5 KiB
TypeScript
130 lines
3.5 KiB
TypeScript
import { ReactNode } from 'react'
|
|
import { describe, it, expect, vi } from 'vitest'
|
|
import { render, screen, fireEvent } from '@testing-library/react'
|
|
import { BrowserRouter } from 'react-router-dom'
|
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
|
|
import Layout from '../Layout'
|
|
import { ThemeProvider } from '../../context/ThemeContext'
|
|
|
|
const mockLogout = vi.fn()
|
|
|
|
// Mock AuthContext
|
|
vi.mock('../../hooks/useAuth', () => ({
|
|
useAuth: () => ({
|
|
logout: mockLogout,
|
|
}),
|
|
}))
|
|
|
|
// Mock API
|
|
vi.mock('../../api/health', () => ({
|
|
checkHealth: vi.fn().mockResolvedValue({
|
|
version: '0.1.0',
|
|
git_commit: 'abcdef1',
|
|
}),
|
|
}))
|
|
|
|
const renderWithProviders = (children: ReactNode) => {
|
|
const queryClient = new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
retry: false,
|
|
},
|
|
},
|
|
})
|
|
|
|
return render(
|
|
<QueryClientProvider client={queryClient}>
|
|
<BrowserRouter>
|
|
<ThemeProvider>
|
|
{children}
|
|
</ThemeProvider>
|
|
</BrowserRouter>
|
|
</QueryClientProvider>
|
|
)
|
|
}
|
|
|
|
describe('Layout', () => {
|
|
it('renders the application title', () => {
|
|
renderWithProviders(
|
|
<Layout>
|
|
<div>Test Content</div>
|
|
</Layout>
|
|
)
|
|
|
|
expect(screen.getAllByText('CPM+')[0]).toBeInTheDocument()
|
|
})
|
|
|
|
it('renders all navigation items', () => {
|
|
renderWithProviders(
|
|
<Layout>
|
|
<div>Test Content</div>
|
|
</Layout>
|
|
)
|
|
|
|
expect(screen.getByText('Dashboard')).toBeInTheDocument()
|
|
expect(screen.getByText('Proxy Hosts')).toBeInTheDocument()
|
|
expect(screen.getByText('Remote Servers')).toBeInTheDocument()
|
|
expect(screen.getByText('Certificates')).toBeInTheDocument()
|
|
expect(screen.getByText('Import Caddyfile')).toBeInTheDocument()
|
|
expect(screen.getByText('Settings')).toBeInTheDocument()
|
|
})
|
|
|
|
it('renders children content', () => {
|
|
renderWithProviders(
|
|
<Layout>
|
|
<div data-testid="test-content">Test Content</div>
|
|
</Layout>
|
|
)
|
|
|
|
expect(screen.getByTestId('test-content')).toBeInTheDocument()
|
|
})
|
|
|
|
it('displays version information', async () => {
|
|
renderWithProviders(
|
|
<Layout>
|
|
<div>Test Content</div>
|
|
</Layout>
|
|
)
|
|
|
|
expect(await screen.findByText('Version 0.1.0')).toBeInTheDocument()
|
|
})
|
|
|
|
it('calls logout when logout button is clicked', () => {
|
|
renderWithProviders(
|
|
<Layout>
|
|
<div>Test Content</div>
|
|
</Layout>
|
|
)
|
|
|
|
const logoutButton = screen.getByText('Logout')
|
|
fireEvent.click(logoutButton)
|
|
|
|
expect(mockLogout).toHaveBeenCalled()
|
|
})
|
|
|
|
it('toggles sidebar on mobile', () => {
|
|
renderWithProviders(
|
|
<Layout>
|
|
<div>Test Content</div>
|
|
</Layout>
|
|
)
|
|
|
|
// Initially sidebar is hidden on mobile (by CSS class, but we can check if the toggle button exists)
|
|
// The toggle button has text '☰' when closed
|
|
const toggleButton = screen.getByText('☰')
|
|
fireEvent.click(toggleButton)
|
|
|
|
// Now it should show '✕'
|
|
expect(screen.getByText('✕')).toBeInTheDocument()
|
|
|
|
// And the overlay should be present
|
|
// The overlay has class 'fixed inset-0 bg-black/50 z-20 lg:hidden'
|
|
// We can find it by class or just assume if we click it it closes
|
|
// Let's try to click the overlay. It doesn't have text.
|
|
// We can query by selector if we add a test id or just rely on structure.
|
|
// But let's just click the toggle button again to close.
|
|
fireEvent.click(screen.getByText('✕'))
|
|
expect(screen.getByText('☰')).toBeInTheDocument()
|
|
})
|
|
})
|