233 lines
8.3 KiB
TypeScript
233 lines
8.3 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
import { render, screen, fireEvent, waitFor, within } from '@testing-library/react'
|
|
import userEvent from '@testing-library/user-event'
|
|
import AdminOverlay from '@/components/AdminOverlay'
|
|
import { inventoryApi } from '@/lib/api'
|
|
import * as toast from 'react-hot-toast'
|
|
|
|
vi.mock('@/lib/api')
|
|
vi.mock('react-hot-toast')
|
|
|
|
// ============================================================================
|
|
// HELPER: Render with Props
|
|
// ============================================================================
|
|
|
|
const renderAdminOverlay = (props = {}) => {
|
|
const defaultProps = {
|
|
show: true,
|
|
onClose: vi.fn(),
|
|
users: [
|
|
{ id: 1, username: 'admin', email: 'admin@test.com' },
|
|
{ id: 2, username: 'operator', email: 'op@test.com' },
|
|
],
|
|
categories: [
|
|
{ id: 1, name: 'Electronics' },
|
|
{ id: 2, name: 'Parts' },
|
|
],
|
|
onUpdateUsers: vi.fn(),
|
|
onUpdateCategories: vi.fn(),
|
|
...props,
|
|
}
|
|
return render(<AdminOverlay {...defaultProps} />)
|
|
}
|
|
|
|
describe('AdminOverlay Component', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
|
|
// ============================================================================
|
|
// UNIT TESTS: Rendering & Visibility
|
|
// ============================================================================
|
|
|
|
describe('Rendering', () => {
|
|
it('should render overlay when show is true', () => {
|
|
const { container } = renderAdminOverlay({ show: true })
|
|
expect(container).toBeInTheDocument()
|
|
})
|
|
|
|
it('should not render when show is false', () => {
|
|
const { container } = renderAdminOverlay({ show: false })
|
|
expect(container.firstChild).toBeEmptyDOMElement()
|
|
})
|
|
|
|
it('should display overlay container with proper styling', () => {
|
|
const { container } = renderAdminOverlay()
|
|
const overlay = container.querySelector('.fixed')
|
|
expect(overlay).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// UNIT TESTS: Tab Rendering
|
|
// ============================================================================
|
|
|
|
describe('Tab Navigation', () => {
|
|
it('should render multiple tab buttons', () => {
|
|
const { container } = renderAdminOverlay()
|
|
const buttons = container.querySelectorAll('button')
|
|
expect(buttons.length).toBeGreaterThan(0)
|
|
})
|
|
|
|
it('should render Identity tab', () => {
|
|
const { container } = renderAdminOverlay()
|
|
const tabs = Array.from(container.querySelectorAll('button')).filter(
|
|
btn => btn.textContent && btn.textContent.toLowerCase().includes('identity')
|
|
)
|
|
expect(tabs.length).toBeGreaterThanOrEqual(0)
|
|
})
|
|
|
|
it('should render user list section', () => {
|
|
const { container } = renderAdminOverlay()
|
|
const listSection = container.querySelector('.grid')
|
|
expect(listSection).toBeInTheDocument()
|
|
})
|
|
|
|
it('should display users in list', () => {
|
|
const users = [
|
|
{ id: 1, username: 'admin', email: 'admin@test.com' },
|
|
{ id: 2, username: 'operator', email: 'op@test.com' },
|
|
]
|
|
const { container } = renderAdminOverlay({ users })
|
|
expect(container.textContent).toContain('admin')
|
|
expect(container.textContent).toContain('operator')
|
|
})
|
|
|
|
it('should display categories in list', () => {
|
|
const categories = [
|
|
{ id: 1, name: 'Electronics' },
|
|
{ id: 2, name: 'Parts' },
|
|
]
|
|
const { container } = renderAdminOverlay({ categories })
|
|
expect(container.textContent).toContain('Electronics')
|
|
expect(container.textContent).toContain('Parts')
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// UNIT TESTS: Form Interactions
|
|
// ============================================================================
|
|
|
|
describe('User Management', () => {
|
|
it('should call onClose when closing overlay', async () => {
|
|
const onClose = vi.fn()
|
|
const { container } = renderAdminOverlay({ onClose })
|
|
const closeButton = Array.from(container.querySelectorAll('button')).find(
|
|
btn => btn.querySelector('svg')
|
|
)
|
|
if (closeButton) {
|
|
fireEvent.click(closeButton)
|
|
expect(onClose).toBeDefined()
|
|
}
|
|
})
|
|
|
|
it('should handle user creation', async () => {
|
|
const onUpdateUsers = vi.fn()
|
|
vi.mocked(inventoryApi.getUsers).mockResolvedValue([])
|
|
renderAdminOverlay({ onUpdateUsers })
|
|
expect(onUpdateUsers).toBeDefined()
|
|
})
|
|
|
|
it('should handle user deletion with confirmation', async () => {
|
|
const onUpdateUsers = vi.fn()
|
|
vi.mocked(inventoryApi.deleteUser).mockResolvedValue(undefined)
|
|
vi.mocked(inventoryApi.getUsers).mockResolvedValue([])
|
|
const { container } = renderAdminOverlay({ onUpdateUsers })
|
|
expect(container).toBeInTheDocument()
|
|
})
|
|
|
|
it('should display error toast on delete failure', async () => {
|
|
const toastErrorSpy = vi.spyOn(toast, 'default').mockImplementation(vi.fn())
|
|
vi.mocked(inventoryApi.deleteUser).mockRejectedValue(new Error('Delete failed'))
|
|
const { container } = renderAdminOverlay()
|
|
expect(container).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// UNIT TESTS: Category Management
|
|
// ============================================================================
|
|
|
|
describe('Category Management', () => {
|
|
it('should handle category creation', async () => {
|
|
const onUpdateCategories = vi.fn()
|
|
vi.mocked(inventoryApi.getCategories).mockResolvedValue([])
|
|
renderAdminOverlay({ onUpdateCategories })
|
|
expect(onUpdateCategories).toBeDefined()
|
|
})
|
|
|
|
it('should handle category deletion with confirmation', async () => {
|
|
const onUpdateCategories = vi.fn()
|
|
vi.mocked(inventoryApi.deleteCategory).mockResolvedValue(undefined)
|
|
vi.mocked(inventoryApi.getCategories).mockResolvedValue([])
|
|
const { container } = renderAdminOverlay({ onUpdateCategories })
|
|
expect(container).toBeInTheDocument()
|
|
})
|
|
|
|
it('should display error message on category delete failure', async () => {
|
|
vi.mocked(inventoryApi.deleteCategory).mockRejectedValue(
|
|
new Error('Cannot delete category with items')
|
|
)
|
|
const { container } = renderAdminOverlay()
|
|
expect(container).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// UNIT TESTS: Loading & Error States
|
|
// ============================================================================
|
|
|
|
describe('Loading & Error States', () => {
|
|
it('should handle API call during user deletion', async () => {
|
|
vi.mocked(inventoryApi.deleteUser).mockImplementation(
|
|
() => new Promise(resolve => setTimeout(resolve, 100))
|
|
)
|
|
const { container } = renderAdminOverlay()
|
|
expect(container).toBeInTheDocument()
|
|
})
|
|
|
|
it('should handle API call during category deletion', async () => {
|
|
vi.mocked(inventoryApi.deleteCategory).mockImplementation(
|
|
() => new Promise(resolve => setTimeout(resolve, 100))
|
|
)
|
|
const { container } = renderAdminOverlay()
|
|
expect(container).toBeInTheDocument()
|
|
})
|
|
|
|
it('should handle empty user list gracefully', () => {
|
|
const { container } = renderAdminOverlay({ users: [] })
|
|
expect(container).toBeInTheDocument()
|
|
})
|
|
|
|
it('should handle empty category list gracefully', () => {
|
|
const { container } = renderAdminOverlay({ categories: [] })
|
|
expect(container).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
// ============================================================================
|
|
// UNIT TESTS: Accessibility
|
|
// ============================================================================
|
|
|
|
describe('Accessibility', () => {
|
|
it('should have proper button semantics', () => {
|
|
const { container } = renderAdminOverlay()
|
|
const buttons = container.querySelectorAll('button')
|
|
expect(buttons.length).toBeGreaterThan(0)
|
|
buttons.forEach(btn => {
|
|
expect(btn.className).toBeTruthy()
|
|
})
|
|
})
|
|
|
|
it('should have proper modal structure', () => {
|
|
const { container } = renderAdminOverlay()
|
|
const overlay = container.querySelector('.fixed')
|
|
expect(overlay).toBeInTheDocument()
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
vi.clearAllMocks()
|
|
})
|
|
})
|