Skip to content

Commit

Permalink
Merge pull request #36 from AntaresSimulatorTeam/fix/ANT-2622_refacto…
Browse files Browse the repository at this point in the history
…_services

Fix/ant 2622 refacto services
  • Loading branch information
vargastat authored Jan 29, 2025
2 parents ceb100a + 32dc7de commit 9e64ea6
Show file tree
Hide file tree
Showing 17 changed files with 483 additions and 152 deletions.
2 changes: 1 addition & 1 deletion src/hooks/test/useFetchProjectList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ describe('useFetchProjectList', () => {
});

it('fetches projects on mount', async () => {
const { result } = renderHook(() => useFetchProjectList('', 0, 9, false));
const { result } = renderHook(() => useFetchProjectList('mouad', 0, 9, false));

await waitFor(() => {
expect(result.current.projects).toEqual([
Expand Down
117 changes: 107 additions & 10 deletions src/hooks/test/useHandlePinnedProjectList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/

import { Queries, renderHook, RenderHookOptions, waitFor } from '@testing-library/react';
import { act, Queries, renderHook, RenderHookOptions, waitFor } from '@testing-library/react';
import { useHandlePinnedProjectList } from '@/hooks/useHandlePinnedProjectList';
import { afterEach, beforeEach, describe, expectTypeOf, it, Mock, vi } from 'vitest';
import {
PinnedProjectProvider,
PinnedProjectProviderProps,
usePinnedProjectDispatch,
} from '@/store/contexts/ProjectContext';
import { fetchPinnedProjects, pinProject } from '@/shared/services/pinnedProjectService.ts';
import React from 'react';
import { PINNED_PROJECT_ACTION } from '@/shared/enum/project.ts';
import { v4 as uuidv4 } from 'uuid';
import { notifyToast } from '@/shared/notification/notification.tsx';

const mockProjectsApiResponse = [
{
Expand Down Expand Up @@ -52,18 +57,28 @@ const mockProjectsApiResponse = [
vi.mock('@/envVariables', () => ({
getEnvVariables: vi.fn(() => 'https://mockapi.com'),
}));
vi.mock('@/shared/notification/notification');
vi.mock('uuid', () => ({
v4: vi.fn(() => 'mocked-uuid'),
}));

vi.mock('@/shared/services/pinnedProjectService');
vi.mock('@/store/contexts/ProjectContext', async (importOriginal) => {
const actual: Mock = await importOriginal();
const mockDispatch = vi.fn();
return {
...actual,
usePinnedProjectDispatch: vi.fn(() => mockDispatch),
usePinnedProject: vi.fn(),
usePinnedProjectDispatch: vi.fn(() => {
return {
dispatch: vi.fn(),
};
}),
};
});

describe('useHandlePinnedProjectList', () => {
const mockUsePinnedProjectDispatch = usePinnedProjectDispatch as Mock;
const mockUsePinnedProjectDispatch = usePinnedProjectDispatch as Mock<typeof usePinnedProjectDispatch>;
const mockDispatch = vi.fn().mockImplementation(vi.fn());

beforeEach(() => {
global.fetch = vi.fn();
Expand All @@ -75,10 +90,10 @@ describe('useHandlePinnedProjectList', () => {
});

it('should trigger getPinnedProject method on init and call dispatch to update pinned project list correctly', async () => {
global.fetch = vi.fn().mockResolvedValueOnce({
ok: true,
json: async () => mockProjectsApiResponse,
});
const mockFetchPinnedProjects = fetchPinnedProjects as Mock;
mockFetchPinnedProjects.mockResolvedValueOnce(mockProjectsApiResponse);

mockUsePinnedProjectDispatch.mockReturnValue(mockDispatch);

const wrapper = ({ children, initialValue }: PinnedProjectProviderProps) => (
<PinnedProjectProvider children={children} initialValue={initialValue}></PinnedProjectProvider>
Expand All @@ -93,9 +108,91 @@ describe('useHandlePinnedProjectList', () => {
expectTypeOf(result.current.getPinnedProjects).toBeFunction();
expectTypeOf(result.current.handleUnpinProject).toBeFunction();
expectTypeOf(result.current.handlePinProject).toBeFunction();
expect(global.fetch).toHaveBeenCalledTimes(1);
expect(global.fetch).toHaveBeenCalledWith('https://mockapi.com/v1/project/pinned?userId=me00247');
expect(fetchPinnedProjects).toHaveBeenCalledTimes(1);
expect(fetchPinnedProjects).toHaveBeenCalledWith('me00247');
expect(fetchPinnedProjects).toHaveReturned(mockProjectsApiResponse);
expect(mockUsePinnedProjectDispatch).toHaveBeenCalledTimes(1);
expect(mockDispatch).toHaveBeenCalledTimes(1);
expect(mockDispatch).toHaveBeenCalledWith({
type: PINNED_PROJECT_ACTION.INIT_LIST,
payload: mockProjectsApiResponse,
});
});
});

it('should call handlePinProject that calls pinProject and dispatch and update pinned project list correctly', async () => {
const mockPinProject = pinProject as Mock;
const mockPinProjectResponse = {
id: '3',
name: 'Bilan previsionnel 2025',
createdBy: 'zayd guillaume pegase',
creationDate: '2024-07-25T10:09:41',
studies: [7, 8],
tags: ['figma', 'config', 'modal'],
description: 'In the world of software development, achieving perfection is a journey rather than a destination.',
projectId: '3',
pinned: true,
};
mockPinProject.mockResolvedValueOnce(mockPinProjectResponse);

mockUsePinnedProjectDispatch.mockReturnValue(mockDispatch);

const id = uuidv4();

const wrapper = ({ children, initialValue }: PinnedProjectProviderProps) => (
<PinnedProjectProvider children={children} initialValue={initialValue}></PinnedProjectProvider>
);

const { result } = renderHook(() => useHandlePinnedProjectList(), {
wrapper,
initialProps: { initialValue: { pinnedProject: [] } },
} as RenderHookOptions<{ initialValue: { pinnedProject: never[] } }, Queries>);

await act(async () => result.current.handlePinProject('me00247'));

await waitFor(() => {
expect(pinProject).toHaveBeenCalledWith('me00247');
expect(pinProject).toHaveReturned(mockPinProjectResponse);
expect(mockDispatch).toHaveBeenCalledTimes(1);
expect(mockDispatch).toHaveBeenCalledWith({
type: PINNED_PROJECT_ACTION.ADD_ITEM,
payload: mockPinProjectResponse,
});
expect(notifyToast).toHaveBeenCalledWith({
id: id,
type: 'success',
message: 'Project pinned successfully',
});
});
});

it('should call handlePinProject and catch error when pinProject throws one', async () => {
const mockPinProject = pinProject as Mock;
mockPinProject.mockRejectedValueOnce('error');

mockUsePinnedProjectDispatch.mockReturnValue(mockDispatch);

const id = uuidv4();

const wrapper = ({ children, initialValue }: PinnedProjectProviderProps) => (
<PinnedProjectProvider children={children} initialValue={initialValue}></PinnedProjectProvider>
);

const { result } = renderHook(() => useHandlePinnedProjectList(), {
wrapper,
initialProps: { initialValue: { pinnedProject: [] } },
} as RenderHookOptions<{ initialValue: { pinnedProject: never[] } }, Queries>);

await act(async () => result.current.handlePinProject('me00247'));

await waitFor(() => {
expect(pinProject).toHaveBeenCalledWith('me00247');
expect(mockDispatch).toHaveBeenCalledTimes(0);
expect(notifyToast).toHaveBeenCalledWith({
id: id,
type: 'error',
message: 'Project already pinned',
});
});
});
});
4 changes: 2 additions & 2 deletions src/hooks/test/useStudyTableDisplay.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ describe('useStudyTableDisplay', () => {
expect(result.current.rows).toHaveLength(2);
expect(result.current.rows).toEqual(mockResponse.content);
expect(result.current.count).toEqual(2);
//expect(global.fetch).toHaveBeenCalledTimes(1);
//expect(global.fetch).toHaveBeenCalledTimes(1); TODO: ANT-2719
expect(global.fetch).toHaveBeenCalledWith(
'https://mockapi.com/v1/study/search?page=1&size=9&projectId=&search=test&sortColumn=status&sortDirection=desc',
);
Expand All @@ -68,7 +68,7 @@ describe('useStudyTableDisplay', () => {
renderHook(() => useStudyTableDisplay({ searchTerm: 'mouad', sortBy: { project: 'asc' }, reloadStudies: true }));
});

//expect(global.fetch).toHaveBeenCalledTimes(1);
//expect(global.fetch).toHaveBeenCalledTimes(1); TODO: ANT-2719
expect(global.fetch).toHaveBeenCalledWith(
'https://mockapi.com/v1/study/search?page=1&size=9&projectId=&search=mouad&sortColumn=project&sortDirection=asc',
);
Expand Down
28 changes: 14 additions & 14 deletions src/hooks/useFetchProjectList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { useCallback, useEffect, useState } from 'react';
import { ProjectInfo } from '@/shared/types/pegase/Project.type.ts';
import { getEnvVariables } from '@/envVariables.ts';
import { fetchProjectFromSearchTerm } from '@/shared/services/projectService.ts';

export const useFetchProjectList = (
searchTerm: string,
Expand All @@ -16,22 +16,22 @@ export const useFetchProjectList = (
) => {
const [projects, setProjects] = useState<ProjectInfo[]>([]);
const [count, setCount] = useState(0);
const BASE_URL = getEnvVariables('VITE_BACK_END_BASE_URL');

const fetchProjects = useCallback(async () => {
const url = `${BASE_URL}/v1/project/search?page=${current + 1}&size=${intervalSize}&search=${searchTerm || ''}`;
fetch(url)
.then((response) => response.json())
.then((json) => {
setProjects(json.content);
setCount(json.totalElements);
})
.catch((error) => console.error(error));
}, [current, intervalSize, searchTerm]);
const fetchProjects = useCallback(
async (searchTerm, current, intervalSize) => {
fetchProjectFromSearchTerm(searchTerm, current, intervalSize)
.then((json) => {
setProjects(json.content);
setCount(json.totalElements);
})
.catch((error) => console.error(error));
},
[current, intervalSize, searchTerm],
);

useEffect(() => {
void fetchProjects();
}, [BASE_URL, current, searchTerm, intervalSize, shouldRefetch]);
void fetchProjects(searchTerm, current, intervalSize);
}, [current, searchTerm, intervalSize, shouldRefetch]);

return { projects, count, refetch: fetchProjects };
};
1 change: 0 additions & 1 deletion src/hooks/useHandlePinnedProjectList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export const useHandlePinnedProjectList = () => {
const toastId = uuidv4();
try {
const newProject = await pinProject(projectId);

if (newProject) {
dispatch?.({
type: PINNED_PROJECT_ACTION.ADD_ITEM,
Expand Down
2 changes: 1 addition & 1 deletion src/pages/pegase/home/components/StudyTableDisplay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { addSortColumn } from './StudyTableUtils';
import StudiesPagination from './StudiesPagination';
import { RowSelectionState } from '@tanstack/react-table';
import StudyCreationModal from '../../studies/StudyCreationModal';
import { deleteStudy } from '@/pages/pegase/home/components/studyService';
import { deleteStudy } from '@/shared/services/studyService';
import StdSimpleTable from '@/components/common/data/stdSimpleTable/StdSimpleTable';
import { RdsButton } from 'rte-design-system-react';
import { useStudyTableDisplay } from '@/hooks/useStudyTableDisplay';
Expand Down
74 changes: 0 additions & 74 deletions src/pages/pegase/home/components/studyService.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/pages/pegase/studies/KeywordsInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import { useState } from 'react';
import { RdsButton, RdsIcon, RdsIconId, RdsInputText } from 'rte-design-system-react';
import { fetchSuggestedKeywords } from '@/pages/pegase/home/components/studyService';
import { fetchSuggestedKeywords } from '@/shared/services/studyService';

const MAX_KEYWORDS = 6;

Expand Down
16 changes: 3 additions & 13 deletions src/pages/pegase/studies/ProjectInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,14 @@
*/

// src/components/ProjectInput.tsx
import React, { useState, useEffect } from 'react';
import React, { useEffect, useState } from 'react';
import { RdsInputText } from 'rte-design-system-react';
import { getEnvVariables } from '@/envVariables';
import { fetchProjectsFromPartialName } from '@/shared/services/projectService';

interface ProjectManagerProps {
value: string;
onChange: (value: string) => void;
}
const BASE_URL = getEnvVariables('VITE_BACK_END_BASE_URL');

const fetchProjects = async (query: string): Promise<string[]> => {
const response = await fetch(`${BASE_URL}/v1/project/autocomplete?partialName=${query}`);
if (!response.ok) {
throw new Error('Failed to fetch projects');
}
const data = await response.json();
return data.map((project: { name: string }) => project.name); // Extract and return only the 'name' property
};

const ProjectInput: React.FC<ProjectManagerProps> = ({ value, onChange }) => {
const [projects, setProjects] = useState<string[]>([]);
Expand All @@ -32,7 +22,7 @@ const ProjectInput: React.FC<ProjectManagerProps> = ({ value, onChange }) => {
useEffect(() => {
const loadProjects = async () => {
try {
const projectList = await fetchProjects(value);
const projectList = await fetchProjectsFromPartialName(value);
setProjects(projectList);
} catch (error) {
setErrorMessage('Failed to fetch projects');
Expand Down
4 changes: 2 additions & 2 deletions src/pages/pegase/studies/StudyCreationModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import { useTranslation } from 'react-i18next';
import KeywordsInput from '@/pages/pegase/studies/KeywordsInput';
import HorizonInput from '@/pages/pegase/studies/HorizonInput';
import ProjectInput from '@/pages/pegase/studies/ProjectInput';
import { saveStudy } from '@/pages/pegase/home/components/studyService';
import { StudyDTO } from '@/shared/types/index';
import { saveStudy } from '@/shared/services/studyService';
import { StudyDTO } from '@/shared/types';

interface StudyCreationModalProps {
isOpen?: boolean;
Expand Down
6 changes: 5 additions & 1 deletion src/shared/const/apiEndPoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ import { getEnvVariables } from '@/envVariables.ts';
const BASE_URL = getEnvVariables('VITE_BACK_END_BASE_URL');

// STUDY
export const STUDY_ENDPOINT = `${BASE_URL}/v1/study`;
export const STUDY_SEARCH_ENDPOINT = `${BASE_URL}/v1/study/search`;
export const STUDY_KEYWORDS_SEARCH_ENDPOINT = `${BASE_URL}/v1/study/keywords/search`;

// PINNED PROJECT
export const PROJECT_PINNED_ENDPOINT = `${BASE_URL}/v1/project/pinned`;
export const PROJECT_UNPIN_ENDPOINT = `${BASE_URL}/v1/project/unpin`;
export const PROJECT_PIN_PROJECT = `${BASE_URL}/v1/project/pin`;
export const PROJECT_PIN_ENDPOINT = `${BASE_URL}/v1/project/pin`;

// PROJECT
export const PROJECT_ENDPOINT = `${BASE_URL}/v1/project`;
export const PROJECT_AUTOCOMPLETE_ENDPOINT = `${BASE_URL}/v1/project/autocomplete`;
export const PROJECT_SEARCH_ENDPOINT = `${BASE_URL}/v1/project/search`;
Loading

0 comments on commit 9e64ea6

Please sign in to comment.