import { describe, it, expect, beforeEach, vi } from 'vitest' import { renderHook, act } from '@testing-library/react' import { useAnalytics } from './useAnalytics' import * as api from '@/services/api' vi.mock('@/services/api') describe('useAnalytics', () => { beforeEach(() => { vi.clearAllMocks() }) it('initializes with null data', () => { const { result } = renderHook(() => useAnalytics(null)) expect(result.current.data).toBeNull() expect(result.current.isLoading).toBe(false) }) it('fetches analytics data on mount', async () => { const mockData = { workspaceId: 'ws1', childId: 'child1', parentProjections: [], combinedDaysUsed: 10, combinedDaysLeft: 15, combinedExpiringAtB4: 50, } vi.mocked(api.getWorkspaceOverview).mockResolvedValue(mockData as any) renderHook(() => useAnalytics('ws1', 'child1')) await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)) }) expect(api.getWorkspaceOverview).toHaveBeenCalledWith('ws1', 'child1') }) it('handles fetch errors', async () => { vi.mocked(api.getWorkspaceOverview).mockRejectedValue(new Error('API error')) const { result } = renderHook(() => useAnalytics('ws1', 'child1')) await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)) }) expect(result.current.error).toContain('API error') }) it('falls back to a generic message when a non-Error value is thrown', async () => { vi.mocked(api.getWorkspaceOverview).mockRejectedValue('some string failure') const { result } = renderHook(() => useAnalytics('ws1', 'child1')) await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)) }) expect(result.current.error).toBe('Failed to fetch analytics') expect(result.current.data).toBeNull() }) it('does not fetch when workspaceId is null', () => { vi.mocked(api.getWorkspaceOverview).mockResolvedValue({} as any) renderHook(() => useAnalytics(null)) expect(api.getWorkspaceOverview).not.toHaveBeenCalled() }) it('does not fetch when childId is missing', () => { vi.mocked(api.getWorkspaceOverview).mockResolvedValue({} as any) renderHook(() => useAnalytics('ws1')) expect(api.getWorkspaceOverview).not.toHaveBeenCalled() }) it('provides refresh function', async () => { vi.mocked(api.getWorkspaceOverview).mockResolvedValue({} as any) const { result } = renderHook(() => useAnalytics('ws1', 'child1')) await act(async () => { await result.current.refresh() }) expect(api.getWorkspaceOverview).toHaveBeenCalled() }) it('clears stale data instead of leaving it rendered when the workspace/child identity is lost', async () => { const mockData = { workspaceId: 'ws1', childId: 'child1', parentProjections: [], combinedDaysUsed: 76, combinedDaysLeft: 164, combinedExpiringAtB4: 50, } vi.mocked(api.getWorkspaceOverview).mockResolvedValue(mockData as any) const { result, rerender } = renderHook( ({ workspaceId, childId }: { workspaceId: string | null; childId: string | undefined }) => useAnalytics(workspaceId, childId), { initialProps: { workspaceId: 'ws1' as string | null, childId: 'child1' as string | undefined } }, ) await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)) }) expect(result.current.data).toEqual(mockData) // Simulate a logout clearing the workspace/child selection — this must // not leave the previous account's figures on screen. rerender({ workspaceId: null, childId: undefined }) await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)) }) expect(result.current.data).toBeNull() }) })