Skip to content

Commit

Permalink
Merge pull request #79 from DNUM-SocialGouv/c2s-261-using-context
Browse files Browse the repository at this point in the history
C2S 261 - Onglet Modérateurs Utilisateurs
  • Loading branch information
SamiTliliDNUM authored Sep 21, 2024
2 parents 859dae6 + 631b72d commit f3b1c49
Show file tree
Hide file tree
Showing 22 changed files with 1,958 additions and 1,682 deletions.
1 change: 0 additions & 1 deletion src/components/moderatorEstablishments/filters/Filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ export const Filters = () => {
signal: newAbortController.signal,
})
.then((response) => {
console.log('response.data', response.data);
setActiveOC(response.data.ocActifsCount);
setPointsAccueilCount(response.data.pointsAccueilCount);
setAvailableRegions(response.data.regions);
Expand Down
101 changes: 101 additions & 0 deletions src/components/moderatorUsers/ModeratorUsers.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import '@testing-library/jest-dom';
import { render, screen, waitFor } from '@testing-library/react';
import { ModeratorUsers } from './ModeratorUsers';
import { LoginContext } from '@/contexts/LoginContext';
import { axiosInstance } from '@/RequestInterceptor';
import { axe, toHaveNoViolations } from 'jest-axe';
import { UserStatus } from '@/domain/ModerateurUsers';
import { useUserContext } from '@/contexts/UserContext';

expect.extend(toHaveNoViolations);

jest.mock('@/contexts/UserContext');
jest.mock('@/RequestInterceptor', () => ({
axiosInstance: {
get: jest.fn(),
},
}));

const mockSetUsers = jest.fn();
const mockUseUserContext = {
users: [],
setUsers: mockSetUsers,
statut: UserStatus.Validated.toString(),
organisationType: 'ORGANISME_COMPLEMENTAIRE',
searchTerm: '',
};

beforeEach(() => {
(useUserContext as jest.Mock).mockReturnValue(mockUseUserContext);
(axiosInstance.get as jest.Mock).mockResolvedValue({
data: { list: [], count: 0 },
});
});

afterEach(() => {
jest.clearAllMocks();
});
describe('ModeratorUsers', () => {
it('sould render the component without accessibility violations', async () => {
// GIVEN
render(
<LoginContext.Provider
value={{
isLogged: true,
setIsLogged: () => undefined,
}}
>
<ModeratorUsers />
</LoginContext.Provider>
);
// THEN
const moderatorUsers = screen.getByTestId('moderatorUsers');
waitFor(async () => {
expect(await axe(moderatorUsers)).toHaveNoViolations();
});
});

it('should display the loader and fetch user count when logged in', async () => {
// GIVEN
const mockResponse = { data: { membreCount: 5 } };
(axiosInstance.get as jest.Mock).mockResolvedValueOnce(mockResponse);

render(
<LoginContext.Provider
value={{
isLogged: false,
setIsLogged: () => undefined,
}}
>
<ModeratorUsers />
</LoginContext.Provider>
);
// THEN
expect(screen.getByRole('alert')).toBeVisible();

waitFor(() => {
expect(axiosInstance.get).toHaveBeenCalledWith(
'/moderateur/membres/home',
{ withCredentials: true }
);
expect(screen.getByText(/5/)).toBeInTheDocument();
});
});

it('should not fetch user count when not logged in', () => {
render(
<LoginContext.Provider
value={{
isLogged: true,
setIsLogged: () => undefined,
}}
>
<ModeratorUsers />
</LoginContext.Provider>
);
// THEN
waitFor(() => {
expect(axiosInstance.get).not.toHaveBeenCalled();
});
});
});
88 changes: 27 additions & 61 deletions src/components/moderatorUsers/ModeratorUsers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@ import { Filters } from './filters/Filters';
import { Users } from './users/Users';
import { Loader } from '@/components/common/loader/Loader';
import { MODERATOR_USERS } from '@/wording';
import { UserProvider } from '@/contexts/UserContext';
import { useKeycloak } from '@react-keycloak/web';
import { axiosInstance } from '@/RequestInterceptor';
import { useEffect, useState } from 'react';
import { useContext, useEffect, useState } from 'react';
import { LoginContext } from '@/contexts/LoginContext';

const apiEndpoint = '/moderateur/membres/home';

Expand All @@ -16,72 +15,39 @@ interface UserApiResponse {
}

export const ModeratorUsers = () => {
return (
<UserProvider>
<ModeratorUsersContent />
</UserProvider>
);
};

const ModeratorUsersContent = () => {
const [isLogged, setIsLogged] = useState(false);
const [isLoading, setIsLoading] = useState(true);
const [usersCount, setUsersCount] = useState<number>(0);

const { keycloak } = useKeycloak();
const { isLogged } = useContext(LoginContext);

useEffect(() => {
const sendMyToken = (token: string) => {
let result: boolean | null;

fetch('/api/public/login', {
method: 'POST',
headers: { 'Content-Type': 'text/plain' },
credentials: 'include',
body: token,
})
.then(() => {
result = true;
setIsLogged(true);
})
.catch(() => {
result = false;
})
.finally(() => {
return result;
if (isLogged) {
axiosInstance
.get<UserApiResponse>(apiEndpoint, { withCredentials: true })
.then((response) => {
setUsersCount(response.data.membreCount);
setIsLoading(false);
});
return '';
};
sendMyToken(keycloak.token!);
}, [keycloak.token]);

useEffect(() => {
if (keycloak.authenticated) {
setIsLogged(true);
}
}, [keycloak.authenticated]);

useEffect(() => {
axiosInstance
.get<UserApiResponse | null>(apiEndpoint, { withCredentials: true })
.then((response) => {
setUsersCount(response?.data?.membreCount || 0);
});
}, [isLogged]);
}, [isLoading, isLogged]);

return (
<div className="fr-container--fluid">
{isLogged && (
<>
<TabHeader
icon={<Avatar />}
pageTitle={MODERATOR_USERS.pageTitle}
pageDetail={MODERATOR_USERS.pageDetail(usersCount)}
/>
<Filters />
<Users />
</>
<>
{!isLogged && isLoading ? (
<Loader />
) : (
<div className="fr-container--fluid" data-testid="moderatorUsers">
<>
<TabHeader
icon={<Avatar />}
pageTitle={MODERATOR_USERS.pageTitle}
pageDetail={MODERATOR_USERS.pageDetail(usersCount)}
/>
<Filters />
<Users />
</>
</div>
)}
{!isLogged && <Loader />}
</div>
</>
);
};
2 changes: 1 addition & 1 deletion src/components/moderatorUsers/filters/Filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const Filters = () => {
setSearchTerm('');
}
}
}, [statut, organisationType]);
}, [statut, organisationType, searchTerm, setSearchTerm]);

const handleStatusChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
setStatut(event.target.value);
Expand Down
1 change: 1 addition & 0 deletions src/components/moderatorUsers/userBlock/UserBlock.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ describe('UserBlock', () => {
telephoneOrganisation: null,
pointAccueil: false,
};

it('should handle validation', async () => {
// GIVEN
const onDataUpdate = jest.fn();
Expand Down
77 changes: 18 additions & 59 deletions src/components/moderatorUsers/users/Users.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,61 +3,25 @@ import { UserBlock } from '../userBlock/UserBlock';
import { Pagination } from '@/components/common/pagination/Pagination';
import { SectionTitle } from '@/components/common/sectionTitle/SectionTitle';
import { axiosInstance } from '@/RequestInterceptor';
import { UserApiResponse } from '@/domain/ModerateurUsers';
import { QueryFilters, UserApiResponse } from '@/domain/ModerateurUsers';
import { useUserContext } from '@/contexts/UserContext';
import { MODERATOR_USERS } from '@/wording';
import { OrganisationType } from '@/domain/Commons';
import { UserStatus } from '@/domain/ModerateurUsers';

//todo: extract membersQuery function
interface QueryFilters {
statut?: string;
groupe?: OrganisationType;
size?: number;
page?: number;
search?: string;
}
import { AxiosError } from 'axios';
import { usersQuery } from '@/utils/moderatorUser.helper';

const USERS_PER_PAGE = 5;

const usersQuery = (filters: QueryFilters): string => {
const queryParameters = [];

if (filters.statut !== undefined) {
queryParameters.push(`statut=${filters.statut}`);
}

if (filters.groupe !== undefined && filters.groupe !== '') {
queryParameters.push(`groupe=${filters.groupe}`);
}

if (filters.page !== undefined) {
queryParameters.push(`page=${filters.page}`);
}

if (filters.size !== undefined) {
queryParameters.push(`size=${filters.size}`);
}

if (filters.search !== undefined && filters.search !== '') {
queryParameters.push(`search=${filters.search}`);
}

return queryParameters.length ? `?${queryParameters.join('&')}` : '';
};

const formatEndpoint = (filters: QueryFilters) =>
`/moderateur/membres${usersQuery(filters)}`;

export const Users = () => {
//todo: refactor > setusers en state (utilisé que dans ce composant) ?
//Todo: refactor > setusers en state (utilisé que dans ce composant) ?
const { users, setUsers, statut, organisationType, searchTerm } =
useUserContext();
const [dataUpdated, setDataUpdated] = useState(false);
const [currentPage, setCurrentPage] = useState<number>(1);
const [totalUsers, setTotalUsers] = useState<number>(0);
const [abortController, setAbortController] =
useState<AbortController | null>(null);

const listRef = useRef<HTMLUListElement>(null);

Expand Down Expand Up @@ -109,34 +73,29 @@ export const Users = () => {
}, [statut, organisationType, searchTerm]);

useEffect(() => {
if (abortController) {
abortController.abort();
}

const newAbortController = new AbortController();
setAbortController(newAbortController);

axiosInstance
.get<UserApiResponse>(apiEndpoint, {
withCredentials: true,
signal: newAbortController.signal,
})
.then((response) => {
setUsers(response.data.list);
setTotalUsers(response.data.count);
})
.catch((error) => {
if (error.name === 'AbortError') {
console.log('Request was aborted');
} else {
console.error('Error fetching data:', error);
}
.catch((error: AxiosError) => {
console.error(
`Error while fetching users: ${error.message}`,
`Error code: ${error.code}`
);
});

return () => {
newAbortController.abort();
};
}, [dataUpdated, statut, organisationType, searchTerm, currentPage]);
}, [
dataUpdated,
statut,
organisationType,
searchTerm,
currentPage,
apiEndpoint,
setUsers,
]);

return (
<div className="fr-container--fluid" data-testid="users">
Expand Down
Loading

0 comments on commit f3b1c49

Please sign in to comment.