import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest' import { flushPromises, mount } from '@vue/test-utils' import UsageView from '../UsageView.vue' const { list, getStats, getSnapshotV2, getById } = vi.hoisted(() => { vi.stubGlobal('localStorage', { getItem: vi.fn(() => null), setItem: vi.fn(), removeItem: vi.fn(), }) return { list: vi.fn(), getStats: vi.fn(), getSnapshotV2: vi.fn(), getById: vi.fn(), } }) const messages: Record = { 'admin.dashboard.timeRange': 'Time Range', 'admin.dashboard.day': 'Day', 'admin.dashboard.hour': 'Hour', 'admin.usage.failedToLoadUser': 'Failed to load user', } const formatLocalDate = (date: Date): string => { const year = date.getFullYear() const month = String(date.getMonth() + 1).padStart(2, '0') const day = String(date.getDate()).padStart(2, '0') return `${year}-${month}-${day}` } vi.mock('@/api/admin', () => ({ adminAPI: { usage: { list, getStats, }, dashboard: { getSnapshotV2, }, users: { getById, }, }, })) vi.mock('@/api/admin/usage', () => ({ adminUsageAPI: { list: vi.fn(), }, })) vi.mock('@/stores/app', () => ({ useAppStore: () => ({ showError: vi.fn(), showWarning: vi.fn(), showSuccess: vi.fn(), showInfo: vi.fn(), }), })) vi.mock('@/utils/format', () => ({ formatReasoningEffort: (value: string | null | undefined) => value ?? '-', })) vi.mock('vue-i18n', async () => { const actual = await vi.importActual('vue-i18n') return { ...actual, useI18n: () => ({ t: (key: string) => messages[key] ?? key, }), } }) vi.mock('vue-router', () => ({ useRoute: () => ({ query: {} }) })) const AppLayoutStub = { template: '
' } const UsageFiltersStub = { template: '
' } const ModelDistributionChartStub = { props: ['metric'], emits: ['update:metric'], template: `
{{ metric }}
`, } const GroupDistributionChartStub = { props: ['metric'], emits: ['update:metric'], template: `
{{ metric }}
`, } describe('admin UsageView distribution metric toggles', () => { beforeEach(() => { vi.useFakeTimers() list.mockReset() getStats.mockReset() getSnapshotV2.mockReset() getById.mockReset() list.mockResolvedValue({ items: [], total: 0, pages: 0, }) getStats.mockResolvedValue({ total_requests: 0, total_input_tokens: 0, total_output_tokens: 0, total_cache_tokens: 0, total_tokens: 0, total_cost: 0, total_actual_cost: 0, average_duration_ms: 0, }) getSnapshotV2.mockResolvedValue({ trend: [], models: [], groups: [], }) }) afterEach(() => { vi.useRealTimers() }) it('keeps model and group metric toggles independent without refetching chart data', async () => { const wrapper = mount(UsageView, { global: { stubs: { AppLayout: AppLayoutStub, UsageStatsCards: true, UsageFilters: UsageFiltersStub, UsageTable: true, UsageExportProgress: true, UsageCleanupDialog: true, UserBalanceHistoryModal: true, Pagination: true, Select: true, DateRangePicker: true, Icon: true, TokenUsageTrend: true, ModelDistributionChart: ModelDistributionChartStub, GroupDistributionChart: GroupDistributionChartStub, }, }, }) vi.advanceTimersByTime(120) await flushPromises() expect(getSnapshotV2).toHaveBeenCalledTimes(1) const now = new Date() const yesterday = new Date(now.getTime() - 24 * 60 * 60 * 1000) expect(getSnapshotV2).toHaveBeenCalledWith(expect.objectContaining({ start_date: formatLocalDate(yesterday), end_date: formatLocalDate(now), granularity: 'hour' })) const modelChart = wrapper.find('[data-test="model-chart"]') const groupChart = wrapper.find('[data-test="group-chart"]') expect(modelChart.find('.metric').text()).toBe('tokens') expect(groupChart.find('.metric').text()).toBe('tokens') await modelChart.find('.switch-metric').trigger('click') await flushPromises() expect(modelChart.find('.metric').text()).toBe('actual_cost') expect(groupChart.find('.metric').text()).toBe('tokens') expect(getSnapshotV2).toHaveBeenCalledTimes(1) await groupChart.find('.switch-metric').trigger('click') await flushPromises() expect(modelChart.find('.metric').text()).toBe('actual_cost') expect(groupChart.find('.metric').text()).toBe('actual_cost') expect(getSnapshotV2).toHaveBeenCalledTimes(1) }) })