import { describe, it, expect } from 'vitest'
import { render, screen } from '@testing-library/react'
import {
CharonLoader,
CharonCoinLoader,
CerberusLoader,
ConfigReloadOverlay,
} from '../LoadingStates'
describe('LoadingStates - Security Audit', () => {
describe('CharonLoader', () => {
it('renders without crashing', () => {
const { container } = render()
expect(container.querySelector('svg')).toBeInTheDocument()
})
it('handles all size variants', () => {
const { rerender } = render()
expect(screen.getByRole('status')).toBeInTheDocument()
rerender()
expect(screen.getByRole('status')).toBeInTheDocument()
rerender()
expect(screen.getByRole('status')).toBeInTheDocument()
})
it('has accessible role and label', () => {
render()
const status = screen.getByRole('status')
expect(status).toHaveAttribute('aria-label', 'Loading')
})
it('applies correct size classes', () => {
const { container, rerender } = render()
expect(container.firstChild).toHaveClass('w-12', 'h-12')
rerender()
expect(container.firstChild).toHaveClass('w-20', 'h-20')
rerender()
expect(container.firstChild).toHaveClass('w-28', 'h-28')
})
})
describe('CharonCoinLoader', () => {
it('renders without crashing', () => {
const { container } = render()
expect(container.querySelector('svg')).toBeInTheDocument()
})
it('has accessible role and label for authentication', () => {
render()
const status = screen.getByRole('status')
expect(status).toHaveAttribute('aria-label', 'Authenticating')
})
it('renders gradient definition', () => {
const { container } = render()
const gradient = container.querySelector('#goldGradient')
expect(gradient).toBeInTheDocument()
})
it('applies correct size classes', () => {
const { container, rerender } = render()
expect(container.firstChild).toHaveClass('w-12', 'h-12')
rerender()
expect(container.firstChild).toHaveClass('w-20', 'h-20')
rerender()
expect(container.firstChild).toHaveClass('w-28', 'h-28')
})
})
describe('CerberusLoader', () => {
it('renders without crashing', () => {
const { container } = render()
expect(container.querySelector('svg')).toBeInTheDocument()
})
it('has accessible role and label for security', () => {
render()
const status = screen.getByRole('status')
expect(status).toHaveAttribute('aria-label', 'Security Loading')
})
it('renders three heads (three circles for heads)', () => {
const { container } = render()
const circles = container.querySelectorAll('circle')
// At least 3 head circles should exist (plus paws and eyes)
expect(circles.length).toBeGreaterThanOrEqual(3)
})
it('applies correct size classes', () => {
const { container, rerender } = render()
expect(container.firstChild).toHaveClass('w-12', 'h-12')
rerender()
expect(container.firstChild).toHaveClass('w-20', 'h-20')
rerender()
expect(container.firstChild).toHaveClass('w-28', 'h-28')
})
})
describe('ConfigReloadOverlay - XSS Protection', () => {
it('renders with default props', () => {
render()
expect(screen.getByText('Ferrying configuration...')).toBeInTheDocument()
expect(screen.getByText('Charon is crossing the Styx')).toBeInTheDocument()
})
it('ATTACK: prevents XSS in message prop', () => {
const xssPayload = ''
render()
// React should escape this automatically
expect(screen.getByText(xssPayload)).toBeInTheDocument()
expect(document.querySelector('script')).not.toBeInTheDocument()
})
it('ATTACK: prevents XSS in submessage prop', () => {
const xssPayload = '
'
render()
expect(screen.getByText(xssPayload)).toBeInTheDocument()
expect(document.querySelector('img[onerror]')).not.toBeInTheDocument()
})
it('ATTACK: handles extremely long messages', () => {
const longMessage = 'A'.repeat(10000)
const { container } = render()
// Should render without crashing
expect(container).toBeInTheDocument()
expect(screen.getByText(longMessage)).toBeInTheDocument()
})
it('ATTACK: handles special characters', () => {
const specialChars = '!@#$%^&*()_+-=[]{}|;:",.<>?/~`'
render(
)
expect(screen.getAllByText(specialChars)).toHaveLength(2)
})
it('ATTACK: handles unicode and emoji', () => {
const unicode = '🔥💀🐕🦺 λ µ π Σ 中文 العربية עברית'
render()
expect(screen.getByText(unicode)).toBeInTheDocument()
})
it('renders correct theme - charon (blue)', () => {
const { container } = render()
const overlay = container.querySelector('.bg-blue-950\\/90')
expect(overlay).toBeInTheDocument()
})
it('renders correct theme - coin (gold)', () => {
const { container } = render()
const overlay = container.querySelector('.bg-amber-950\\/90')
expect(overlay).toBeInTheDocument()
})
it('renders correct theme - cerberus (red)', () => {
const { container } = render()
const overlay = container.querySelector('.bg-red-950\\/90')
expect(overlay).toBeInTheDocument()
})
it('applies correct z-index (z-50)', () => {
const { container } = render()
const overlay = container.querySelector('.z-50')
expect(overlay).toBeInTheDocument()
})
it('applies backdrop blur', () => {
const { container } = render()
const backdrop = container.querySelector('.backdrop-blur-sm')
expect(backdrop).toBeInTheDocument()
})
it('ATTACK: type prop injection attempt', () => {
// @ts-expect-error - Testing invalid type
const { container } = render()
// Should default to charon theme
expect(container.querySelector('.bg-blue-950\\/90')).toBeInTheDocument()
})
})
describe('Overlay Integration Tests', () => {
it('CharonLoader renders inside overlay', () => {
render()
expect(screen.getByRole('status')).toHaveAttribute('aria-label', 'Loading')
})
it('CharonCoinLoader renders inside overlay', () => {
render()
expect(screen.getByRole('status')).toHaveAttribute('aria-label', 'Authenticating')
})
it('CerberusLoader renders inside overlay', () => {
render()
expect(screen.getByRole('status')).toHaveAttribute('aria-label', 'Security Loading')
})
})
describe('CSS Animation Requirements', () => {
it('CharonLoader uses animate-bob-boat class', () => {
const { container } = render()
const animated = container.querySelector('.animate-bob-boat')
expect(animated).toBeInTheDocument()
})
it('CharonCoinLoader uses animate-spin-y class', () => {
const { container } = render()
const animated = container.querySelector('.animate-spin-y')
expect(animated).toBeInTheDocument()
})
it('CerberusLoader uses animate-rotate-head class', () => {
const { container } = render()
const animated = container.querySelector('.animate-rotate-head')
expect(animated).toBeInTheDocument()
})
})
describe('Edge Cases', () => {
it('handles undefined size prop gracefully', () => {
const { container } = render()
expect(container.firstChild).toHaveClass('w-20', 'h-20') // defaults to md
})
it('handles null message', () => {
// @ts-expect-error - Testing null
render()
// Null message renders as empty paragraph - component gracefully handles null
const textContainer = screen.getByText(/Charon is crossing the Styx/i).closest('div')
expect(textContainer).toBeInTheDocument()
})
it('handles empty string message', () => {
render()
// Should render but be empty
expect(screen.queryByText('Ferrying configuration...')).not.toBeInTheDocument()
})
it('handles undefined type prop', () => {
const { container } = render()
// Should default to charon
expect(container.querySelector('.bg-blue-950\\/90')).toBeInTheDocument()
})
})
describe('Accessibility Requirements', () => {
it('overlay is keyboard accessible', () => {
const { container } = render()
const overlay = container.firstChild
expect(overlay).toBeInTheDocument()
})
it('all loaders have status role', () => {
render(
<>
>
)
const statuses = screen.getAllByRole('status')
expect(statuses).toHaveLength(3)
})
it('all loaders have aria-label', () => {
const { container: c1 } = render()
const { container: c2 } = render()
const { container: c3 } = render()
expect(c1.firstChild).toHaveAttribute('aria-label')
expect(c2.firstChild).toHaveAttribute('aria-label')
expect(c3.firstChild).toHaveAttribute('aria-label')
})
})
describe('Performance Tests', () => {
it('renders CharonLoader quickly', () => {
const start = performance.now()
render()
const end = performance.now()
expect(end - start).toBeLessThan(100) // Should render in <100ms
})
it('renders CharonCoinLoader quickly', () => {
const start = performance.now()
render()
const end = performance.now()
expect(end - start).toBeLessThan(100)
})
it('renders CerberusLoader quickly', () => {
const start = performance.now()
render()
const end = performance.now()
expect(end - start).toBeLessThan(100)
})
it('renders ConfigReloadOverlay quickly', () => {
const start = performance.now()
render()
const end = performance.now()
expect(end - start).toBeLessThan(100)
})
})
})