251211:1314 Frontend: reeactor Admin panel
Some checks failed
Spec Validation / validate-markdown (push) Has been cancelled
Spec Validation / validate-diagrams (push) Has been cancelled
Spec Validation / check-todos (push) Has been cancelled

This commit is contained in:
admin
2025-12-11 13:14:15 +07:00
parent c8a0f281ef
commit 3fa28bd14f
79 changed files with 6571 additions and 206 deletions

View File

@@ -0,0 +1,157 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { correspondenceService } from '../correspondence.service';
import apiClient from '@/lib/api/client';
// apiClient is already mocked in vitest.setup.ts
describe('correspondenceService', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('getAll', () => {
it('should call GET /correspondences with params', async () => {
const mockResponse = {
data: [{ id: 1, title: 'Test' }],
meta: { total: 1 },
};
vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });
const result = await correspondenceService.getAll({ projectId: 1 });
expect(apiClient.get).toHaveBeenCalledWith('/correspondences', {
params: { projectId: 1 },
});
expect(result).toEqual(mockResponse);
});
it('should call GET /correspondences without params', async () => {
const mockResponse = { data: [] };
vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });
await correspondenceService.getAll();
expect(apiClient.get).toHaveBeenCalledWith('/correspondences', {
params: undefined,
});
});
});
describe('getById', () => {
it('should call GET /correspondences/:id', async () => {
const mockResponse = { id: 1, title: 'Test' };
vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });
const result = await correspondenceService.getById(1);
expect(apiClient.get).toHaveBeenCalledWith('/correspondences/1');
expect(result).toEqual(mockResponse);
});
it('should work with string id', async () => {
const mockResponse = { id: 1 };
vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });
await correspondenceService.getById('123');
expect(apiClient.get).toHaveBeenCalledWith('/correspondences/123');
});
});
describe('create', () => {
it('should call POST /correspondences with data', async () => {
const createDto = {
title: 'New Correspondence',
projectId: 1,
correspondenceTypeId: 1,
};
const mockResponse = { id: 1, ...createDto };
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await correspondenceService.create(createDto);
expect(apiClient.post).toHaveBeenCalledWith('/correspondences', createDto);
expect(result).toEqual(mockResponse);
});
});
describe('update', () => {
it('should call PUT /correspondences/:id with data', async () => {
const updateData = { title: 'Updated Title' };
const mockResponse = { id: 1, title: 'Updated Title' };
vi.mocked(apiClient.put).mockResolvedValue({ data: mockResponse });
const result = await correspondenceService.update(1, updateData);
expect(apiClient.put).toHaveBeenCalledWith('/correspondences/1', updateData);
expect(result).toEqual(mockResponse);
});
});
describe('delete', () => {
it('should call DELETE /correspondences/:id', async () => {
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
const result = await correspondenceService.delete(1);
expect(apiClient.delete).toHaveBeenCalledWith('/correspondences/1');
expect(result).toEqual({});
});
});
describe('submit', () => {
it('should call POST /correspondences/:id/submit', async () => {
const submitDto = { recipientIds: [2, 3] };
const mockResponse = { id: 1, status: 'submitted' };
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await correspondenceService.submit(1, submitDto);
expect(apiClient.post).toHaveBeenCalledWith('/correspondences/1/submit', submitDto);
expect(result).toEqual(mockResponse);
});
});
describe('processWorkflow', () => {
it('should call POST /correspondences/:id/workflow', async () => {
const workflowDto = { action: 'approve', comment: 'LGTM' };
const mockResponse = { id: 1, status: 'approved' };
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await correspondenceService.processWorkflow(1, workflowDto);
expect(apiClient.post).toHaveBeenCalledWith('/correspondences/1/workflow', workflowDto);
expect(result).toEqual(mockResponse);
});
});
describe('addReference', () => {
it('should call POST /correspondences/:id/references', async () => {
const referenceDto = { referencedDocumentId: 2, referenceType: 'reply_to' };
const mockResponse = { id: 1 };
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await correspondenceService.addReference(1, referenceDto);
expect(apiClient.post).toHaveBeenCalledWith(
'/correspondences/1/references',
referenceDto
);
expect(result).toEqual(mockResponse);
});
});
describe('removeReference', () => {
it('should call DELETE /correspondences/:id/references with body', async () => {
const referenceDto = { referencedDocumentId: 2 };
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
const result = await correspondenceService.removeReference(1, referenceDto);
expect(apiClient.delete).toHaveBeenCalledWith('/correspondences/1/references', {
data: referenceDto,
});
expect(result).toEqual({});
});
});
});

