mantimon-tcg/frontend/src/components/ui/LoadingOverlay.spec.ts
Cal Corum 0dc52f74bc Add app shell with layouts and navigation (F0-007)
Create responsive layout system based on route meta:
- DefaultLayout: sidebar (desktop) / bottom tabs (mobile)
- MinimalLayout: centered content for auth pages
- GameLayout: full viewport for Phaser game

Navigation components:
- NavSidebar: desktop sidebar with main nav + user menu
- NavBottomTabs: mobile bottom tab bar

UI components (tied to UI store):
- LoadingOverlay: full-screen overlay with spinner
- ToastContainer: stacked notification toasts

Also adds Vue Router meta type declarations.

Phase F0 is now complete (8/8 tasks).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 11:26:15 -06:00

92 lines
2.8 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest'
import { mount } from '@vue/test-utils'
import { setActivePinia, createPinia } from 'pinia'
import { useUiStore } from '@/stores/ui'
import LoadingOverlay from './LoadingOverlay.vue'
describe('LoadingOverlay', () => {
beforeEach(() => {
setActivePinia(createPinia())
// Clear any teleported content from previous tests
document.body.innerHTML = ''
})
describe('visibility', () => {
it('is not visible when not loading', () => {
/**
* Test that the overlay is hidden by default.
*
* When no loading operations are active, the overlay should
* not be visible to avoid blocking the UI.
*/
mount(LoadingOverlay)
const ui = useUiStore()
expect(ui.isLoading).toBe(false)
expect(document.body.querySelector('.fixed')).toBeNull()
})
it('is visible when loading', async () => {
/**
* Test that the overlay appears when loading starts.
*
* The overlay should be teleported to body and be visible
* when showLoading() is called.
*/
mount(LoadingOverlay)
const ui = useUiStore()
ui.showLoading()
// Wait for the teleport and transition
await new Promise(resolve => setTimeout(resolve, 50))
expect(ui.isLoading).toBe(true)
expect(document.body.querySelector('.fixed')).not.toBeNull()
})
it('shows loading message when provided', async () => {
/**
* Test that loading messages are displayed.
*
* Components can provide context about what's loading,
* which should be shown to the user.
*/
mount(LoadingOverlay)
const ui = useUiStore()
ui.showLoading('Saving game...')
await new Promise(resolve => setTimeout(resolve, 50))
expect(document.body.textContent).toContain('Saving game...')
})
it('hides when loading count reaches zero', async () => {
/**
* Test that the overlay hides after all loading calls complete.
*
* Multiple components may trigger loading simultaneously.
* The overlay should stay visible until all are done.
*/
mount(LoadingOverlay)
const ui = useUiStore()
ui.showLoading()
ui.showLoading() // Second concurrent load
await new Promise(resolve => setTimeout(resolve, 50))
expect(document.body.querySelector('.fixed')).not.toBeNull()
ui.hideLoading() // First load done
await new Promise(resolve => setTimeout(resolve, 50))
expect(document.body.querySelector('.fixed')).not.toBeNull() // Still visible
ui.hideLoading() // Second load done
await new Promise(resolve => setTimeout(resolve, 250)) // Wait for transition
expect(document.body.querySelector('.fixed')).toBeNull() // Now hidden
})
})
})