Files
noteflow/client/src/hooks/projects/use-project-mutations.test.tsx
Travis Vasceannie 2641a9fc03
Some checks failed
CI / test-python (push) Failing after 17m22s
CI / test-rust (push) Has been cancelled
CI / test-typescript (push) Has been cancelled
optimization
2026-01-25 01:40:14 +00:00

272 lines
6.9 KiB
TypeScript

import { renderHook, waitFor, act } from '@testing-library/react';
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { useCreateProject, useDeleteProject } from './use-project-mutations';
import type { Project } from '@/api/types/projects';
import type { CreateProjectRequest } from '@/api/types/projects';
vi.mock('@/hooks/ui/use-toast', () => ({
useToast: () => ({
toast: vi.fn(),
}),
}));
const mockAPI = {
createProject: vi.fn(),
deleteProject: vi.fn(),
};
vi.mock('@/api/interface', () => ({
getAPI: () => mockAPI,
}));
const createMockProject = (overrides?: Partial<Project>): Project => ({
id: 'project-123',
workspace_id: 'workspace-456',
name: 'Test Project',
description: 'Test description',
is_default: false,
is_archived: false,
created_at: Date.now() / 1000,
updated_at: Date.now() / 1000,
...overrides,
});
describe('useCreateProject', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should call API with correct request', async () => {
const { result } = renderHook(() => useCreateProject());
const request: CreateProjectRequest = {
workspace_id: 'w1',
name: 'New Project',
description: 'A new project',
};
const project = createMockProject({
id: 'p1',
...request,
});
mockAPI.createProject.mockResolvedValue(project);
await result.current.mutate(request);
await waitFor(() => {
expect(mockAPI.createProject).toHaveBeenCalledWith(request);
});
});
it('should return project on success', async () => {
const { result } = renderHook(() => useCreateProject());
const request: CreateProjectRequest = {
workspace_id: 'w1',
name: 'New Project',
};
const project = createMockProject({
id: 'p1',
...request,
});
mockAPI.createProject.mockResolvedValue(project);
await result.current.mutate(request);
expect(mockAPI.createProject).toHaveBeenCalledWith(request);
});
it('should expose loading state', async () => {
const { result } = renderHook(() => useCreateProject());
expect(result.current.isLoading).toBe(false);
mockAPI.createProject.mockImplementation(
() =>
new Promise((resolve) =>
setTimeout(
() =>
resolve(
createMockProject({
id: 'p1',
workspace_id: 'w1',
})
),
50
)
)
);
const mutatePromise = result.current.mutate({
workspace_id: 'w1',
name: 'New Project',
});
await waitFor(() => {
expect(result.current.isLoading).toBe(true);
});
await act(async () => {
await mutatePromise;
});
expect(result.current.isLoading).toBe(false);
});
it('should expose error state', async () => {
const { result } = renderHook(() => useCreateProject());
const error = new Error('API Error');
mockAPI.createProject.mockRejectedValue(error);
await act(async () => {
await result.current.mutate({
workspace_id: 'w1',
name: 'New Project',
});
});
expect(result.current.error).toBeTruthy();
expect(result.current.error?.message).toBe('API Error');
});
it('should clear error on successful mutation', async () => {
const { result } = renderHook(() => useCreateProject());
mockAPI.createProject.mockRejectedValueOnce(new Error('First error'));
await act(async () => {
await result.current.mutate({
workspace_id: 'w1',
name: 'Project 1',
});
});
expect(result.current.error).toBeTruthy();
mockAPI.createProject.mockResolvedValueOnce(
createMockProject({ id: 'p1', workspace_id: 'w1' })
);
await act(async () => {
await result.current.mutate({
workspace_id: 'w1',
name: 'Project 2',
});
});
expect(result.current.error).toBeNull();
});
it('should handle workspace_id correctly', async () => {
const { result } = renderHook(() => useCreateProject());
const request: CreateProjectRequest = {
workspace_id: 'workspace-789',
name: 'New Project',
description: 'Test',
};
const project = createMockProject({
id: 'p1',
...request,
});
mockAPI.createProject.mockResolvedValue(project);
await result.current.mutate(request);
expect(mockAPI.createProject).toHaveBeenCalledWith(request);
});
});
describe('useDeleteProject', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should call API with project ID', async () => {
const { result } = renderHook(() => useDeleteProject());
mockAPI.deleteProject.mockResolvedValue(true);
await result.current.mutate('project-123');
await waitFor(() => {
expect(mockAPI.deleteProject).toHaveBeenCalledWith('project-123');
});
});
it('should return true on success', async () => {
const { result } = renderHook(() => useDeleteProject());
mockAPI.deleteProject.mockResolvedValue(true);
await result.current.mutate('project-123');
expect(mockAPI.deleteProject).toHaveBeenCalledWith('project-123');
});
it('should expose loading state', async () => {
const { result } = renderHook(() => useDeleteProject());
expect(result.current.isLoading).toBe(false);
mockAPI.deleteProject.mockImplementation(
() =>
new Promise((resolve) => setTimeout(() => resolve(true), 50))
);
const mutatePromise = result.current.mutate('project-123');
await waitFor(() => {
expect(result.current.isLoading).toBe(true);
});
await act(async () => {
await mutatePromise;
});
expect(result.current.isLoading).toBe(false);
});
it('should expose error state', async () => {
const { result } = renderHook(() => useDeleteProject());
const error = new Error('Delete failed');
mockAPI.deleteProject.mockRejectedValue(error);
await act(async () => {
await result.current.mutate('project-123');
});
expect(result.current.error).toBeTruthy();
expect(result.current.error?.message).toBe('Delete failed');
});
it('should handle API returning false (not found)', async () => {
const { result } = renderHook(() => useDeleteProject());
mockAPI.deleteProject.mockResolvedValue(false);
await result.current.mutate('missing-project');
expect(mockAPI.deleteProject).toHaveBeenCalledWith('missing-project');
});
it('should clear error on successful mutation', async () => {
const { result } = renderHook(() => useDeleteProject());
mockAPI.deleteProject.mockRejectedValueOnce(new Error('First error'));
await act(async () => {
await result.current.mutate('project-123');
});
expect(result.current.error).toBeTruthy();
mockAPI.deleteProject.mockResolvedValueOnce(true);
await act(async () => {
await result.current.mutate('project-456');
});
expect(result.current.error).toBeNull();
});
});