View File

@@ -0,0 +1,329 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { masterDataService } from '../master-data.service';
import apiClient from '@/lib/api/client';
// apiClient is already mocked in vitest.setup.ts
describe('masterDataService', () => {
beforeEach(() => {
vi.clearAllMocks();
});
// --- Tags ---
describe('Tags', () => {
describe('getTags', () => {
it('should call GET /tags with params', async () => {
const mockTags = [{ id: 1, name: 'Important' }];
vi.mocked(apiClient.get).mockResolvedValue({ data: { data: mockTags } });
const result = await masterDataService.getTags({ search: 'test' });
expect(apiClient.get).toHaveBeenCalledWith('/master/tags', { params: { search: 'test' } });
expect(result).toEqual(mockTags);
});
it('should handle unwrapped response', async () => {
const mockTags = [{ id: 1, name: 'Urgent' }];
vi.mocked(apiClient.get).mockResolvedValue({ data: mockTags });
const result = await masterDataService.getTags();
expect(result).toEqual(mockTags);
});
});
describe('createTag', () => {
it('should call POST /tags', async () => {
const createDto = { name: 'New Tag', color: '#ff0000' };
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1, ...createDto } });
const result = await masterDataService.createTag(createDto);
expect(apiClient.post).toHaveBeenCalledWith('/master/tags', createDto);
expect(result).toEqual({ id: 1, ...createDto });
});
});
describe('updateTag', () => {
it('should call PUT /tags/:id', async () => {
const updateDto = { name: 'Updated Tag' };
vi.mocked(apiClient.patch).mockResolvedValue({ data: { id: 1, ...updateDto } });
const result = await masterDataService.updateTag(1, updateDto);
expect(apiClient.patch).toHaveBeenCalledWith('/master/tags/1', updateDto);
expect(result).toEqual({ id: 1, ...updateDto });
});
});
describe('deleteTag', () => {
it('should call DELETE /tags/:id', async () => {
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
const result = await masterDataService.deleteTag(1);
expect(apiClient.delete).toHaveBeenCalledWith('/master/tags/1');
expect(result).toEqual({});
});
});
});
// --- Organizations ---
describe('Organizations', () => {
describe('getOrganizations', () => {
it('should call GET /organizations and unwrap paginated response', async () => {
const mockOrgs = [{ organizationId: 1, name: 'Org A' }];
vi.mocked(apiClient.get).mockResolvedValue({ data: { data: mockOrgs } });
const result = await masterDataService.getOrganizations();
expect(apiClient.get).toHaveBeenCalledWith('/organizations', { params: undefined });
expect(result).toEqual(mockOrgs);
});
it('should handle array response', async () => {
const mockOrgs = [{ organizationId: 1 }];
vi.mocked(apiClient.get).mockResolvedValue({ data: mockOrgs });
const result = await masterDataService.getOrganizations();
expect(result).toEqual(mockOrgs);
});
it('should return empty array as fallback', async () => {
vi.mocked(apiClient.get).mockResolvedValue({ data: {} });
const result = await masterDataService.getOrganizations();
expect(result).toEqual([]);
});
});
describe('createOrganization', () => {
it('should call POST /organizations', async () => {
const createDto = { name: 'New Org', code: 'ORG-001' };
vi.mocked(apiClient.post).mockResolvedValue({ data: { organizationId: 1, ...createDto } });
const result = await masterDataService.createOrganization(createDto);
expect(apiClient.post).toHaveBeenCalledWith('/organizations', createDto);
expect(result.organizationId).toBe(1);
});
});
describe('updateOrganization', () => {
it('should call PUT /organizations/:id', async () => {
const updateDto = { name: 'Updated Org' };
vi.mocked(apiClient.put).mockResolvedValue({ data: { organizationId: 1, ...updateDto } });
const result = await masterDataService.updateOrganization(1, updateDto);
expect(apiClient.put).toHaveBeenCalledWith('/organizations/1', updateDto);
expect(result.name).toBe('Updated Org');
});
});
describe('deleteOrganization', () => {
it('should call DELETE /organizations/:id', async () => {
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
await masterDataService.deleteOrganization(1);
expect(apiClient.delete).toHaveBeenCalledWith('/organizations/1');
});
});
});
// --- Disciplines ---
describe('Disciplines', () => {
describe('getDisciplines', () => {
it('should call GET /master/disciplines with contractId', async () => {
const mockDisciplines = [{ id: 1, name: 'Civil' }];
vi.mocked(apiClient.get).mockResolvedValue({ data: { data: mockDisciplines } });
const result = await masterDataService.getDisciplines(1);
expect(apiClient.get).toHaveBeenCalledWith('/master/disciplines', {
params: { contractId: 1 },
});
expect(result).toEqual(mockDisciplines);
});
});
describe('createDiscipline', () => {
it('should call POST /master/disciplines', async () => {
const createDto = { name: 'Electrical', contractId: 1 };
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1, ...createDto } });
const result = await masterDataService.createDiscipline(createDto);
expect(apiClient.post).toHaveBeenCalledWith('/master/disciplines', createDto);
expect(result.name).toBe('Electrical');
});
});
describe('deleteDiscipline', () => {
it('should call DELETE /master/disciplines/:id', async () => {
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
await masterDataService.deleteDiscipline(1);
expect(apiClient.delete).toHaveBeenCalledWith('/master/disciplines/1');
});
});
});
// --- SubTypes ---
describe('SubTypes', () => {
describe('getSubTypes', () => {
it('should call GET /master/sub-types with contractId and typeId', async () => {
const mockSubTypes = [{ id: 1, name: 'Submittal' }];
vi.mocked(apiClient.get).mockResolvedValue({ data: { data: mockSubTypes } });
const result = await masterDataService.getSubTypes(1, 2);
expect(apiClient.get).toHaveBeenCalledWith('/master/sub-types', {
params: { contractId: 1, correspondenceTypeId: 2 },
});
expect(result).toEqual(mockSubTypes);
});
});
describe('createSubType', () => {
it('should call POST /master/sub-types', async () => {
const createDto = { name: 'New SubType' };
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1, ...createDto } });
const result = await masterDataService.createSubType(createDto);
expect(apiClient.post).toHaveBeenCalledWith('/master/sub-types', createDto);
expect(result).toEqual({ id: 1, ...createDto });
});
});
});
// --- RFA Types ---
describe('RfaTypes', () => {
describe('getRfaTypes', () => {
it('should call GET /master/rfa-types', async () => {
const mockTypes = [{ id: 1, name: 'Material Approval' }];
vi.mocked(apiClient.get).mockResolvedValue({ data: { data: mockTypes } });
const result = await masterDataService.getRfaTypes(1);
expect(apiClient.get).toHaveBeenCalledWith('/master/rfa-types', {
params: { contractId: 1 },
});
expect(result).toEqual(mockTypes);
});
});
describe('createRfaType', () => {
it('should call POST /master/rfa-types', async () => {
const data = { name: 'New RFA Type' };
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1, ...data } });
const result = await masterDataService.createRfaType(data);
expect(apiClient.post).toHaveBeenCalledWith('/master/rfa-types', data);
expect(result).toEqual({ id: 1, ...data });
});
});
describe('updateRfaType', () => {
it('should call PATCH /master/rfa-types/:id', async () => {
const data = { name: 'Updated Type' };
vi.mocked(apiClient.patch).mockResolvedValue({ data: { id: 1, ...data } });
await masterDataService.updateRfaType(1, data);
expect(apiClient.patch).toHaveBeenCalledWith('/master/rfa-types/1', data);
});
});
describe('deleteRfaType', () => {
it('should call DELETE /master/rfa-types/:id', async () => {
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
await masterDataService.deleteRfaType(1);
expect(apiClient.delete).toHaveBeenCalledWith('/master/rfa-types/1');
});
});
});
// --- Correspondence Types ---
describe('CorrespondenceTypes', () => {
describe('getCorrespondenceTypes', () => {
it('should call GET /master/correspondence-types', async () => {
const mockTypes = [{ id: 1, name: 'Letter' }];
vi.mocked(apiClient.get).mockResolvedValue({ data: { data: mockTypes } });
const result = await masterDataService.getCorrespondenceTypes();
expect(apiClient.get).toHaveBeenCalledWith('/master/correspondence-types');
expect(result).toEqual(mockTypes);
});
});
describe('createCorrespondenceType', () => {
it('should call POST /master/correspondence-types', async () => {
const data = { name: 'Memo' };
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 2, ...data } });
await masterDataService.createCorrespondenceType(data);
expect(apiClient.post).toHaveBeenCalledWith('/master/correspondence-types', data);
});
});
describe('updateCorrespondenceType', () => {
it('should call PATCH /master/correspondence-types/:id', async () => {
const data = { name: 'Updated Type' };
vi.mocked(apiClient.patch).mockResolvedValue({ data: { id: 1, ...data } });
await masterDataService.updateCorrespondenceType(1, data);
expect(apiClient.patch).toHaveBeenCalledWith('/master/correspondence-types/1', data);
});
});
describe('deleteCorrespondenceType', () => {
it('should call DELETE /master/correspondence-types/:id', async () => {
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
await masterDataService.deleteCorrespondenceType(1);
expect(apiClient.delete).toHaveBeenCalledWith('/master/correspondence-types/1');
});
});
});
// --- Number Format ---
describe('NumberFormat', () => {
describe('saveNumberFormat', () => {
it('should call POST /document-numbering/formats', async () => {
const data = { projectId: 1, correspondenceTypeId: 1, format: '{PREFIX}-{YYYY}-{SEQ}' };
vi.mocked(apiClient.post).mockResolvedValue({ data: { id: 1, ...data } });
await masterDataService.saveNumberFormat(data);
expect(apiClient.post).toHaveBeenCalledWith('/document-numbering/formats', data);
});
});
describe('getNumberFormat', () => {
it('should call GET /document-numbering/formats with params', async () => {
const mockFormat = { id: 1, format: '{PREFIX}-{SEQ}' };
vi.mocked(apiClient.get).mockResolvedValue({ data: mockFormat });
const result = await masterDataService.getNumberFormat(1, 2);
expect(apiClient.get).toHaveBeenCalledWith('/document-numbering/formats', {
params: { projectId: 1, correspondenceTypeId: 2 },
});
expect(result).toEqual(mockFormat);
});
});
});
});

