mirror of
https://github.com/lordmathis/lemma.git
synced 2025-11-05 15:44:21 +00:00
581 lines
17 KiB
TypeScript
581 lines
17 KiB
TypeScript
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { renderHook, act, waitFor } from '@testing-library/react';
|
|
import { useAdminData } from './useAdminData';
|
|
import * as adminApi from '@/api/admin';
|
|
import {
|
|
UserRole,
|
|
Theme,
|
|
type SystemStats,
|
|
type User,
|
|
type WorkspaceStats,
|
|
} from '@/types/models';
|
|
|
|
// Mock dependencies
|
|
vi.mock('@/api/admin');
|
|
vi.mock('@mantine/notifications', () => ({
|
|
notifications: {
|
|
show: vi.fn(),
|
|
},
|
|
}));
|
|
|
|
// Import notifications for assertions
|
|
import { notifications } from '@mantine/notifications';
|
|
|
|
// Mock data
|
|
const mockSystemStats: SystemStats = {
|
|
totalUsers: 10,
|
|
activeUsers: 8,
|
|
totalWorkspaces: 15,
|
|
totalFiles: 150,
|
|
totalSize: 1024000,
|
|
};
|
|
|
|
const mockUsers: User[] = [
|
|
{
|
|
id: 1,
|
|
email: 'admin@example.com',
|
|
displayName: 'Admin User',
|
|
role: UserRole.Admin,
|
|
theme: Theme.Dark,
|
|
createdAt: '2024-01-01T00:00:00Z',
|
|
lastWorkspaceId: 1,
|
|
},
|
|
{
|
|
id: 2,
|
|
email: 'editor@example.com',
|
|
displayName: 'Editor User',
|
|
role: UserRole.Editor,
|
|
theme: Theme.Dark,
|
|
createdAt: '2024-01-02T00:00:00Z',
|
|
lastWorkspaceId: 2,
|
|
},
|
|
];
|
|
|
|
const mockWorkspaceStats: WorkspaceStats[] = [
|
|
{
|
|
userID: 1,
|
|
userEmail: 'admin@example.com',
|
|
workspaceID: 1,
|
|
workspaceName: 'admin-workspace',
|
|
workspaceCreatedAt: '2024-01-01T00:00:00Z',
|
|
fileCountStats: {
|
|
totalFiles: 10,
|
|
totalSize: 204800,
|
|
},
|
|
},
|
|
{
|
|
userID: 2,
|
|
userEmail: 'editor@example.com',
|
|
workspaceID: 2,
|
|
workspaceName: 'editor-workspace',
|
|
workspaceCreatedAt: '2024-01-02T00:00:00Z',
|
|
fileCountStats: {
|
|
totalFiles: 15,
|
|
totalSize: 307200,
|
|
},
|
|
},
|
|
];
|
|
|
|
describe('useAdminData', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('stats data type', () => {
|
|
it('initializes with empty stats and loading state', async () => {
|
|
const { result } = renderHook(() => useAdminData('stats'));
|
|
|
|
expect(result.current.data).toEqual({});
|
|
expect(result.current.loading).toBe(true);
|
|
expect(result.current.error).toBeNull();
|
|
expect(typeof result.current.reload).toBe('function');
|
|
|
|
// Wait for the hook to complete its async initialization
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
});
|
|
|
|
it('loads system stats successfully', async () => {
|
|
const mockGetSystemStats = vi.mocked(adminApi.getSystemStats);
|
|
mockGetSystemStats.mockResolvedValue(mockSystemStats);
|
|
|
|
const { result } = renderHook(() => useAdminData('stats'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.data).toEqual(mockSystemStats);
|
|
expect(result.current.error).toBeNull();
|
|
expect(mockGetSystemStats).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('handles stats loading errors', async () => {
|
|
const mockGetSystemStats = vi.mocked(adminApi.getSystemStats);
|
|
mockGetSystemStats.mockRejectedValue(new Error('Failed to load stats'));
|
|
|
|
const { result } = renderHook(() => useAdminData('stats'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.data).toEqual({});
|
|
expect(result.current.error).toBe('Failed to load stats');
|
|
expect(notifications.show).toHaveBeenCalledWith({
|
|
title: 'Error',
|
|
message: 'Failed to load stats: Failed to load stats',
|
|
color: 'red',
|
|
});
|
|
});
|
|
|
|
it('reloads stats data', async () => {
|
|
const mockGetSystemStats = vi.mocked(adminApi.getSystemStats);
|
|
mockGetSystemStats.mockResolvedValue(mockSystemStats);
|
|
|
|
const { result } = renderHook(() => useAdminData('stats'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(mockGetSystemStats).toHaveBeenCalledTimes(1);
|
|
|
|
await act(async () => {
|
|
await result.current.reload();
|
|
});
|
|
|
|
expect(mockGetSystemStats).toHaveBeenCalledTimes(2);
|
|
expect(result.current.data).toEqual(mockSystemStats);
|
|
});
|
|
});
|
|
|
|
describe('users data type', () => {
|
|
it('initializes with empty users array and loading state', async () => {
|
|
const { result } = renderHook(() => useAdminData('users'));
|
|
|
|
expect(result.current.data).toEqual([]);
|
|
expect(result.current.loading).toBe(true);
|
|
expect(result.current.error).toBeNull();
|
|
expect(typeof result.current.reload).toBe('function');
|
|
|
|
// Wait for the hook to complete its async initialization
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
});
|
|
|
|
it('loads users successfully', async () => {
|
|
const mockGetUsers = vi.mocked(adminApi.getUsers);
|
|
mockGetUsers.mockResolvedValue(mockUsers);
|
|
|
|
const { result } = renderHook(() => useAdminData('users'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.data).toEqual(mockUsers);
|
|
expect(result.current.error).toBeNull();
|
|
expect(mockGetUsers).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('handles users loading errors', async () => {
|
|
const mockGetUsers = vi.mocked(adminApi.getUsers);
|
|
mockGetUsers.mockRejectedValue(new Error('Failed to load users'));
|
|
|
|
const { result } = renderHook(() => useAdminData('users'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.data).toEqual([]);
|
|
expect(result.current.error).toBe('Failed to load users');
|
|
expect(notifications.show).toHaveBeenCalledWith({
|
|
title: 'Error',
|
|
message: 'Failed to load users: Failed to load users',
|
|
color: 'red',
|
|
});
|
|
});
|
|
|
|
it('reloads users data', async () => {
|
|
const mockGetUsers = vi.mocked(adminApi.getUsers);
|
|
mockGetUsers.mockResolvedValue(mockUsers);
|
|
|
|
const { result } = renderHook(() => useAdminData('users'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(mockGetUsers).toHaveBeenCalledTimes(1);
|
|
|
|
await act(async () => {
|
|
await result.current.reload();
|
|
});
|
|
|
|
expect(mockGetUsers).toHaveBeenCalledTimes(2);
|
|
expect(result.current.data).toEqual(mockUsers);
|
|
});
|
|
|
|
it('handles empty users array', async () => {
|
|
const mockGetUsers = vi.mocked(adminApi.getUsers);
|
|
mockGetUsers.mockResolvedValue([]);
|
|
|
|
const { result } = renderHook(() => useAdminData('users'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.data).toEqual([]);
|
|
expect(result.current.error).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('workspaces data type', () => {
|
|
it('initializes with empty workspaces array and loading state', async () => {
|
|
const { result } = renderHook(() => useAdminData('workspaces'));
|
|
|
|
expect(result.current.data).toEqual([]);
|
|
expect(result.current.loading).toBe(true);
|
|
expect(result.current.error).toBeNull();
|
|
expect(typeof result.current.reload).toBe('function');
|
|
|
|
// Wait for the hook to complete its async initialization
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
});
|
|
|
|
it('loads workspaces successfully', async () => {
|
|
const mockGetWorkspaces = vi.mocked(adminApi.getWorkspaces);
|
|
mockGetWorkspaces.mockResolvedValue(mockWorkspaceStats);
|
|
|
|
const { result } = renderHook(() => useAdminData('workspaces'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.data).toEqual(mockWorkspaceStats);
|
|
expect(result.current.error).toBeNull();
|
|
expect(mockGetWorkspaces).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('handles workspaces loading errors', async () => {
|
|
const mockGetWorkspaces = vi.mocked(adminApi.getWorkspaces);
|
|
mockGetWorkspaces.mockRejectedValue(
|
|
new Error('Failed to load workspaces')
|
|
);
|
|
|
|
const { result } = renderHook(() => useAdminData('workspaces'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.data).toEqual([]);
|
|
expect(result.current.error).toBe('Failed to load workspaces');
|
|
expect(notifications.show).toHaveBeenCalledWith({
|
|
title: 'Error',
|
|
message: 'Failed to load workspaces: Failed to load workspaces',
|
|
color: 'red',
|
|
});
|
|
});
|
|
|
|
it('reloads workspaces data', async () => {
|
|
const mockGetWorkspaces = vi.mocked(adminApi.getWorkspaces);
|
|
mockGetWorkspaces.mockResolvedValue(mockWorkspaceStats);
|
|
|
|
const { result } = renderHook(() => useAdminData('workspaces'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(mockGetWorkspaces).toHaveBeenCalledTimes(1);
|
|
|
|
await act(async () => {
|
|
await result.current.reload();
|
|
});
|
|
|
|
expect(mockGetWorkspaces).toHaveBeenCalledTimes(2);
|
|
expect(result.current.data).toEqual(mockWorkspaceStats);
|
|
});
|
|
|
|
it('handles workspaces with minimal configuration', async () => {
|
|
const minimalWorkspaceStats: WorkspaceStats[] = [
|
|
{
|
|
userID: 3,
|
|
userEmail: 'minimal@example.com',
|
|
workspaceID: 3,
|
|
workspaceName: 'minimal-workspace',
|
|
workspaceCreatedAt: '2024-01-03T00:00:00Z',
|
|
},
|
|
];
|
|
|
|
const mockGetWorkspaces = vi.mocked(adminApi.getWorkspaces);
|
|
mockGetWorkspaces.mockResolvedValue(minimalWorkspaceStats);
|
|
|
|
const { result } = renderHook(() => useAdminData('workspaces'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.data).toEqual(minimalWorkspaceStats);
|
|
expect(result.current.error).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('error handling', () => {
|
|
it('handles API errors with error response object', async () => {
|
|
const mockGetSystemStats = vi.mocked(adminApi.getSystemStats);
|
|
// Create a properly typed error object to simulate API error response
|
|
const errorWithResponse = new Error('Request failed');
|
|
type ErrorWithResponse = Error & {
|
|
response: {
|
|
data: {
|
|
error: string;
|
|
};
|
|
};
|
|
};
|
|
(errorWithResponse as ErrorWithResponse).response = {
|
|
data: {
|
|
error: 'Custom API error message',
|
|
},
|
|
};
|
|
mockGetSystemStats.mockRejectedValue(errorWithResponse);
|
|
|
|
const { result } = renderHook(() => useAdminData('stats'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.error).toBe('Custom API error message');
|
|
expect(notifications.show).toHaveBeenCalledWith({
|
|
title: 'Error',
|
|
message: 'Failed to load stats: Custom API error message',
|
|
color: 'red',
|
|
});
|
|
});
|
|
|
|
it('clears error on successful reload', async () => {
|
|
const mockGetSystemStats = vi.mocked(adminApi.getSystemStats);
|
|
mockGetSystemStats
|
|
.mockRejectedValueOnce(new Error('Initial error'))
|
|
.mockResolvedValueOnce(mockSystemStats);
|
|
|
|
const { result } = renderHook(() => useAdminData('stats'));
|
|
|
|
// Wait for initial error
|
|
await waitFor(() => {
|
|
expect(result.current.error).toBe('Initial error');
|
|
});
|
|
|
|
// Reload successfully
|
|
await act(async () => {
|
|
await result.current.reload();
|
|
});
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.error).toBeNull();
|
|
expect(result.current.data).toEqual(mockSystemStats);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('loading state management', () => {
|
|
it('manages loading state correctly through full lifecycle', async () => {
|
|
const mockGetSystemStats = vi.mocked(adminApi.getSystemStats);
|
|
let resolvePromise: (value: SystemStats) => void;
|
|
const pendingPromise = new Promise<SystemStats>((resolve) => {
|
|
resolvePromise = resolve;
|
|
});
|
|
mockGetSystemStats.mockReturnValue(pendingPromise);
|
|
|
|
const { result } = renderHook(() => useAdminData('stats'));
|
|
|
|
// Initial load should be loading
|
|
expect(result.current.loading).toBe(true);
|
|
|
|
// Resolve initial load
|
|
await act(async () => {
|
|
resolvePromise!(mockSystemStats);
|
|
await pendingPromise;
|
|
});
|
|
|
|
expect(result.current.loading).toBe(false);
|
|
|
|
// Test reload loading state
|
|
let resolveReload: (value: SystemStats) => void;
|
|
const reloadPromise = new Promise<SystemStats>((resolve) => {
|
|
resolveReload = resolve;
|
|
});
|
|
mockGetSystemStats.mockReturnValueOnce(reloadPromise);
|
|
|
|
act(() => {
|
|
void result.current.reload();
|
|
});
|
|
|
|
expect(result.current.loading).toBe(true);
|
|
|
|
await act(async () => {
|
|
resolveReload!(mockSystemStats);
|
|
await reloadPromise;
|
|
});
|
|
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('data consistency', () => {
|
|
it('handles data type parameter changes', async () => {
|
|
const mockGetSystemStats = vi.mocked(adminApi.getSystemStats);
|
|
const mockGetUsers = vi.mocked(adminApi.getUsers);
|
|
|
|
mockGetSystemStats.mockResolvedValue(mockSystemStats);
|
|
mockGetUsers.mockResolvedValue(mockUsers);
|
|
|
|
const { result, rerender } = renderHook(
|
|
({ type }) => useAdminData(type),
|
|
{
|
|
initialProps: { type: 'stats' as const } as {
|
|
type: 'stats' | 'users' | 'workspaces';
|
|
},
|
|
}
|
|
);
|
|
|
|
// Wait for stats to load
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.data).toEqual(mockSystemStats);
|
|
|
|
// Change to users type
|
|
rerender({ type: 'users' as const });
|
|
|
|
// Should reset to loading and empty array for users
|
|
expect(result.current.loading).toBe(true);
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
expect(result.current.data).toEqual(mockUsers);
|
|
expect(mockGetUsers).toHaveBeenCalledTimes(1);
|
|
});
|
|
it('handles data type changes correctly with different initial values', async () => {
|
|
const mockGetSystemStats = vi.mocked(adminApi.getSystemStats);
|
|
const mockGetUsers = vi.mocked(adminApi.getUsers);
|
|
const mockGetWorkspaces = vi.mocked(adminApi.getWorkspaces);
|
|
|
|
mockGetSystemStats.mockResolvedValue(mockSystemStats);
|
|
mockGetUsers.mockResolvedValue(mockUsers);
|
|
mockGetWorkspaces.mockResolvedValue(mockWorkspaceStats);
|
|
|
|
const { result, rerender } = renderHook(
|
|
({ type }) => useAdminData(type),
|
|
{
|
|
initialProps: { type: 'stats' as const } as {
|
|
type: 'stats' | 'users' | 'workspaces';
|
|
},
|
|
}
|
|
);
|
|
|
|
// Wait for stats to load
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
expect(result.current.data).toEqual(mockSystemStats);
|
|
|
|
// Change to users type - should reset to empty array and reload
|
|
act(() => {
|
|
rerender({ type: 'users' as const });
|
|
});
|
|
|
|
// Data should reset to empty array immediately when type changes
|
|
expect(result.current.data).toEqual([]);
|
|
expect(result.current.loading).toBe(true);
|
|
expect(result.current.error).toBeNull();
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
expect(result.current.data).toEqual(mockUsers);
|
|
|
|
// Change to workspaces type - should reset to empty array and reload
|
|
act(() => {
|
|
rerender({ type: 'workspaces' as const });
|
|
});
|
|
|
|
// Data should reset to empty array immediately when type changes
|
|
expect(result.current.data).toEqual([]);
|
|
expect(result.current.loading).toBe(true);
|
|
expect(result.current.error).toBeNull();
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
expect(result.current.data).toEqual(mockWorkspaceStats);
|
|
|
|
// Verify correct API calls were made
|
|
expect(mockGetSystemStats).toHaveBeenCalledTimes(1);
|
|
expect(mockGetUsers).toHaveBeenCalledTimes(1);
|
|
expect(mockGetWorkspaces).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|
|
|
|
describe('function stability', () => {
|
|
it('maintains stable reload function reference', async () => {
|
|
const { result, rerender } = renderHook(() => useAdminData('stats'));
|
|
|
|
const initialReload = result.current.reload;
|
|
|
|
rerender();
|
|
|
|
expect(result.current.reload).toBe(initialReload);
|
|
|
|
// Wait for the hook to complete its async initialization
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('concurrent operations', () => {
|
|
it('handles multiple concurrent reloads', async () => {
|
|
const mockGetSystemStats = vi.mocked(adminApi.getSystemStats);
|
|
mockGetSystemStats.mockResolvedValue(mockSystemStats);
|
|
|
|
const { result } = renderHook(() => useAdminData('stats'));
|
|
|
|
await waitFor(() => {
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
|
|
// Trigger multiple reloads
|
|
await act(async () => {
|
|
await Promise.all([
|
|
result.current.reload(),
|
|
result.current.reload(),
|
|
result.current.reload(),
|
|
]);
|
|
});
|
|
|
|
expect(mockGetSystemStats).toHaveBeenCalledTimes(4); // 1 initial + 3 reloads
|
|
expect(result.current.data).toEqual(mockSystemStats);
|
|
expect(result.current.loading).toBe(false);
|
|
});
|
|
});
|
|
});
|