test(frontend): raise overall statement coverage to 30.42% for Phase 1 MVP

This commit is contained in:
2026-06-13 22:33:11 +07:00
parent 190b9a3af5
commit 9c5df0abdb
37 changed files with 6128 additions and 24 deletions
@@ -0,0 +1,209 @@
// File: frontend/hooks/__tests__/use-ai-prompts.test.ts
// Change Log:
// - 2026-06-13: Initial creation - test coverage for useAiPrompts and useSandboxRun hooks
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { createTestQueryClient } from '@/lib/test-utils';
import { useAiPrompts, useSandboxRun } from '../use-ai-prompts';
import { aiPromptsService } from '@/lib/services/ai-prompts.service';
import { adminAiService } from '@/lib/services/admin-ai.service';
// Mock services
vi.mock('@/lib/services/ai-prompts.service', () => ({
aiPromptsService: {
listVersions: vi.fn(),
createVersion: vi.fn(),
activateVersion: vi.fn(),
deleteVersion: vi.fn(),
updateNote: vi.fn(),
},
}));
vi.mock('@/lib/services/admin-ai.service', () => ({
adminAiService: {
getSandboxJobStatus: vi.fn(),
submitSandboxExtract: vi.fn(),
},
}));
describe('useAiPrompts hooks', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('useAiPrompts', () => {
it('ควรดึงข้อมูล prompt versions สำเร็จ', async () => {
const mockData = [{ versionNumber: 1, template: 'test', isActive: true }];
vi.mocked(aiPromptsService.listVersions).mockResolvedValue(mockData as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useAiPrompts('RFA'), { wrapper });
await waitFor(() => {
expect(result.current.versionsQuery.isSuccess).toBe(true);
});
expect(result.current.versionsQuery.data).toEqual(mockData);
expect(aiPromptsService.listVersions).toHaveBeenCalledWith('RFA');
});
it('ควรเรียก createVersion สำเร็จ', async () => {
vi.mocked(aiPromptsService.createVersion).mockResolvedValue({ success: true } as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useAiPrompts('RFA'), { wrapper });
await act(async () => {
await result.current.createMutation.mutateAsync('new template');
});
expect(aiPromptsService.createVersion).toHaveBeenCalledWith('RFA', 'new template');
});
it('ควรเรียก activateVersion สำเร็จ', async () => {
vi.mocked(aiPromptsService.activateVersion).mockResolvedValue({ success: true } as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useAiPrompts('RFA'), { wrapper });
await act(async () => {
await result.current.activateMutation.mutateAsync(2);
});
expect(aiPromptsService.activateVersion).toHaveBeenCalledWith('RFA', 2);
});
it('ควรเรียก deleteVersion สำเร็จ', async () => {
vi.mocked(aiPromptsService.deleteVersion).mockResolvedValue({ success: true } as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useAiPrompts('RFA'), { wrapper });
await act(async () => {
await result.current.deleteMutation.mutateAsync(3);
});
expect(aiPromptsService.deleteVersion).toHaveBeenCalledWith('RFA', 3);
});
it('ควรเรียก updateNote สำเร็จ', async () => {
vi.mocked(aiPromptsService.updateNote).mockResolvedValue({ success: true } as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useAiPrompts('RFA'), { wrapper });
await act(async () => {
await result.current.updateNoteMutation.mutateAsync({ versionNumber: 1, note: 'New Note' });
});
expect(aiPromptsService.updateNote).toHaveBeenCalledWith('RFA', 1, 'New Note');
});
});
});
describe('useSandboxRun hook', () => {
beforeEach(() => {
vi.useFakeTimers();
vi.clearAllMocks();
});
afterEach(() => {
vi.useRealTimers();
});
it('ควรเริ่มต้นด้วยสถานะว่างเปล่า', () => {
const { result } = renderHook(() => useSandboxRun());
expect(result.current.state).toEqual({
isRunning: false,
progress: 0,
statusText: '',
result: null,
});
expect(result.current.jobId).toBeNull();
});
it('ควรสามารถส่ง job และจำลอง polling จนกระทั่งเสร็จสิ้น (completed)', async () => {
const mockFile = new File(['pdf-content'], 'test.pdf', { type: 'application/pdf' });
vi.mocked(adminAiService.submitSandboxExtract).mockResolvedValue({ requestPublicId: 'job-123' } as any);
let pollCount = 0;
vi.mocked(adminAiService.getSandboxJobStatus).mockImplementation(async () => {
pollCount += 1;
if (pollCount === 1) return { status: 'pending' } as any;
if (pollCount === 2) return { status: 'processing' } as any;
return { status: 'completed', metadata: { test: 1 } } as any;
});
const onCompletedMock = vi.fn();
const { result } = renderHook(() => useSandboxRun(onCompletedMock));
let jobIdPromise: Promise<string> | undefined;
act(() => {
jobIdPromise = result.current.submit(mockFile, 'project-1', 'contract-1');
});
await act(async () => {
await jobIdPromise;
});
expect(result.current.jobId).toBe('job-123');
expect(result.current.state.isRunning).toBe(true);
expect(result.current.state.progress).toBe(30);
expect(result.current.state.statusText).toBe('ai.prompt.statusPending');
await act(async () => {
await vi.advanceTimersByTimeAsync(4000);
});
expect(result.current.state.progress).toBe(70);
expect(result.current.state.statusText).toBe('ai.prompt.statusProcessing');
await act(async () => {
await vi.advanceTimersByTimeAsync(4000);
});
expect(result.current.state.isRunning).toBe(false);
expect(result.current.state.progress).toBe(100);
expect(result.current.state.statusText).toBe('ai.prompt.statusCompleted');
expect(onCompletedMock).toHaveBeenCalled();
});
it('ควรหยุดการ polling เมื่อสถานะเป็น failed', async () => {
const mockFile = new File(['pdf-content'], 'test.pdf', { type: 'application/pdf' });
vi.mocked(adminAiService.submitSandboxExtract).mockResolvedValue({ requestPublicId: 'job-failed' } as any);
vi.mocked(adminAiService.getSandboxJobStatus).mockResolvedValue({ status: 'failed' } as any);
const { result } = renderHook(() => useSandboxRun());
let jobIdPromise: Promise<string> | undefined;
act(() => {
jobIdPromise = result.current.submit(mockFile, 'project-1');
});
await act(async () => {
await jobIdPromise;
});
await act(async () => {
await vi.advanceTimersByTimeAsync(0);
});
expect(result.current.state.isRunning).toBe(false);
expect(result.current.state.progress).toBe(100);
expect(result.current.state.statusText).toBe('ai.prompt.statusFailed');
expect(result.current.jobId).toBeNull();
});
it('ควรหยุดการ polling เมื่อสถานะเป็น cancelled', async () => {
vi.mocked(adminAiService.getSandboxJobStatus).mockResolvedValue({ status: 'cancelled' } as any);
const { result } = renderHook(() => useSandboxRun());
act(() => {
result.current.startPolling('job-cancelled');
});
expect(result.current.jobId).toBe('job-cancelled');
await act(async () => {
await vi.advanceTimersByTimeAsync(0);
});
expect(result.current.state.isRunning).toBe(false);
expect(result.current.state.progress).toBe(100);
expect(result.current.state.statusText).toBe('ai.prompt.statusCancelled');
expect(result.current.jobId).toBeNull();
});
it('ควรจะทำงานต่อเงียบๆ และพยายามต่อเมื่อเกิด network error ระหว่าง polling', async () => {
vi.mocked(adminAiService.getSandboxJobStatus).mockRejectedValue(new Error('Network error'));
const { result } = renderHook(() => useSandboxRun());
act(() => {
result.current.startPolling('job-error');
});
await act(async () => {
await vi.advanceTimersByTimeAsync(0);
});
expect(result.current.jobId).toBe('job-error');
});
it('ควรสามารถรีเซ็ตสถานะกลับสู่ค่าเริ่มต้นได้', () => {
const { result } = renderHook(() => useSandboxRun());
act(() => {
result.current.startPolling('job-to-reset');
});
expect(result.current.jobId).toBe('job-to-reset');
act(() => {
result.current.reset();
});
expect(result.current.jobId).toBeNull();
expect(result.current.state.isRunning).toBe(false);
});
});
@@ -0,0 +1,75 @@
// File: frontend/hooks/__tests__/use-dashboard.test.ts
// Change Log:
// - 2026-06-13: Initial creation - test coverage for use-dashboard hooks
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { createTestQueryClient } from '@/lib/test-utils';
import { useDashboardStats, useRecentActivity, usePendingTasks, dashboardKeys } from '../use-dashboard';
import { dashboardService } from '@/lib/services/dashboard.service';
// Mock services
vi.mock('@/lib/services/dashboard.service', () => ({
dashboardService: {
getStats: vi.fn(),
getRecentActivity: vi.fn(),
getPendingTasks: vi.fn(),
},
}));
describe('use-dashboard hooks', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('dashboardKeys', () => {
it('ควรสร้าง cache keys ที่ถูกต้อง', () => {
expect(dashboardKeys.all).toEqual(['dashboard']);
expect(dashboardKeys.stats('proj-1')).toEqual(['dashboard', 'stats', 'proj-1']);
expect(dashboardKeys.activity('proj-2')).toEqual(['dashboard', 'activity', 'proj-2']);
expect(dashboardKeys.pending('proj-3')).toEqual(['dashboard', 'pending', 'proj-3']);
});
});
describe('useDashboardStats', () => {
it('ควรดึงข้อมูล stats สำเร็จ', async () => {
const mockData = { totalDocuments: 10, pendingApprovals: 2 };
vi.mocked(dashboardService.getStats).mockResolvedValue(mockData as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useDashboardStats('proj-1'), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(dashboardService.getStats).toHaveBeenCalledWith('proj-1');
});
});
describe('useRecentActivity', () => {
it('ควรดึงข้อมูล recent activity สำเร็จ', async () => {
const mockData = [{ id: 'act-1', action: 'CREATE' }];
vi.mocked(dashboardService.getRecentActivity).mockResolvedValue(mockData as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useRecentActivity('proj-1'), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(dashboardService.getRecentActivity).toHaveBeenCalledWith('proj-1');
});
});
describe('usePendingTasks', () => {
it('ควรดึงข้อมูล pending tasks สำเร็จ', async () => {
const mockData = [{ publicId: 'task-1', title: 'Task 1' }];
vi.mocked(dashboardService.getPendingTasks).mockResolvedValue(mockData as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => usePendingTasks('proj-1'), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(dashboardService.getPendingTasks).toHaveBeenCalledWith('proj-1');
});
});
});
@@ -0,0 +1,118 @@
// File: frontend/hooks/__tests__/use-delegation.test.ts
// Change Log:
// - 2026-06-13: Initial creation - test coverage for use-delegation hooks
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { createTestQueryClient } from '@/lib/test-utils';
import { useMyDelegations, useCreateDelegation, useRevokeDelegation, delegationKeys } from '../use-delegation';
import apiClient from '@/lib/api/client';
import { toast } from 'sonner';
// Mock apiClient
vi.mock('@/lib/api/client', () => ({
default: {
get: vi.fn(),
post: vi.fn(),
delete: vi.fn(),
},
}));
describe('use-delegation hooks', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('delegationKeys', () => {
it('ควรสร้าง cache keys ที่ถูกต้อง', () => {
expect(delegationKeys.all).toEqual(['delegations']);
expect(delegationKeys.mine()).toEqual(['delegations', 'mine']);
});
});
describe('useMyDelegations', () => {
it('ควรดึงข้อมูล delegations ของฉันสำเร็จ', async () => {
const mockData = [{ publicId: 'uuid-deleg-1', scope: 'PROJECT' }];
vi.mocked(apiClient.get).mockResolvedValue({ data: mockData });
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useMyDelegations(), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(apiClient.get).toHaveBeenCalledWith('/delegations');
});
});
describe('useCreateDelegation', () => {
it('ควรสร้าง delegation สำเร็จและแสดง toast success', async () => {
vi.mocked(apiClient.post).mockResolvedValue({ data: { success: true } });
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useCreateDelegation(), { wrapper });
const mockDto = {
delegateUserPublicId: 'uuid-user-1',
scope: 'PROJECT' as const,
startDate: '2026-01-01',
endDate: '2026-01-10',
};
await act(async () => {
await result.current.mutateAsync(mockDto);
});
expect(apiClient.post).toHaveBeenCalledWith('/delegations', mockDto);
expect(toast.success).toHaveBeenCalledWith('Delegation created successfully');
});
it('ควรแสดง toast error เมื่อสร้าง delegation ล้มเหลว', async () => {
const mockError = new Error('API Error');
vi.mocked(apiClient.post).mockRejectedValue(mockError);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useCreateDelegation(), { wrapper });
const mockDto = {
delegateUserPublicId: 'uuid-user-1',
scope: 'PROJECT' as const,
startDate: '2026-01-01',
endDate: '2026-01-10',
};
await act(async () => {
try {
await result.current.mutateAsync(mockDto);
} catch {
// Expected
}
});
expect(toast.error).toHaveBeenCalledWith('Failed to create delegation', {
description: 'API Error',
});
});
});
describe('useRevokeDelegation', () => {
it('ควรลบ delegation (revoke) สำเร็จและแสดง toast success', async () => {
vi.mocked(apiClient.delete).mockResolvedValue({ data: { success: true } });
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useRevokeDelegation(), { wrapper });
await act(async () => {
await result.current.mutateAsync('uuid-deleg-1');
});
expect(apiClient.delete).toHaveBeenCalledWith('/delegations/uuid-deleg-1');
expect(toast.success).toHaveBeenCalledWith('Delegation revoked');
});
it('ควรแสดง toast error เมื่อยกเลิก delegation ล้มเหลว', async () => {
const mockError = new Error('API Error');
vi.mocked(apiClient.delete).mockRejectedValue(mockError);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useRevokeDelegation(), { wrapper });
await act(async () => {
try {
await result.current.mutateAsync('uuid-deleg-1');
} catch {
// Expected
}
});
expect(toast.error).toHaveBeenCalledWith('Failed to revoke delegation', {
description: 'API Error',
});
});
});
});
@@ -0,0 +1,286 @@
// File: frontend/hooks/__tests__/use-master-data.test.ts
// Change Log:
// - 2026-06-13: Initial creation - test coverage for use-master-data hooks
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { createTestQueryClient } from '@/lib/test-utils';
import {
useOrganizations,
useCreateOrganization,
useUpdateOrganization,
useDeleteOrganization,
useDisciplines,
useProjects,
useContracts,
useCorrespondenceTypes,
useContractDrawingCategories,
useShopMainCategories,
useShopSubCategories,
masterDataKeys,
} from '../use-master-data';
import { masterDataService } from '@/lib/services/master-data.service';
import { organizationService } from '@/lib/services/organization.service';
import { projectService } from '@/lib/services/project.service';
import { contractService } from '@/lib/services/contract.service';
import { toast } from 'sonner';
// Mock services
vi.mock('@/lib/services/master-data.service', () => ({
masterDataService: {
createOrganization: vi.fn(),
updateOrganization: vi.fn(),
deleteOrganization: vi.fn(),
getDisciplines: vi.fn(),
getCorrespondenceTypes: vi.fn(),
getContractDrawingCategories: vi.fn(),
getShopMainCategories: vi.fn(),
getShopSubCategories: vi.fn(),
},
}));
vi.mock('@/lib/services/organization.service', () => ({
organizationService: {
getAll: vi.fn(),
},
}));
vi.mock('@/lib/services/project.service', () => ({
projectService: {
getAll: vi.fn(),
},
}));
vi.mock('@/lib/services/contract.service', () => ({
contractService: {
getAll: vi.fn(),
},
}));
describe('use-master-data hooks', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('masterDataKeys', () => {
it('ควรสร้าง cache keys ที่ถูกต้อง', () => {
expect(masterDataKeys.all).toEqual(['masterData']);
expect(masterDataKeys.organizations()).toEqual(['masterData', 'organizations']);
expect(masterDataKeys.correspondenceTypes()).toEqual(['masterData', 'correspondenceTypes']);
expect(masterDataKeys.disciplines('uuid-1')).toEqual(['masterData', 'disciplines', 'uuid-1']);
});
});
describe('useOrganizations', () => {
it('ควรดึงข้อมูลองค์กรสำเร็จ', async () => {
const mockData = [{ publicId: 'uuid-org-1', organizationName: 'Org A' }];
vi.mocked(organizationService.getAll).mockResolvedValue(mockData);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useOrganizations({ isActive: true }), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(organizationService.getAll).toHaveBeenCalledWith({ isActive: true });
});
});
describe('useCreateOrganization', () => {
it('ควรสร้างองค์กรสำเร็จและแสดง toast success', async () => {
const mockResponse = { publicId: 'uuid-org-1', organizationName: 'New Org' };
vi.mocked(masterDataService.createOrganization).mockResolvedValue(mockResponse);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useCreateOrganization(), { wrapper });
await act(async () => {
await result.current.mutateAsync({ organizationName: 'New Org', organizationCode: 'ORG' });
});
expect(masterDataService.createOrganization).toHaveBeenCalledWith({ organizationName: 'New Org', organizationCode: 'ORG' });
expect(toast.success).toHaveBeenCalledWith('Organization created successfully');
});
it('ควรแสดง toast error เมื่อสร้างไม่สำเร็จ', async () => {
const mockError = {
message: 'Error',
response: { data: { message: 'Duplicate code' } },
};
vi.mocked(masterDataService.createOrganization).mockRejectedValue(mockError);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useCreateOrganization(), { wrapper });
await act(async () => {
try {
await result.current.mutateAsync({ organizationName: 'New Org', organizationCode: 'ORG' });
} catch {
// คาดหวังว่าจะเกิด error
}
});
expect(toast.error).toHaveBeenCalledWith('Failed to create organization', {
description: 'Duplicate code',
});
});
});
describe('useUpdateOrganization', () => {
it('ควรแก้ไของค์กรสำเร็จและแสดง toast success', async () => {
const mockResponse = { publicId: 'uuid-org-1', organizationName: 'Updated Org' };
vi.mocked(masterDataService.updateOrganization).mockResolvedValue(mockResponse);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useUpdateOrganization(), { wrapper });
await act(async () => {
await result.current.mutateAsync({ uuid: 'uuid-org-1', data: { organizationName: 'Updated Org' } });
});
expect(masterDataService.updateOrganization).toHaveBeenCalledWith('uuid-org-1', { organizationName: 'Updated Org' });
expect(toast.success).toHaveBeenCalledWith('Organization updated successfully');
});
it('ควรแสดง toast error เมื่อแก้ไขไม่สำเร็จ', async () => {
const mockError = {
message: 'Error',
response: { data: { message: 'Not found' } },
};
vi.mocked(masterDataService.updateOrganization).mockRejectedValue(mockError);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useUpdateOrganization(), { wrapper });
await act(async () => {
try {
await result.current.mutateAsync({ uuid: 'uuid-org-1', data: { organizationName: 'Updated Org' } });
} catch {
// คาดหวังว่าจะเกิด error
}
});
expect(toast.error).toHaveBeenCalledWith('Failed to update organization', {
description: 'Not found',
});
});
});
describe('useDeleteOrganization', () => {
it('ควรลบองค์กรสำเร็จและแสดง toast success', async () => {
vi.mocked(masterDataService.deleteOrganization).mockResolvedValue({});
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useDeleteOrganization(), { wrapper });
await act(async () => {
await result.current.mutateAsync('uuid-org-1');
});
expect(masterDataService.deleteOrganization).toHaveBeenCalledWith('uuid-org-1');
expect(toast.success).toHaveBeenCalledWith('Organization deleted successfully');
});
it('ควรแสดง toast error เมื่อลบไม่สำเร็จ', async () => {
const mockError = {
message: 'Error',
response: { data: { message: 'Constraint violation' } },
};
vi.mocked(masterDataService.deleteOrganization).mockRejectedValue(mockError);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useDeleteOrganization(), { wrapper });
await act(async () => {
try {
await result.current.mutateAsync('uuid-org-1');
} catch {
// คาดหวังว่าจะเกิด error
}
});
expect(toast.error).toHaveBeenCalledWith('Failed to delete organization', {
description: 'Constraint violation',
});
});
});
describe('useDisciplines', () => {
it('ควรดึงข้อมูลสาขา (Disciplines) สำเร็จ', async () => {
const mockData = [{ publicId: 'uuid-disp-1', disciplineCode: 'CIV' }];
vi.mocked(masterDataService.getDisciplines).mockResolvedValue(mockData);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useDisciplines('uuid-contract-1'), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(masterDataService.getDisciplines).toHaveBeenCalledWith('uuid-contract-1');
});
});
describe('useProjects', () => {
it('ควรดึงข้อมูลโครงการ (Projects) สำเร็จ', async () => {
const mockData = [{ publicId: 'uuid-proj-1', projectName: 'Project A' }];
vi.mocked(projectService.getAll).mockResolvedValue(mockData);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useProjects(true), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(projectService.getAll).toHaveBeenCalledWith({ isActive: true });
});
});
describe('useContracts', () => {
it('ควรดึงข้อมูลสัญญา (Contracts) สำเร็จ', async () => {
const mockData = [{ publicId: 'uuid-cont-1', name: 'Contract A' }];
vi.mocked(contractService.getAll).mockResolvedValue(mockData);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useContracts('uuid-proj-1'), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(contractService.getAll).toHaveBeenCalledWith({ projectId: 'uuid-proj-1' });
});
});
describe('useCorrespondenceTypes', () => {
it('ควรดึงข้อมูลชนิดจดหมายนำส่งสำเร็จ', async () => {
const mockData = [{ publicId: 'uuid-corr-1', typeCode: 'RFA' }];
vi.mocked(masterDataService.getCorrespondenceTypes).mockResolvedValue(mockData);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useCorrespondenceTypes(), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(masterDataService.getCorrespondenceTypes).toHaveBeenCalled();
});
});
describe('useContractDrawingCategories', () => {
it('ควรดึงข้อมูลหมวดหมู่แบบคู่สัญญาสำเร็จ', async () => {
const mockData = [{ id: 1, categoryName: 'Design Drawing' }];
vi.mocked(masterDataService.getContractDrawingCategories).mockResolvedValue(mockData);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useContractDrawingCategories('uuid-proj-1'), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(masterDataService.getContractDrawingCategories).toHaveBeenCalledWith('uuid-proj-1');
});
});
describe('useShopMainCategories', () => {
it('ควรดึงข้อมูลหมวดหมู่หลักแบบรายละเอียดก่อสร้างสำเร็จ', async () => {
const mockData = [{ id: 1, mainCategoryName: 'Structural' }];
vi.mocked(masterDataService.getShopMainCategories).mockResolvedValue(mockData);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useShopMainCategories(123), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(masterDataService.getShopMainCategories).toHaveBeenCalledWith(123);
});
});
describe('useShopSubCategories', () => {
it('ควรดึงข้อมูลหมวดหมู่ย่อยสำเร็จ', async () => {
const mockData = [{ id: 10, subCategoryName: 'Foundation' }];
vi.mocked(masterDataService.getShopSubCategories).mockResolvedValue(mockData);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useShopSubCategories(123, 1), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(masterDataService.getShopSubCategories).toHaveBeenCalledWith(123, 1);
});
});
});
@@ -0,0 +1,165 @@
// File: frontend/hooks/__tests__/use-numbering.test.ts
// Change Log:
// - 2026-06-13: Initial creation - test coverage for use-numbering hooks
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { createTestQueryClient } from '@/lib/test-utils';
import {
useTemplates,
useSaveTemplate,
useNumberingMetrics,
useNumberingAuditLogs,
useManualOverrideNumbering,
useVoidAndReplaceNumbering,
useCancelNumbering,
useBulkImportNumbering,
numberingKeys,
} from '../use-numbering';
import { documentNumberingService } from '@/lib/services/document-numbering.service';
import { numberingApi } from '@/lib/api/numbering';
// Mock services
vi.mock('@/lib/services/document-numbering.service', () => ({
documentNumberingService: {
getMetrics: vi.fn(),
getAuditLogs: vi.fn(),
manualOverride: vi.fn(),
voidAndReplace: vi.fn(),
cancelNumber: vi.fn(),
bulkImport: vi.fn(),
},
}));
vi.mock('@/lib/api/numbering', () => ({
numberingApi: {
getTemplates: vi.fn(),
saveTemplate: vi.fn(),
},
}));
describe('use-numbering hooks', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('numberingKeys', () => {
it('ควรสร้าง cache keys ที่ถูกต้อง', () => {
expect(numberingKeys.all).toEqual(['numbering']);
expect(numberingKeys.templates()).toEqual(['numbering', 'templates']);
expect(numberingKeys.metrics()).toEqual(['numbering', 'metrics']);
expect(numberingKeys.auditLogs({ page: 1 })).toEqual(['numbering', 'auditLogs', { page: 1 }]);
});
});
describe('useTemplates', () => {
it('ควรดึงข้อมูล templates สำเร็จ', async () => {
const mockData = [{ id: 1, templateName: 'Temp A' }];
vi.mocked(numberingApi.getTemplates).mockResolvedValue(mockData as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useTemplates(), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(numberingApi.getTemplates).toHaveBeenCalled();
});
});
describe('useSaveTemplate', () => {
it('ควรบันทึก template สำเร็จ', async () => {
const mockResponse = { id: 1, success: true };
vi.mocked(numberingApi.saveTemplate).mockResolvedValue(mockResponse as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useSaveTemplate(), { wrapper });
await act(async () => {
await result.current.mutateAsync({ templateName: 'New Temp' } as any);
});
expect(numberingApi.saveTemplate).toHaveBeenCalledWith({ templateName: 'New Temp' });
});
});
describe('useNumberingMetrics', () => {
it('ควรดึงข้อมูล metrics สำเร็จ', async () => {
const mockData = { totalGenerated: 100 };
vi.mocked(documentNumberingService.getMetrics).mockResolvedValue(mockData as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useNumberingMetrics(), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(documentNumberingService.getMetrics).toHaveBeenCalled();
});
});
describe('useNumberingAuditLogs', () => {
it('ควรดึงข้อมูล audit logs สำเร็จ', async () => {
const mockData = [{ id: 1, action: 'OVERRIDE' }];
vi.mocked(documentNumberingService.getAuditLogs).mockResolvedValue(mockData as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useNumberingAuditLogs({ page: 1 }), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(documentNumberingService.getAuditLogs).toHaveBeenCalled();
});
});
describe('useManualOverrideNumbering', () => {
it('ควรทำ manual override สำเร็จ', async () => {
const mockResponse = { success: true };
vi.mocked(documentNumberingService.manualOverride).mockResolvedValue(mockResponse as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useManualOverrideNumbering(), { wrapper });
const mockDto = { documentNumber: 'DOC-001', reason: 'Urgent' };
await act(async () => {
await result.current.mutateAsync(mockDto as any);
});
expect(documentNumberingService.manualOverride).toHaveBeenCalledWith(mockDto);
});
});
describe('useVoidAndReplaceNumbering', () => {
it('ควรทำ void and replace สำเร็จ', async () => {
const mockResponse = { success: true };
vi.mocked(documentNumberingService.voidAndReplace).mockResolvedValue(mockResponse as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useVoidAndReplaceNumbering(), { wrapper });
const mockDto = { originalNumber: 'DOC-001', newNumber: 'DOC-002' };
await act(async () => {
await result.current.mutateAsync(mockDto as any);
});
expect(documentNumberingService.voidAndReplace).toHaveBeenCalledWith(mockDto);
});
});
describe('useCancelNumbering', () => {
it('ควรทำ cancel numbering สำเร็จ', async () => {
const mockResponse = { success: true };
vi.mocked(documentNumberingService.cancelNumber).mockResolvedValue(mockResponse as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useCancelNumbering(), { wrapper });
const mockDto = { documentNumber: 'DOC-001' };
await act(async () => {
await result.current.mutateAsync(mockDto as any);
});
expect(documentNumberingService.cancelNumber).toHaveBeenCalledWith(mockDto);
});
});
describe('useBulkImportNumbering', () => {
it('ควรทำ bulk import สำเร็จ', async () => {
const mockResponse = { success: true };
vi.mocked(documentNumberingService.bulkImport).mockResolvedValue(mockResponse as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useBulkImportNumbering(), { wrapper });
const mockDto = [{ documentNumber: 'DOC-001', projectId: 1, sequenceNumber: 1 }];
await act(async () => {
await result.current.mutateAsync(mockDto);
});
expect(documentNumberingService.bulkImport).toHaveBeenCalledWith(mockDto);
});
});
});
@@ -0,0 +1,195 @@
// File: frontend/hooks/__tests__/use-review-teams.test.ts
// Change Log:
// - 2026-06-13: Initial creation - test coverage for use-review-teams hooks
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { createTestQueryClient } from '@/lib/test-utils';
import {
useReviewTeams,
useReviewTeam,
useCreateReviewTeam,
useUpdateReviewTeam,
useAddTeamMember,
useRemoveTeamMember,
reviewTeamKeys,
} from '../use-review-teams';
import { reviewTeamService } from '@/lib/services/review-team.service';
import { toast } from 'sonner';
// Mock reviewTeamService
vi.mock('@/lib/services/review-team.service', () => ({
reviewTeamService: {
getAll: vi.fn(),
getByPublicId: vi.fn(),
create: vi.fn(),
update: vi.fn(),
addMember: vi.fn(),
removeMember: vi.fn(),
},
}));
describe('use-review-teams hooks', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('reviewTeamKeys', () => {
it('ควรสร้าง cache keys ที่ถูกต้อง', () => {
expect(reviewTeamKeys.all).toEqual(['reviewTeams']);
expect(reviewTeamKeys.lists()).toEqual(['reviewTeams', 'list']);
expect(reviewTeamKeys.list({ search: 'A' })).toEqual(['reviewTeams', 'list', { search: 'A' }]);
expect(reviewTeamKeys.details()).toEqual(['reviewTeams', 'detail']);
expect(reviewTeamKeys.detail('uuid-1')).toEqual(['reviewTeams', 'detail', 'uuid-1']);
});
});
describe('useReviewTeams', () => {
it('ควรดึงข้อมูล lists ของ review teams สำเร็จ', async () => {
const mockData = [{ publicId: 'uuid-team-1', teamName: 'Team A' }];
vi.mocked(reviewTeamService.getAll).mockResolvedValue(mockData as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useReviewTeams({ search: 'A' }), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(reviewTeamService.getAll).toHaveBeenCalledWith({ search: 'A' });
});
});
describe('useReviewTeam', () => {
it('ควรดึงข้อมูลรายละเอียด review team สำเร็จ', async () => {
const mockData = { publicId: 'uuid-team-1', teamName: 'Team A' };
vi.mocked(reviewTeamService.getByPublicId).mockResolvedValue(mockData as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useReviewTeam('uuid-team-1'), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(reviewTeamService.getByPublicId).toHaveBeenCalledWith('uuid-team-1');
});
});
describe('useCreateReviewTeam', () => {
it('ควรสร้าง review team สำเร็จและแสดง toast success', async () => {
vi.mocked(reviewTeamService.create).mockResolvedValue({ success: true } as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useCreateReviewTeam(), { wrapper });
const mockDto = { teamName: 'New Team', projectId: 1 };
await act(async () => {
await result.current.mutateAsync(mockDto as any);
});
expect(reviewTeamService.create).toHaveBeenCalledWith(mockDto);
expect(toast.success).toHaveBeenCalledWith('Review Team created successfully');
});
it('ควรแสดง toast error เมื่อสร้าง review team ล้มเหลว', async () => {
const mockError = new Error('API Error');
vi.mocked(reviewTeamService.create).mockRejectedValue(mockError);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useCreateReviewTeam(), { wrapper });
await act(async () => {
try {
await result.current.mutateAsync({ teamName: 'New Team' } as any);
} catch {
// Expected
}
});
expect(toast.error).toHaveBeenCalledWith('Failed to create Review Team', {
description: 'API Error',
});
});
});
describe('useUpdateReviewTeam', () => {
it('ควรปรับปรุงข้อมูลทีมสำเร็จและแสดง toast success', async () => {
vi.mocked(reviewTeamService.update).mockResolvedValue({ success: true } as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useUpdateReviewTeam(), { wrapper });
await act(async () => {
await result.current.mutateAsync({ publicId: 'uuid-team-1', data: { teamName: 'Updated Team' } });
});
expect(reviewTeamService.update).toHaveBeenCalledWith('uuid-team-1', { teamName: 'Updated Team' });
expect(toast.success).toHaveBeenCalledWith('Review Team updated');
});
it('ควรแสดง toast error เมื่อปรับปรุงข้อมูลทีมล้มเหลว', async () => {
const mockError = new Error('API Error');
vi.mocked(reviewTeamService.update).mockRejectedValue(mockError);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useUpdateReviewTeam(), { wrapper });
await act(async () => {
try {
await result.current.mutateAsync({ publicId: 'uuid-team-1', data: { teamName: 'Updated Team' } });
} catch {
// Expected
}
});
expect(toast.error).toHaveBeenCalledWith('Failed to update Review Team', {
description: 'API Error',
});
});
});
describe('useAddTeamMember', () => {
it('ควรเพิ่มสมาชิกเข้าทีมสำเร็จและแสดง toast success', async () => {
vi.mocked(reviewTeamService.addMember).mockResolvedValue({ success: true } as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useAddTeamMember(), { wrapper });
await act(async () => {
await result.current.mutateAsync({ teamPublicId: 'uuid-team-1', data: { userPublicId: 'uuid-user-1', role: 'REVIEWER' } as any });
});
expect(reviewTeamService.addMember).toHaveBeenCalledWith('uuid-team-1', { userPublicId: 'uuid-user-1', role: 'REVIEWER' });
expect(toast.success).toHaveBeenCalledWith('Member added to team');
});
it('ควรแสดง toast error เมื่อเพิ่มสมาชิกเข้าทีมล้มเหลว', async () => {
const mockError = new Error('API Error');
vi.mocked(reviewTeamService.addMember).mockRejectedValue(mockError);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useAddTeamMember(), { wrapper });
await act(async () => {
try {
await result.current.mutateAsync({ teamPublicId: 'uuid-team-1', data: { userPublicId: 'uuid-user-1', role: 'REVIEWER' } as any });
} catch {
// Expected
}
});
expect(toast.error).toHaveBeenCalledWith('Failed to add member', {
description: 'API Error',
});
});
});
describe('useRemoveTeamMember', () => {
it('ควรลบสมาชิกออกจากทีมสำเร็จและแสดง toast success', async () => {
vi.mocked(reviewTeamService.removeMember).mockResolvedValue({ success: true } as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useRemoveTeamMember(), { wrapper });
await act(async () => {
await result.current.mutateAsync({ teamPublicId: 'uuid-team-1', memberPublicId: 'uuid-member-1' });
});
expect(reviewTeamService.removeMember).toHaveBeenCalledWith('uuid-team-1', 'uuid-member-1');
expect(toast.success).toHaveBeenCalledWith('Member removed from team');
});
it('ควรแสดง toast error เมื่อลบสมาชิกออกจากทีมล้มเหลว', async () => {
const mockError = new Error('API Error');
vi.mocked(reviewTeamService.removeMember).mockRejectedValue(mockError);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useRemoveTeamMember(), { wrapper });
await act(async () => {
try {
await result.current.mutateAsync({ teamPublicId: 'uuid-team-1', memberPublicId: 'uuid-member-1' });
} catch {
// Expected
}
});
expect(toast.error).toHaveBeenCalledWith('Failed to remove member', {
description: 'API Error',
});
});
});
});
@@ -0,0 +1,62 @@
// File: frontend/hooks/__tests__/use-transmittal.test.ts
// Change Log:
// - 2026-06-13: Initial creation - test coverage for useTransmittal hook
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { createTestQueryClient } from '@/lib/test-utils';
import { useTransmittal, transmittalKeys } from '../use-transmittal';
import { transmittalService } from '@/lib/services/transmittal.service';
// Mock service
vi.mock('@/lib/services/transmittal.service', () => ({
transmittalService: {
getByUuid: vi.fn(),
},
}));
describe('useTransmittal hook', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('transmittalKeys', () => {
it('ควรสร้าง cache keys ที่ถูกต้อง', () => {
expect(transmittalKeys.all).toEqual(['transmittals']);
expect(transmittalKeys.detail('uuid-1')).toEqual(['transmittals', 'detail', 'uuid-1']);
});
});
describe('useTransmittal', () => {
it('ควรดึงรายละเอียด transmittal สำเร็จ', async () => {
const mockData = { publicId: 'uuid-1', transmittalNumber: 'TR-001' };
vi.mocked(transmittalService.getByUuid).mockResolvedValue(mockData as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useTransmittal('uuid-1'), { wrapper });
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.transmittal).toEqual(mockData);
expect(transmittalService.getByUuid).toHaveBeenCalledWith('uuid-1');
});
it('ควรดึงรายละเอียด transmittal สำเร็จในแบบ wrapped response', async () => {
const mockData = { publicId: 'uuid-2', transmittalNumber: 'TR-002' };
vi.mocked(transmittalService.getByUuid).mockResolvedValue({ data: mockData } as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useTransmittal('uuid-2'), { wrapper });
await waitFor(() => {
expect(result.current.isLoading).toBe(false);
});
expect(result.current.transmittal).toEqual(mockData);
});
it('ไม่ควรทำงานเมื่อไม่ระบุ uuid', async () => {
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useTransmittal(undefined), { wrapper });
expect(result.current.isLoading).toBe(false);
expect(result.current.transmittal).toBeUndefined();
expect(transmittalService.getByUuid).not.toHaveBeenCalled();
});
});
});
@@ -0,0 +1,142 @@
// File: frontend/hooks/__tests__/use-workflow-history.test.ts
// Change Log:
// - 2026-06-13: Initial creation - test coverage for useWorkflowHistory hook
// - 2026-06-13: Refactor to use static imports and createTestQueryClient helper
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor } from '@testing-library/react';
import { useWorkflowHistory, workflowHistoryKeys } from '../use-workflow-history';
import { createTestQueryClient } from '@/lib/test-utils';
import { workflowEngineService } from '@/lib/services/workflow-engine.service';
// Mock workflowEngineService
vi.mock('@/lib/services/workflow-engine.service', () => ({
workflowEngineService: {
getHistory: vi.fn(),
},
}));
describe('useWorkflowHistory', () => {
const mockInstanceId = '019505a1-7c3e-7000-8000-abc123def456';
const mockHistory = [
{
id: 1,
fromState: 'DFT',
toState: 'FAP',
action: 'SUBMIT',
actorId: 'user-uuid',
actorName: 'Test User',
timestamp: '2026-01-01T00:00:00Z',
comments: 'Submitted for approval',
},
];
beforeEach(() => {
vi.clearAllMocks();
});
it('should return workflow history data', async () => {
(workflowEngineService.getHistory as any).mockResolvedValue(mockHistory);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useWorkflowHistory(mockInstanceId), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockHistory);
expect(workflowEngineService.getHistory).toHaveBeenCalledWith(mockInstanceId);
});
it('should be disabled when instanceId is undefined', () => {
(workflowEngineService.getHistory as any).mockResolvedValue(mockHistory);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useWorkflowHistory(undefined), { wrapper });
expect(result.current.fetchStatus).toBe('idle');
expect(workflowEngineService.getHistory).not.toHaveBeenCalled();
});
it('should be disabled when instanceId is empty string', () => {
(workflowEngineService.getHistory as any).mockResolvedValue(mockHistory);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useWorkflowHistory(''), { wrapper });
expect(result.current.fetchStatus).toBe('idle');
expect(workflowEngineService.getHistory).not.toHaveBeenCalled();
});
it('should handle fetch error', async () => {
(workflowEngineService.getHistory as any).mockRejectedValue(new Error('API Error'));
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useWorkflowHistory(mockInstanceId), { wrapper });
await waitFor(() => {
expect(result.current.isError).toBe(true);
});
expect(result.current.error).toBeTruthy();
});
it('should not retry on failure (retry: false)', async () => {
(workflowEngineService.getHistory as any).mockRejectedValue(new Error('API Error'));
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useWorkflowHistory(mockInstanceId), { wrapper });
await waitFor(() => {
expect(result.current.isError).toBe(true);
});
// Should only be called once (no retry)
expect(workflowEngineService.getHistory).toHaveBeenCalledTimes(1);
});
it('should use correct query key', async () => {
(workflowEngineService.getHistory as any).mockResolvedValue(mockHistory);
const { wrapper } = createTestQueryClient();
renderHook(() => useWorkflowHistory(mockInstanceId), { wrapper });
await waitFor(() => {
expect(workflowEngineService.getHistory).toHaveBeenCalled();
});
// Verify query key structure
const expectedKey = workflowHistoryKeys.instance(mockInstanceId);
expect(expectedKey).toEqual(['workflow-history', mockInstanceId]);
});
it('should return empty array when no history exists', async () => {
(workflowEngineService.getHistory as any).mockResolvedValue([]);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useWorkflowHistory(mockInstanceId), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual([]);
});
it('should handle multiple instance IDs correctly', async () => {
(workflowEngineService.getHistory as any).mockResolvedValue(mockHistory);
const { wrapper } = createTestQueryClient();
const { result: result1 } = renderHook(() => useWorkflowHistory('instance-1'), { wrapper });
const { result: result2 } = renderHook(() => useWorkflowHistory('instance-2'), { wrapper });
await waitFor(() => {
expect(result1.current.isSuccess).toBe(true);
expect(result2.current.isSuccess).toBe(true);
});
expect(workflowEngineService.getHistory).toHaveBeenCalledWith('instance-1');
expect(workflowEngineService.getHistory).toHaveBeenCalledWith('instance-2');
});
});
@@ -0,0 +1,152 @@
// File: frontend/hooks/__tests__/use-workflows.test.ts
// Change Log:
// - 2026-06-13: Initial creation - test coverage for use-workflows hooks
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { renderHook, waitFor, act } from '@testing-library/react';
import { createTestQueryClient } from '@/lib/test-utils';
import {
useWorkflowDefinitions,
useWorkflowDefinition,
useCreateWorkflowDefinition,
useUpdateWorkflowDefinition,
useDeleteWorkflowDefinition,
useEvaluateWorkflow,
useGetAvailableActions,
useValidateDsl,
workflowKeys,
} from '../use-workflows';
import { workflowEngineService } from '@/lib/services/workflow-engine.service';
// Mock services
vi.mock('@/lib/services/workflow-engine.service', () => ({
workflowEngineService: {
getDefinitions: vi.fn(),
getDefinitionById: vi.fn(),
createDefinition: vi.fn(),
updateDefinition: vi.fn(),
deleteDefinition: vi.fn(),
evaluate: vi.fn(),
getAvailableActions: vi.fn(),
validateDsl: vi.fn(),
},
}));
describe('use-workflows hooks', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('workflowKeys', () => {
it('ควรสร้าง cache keys ที่ถูกต้อง', () => {
expect(workflowKeys.all).toEqual(['workflows']);
expect(workflowKeys.definitions()).toEqual(['workflows', 'definitions']);
expect(workflowKeys.definition('uuid-1')).toEqual(['workflows', 'definitions', 'uuid-1']);
});
});
describe('useWorkflowDefinitions', () => {
it('ควรดึงข้อมูล definitions สำเร็จ', async () => {
const mockData = [{ publicId: 'uuid-1', workflowName: 'Workflow A' }];
vi.mocked(workflowEngineService.getDefinitions).mockResolvedValue(mockData as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useWorkflowDefinitions(), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(workflowEngineService.getDefinitions).toHaveBeenCalled();
});
});
describe('useWorkflowDefinition', () => {
it('ควรดึงข้อมูล definition ตาม id สำเร็จ', async () => {
const mockData = { publicId: 'uuid-1', workflowName: 'Workflow A' };
vi.mocked(workflowEngineService.getDefinitionById).mockResolvedValue(mockData as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useWorkflowDefinition('uuid-1'), { wrapper });
await waitFor(() => {
expect(result.current.isSuccess).toBe(true);
});
expect(result.current.data).toEqual(mockData);
expect(workflowEngineService.getDefinitionById).toHaveBeenCalledWith('uuid-1');
});
});
describe('useCreateWorkflowDefinition', () => {
it('ควรสร้าง workflow definition สำเร็จ', async () => {
const mockResponse = { publicId: 'uuid-1', workflowName: 'Workflow A' };
vi.mocked(workflowEngineService.createDefinition).mockResolvedValue(mockResponse as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useCreateWorkflowDefinition(), { wrapper });
await act(async () => {
await result.current.mutateAsync({ workflowName: 'Workflow A', dslDefinition: {} } as any);
});
expect(workflowEngineService.createDefinition).toHaveBeenCalledWith({ workflowName: 'Workflow A', dslDefinition: {} });
});
});
describe('useUpdateWorkflowDefinition', () => {
it('ควรแก้ไข workflow definition สำเร็จ', async () => {
const mockResponse = { publicId: 'uuid-1', workflowName: 'Workflow A Updated' };
vi.mocked(workflowEngineService.updateDefinition).mockResolvedValue(mockResponse as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useUpdateWorkflowDefinition(), { wrapper });
await act(async () => {
await result.current.mutateAsync({ id: 'uuid-1', data: { workflowName: 'Workflow A Updated' } as any });
});
expect(workflowEngineService.updateDefinition).toHaveBeenCalledWith('uuid-1', { workflowName: 'Workflow A Updated' });
});
});
describe('useDeleteWorkflowDefinition', () => {
it('ควรลบ workflow definition สำเร็จ', async () => {
vi.mocked(workflowEngineService.deleteDefinition).mockResolvedValue({} as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useDeleteWorkflowDefinition(), { wrapper });
await act(async () => {
await result.current.mutateAsync('uuid-1');
});
expect(workflowEngineService.deleteDefinition).toHaveBeenCalledWith('uuid-1');
});
});
describe('useEvaluateWorkflow', () => {
it('ควรประเมิน workflow สำเร็จ', async () => {
const mockResponse = { status: 'APPROVED' };
vi.mocked(workflowEngineService.evaluate).mockResolvedValue(mockResponse as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useEvaluateWorkflow(), { wrapper });
await act(async () => {
await result.current.mutateAsync({ workflowUuid: 'uuid-1', action: 'APPROVE' } as any);
});
expect(workflowEngineService.evaluate).toHaveBeenCalledWith({ workflowUuid: 'uuid-1', action: 'APPROVE' });
});
});
describe('useGetAvailableActions', () => {
it('ควรดึง actions ที่ใช้งานได้สำเร็จ', async () => {
const mockResponse = ['APPROVE', 'REJECT'];
vi.mocked(workflowEngineService.getAvailableActions).mockResolvedValue(mockResponse as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useGetAvailableActions(), { wrapper });
await act(async () => {
await result.current.mutateAsync({ workflowUuid: 'uuid-1' } as any);
});
expect(workflowEngineService.getAvailableActions).toHaveBeenCalledWith({ workflowUuid: 'uuid-1' });
});
});
describe('useValidateDsl', () => {
it('ควรตรวจสอบความถูกต้องของ DSL สำเร็จ', async () => {
const mockResponse = { valid: true };
vi.mocked(workflowEngineService.validateDsl).mockResolvedValue(mockResponse as any);
const { wrapper } = createTestQueryClient();
const { result } = renderHook(() => useValidateDsl(), { wrapper });
await act(async () => {
await result.current.mutateAsync({ steps: [] });
});
expect(workflowEngineService.validateDsl).toHaveBeenCalledWith({ steps: [] });
});
});
});