A try of overhauling the frontend tests, bailed after 24h+
This commit is contained in:
@@ -0,0 +1,312 @@
|
||||
import { renderHook } from '@testing-library/react';
|
||||
import { useSleepSession, useSessions } from '../../../src/hooks/useSleepSession';
|
||||
import * as api from '../../../src/api/client';
|
||||
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { vi, describe, it, expect, beforeEach } from 'vitest';
|
||||
import type { SleepSessionRead, SessionListResponse } from '../../../src/types';
|
||||
|
||||
// Mock the API client
|
||||
vi.mock('../../../src/api/client', () => ({
|
||||
fetchActiveSession: vi.fn(),
|
||||
createSession: vi.fn(),
|
||||
endSession: vi.fn(),
|
||||
fetchSessions: vi.fn(),
|
||||
deleteSession: vi.fn(),
|
||||
updateSession: vi.fn(),
|
||||
}));
|
||||
|
||||
const createTestQueryClient = () => new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
retry: false,
|
||||
gcTime: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={createTestQueryClient()}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
);
|
||||
vi.mock('../../../src/api/client', () => ({
|
||||
fetchActiveSession: vi.fn(),
|
||||
createSession: vi.fn(),
|
||||
endSession: vi.fn(),
|
||||
fetchSessions: vi.fn(),
|
||||
deleteSession: vi.fn(),
|
||||
updateSession: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock React Query as requested
|
||||
vi.mock('@tanstack/react-query', async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import('@tanstack/react-query')>();
|
||||
return {
|
||||
...actual,
|
||||
useQuery: vi.fn(),
|
||||
useMutation: vi.fn(),
|
||||
useQueryClient: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe('useSleepSession and useSessions hooks', () => {
|
||||
const mockInvalidateQueries = vi.fn();
|
||||
const mockQueryClient = { invalidateQueries: mockInvalidateQueries };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
(useQueryClient as any).mockReturnValue(mockQueryClient);
|
||||
});
|
||||
|
||||
describe('useSleepSession', () => {
|
||||
const mockSession: SleepSessionRead = {
|
||||
id: 1,
|
||||
start_time: '2026-06-10T10:00:00Z',
|
||||
end_time: null,
|
||||
notes: 'Testing session',
|
||||
created_at: '2026-06-10T10:00:00Z',
|
||||
updated_at: '2026-06-10T10:00:00Z',
|
||||
};
|
||||
|
||||
it('Test Case 6 (State): should return correct loading and session states', () => {
|
||||
(useQuery as any).mockReturnValue({
|
||||
data: mockSession,
|
||||
isLoading: false,
|
||||
isFetching: false,
|
||||
});
|
||||
|
||||
(useMutation as any).mockImplementation(({ mutationFn }: { mutationFn: (args: any) => Promise<any> }) => ({
|
||||
mutateAsync: vi.fn().mockImplementation(mutationFn),
|
||||
isPending: false,
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() => useSleepSession());
|
||||
|
||||
expect(result.current.activeSession).toEqual(mockSession);
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
it('Test Case 1: should successfully create a session and invalidate queries', async () => {
|
||||
(useQuery as any).mockReturnValue({ data: null, isLoading: false, isFetching: false });
|
||||
(api.createSession as any).mockResolvedValue(mockSession);
|
||||
|
||||
(useMutation as any).mockImplementation(({ mutationFn, onSuccess }: { mutationFn: (args: any) => Promise<any>, onSuccess?: () => void }) => ({
|
||||
mutateAsync: async (args: any) => {
|
||||
const res = await mutationFn(args);
|
||||
if (onSuccess) onSuccess();
|
||||
return res;
|
||||
},
|
||||
isPending: false,
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() => useSleepSession());
|
||||
|
||||
await result.current.createSession({ start_time: '2026-06-10T10:00:00Z' } as any);
|
||||
|
||||
expect(api.createSession).toHaveBeenCalled();
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['activeSession'] });
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['sessions'] });
|
||||
});
|
||||
|
||||
it('Test Case 2: should successfully end a session and invalidate queries', async () => {
|
||||
(useQuery as any).mockReturnValue({ data: mockSession, isLoading: false, isFetching: false });
|
||||
(api.endSession as any).mockResolvedValue({ ...mockSession, end_time: '2026-06-10T12:00:00Z' });
|
||||
|
||||
(useMutation as any).mockImplementation(({ mutationFn, onSuccess }: { mutationFn: (args: any) => Promise<any>, onSuccess?: () => void }) => ({
|
||||
mutateAsync: async (args: any) => {
|
||||
const res = await mutationFn(args);
|
||||
if (onSuccess) onSuccess();
|
||||
return res;
|
||||
},
|
||||
isPending: false,
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() => useSleepSession());
|
||||
|
||||
await result.current.endSession({ sessionId: 1, endTime: '2026-06-10T12:00:00Z' } as any);
|
||||
|
||||
expect(api.endSession).toHaveBeenCalled();
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['activeSession'] });
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['sessions'] });
|
||||
});
|
||||
|
||||
it('Test Case 5 (Error Handling): should propagate errors during session creation', async () => {
|
||||
(useQuery as any).mockReturnValue({ data: null, isLoading: false, isFetching: false });
|
||||
const error = new Error('API Error');
|
||||
(api.createSession as any).mockRejectedValue(error);
|
||||
|
||||
(useMutation as any).mockImplementation(({ mutationFn }: { mutationFn: (args: any) => Promise<any> }) => ({
|
||||
mutateAsync: async (args: any) => {
|
||||
return await mutationFn(args);
|
||||
},
|
||||
isPending: false,
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() => useSleepSession());
|
||||
|
||||
await expect(result.current.createSession({ start_time: '...' } as any)).rejects.toThrow('API Error');
|
||||
});
|
||||
|
||||
it('Test Case 6 (State): should show isCreating when mutation is pending', () => {
|
||||
(useQuery as any).mockReturnValue({ data: null, isLoading: false, isFetching: false });
|
||||
(useMutation as any).mockReturnValueOnce({
|
||||
mutateAsync: vi.fn(),
|
||||
isPending: true,
|
||||
}).mockReturnValueOnce({
|
||||
mutateAsync: vi.fn(),
|
||||
isPending: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useSleepSession());
|
||||
expect(result.current.isCreating).toBe(true);
|
||||
});
|
||||
|
||||
it('Test Case 6 (State): should show isEnding when mutation is pending', () => {
|
||||
(useQuery as any).mockReturnValue({ data: mockSession, isLoading: false, isFetching: false });
|
||||
(useMutation as any).mockReturnValueOnce({
|
||||
mutateAsync: vi.fn(),
|
||||
isPending: false,
|
||||
}).mockReturnValueOnce({
|
||||
mutateAsync: vi.fn(),
|
||||
isPending: true,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useSleepSession());
|
||||
expect(result.current.isEnding).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSessions', () => {
|
||||
const mockSessionsResponse: SessionListResponse = {
|
||||
sessions: [
|
||||
{ id: 1, start_time: '2026-06-10T10:00:00Z', end_time: null, notes: null, created_at: '...', updated_at: '...' },
|
||||
{ id: 2, start_time: '2026-06-10T10:00:00Z', end_time: null, notes: null, created_at: '...', updated_at: '...' },
|
||||
],
|
||||
total: 2,
|
||||
};
|
||||
|
||||
it('Test Case 6 (State): should return correct loading and sessions states', () => {
|
||||
(useQuery as any).mockReturnValue({
|
||||
data: mockSessionsResponse,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
(useMutation as any).mockReturnValue({
|
||||
mutateAsync: vi.fn(),
|
||||
isPending: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useSessions());
|
||||
|
||||
expect(result.current.sessions).toEqual(mockSessionsResponse.sessions);
|
||||
expect(result.current.total).toBe(2);
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
it('Test Case 3: should successfully delete a session and invalidate queries', async () => {
|
||||
(useQuery as any).mockReturnValue({ data: mockSessionsResponse, isLoading: false });
|
||||
(api.deleteSession as any).mockResolvedValue(undefined);
|
||||
|
||||
(useMutation as any).mockImplementation(({ mutationFn, onSuccess }: { mutationFn: (args: any) => Promise<any>, onSuccess?: () => void }) => ({
|
||||
mutateAsync: async (args: any) => {
|
||||
const res = await mutationFn(args);
|
||||
if (onSuccess) onSuccess();
|
||||
return res;
|
||||
},
|
||||
isPending: false,
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() => useSessions());
|
||||
|
||||
await (result.current.deleteSession as any)(1);
|
||||
|
||||
expect(api.deleteSession).toHaveBeenCalledWith(1);
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['sessions'] });
|
||||
});
|
||||
|
||||
it('Test Case 4: should successfully update a session and invalidate queries', async () => {
|
||||
(useQuery as any).mockReturnValue({ data: mockSessionsResponse, isLoading: false });
|
||||
const updatePayload = { end_time: '2026-06-10T12:00:00Z' };
|
||||
(api.updateSession as any).mockResolvedValue({ id: 1, ...updatePayload } as any);
|
||||
|
||||
(useMutation as any).mockImplementation(({ mutationFn, onSuccess }: { mutationFn: (args: any) => Promise<any>, onSuccess?: () => void }) => ({
|
||||
mutateAsync: async (args: any) => {
|
||||
const res = await mutationFn(args);
|
||||
if (onSuccess) onSuccess();
|
||||
return res;
|
||||
},
|
||||
isPending: false,
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() => useSessions());
|
||||
|
||||
await (result.current.updateSession as any)({ sessionId: 1, payload: updatePayload });
|
||||
|
||||
expect(api.updateSession).toHaveBeenCalledWith(1, updatePayload);
|
||||
expect(mockInvalidateQueries).toHaveBeenCalledWith({ queryKey: ['sessions'] });
|
||||
});
|
||||
|
||||
it('Test Case 5 (Error Handling): should propagate errors during session deletion', async () => {
|
||||
(useQuery as any).mockReturnValue({ data: mockSessionsResponse, isLoading: false });
|
||||
const error = new Error('Delete failed');
|
||||
(api.deleteSession as any).mockRejectedValue(error);
|
||||
|
||||
(useMutation as any).mockImplementation(({ mutationFn }: { mutationFn: (args: any) => Promise<any> }) => ({
|
||||
mutateAsync: async (args: any) => {
|
||||
return await mutationFn(args);
|
||||
},
|
||||
isPending: false,
|
||||
}));
|
||||
|
||||
const { result } = renderHook(() => useSessions());
|
||||
|
||||
await expect((result.current.deleteSession as any)(1)).rejects.toThrow('Delete failed');
|
||||
});
|
||||
|
||||
it('Test Case 6 (State): should show isDeleting when mutation is pending', () => {
|
||||
(useQuery as any).mockReturnValue({ data: mockSessionsResponse, isLoading: false });
|
||||
(useMutation as any).mockReturnValue({
|
||||
mutateAsync: vi.fn(),
|
||||
isPending: true,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useSessions());
|
||||
expect(result.current.isDeleting).toBe(true);
|
||||
});
|
||||
it('Test Case 7 (Data Undefined): should return empty sessions and 0 total when data is undefined', () => {
|
||||
(useQuery as any).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
});
|
||||
|
||||
(useMutation as any).mockReturnValue({
|
||||
mutateAsync: vi.fn(),
|
||||
isPending: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useSessions());
|
||||
|
||||
expect(result.current.sessions).toEqual([]);
|
||||
expect(result.current.total).toBe(0);
|
||||
});
|
||||
|
||||
it('Test Case 8 (Error Handling): should handle error from fetchSessions gracefully', () => {
|
||||
(useQuery as any).mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: false,
|
||||
isError: true,
|
||||
error: new Error('Fetch failed'),
|
||||
});
|
||||
|
||||
(useMutation as any).mockReturnValue({
|
||||
mutateAsync: vi.fn(),
|
||||
isPending: false,
|
||||
});
|
||||
|
||||
const { result } = renderHook(() => useSessions());
|
||||
|
||||
expect(result.current.sessions).toEqual([]);
|
||||
expect(result.current.total).toBe(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user