Files
babymonitor/frontend/tests/unit/hooks/useStats.test.tsx
T

163 lines
5.6 KiB
TypeScript

import { renderHook, waitFor } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { useStats, useSummary } from '../../../src/hooks/useStats';
import * as api from '../../../src/api/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
// Mock the api client
vi.mock('../../../src/api/client', () => ({
fetchDailySummary: vi.fn(),
fetchTimetable: vi.fn(),
fetchWakeSleepTimes: vi.fn(),
fetchSummary: vi.fn(),
}));
const createTestQueryClient = () => new QueryClient({
defaultOptions: {
queries: {
retry: false,
gcTime: 0,
},
},
});
describe('useStats', () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={createTestQueryClient()}>
{children}
</QueryClientProvider>
);
beforeEach(() => {
vi.clearAllMocks();
});
it('should return data and correctly transform daily stats on success', async () => {
const mockDailySummary = {
days: [
{ date: '2024-01-01', total_minutes: 480, day_minutes: 240, night_minutes: 240 },
{ date: '2024-01-02', total_minutes: 540, day_minutes: 300, night_minutes: 240 },
],
};
const mockTimetable = { slots: [] };
const mockWakeupBedtime = { days: [] };
vi.mocked(api.fetchDailySummary).mockResolvedValueOnce(mockDailySummary);
vi.mocked(api.fetchTimetable).mockResolvedValueOnce(mockTimetable);
vi.mocked(api.fetchWakeSleepTimes).mockResolvedValueOnce(mockWakeupBedtime);
const { result } = renderHook(() => useStats('2024-01-01', '2024-01-02'), { wrapper });
// Initially loading
expect(result.current.isLoading).toBe(true);
await waitFor(() => expect(result.current.isLoading).toBe(false));
// Verify raw data
expect(result.current.dailySummary).toEqual(mockDailySummary);
// Verify transformation (total, day/night split in hours)
expect(result.current.transformedDailySummary).toEqual([
{ date: '2024-01-01', daySleep: 4, nightSleep: 4, total: 8 },
{ date: '2024-01-02', daySleep: 5, nightSleep: 4, total: 9 },
]);
expect(result.current.timetable).toEqual(mockTimetable);
expect(result.current.wakeupBedtime).toEqual(mockWakeupBedtime);
expect(result.current.error).toBeNull();
});
it('should handle empty data from the API', async () => {
const mockEmptyDailySummary = { days: [] };
const mockTimetable = { slots: [] };
const mockWakeupBedtime = { days: [] };
vi.mocked(api.fetchDailySummary).mockResolvedValueOnce(mockEmptyDailySummary);
vi.mocked(api.fetchTimetable).mockResolvedValueOnce(mockTimetable);
vi.mocked(api.fetchWakeSleepTimes).mockResolvedValueOnce(mockWakeupBedtime);
const { result } = renderHook(() => useStats('2024-01-01', '2024-01-02'), { wrapper });
await waitFor(() => expect(result.current.isLoading).toBe(false));
expect(result.current.dailySummary).toEqual(mockEmptyDailySummary);
expect(result.current.transformedDailySummary).toEqual([]);
expect(result.current.error).toBeNull();
});
it('should handle error state when the API request fails', async () => {
const error = new Error('API Failure');
vi.mocked(api.fetchDailySummary).mockRejectedValueOnce(error);
vi.mocked(api.fetchTimetable).mockResolvedValueOnce({ slots: [] });
vi.mocked(api.fetchWakeSleepTimes).mockResolvedValueOnce({ days: [] });
const { result } = renderHook(() => useStats('2024-01-01', '2024-01-02'), { wrapper });
await waitFor(() => expect(result.current.isLoading).toBe(false));
expect(result.current.error).toBeDefined();
expect(result.current.error).toEqual(error);
});
it('should handle loading state transitions', async () => {
let resolvePromise: (value: any) => void;
const pendingPromise = new Promise((resolve) => {
resolvePromise = resolve;
});
vi.mocked(api.fetchDailySummary).mockReturnValueOnce(pendingPromise as any);
vi.mocked(api.fetchTimetable).mockResolvedValueOnce({ slots: [] });
vi.mocked(api.fetchWakeSleepTimes).mockResolvedValueOnce({ days: [] });
const { result } = renderHook(() => useStats('2024-01-01', '2024-01-02'), { wrapper });
// Should be loading while promise is pending
expect(result.current.isLoading).toBe(true);
// Resolve the promise
resolvePromise!({ days: [] });
await waitFor(() => expect(result.current.isLoading).toBe(false));
});
});
describe('useSummary', () => {
const wrapper = ({ children }: { children: React.ReactNode }) => (
<QueryClientProvider client={createTestQueryClient()}>
{children}
</QueryClientProvider>
);
beforeEach(() => {
vi.clearAllMocks();
});
it('should return data on success', async () => {
const mockSummary = {
total_sleeps: 10,
avg_sleeps_per_day: 2,
total_sleeps_all_time: 100,
avg_sleeps_per_day_all_time: 2.5,
};
vi.mocked(api.fetchSummary).mockResolvedValueOnce(mockSummary);
const { result } = renderHook(() => useSummary('2024-01-01', '2024-01-02'), { wrapper });
await waitFor(() => expect(result.current.isSuccess).toBe(true));
expect(result.current.data).toEqual(mockSummary);
expect(api.fetchSummary).toHaveBeenCalledWith('2024-01-01', '2024-01-02');
});
it('should handle error on failure', async () => {
const error = new Error('API Failure');
vi.mocked(api.fetchSummary).mockRejectedValueOnce(error);
const { result } = renderHook(() => useSummary('2024-01-01', '2024-01-02'), { wrapper });
await waitFor(() => expect(result.current.isError).toBe(true));
expect(result.current.error).toEqual(error);
});
});