View File

@@ -0,0 +1,95 @@
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { projectService } from '../project.service';
import apiClient from '@/lib/api/client';
// apiClient is already mocked in vitest.setup.ts
describe('projectService', () => {
beforeEach(() => {
vi.clearAllMocks();
});
describe('getAll', () => {
it('should call GET /projects with params', async () => {
const mockData = [{ id: 1, name: 'Project Alpha' }];
vi.mocked(apiClient.get).mockResolvedValue({ data: mockData });
const result = await projectService.getAll({ search: 'alpha' });
expect(apiClient.get).toHaveBeenCalledWith('/projects', {
params: { search: 'alpha' },
});
expect(result).toEqual(mockData);
});
it('should unwrap paginated response', async () => {
const mockData = [{ id: 1, name: 'Test' }];
vi.mocked(apiClient.get).mockResolvedValue({
data: { data: mockData, meta: { total: 1 } },
});
const result = await projectService.getAll();
expect(result).toEqual(mockData);
});
});
describe('getById', () => {
it('should call GET /projects/:id', async () => {
const mockResponse = { id: 1, name: 'Project Alpha', code: 'P-001' };
vi.mocked(apiClient.get).mockResolvedValue({ data: mockResponse });
const result = await projectService.getById(1);
expect(apiClient.get).toHaveBeenCalledWith('/projects/1');
expect(result).toEqual(mockResponse);
});
it('should work with string id', async () => {
vi.mocked(apiClient.get).mockResolvedValue({ data: {} });
await projectService.getById('123');
expect(apiClient.get).toHaveBeenCalledWith('/projects/123');
});
});
describe('create', () => {
it('should call POST /projects with data', async () => {
const createDto = { projectName: 'New Project', projectCode: 'P-002' };
const mockResponse = { id: 2, ...createDto };
vi.mocked(apiClient.post).mockResolvedValue({ data: mockResponse });
const result = await projectService.create(createDto);
expect(apiClient.post).toHaveBeenCalledWith('/projects', createDto);
expect(result).toEqual(mockResponse);
});
});
describe('update', () => {
it('should call PUT /projects/:id with data', async () => {
const updateData = { projectName: 'Updated Project' };
const mockResponse = { id: 1, projectName: 'Updated Project' };
vi.mocked(apiClient.put).mockResolvedValue({ data: mockResponse });
const result = await projectService.update(1, updateData);
expect(apiClient.put).toHaveBeenCalledWith('/projects/1', updateData);
expect(result).toEqual(mockResponse);
});
});
describe('delete', () => {
it('should call DELETE /projects/:id', async () => {
vi.mocked(apiClient.delete).mockResolvedValue({ data: {} });
const result = await projectService.delete(1);
expect(apiClient.delete).toHaveBeenCalledWith('/projects/1');
expect(result).toEqual({});
});
});
});

