import { describe, it, expect, beforeEach, vi } from 'vitest' import { mount } from '@vue/test-utils' import { createPinia, setActivePinia } from 'pinia' import Dashboard from './Dashboard.vue' import { usePackageStore } from '../stores/packages' import { useRealtimeStore, __resetRealtimeSingleton } from '../stores/realtime' describe('Dashboard.vue', () => { beforeEach(() => { setActivePinia(createPinia()) __resetRealtimeSingleton() // Mock the fetch functions to prevent actual API calls const store = usePackageStore() vi.spyOn(store, 'fetchStats').mockResolvedValue() vi.spyOn(store, 'fetchPackages').mockResolvedValue() }) it('renders dashboard component', () => { const wrapper = mount(Dashboard) expect(wrapper.find('h2').text()).toBe('Dashboard') }) it('displays error message when error occurs', async () => { const wrapper = mount(Dashboard) const store = usePackageStore() store.error = 'Failed to load dashboard' await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('Failed to load dashboard') }) it('displays loading state when loading', async () => { const wrapper = mount(Dashboard) const store = usePackageStore() store.loading = true await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('Loading statistics...') }) it('displays overview stats cards', async () => { const wrapper = mount(Dashboard) const store = usePackageStore() store.loading = false store.stats = { registry: '', total_packages: 2, total_size: 3072, max_cache_size: 10737418240, total_downloads: 30, scanned_packages: 2, vulnerable_packages: 0, blocked_packages: 0, } await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('Total Packages') expect(wrapper.text()).toContain('Total Size') expect(wrapper.text()).toContain('Total Downloads') }) it('displays total packages from stats', async () => { const wrapper = mount(Dashboard) const store = usePackageStore() store.loading = false store.stats = { registry: '', total_packages: 100, total_size: 0, max_cache_size: 10737418240, total_downloads: 0, scanned_packages: 0, vulnerable_packages: 0, blocked_packages: 0, } await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('100') }) it('displays total downloads from stats', async () => { const wrapper = mount(Dashboard) const store = usePackageStore() store.loading = false store.stats = { registry: '', total_packages: 0, total_size: 0, max_cache_size: 10737418240, total_downloads: 500, scanned_packages: 0, vulnerable_packages: 0, blocked_packages: 0, } await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('500') }) it('displays total size from stats', async () => { const wrapper = mount(Dashboard) const store = usePackageStore() store.loading = false store.stats = { registry: '', total_packages: 0, total_size: 1048576, // 1 MB max_cache_size: 10737418240, total_downloads: 0, scanned_packages: 0, vulnerable_packages: 0, blocked_packages: 0, } await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('1 MB') }) it('displays recent packages section', async () => { const wrapper = mount(Dashboard) const store = usePackageStore() store.loading = false store.packages = [ { id: '1', registry: 'npm', name: 'test-package', version: '1.0.0', size: 1024, cached_at: '2025-01-01T00:00:00Z', last_accessed: '2025-01-01T00:00:00Z', download_count: 10, }, ] await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('Recent Packages') }) it('shows recent packages sorted by cached_at', async () => { const wrapper = mount(Dashboard) const store = usePackageStore() store.loading = false store.packages = [ { id: '1', registry: 'npm', name: 'old-package', version: '1.0.0', size: 1024, cached_at: '2025-01-01T00:00:00Z', last_accessed: '2025-01-01T00:00:00Z', download_count: 10, }, { id: '2', registry: 'npm', name: 'new-package', version: '1.0.0', size: 1024, cached_at: '2025-01-02T00:00:00Z', last_accessed: '2025-01-02T00:00:00Z', download_count: 5, }, ] await wrapper.vm.$nextTick() const recentPackagesSection = wrapper.text().split('Recent Packages')[1] // new-package should appear before old-package since it's more recent expect(recentPackagesSection.indexOf('new-package')).toBeLessThan( recentPackagesSection.indexOf('old-package') ) }) it('limits recent packages to 10 items', async () => { const wrapper = mount(Dashboard) const store = usePackageStore() store.loading = false store.packages = Array.from({ length: 15 }, (_, i) => ({ id: `${i}`, registry: 'npm', name: `package${i}`, version: '1.0.0', size: 1024, cached_at: `2025-01-${String(i + 1).padStart(2, '0')}T00:00:00Z`, last_accessed: `2025-01-${String(i + 1).padStart(2, '0')}T00:00:00Z`, download_count: i, })) await wrapper.vm.$nextTick() const recentSection = wrapper.text().split('Recent Packages')[1] // Count how many "package" strings appear (each package has the word "package" in its name) const packageCount = (recentSection.match(/package\d+/g) || []).length expect(packageCount).toBeLessThanOrEqual(10) }) it('handles empty packages array', async () => { const wrapper = mount(Dashboard) const store = usePackageStore() store.loading = false store.packages = [] await wrapper.vm.$nextTick() expect(wrapper.text()).toContain('0') expect(wrapper.text()).toContain('0 B') }) it('renders realtime lastStats over polled stats when present', async () => { const wrapper = mount(Dashboard) const store = usePackageStore() const rt = useRealtimeStore() store.loading = false store.stats = { registry: '', total_packages: 10, total_size: 1024, max_cache_size: 10737418240, total_downloads: 5, scanned_packages: 0, vulnerable_packages: 0, blocked_packages: 0, } rt.lastStats = { total_packages: 999, total_size: 2048, total_downloads: 777, scanned_packages: 0, } await wrapper.vm.$nextTick() // Realtime values should be visible (override polled values). expect(wrapper.text()).toContain('999') expect(wrapper.text()).toContain('777') }) })