strat-gameplay-webapp/frontend-sba/tests/unit/store/ui.spec.ts
Cal Corum 2381456189 test: Skip unstable test suites
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-22 20:18:33 -06:00

419 lines
11 KiB
TypeScript

/**
* UI Store Tests
*
* Tests for toast notifications, modal stack management, and UI state.
*/
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'
import { setActivePinia, createPinia } from 'pinia'
import { useUiStore } from '~/store/ui'
describe('useUiStore', () => {
beforeEach(() => {
// Ensure process.env exists before creating Pinia
if (!process.env) {
(process as any).env = {}
}
process.env.NODE_ENV = 'test'
// Create fresh Pinia instance for each test
setActivePinia(createPinia())
vi.useFakeTimers()
})
afterEach(() => {
vi.restoreAllMocks()
vi.useRealTimers()
})
describe('initialization', () => {
it('initializes with empty state', () => {
const store = useUiStore()
expect(store.toasts).toEqual([])
expect(store.modals).toEqual([])
expect(store.isSidebarOpen).toBe(false)
expect(store.isFullscreen).toBe(false)
expect(store.globalLoading).toBe(false)
expect(store.globalLoadingMessage).toBeNull()
})
it('has correct computed properties on init', () => {
const store = useUiStore()
expect(store.hasToasts).toBe(false)
expect(store.hasModals).toBe(false)
expect(store.currentModal).toBeNull()
})
})
describe('toast management', () => {
it('adds toast with showToast', () => {
const store = useUiStore()
const id = store.showToast('Test message', 'info', 5000)
expect(store.toasts.length).toBe(1)
expect(store.toasts[0]).toMatchObject({
id,
type: 'info',
message: 'Test message',
duration: 5000,
})
expect(store.hasToasts).toBe(true)
})
it('adds success toast with showSuccess', () => {
const store = useUiStore()
const id = store.showSuccess('Success!')
expect(store.toasts.length).toBe(1)
expect(store.toasts[0].type).toBe('success')
expect(store.toasts[0].message).toBe('Success!')
expect(store.toasts[0].duration).toBe(5000) // default
})
it('adds error toast with showError', () => {
const store = useUiStore()
const id = store.showError('Error occurred')
expect(store.toasts.length).toBe(1)
expect(store.toasts[0].type).toBe('error')
expect(store.toasts[0].message).toBe('Error occurred')
expect(store.toasts[0].duration).toBe(7000) // default for errors
})
it('adds warning toast with showWarning', () => {
const store = useUiStore()
const id = store.showWarning('Warning!')
expect(store.toasts.length).toBe(1)
expect(store.toasts[0].type).toBe('warning')
expect(store.toasts[0].duration).toBe(6000) // default for warnings
})
it('adds info toast with showInfo', () => {
const store = useUiStore()
const id = store.showInfo('Info message')
expect(store.toasts.length).toBe(1)
expect(store.toasts[0].type).toBe('info')
expect(store.toasts[0].duration).toBe(5000)
})
it('auto-removes toast after duration', () => {
const store = useUiStore()
store.showToast('Auto-remove', 'info', 1000)
expect(store.toasts.length).toBe(1)
// Fast-forward time
vi.advanceTimersByTime(1000)
expect(store.toasts.length).toBe(0)
})
it('does not auto-remove toast with duration 0', () => {
const store = useUiStore()
store.showToast('Persistent', 'info', 0)
expect(store.toasts.length).toBe(1)
// Fast-forward time
vi.advanceTimersByTime(10000)
// Should still be there
expect(store.toasts.length).toBe(1)
})
it('removes specific toast by ID', () => {
const store = useUiStore()
const id1 = store.showToast('Toast 1', 'info', 0)
const id2 = store.showToast('Toast 2', 'info', 0)
const id3 = store.showToast('Toast 3', 'info', 0)
expect(store.toasts.length).toBe(3)
store.removeToast(id2)
expect(store.toasts.length).toBe(2)
expect(store.toasts.find(t => t.id === id2)).toBeUndefined()
expect(store.toasts.find(t => t.id === id1)).toBeDefined()
expect(store.toasts.find(t => t.id === id3)).toBeDefined()
})
it('clears all toasts', () => {
const store = useUiStore()
store.showToast('Toast 1', 'info', 0)
store.showToast('Toast 2', 'info', 0)
store.showToast('Toast 3', 'info', 0)
expect(store.toasts.length).toBe(3)
store.clearToasts()
expect(store.toasts.length).toBe(0)
expect(store.hasToasts).toBe(false)
})
it('supports toast with action callback', () => {
const store = useUiStore()
const actionCallback = vi.fn()
const id = store.showToast('Toast with action', 'info', 0, {
label: 'Undo',
callback: actionCallback,
})
expect(store.toasts[0].action).toBeDefined()
expect(store.toasts[0].action?.label).toBe('Undo')
// Simulate action click
store.toasts[0].action?.callback()
expect(actionCallback).toHaveBeenCalledOnce()
})
})
describe('modal management', () => {
it('opens modal', () => {
const store = useUiStore()
const id = store.openModal('SubstitutionModal', { teamId: 1 })
expect(store.modals.length).toBe(1)
expect(store.modals[0]).toMatchObject({
id,
component: 'SubstitutionModal',
props: { teamId: 1 },
})
expect(store.hasModals).toBe(true)
expect(store.currentModal).toEqual(store.modals[0])
})
it('supports modal stack (LIFO)', () => {
const store = useUiStore()
const id1 = store.openModal('Modal1')
const id2 = store.openModal('Modal2')
const id3 = store.openModal('Modal3')
expect(store.modals.length).toBe(3)
expect(store.currentModal?.component).toBe('Modal3') // Last opened is current
})
it('closes current modal (top of stack)', () => {
const store = useUiStore()
store.openModal('Modal1')
store.openModal('Modal2')
store.openModal('Modal3')
expect(store.modals.length).toBe(3)
store.closeModal()
expect(store.modals.length).toBe(2)
expect(store.currentModal?.component).toBe('Modal2')
})
it('calls onClose callback when closing modal', () => {
const store = useUiStore()
const onCloseSpy = vi.fn()
store.openModal('TestModal', {}, onCloseSpy)
expect(store.modals.length).toBe(1)
store.closeModal()
expect(onCloseSpy).toHaveBeenCalledOnce()
expect(store.modals.length).toBe(0)
})
it('closes specific modal by ID', () => {
const store = useUiStore()
const id1 = store.openModal('Modal1')
const id2 = store.openModal('Modal2')
const id3 = store.openModal('Modal3')
expect(store.modals.length).toBe(3)
store.closeModalById(id2)
expect(store.modals.length).toBe(2)
expect(store.modals.find(m => m.id === id2)).toBeUndefined()
expect(store.modals.find(m => m.id === id1)).toBeDefined()
expect(store.modals.find(m => m.id === id3)).toBeDefined()
})
it('calls onClose when closing modal by ID', () => {
const store = useUiStore()
const onCloseSpy = vi.fn()
const id = store.openModal('TestModal', {}, onCloseSpy)
store.closeModalById(id)
expect(onCloseSpy).toHaveBeenCalledOnce()
})
it('closes all modals', () => {
const store = useUiStore()
const onClose1 = vi.fn()
const onClose2 = vi.fn()
const onClose3 = vi.fn()
store.openModal('Modal1', {}, onClose1)
store.openModal('Modal2', {}, onClose2)
store.openModal('Modal3', {}, onClose3)
expect(store.modals.length).toBe(3)
store.closeAllModals()
expect(store.modals.length).toBe(0)
expect(store.hasModals).toBe(false)
expect(store.currentModal).toBeNull()
// All onClose callbacks should be called
expect(onClose1).toHaveBeenCalledOnce()
expect(onClose2).toHaveBeenCalledOnce()
expect(onClose3).toHaveBeenCalledOnce()
})
})
describe('UI state management', () => {
it('toggles sidebar', () => {
const store = useUiStore()
expect(store.isSidebarOpen).toBe(false)
store.toggleSidebar()
expect(store.isSidebarOpen).toBe(true)
store.toggleSidebar()
expect(store.isSidebarOpen).toBe(false)
})
it('sets sidebar state directly', () => {
const store = useUiStore()
store.setSidebarOpen(true)
expect(store.isSidebarOpen).toBe(true)
store.setSidebarOpen(false)
expect(store.isSidebarOpen).toBe(false)
})
it('sets fullscreen state', () => {
const store = useUiStore()
store.setFullscreen(true)
expect(store.isFullscreen).toBe(true)
store.setFullscreen(false)
expect(store.isFullscreen).toBe(false)
})
it('shows loading overlay with message', () => {
const store = useUiStore()
store.showLoading('Processing...')
expect(store.globalLoading).toBe(true)
expect(store.globalLoadingMessage).toBe('Processing...')
})
it('shows loading overlay without message', () => {
const store = useUiStore()
store.showLoading()
expect(store.globalLoading).toBe(true)
expect(store.globalLoadingMessage).toBeNull()
})
it('hides loading overlay', () => {
const store = useUiStore()
store.showLoading('Loading...')
expect(store.globalLoading).toBe(true)
store.hideLoading()
expect(store.globalLoading).toBe(false)
expect(store.globalLoadingMessage).toBeNull()
})
})
describe('edge cases', () => {
it('handles removing non-existent toast gracefully', () => {
const store = useUiStore()
store.showToast('Toast', 'info', 0)
expect(store.toasts.length).toBe(1)
// Try to remove non-existent toast
store.removeToast('non-existent-id')
// Should not crash, toast count unchanged
expect(store.toasts.length).toBe(1)
})
it('handles closing non-existent modal gracefully', () => {
const store = useUiStore()
store.openModal('Modal1')
expect(store.modals.length).toBe(1)
// Try to close non-existent modal
store.closeModalById('non-existent-id')
// Should not crash, modal count unchanged
expect(store.modals.length).toBe(1)
})
it('handles closing modal when stack is empty', () => {
const store = useUiStore()
expect(store.modals.length).toBe(0)
// Should not crash
expect(() => store.closeModal()).not.toThrow()
expect(store.modals.length).toBe(0)
})
it('generates unique IDs for toasts', () => {
const store = useUiStore()
const id1 = store.showToast('Toast 1', 'info', 0)
const id2 = store.showToast('Toast 2', 'info', 0)
const id3 = store.showToast('Toast 3', 'info', 0)
// All IDs should be unique
expect(new Set([id1, id2, id3]).size).toBe(3)
})
it('generates unique IDs for modals', () => {
const store = useUiStore()
const id1 = store.openModal('Modal1')
const id2 = store.openModal('Modal2')
const id3 = store.openModal('Modal3')
// All IDs should be unique
expect(new Set([id1, id2, id3]).size).toBe(3)
})
})
})