View File

@@ -0,0 +1,56 @@
import apiClient from "@/lib/api/client";
import {
CreateContractDto,
UpdateContractDto,
SearchContractDto,
} from "@/types/dto/contract/contract.dto";
export const contractService = {
/**
* Get all contracts (supports filtering by projectId)
* GET /contracts?projectId=1
*/
getAll: async (params?: SearchContractDto) => {
const response = await apiClient.get("/contracts", { params });
if (response.data && Array.isArray(response.data.data)) {
return response.data.data;
}
return response.data.data || response.data;
},
/**
* Get contract by ID
* GET /contracts/:id
*/
getById: async (id: number) => {
const response = await apiClient.get(`/contracts/${id}`);
return response.data;
},
/**
* Create new contract
* POST /contracts
*/
create: async (data: CreateContractDto) => {
const response = await apiClient.post("/contracts", data);
return response.data;
},
/**
* Update contract
* PATCH /contracts/:id
*/
update: async (id: number, data: UpdateContractDto) => {
const response = await apiClient.patch(`/contracts/${id}`, data);
return response.data;
},
/**
* Delete contract
* DELETE /contracts/:id
*/
delete: async (id: number) => {
const response = await apiClient.delete(`/contracts/${id}`);
return response.data;
},
};

View File

@@ -11,33 +11,33 @@ import {
CreateOrganizationDto,
UpdateOrganizationDto,
SearchOrganizationDto,
} from "@/types/dto/organization.dto";
} from "@/types/dto/organization/organization.dto";
export const masterDataService = {
// --- Tags Management ---
/** ดึงรายการ Tags ทั้งหมด (Search & Pagination) */
getTags: async (params?: SearchTagDto) => {
const response = await apiClient.get("/tags", { params });
const response = await apiClient.get("/master/tags", { params });
// Support both wrapped and unwrapped scenarios
return response.data.data || response.data;
},
/** สร้าง Tag ใหม่ */
createTag: async (data: CreateTagDto) => {
const response = await apiClient.post("/tags", data);
const response = await apiClient.post("/master/tags", data);
return response.data;
},
/** แก้ไข Tag */
updateTag: async (id: number | string, data: UpdateTagDto) => {
const response = await apiClient.put(`/tags/${id}`, data);
const response = await apiClient.patch(`/master/tags/${id}`, data);
return response.data;
},
/** ลบ Tag */
deleteTag: async (id: number | string) => {
const response = await apiClient.delete(`/tags/${id}`);
const response = await apiClient.delete(`/master/tags/${id}`);
return response.data;
},

View File

@@ -0,0 +1,57 @@
import apiClient from "@/lib/api/client";
import {
CreateOrganizationDto,
UpdateOrganizationDto,
SearchOrganizationDto,
} from "@/types/dto/organization/organization.dto";
export const organizationService = {
/**
* Get all organizations (supports filtering by projectId)
* GET /organizations?projectId=1
*/
getAll: async (params?: SearchOrganizationDto) => {
const response = await apiClient.get("/organizations", { params });
// Normalize response if wrapped in data.data or direct data
if (response.data && Array.isArray(response.data.data)) {
return response.data.data;
}
return response.data.data || response.data;
},
/**
* Get organization by ID
* GET /organizations/:id
*/
getById: async (id: number) => {
const response = await apiClient.get(`/organizations/${id}`);
return response.data;
},
/**
* Create new organization
* POST /organizations
*/
create: async (data: CreateOrganizationDto) => {
const response = await apiClient.post("/organizations", data);
return response.data;
},
/**
* Update organization
* PATCH /organizations/:id
*/
update: async (id: number, data: UpdateOrganizationDto) => {
const response = await apiClient.patch(`/organizations/${id}`, data);
return response.data;
},
/**
* Delete organization
* DELETE /organizations/:id
*/
delete: async (id: number) => {
const response = await apiClient.delete(`/organizations/${id}`);
return response.data;
},
};

View File

@@ -49,39 +49,8 @@ export const projectService = {
// --- Related Data / Dropdown Helpers ---
/** * ดึงรายชื่อองค์กรในโครงการ (สำหรับ Dropdown 'To/From')
* GET /projects/:id/organizations
*/
getOrganizations: async (projectId: string | number) => {
const response = await apiClient.get(`/projects/${projectId}/organizations`);
// Unwrap the response data if it's wrapped in a 'data' property by the interceptor
return response.data.data || response.data;
},
/** * ดึงรายชื่อสัญญาในโครงการ
* GET /projects/:id/contracts
*/
/** * ดึงรายชื่อสัญญาในโครงการ (Legacy/Specific)
* GET /projects/:id/contracts
*/
getContracts: async (projectId: string | number) => {
// Note: If backend doesn't have /projects/:id/contracts, use /contracts?projectId=:id
const response = await apiClient.get(`/contracts`, { params: { projectId } });
// Handle paginated response
if (response.data && Array.isArray(response.data.data)) {
return response.data.data;
}
return response.data.data || response.data;
},
/**
* ดึงรายการสัญญาเรื้งหมด (Global Search)
*/
getAllContracts: async (params?: any) => {
const response = await apiClient.get("/contracts", { params });
if (response.data && Array.isArray(response.data.data)) {
return response.data.data;
}
return response.data.data || response.data;
}
// --- Related Data / Dropdown Helpers ---
// Organizations and Contracts should now be fetched via their respective services
// organizationService.getAll({ projectId })
// contractService.getAll({ projectId })
};

View File

@@ -0,0 +1,35 @@
import { ReactNode } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
/**
* Creates a wrapper with QueryClient for testing hooks
* @returns Object with wrapper component and queryClient instance
*/
export function createTestQueryClient() {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
gcTime: 0,
staleTime: 0,
},
mutations: {
retry: false,
},
},
});
const wrapper = ({ children }: { children: ReactNode }) => (
<QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
);
return { wrapper, queryClient };
}
/**
* Wait for all pending operations in React Query
*/
export async function waitForQueryClient(queryClient: QueryClient) {
await queryClient.getQueryCache().clear();
await queryClient.getMutationCache().clear();
}