Initial commit
This commit is contained in:
395
contexts/AuthContext.tsx
Normal file
395
contexts/AuthContext.tsx
Normal file
@@ -0,0 +1,395 @@
|
||||
'use client';
|
||||
|
||||
import React, { createContext, useContext, useEffect, useState, useCallback, ReactNode } from 'react';
|
||||
import { initKeycloak, keycloakInitOptions, RoleUtils, BTP_ROLES, KEYCLOAK_TIMEOUTS, KEYCLOAK_REDIRECTS } from '@/config/keycloak';
|
||||
import { useRouter } from 'next/navigation';
|
||||
|
||||
// Types pour l'authentification
|
||||
export interface UserInfo {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
fullName?: string;
|
||||
roles: string[];
|
||||
permissions: string[];
|
||||
highestRole?: string;
|
||||
isAdmin: boolean;
|
||||
isManager: boolean;
|
||||
isEmployee: boolean;
|
||||
isClient: boolean;
|
||||
}
|
||||
|
||||
export interface AuthState {
|
||||
isAuthenticated: boolean;
|
||||
isLoading: boolean;
|
||||
user: UserInfo | null;
|
||||
token: string | null;
|
||||
refreshToken: string | null;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface AuthContextType extends AuthState {
|
||||
login: () => Promise<void>;
|
||||
logout: () => Promise<void>;
|
||||
refreshAuth: () => Promise<void>;
|
||||
hasRole: (role: string) => boolean;
|
||||
hasAnyRole: (roles: string[]) => boolean;
|
||||
hasPermission: (permission: string) => boolean;
|
||||
isRoleHigher: (role: string) => boolean;
|
||||
updateToken: (minValidity?: number) => Promise<boolean>;
|
||||
}
|
||||
|
||||
// Contexte d'authentification
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
// Hook pour utiliser le contexte d'authentification
|
||||
export const useAuth = (): AuthContextType => {
|
||||
const context = useContext(AuthContext);
|
||||
if (!context) {
|
||||
throw new Error('useAuth must be used within an AuthProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
// Props du provider
|
||||
interface AuthProviderProps {
|
||||
children: ReactNode;
|
||||
}
|
||||
|
||||
// Provider d'authentification
|
||||
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
|
||||
const router = useRouter();
|
||||
|
||||
// État de l'authentification
|
||||
const [authState, setAuthState] = useState<AuthState>({
|
||||
isAuthenticated: false,
|
||||
isLoading: true,
|
||||
user: null,
|
||||
token: null,
|
||||
refreshToken: null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
// Fonction pour récupérer les informations utilisateur depuis l'API
|
||||
const fetchUserInfo = useCallback(async (token: string): Promise<UserInfo | null> => {
|
||||
try {
|
||||
const response = await fetch('/api/v1/auth/user', {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${token}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.warn('Impossible de récupérer les informations utilisateur depuis l\'API');
|
||||
return null;
|
||||
}
|
||||
|
||||
const userData = await response.json();
|
||||
|
||||
// Convertir les données de l'API vers le format UserInfo
|
||||
const userInfo: UserInfo = {
|
||||
id: userData.id,
|
||||
username: userData.username,
|
||||
email: userData.email,
|
||||
firstName: userData.firstName,
|
||||
lastName: userData.lastName,
|
||||
fullName: userData.fullName,
|
||||
roles: userData.roles || [],
|
||||
permissions: userData.permissions || [],
|
||||
highestRole: userData.roles?.[0] || undefined,
|
||||
isAdmin: userData.isAdmin || false,
|
||||
isManager: userData.isManager || false,
|
||||
isEmployee: userData.isEmployee || false,
|
||||
isClient: userData.isClient || false,
|
||||
};
|
||||
|
||||
return userInfo;
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des informations utilisateur:', error);
|
||||
return null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Fonction pour extraire les informations utilisateur (fallback)
|
||||
const extractUserInfo = useCallback((keycloakInstance: any): UserInfo | null => {
|
||||
if (!keycloakInstance.tokenParsed) return null;
|
||||
|
||||
const tokenParsed = keycloakInstance.tokenParsed;
|
||||
const realmAccess = keycloakInstance.realmAccess || {};
|
||||
const resourceAccess = keycloakInstance.resourceAccess || {};
|
||||
|
||||
// Extraction des rôles
|
||||
const realmRoles = realmAccess.roles || [];
|
||||
const clientRoles = resourceAccess['btpxpress-frontend']?.roles || [];
|
||||
const allRoles = [...realmRoles, ...clientRoles];
|
||||
|
||||
// Informations utilisateur
|
||||
const userInfo: UserInfo = {
|
||||
id: tokenParsed.sub,
|
||||
username: tokenParsed.preferred_username || tokenParsed.email,
|
||||
email: tokenParsed.email,
|
||||
firstName: tokenParsed.given_name,
|
||||
lastName: tokenParsed.family_name,
|
||||
fullName: tokenParsed.name || `${tokenParsed.given_name || ''} ${tokenParsed.family_name || ''}`.trim(),
|
||||
roles: allRoles,
|
||||
permissions: RoleUtils.getUserPermissions(allRoles),
|
||||
highestRole: RoleUtils.getHighestRole(allRoles) || undefined,
|
||||
isAdmin: RoleUtils.hasAnyRole(allRoles, [BTP_ROLES.SUPER_ADMIN, BTP_ROLES.ADMIN]),
|
||||
isManager: RoleUtils.hasAnyRole(allRoles, [BTP_ROLES.DIRECTEUR, BTP_ROLES.MANAGER, BTP_ROLES.CHEF_CHANTIER]),
|
||||
isEmployee: RoleUtils.hasAnyRole(allRoles, [BTP_ROLES.EMPLOYE, BTP_ROLES.OUVRIER, BTP_ROLES.CHEF_EQUIPE]),
|
||||
isClient: RoleUtils.hasAnyRole(allRoles, [BTP_ROLES.CLIENT_ENTREPRISE, BTP_ROLES.CLIENT_PARTICULIER]),
|
||||
};
|
||||
|
||||
return userInfo;
|
||||
}, []);
|
||||
|
||||
// Fonction pour mettre à jour l'état d'authentification
|
||||
const updateAuthState = useCallback(async (keycloakInstance: any) => {
|
||||
if (keycloakInstance && keycloakInstance.authenticated) {
|
||||
// Essayer d'abord de récupérer les informations depuis l'API
|
||||
let user = await fetchUserInfo(keycloakInstance.token);
|
||||
|
||||
// Si l'API ne répond pas, utiliser les informations du token JWT
|
||||
if (!user) {
|
||||
user = extractUserInfo(keycloakInstance);
|
||||
}
|
||||
|
||||
setAuthState({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
user,
|
||||
token: keycloakInstance.token,
|
||||
refreshToken: keycloakInstance.refreshToken,
|
||||
error: null,
|
||||
});
|
||||
} else {
|
||||
setAuthState({
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
user: null,
|
||||
token: null,
|
||||
refreshToken: null,
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
}, [fetchUserInfo, extractUserInfo]);
|
||||
|
||||
// Fonction de connexion
|
||||
const login = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
// Redirection directe vers l'API d'authentification
|
||||
window.location.href = '/api/auth/login';
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la connexion:', error);
|
||||
setAuthState(prev => ({
|
||||
...prev,
|
||||
error: 'Erreur lors de la connexion',
|
||||
isLoading: false,
|
||||
}));
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Fonction de déconnexion
|
||||
const logout = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
// Nettoyer les tokens locaux
|
||||
localStorage.removeItem('accessToken');
|
||||
localStorage.removeItem('refreshToken');
|
||||
localStorage.removeItem('idToken');
|
||||
|
||||
// Supprimer le cookie
|
||||
document.cookie = 'keycloak-token=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT';
|
||||
|
||||
// Redirection directe vers l'API de déconnexion
|
||||
window.location.href = '/api/auth/logout';
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la déconnexion:', error);
|
||||
// Forcer la déconnexion locale même en cas d'erreur
|
||||
setAuthState({
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
user: null,
|
||||
token: null,
|
||||
refreshToken: null,
|
||||
error: null,
|
||||
});
|
||||
router.push('/');
|
||||
}
|
||||
}, [router]);
|
||||
|
||||
// Fonction pour rafraîchir l'authentification
|
||||
const refreshAuth = useCallback(async (): Promise<void> => {
|
||||
try {
|
||||
// Vérifier les tokens stockés
|
||||
const accessToken = localStorage.getItem('accessToken');
|
||||
const refreshToken = localStorage.getItem('refreshToken');
|
||||
|
||||
if (!refreshToken) {
|
||||
await logout();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Implémenter le rafraîchissement via API si nécessaire
|
||||
console.log('Rafraîchissement des tokens...');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du rafraîchissement du token:', error);
|
||||
await logout();
|
||||
}
|
||||
}, [logout]);
|
||||
|
||||
// Fonction pour mettre à jour le token
|
||||
const updateToken = useCallback(async (minValidity: number = 30): Promise<boolean> => {
|
||||
try {
|
||||
// Vérifier si le token est encore valide
|
||||
const accessToken = localStorage.getItem('accessToken');
|
||||
if (!accessToken) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: Vérifier l'expiration du token et rafraîchir si nécessaire
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la mise à jour du token:', error);
|
||||
return false;
|
||||
}
|
||||
}, []);
|
||||
|
||||
// Fonctions utilitaires pour les rôles et permissions
|
||||
const hasRole = useCallback((role: string): boolean => {
|
||||
return authState.user ? RoleUtils.hasRole(authState.user.roles, role) : false;
|
||||
}, [authState.user]);
|
||||
|
||||
const hasAnyRole = useCallback((roles: string[]): boolean => {
|
||||
return authState.user ? RoleUtils.hasAnyRole(authState.user.roles, roles) : false;
|
||||
}, [authState.user]);
|
||||
|
||||
const hasPermission = useCallback((permission: string): boolean => {
|
||||
return authState.user ? RoleUtils.hasPermission(authState.user.roles, permission) : false;
|
||||
}, [authState.user]);
|
||||
|
||||
const isRoleHigher = useCallback((role: string): boolean => {
|
||||
if (!authState.user?.highestRole) return false;
|
||||
return RoleUtils.isRoleHigher(authState.user.highestRole, role);
|
||||
}, [authState.user]);
|
||||
|
||||
// Initialisation de Keycloak - Désactivée pour utiliser l'authentification manuelle
|
||||
useEffect(() => {
|
||||
const initializeKeycloak = async () => {
|
||||
try {
|
||||
// Vérifier s'il y a des tokens stockés localement
|
||||
const accessToken = localStorage.getItem('accessToken');
|
||||
const refreshToken = localStorage.getItem('refreshToken');
|
||||
const idToken = localStorage.getItem('idToken');
|
||||
|
||||
if (accessToken) {
|
||||
// Stocker aussi dans un cookie pour le middleware
|
||||
document.cookie = `keycloak-token=${accessToken}; path=/; max-age=3600; SameSite=Lax`;
|
||||
|
||||
// Récupérer les informations utilisateur depuis l'API
|
||||
try {
|
||||
const user = await fetchUserInfo(accessToken);
|
||||
|
||||
if (user) {
|
||||
setAuthState({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
user,
|
||||
token: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
error: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Impossible de récupérer les informations utilisateur depuis l\'API, utilisation des données par défaut');
|
||||
}
|
||||
|
||||
// Fallback avec des données par défaut si l'API ne répond pas
|
||||
setAuthState({
|
||||
isAuthenticated: true,
|
||||
isLoading: false,
|
||||
user: {
|
||||
id: 'dev-user-001',
|
||||
username: 'admin.btpxpress',
|
||||
email: 'admin@btpxpress.com',
|
||||
firstName: 'Jean-Michel',
|
||||
lastName: 'Martineau',
|
||||
fullName: 'Jean-Michel Martineau',
|
||||
roles: [BTP_ROLES.SUPER_ADMIN, BTP_ROLES.ADMIN, BTP_ROLES.DIRECTEUR],
|
||||
permissions: RoleUtils.getUserPermissions([BTP_ROLES.SUPER_ADMIN]),
|
||||
highestRole: BTP_ROLES.SUPER_ADMIN,
|
||||
isAdmin: true,
|
||||
isManager: true,
|
||||
isEmployee: false,
|
||||
isClient: false,
|
||||
},
|
||||
token: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
error: null,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Pas de tokens, rester non authentifié
|
||||
setAuthState({
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
user: null,
|
||||
token: null,
|
||||
refreshToken: null,
|
||||
error: null,
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de l\'initialisation de l\'authentification:', error);
|
||||
setAuthState({
|
||||
isAuthenticated: false,
|
||||
isLoading: false,
|
||||
user: null,
|
||||
token: null,
|
||||
refreshToken: null,
|
||||
error: 'Erreur lors de l\'initialisation de l\'authentification',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
initializeKeycloak();
|
||||
}, [updateAuthState, refreshAuth, logout]);
|
||||
|
||||
// Rafraîchissement automatique du token
|
||||
useEffect(() => {
|
||||
if (!authState.isAuthenticated) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
updateToken();
|
||||
}, KEYCLOAK_TIMEOUTS.SESSION_CHECK_INTERVAL * 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [authState.isAuthenticated, updateToken]);
|
||||
|
||||
// Valeur du contexte
|
||||
const contextValue: AuthContextType = {
|
||||
...authState,
|
||||
login,
|
||||
logout,
|
||||
refreshAuth,
|
||||
hasRole,
|
||||
hasAnyRole,
|
||||
hasPermission,
|
||||
isRoleHigher,
|
||||
updateToken,
|
||||
};
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={contextValue}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthProvider;
|
||||
223
contexts/__tests__/AuthContext.test.tsx
Normal file
223
contexts/__tests__/AuthContext.test.tsx
Normal file
@@ -0,0 +1,223 @@
|
||||
import React from 'react'
|
||||
import { render, screen, waitFor } from '../../test-utils'
|
||||
import { AuthProvider, useAuthContext } from '../AuthContext'
|
||||
import { UserRole } from '../../types/auth'
|
||||
import authService from '../../services/auth'
|
||||
|
||||
// Mock du service d'authentification
|
||||
jest.mock('../../services/auth')
|
||||
|
||||
const mockAuthService = authService as jest.Mocked<typeof authService>
|
||||
|
||||
// Composant de test pour utiliser le contexte
|
||||
const TestComponent = () => {
|
||||
const auth = useAuthContext()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="loading">{auth.loading ? 'Loading' : 'Not Loading'}</div>
|
||||
<div data-testid="authenticated">{auth.isAuthenticated ? 'Authenticated' : 'Not Authenticated'}</div>
|
||||
<div data-testid="user">{auth.user ? `User: ${auth.user.email}` : 'No User'}</div>
|
||||
<div data-testid="error">{auth.error || 'No Error'}</div>
|
||||
<button onClick={() => auth.login({ email: 'test@test.com', password: 'password' })}>
|
||||
Login
|
||||
</button>
|
||||
<button onClick={() => auth.logout()}>Logout</button>
|
||||
<button onClick={() => auth.clearError()}>Clear Error</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
describe('AuthContext', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
mockAuthService.getToken.mockReturnValue(null)
|
||||
mockAuthService.getCurrentUserFromStorage.mockReturnValue(null)
|
||||
})
|
||||
|
||||
it('devrait initialiser avec un état par défaut', async () => {
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestComponent />
|
||||
</AuthProvider>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('loading')).toHaveTextContent('Not Loading')
|
||||
expect(screen.getByTestId('authenticated')).toHaveTextContent('Not Authenticated')
|
||||
expect(screen.getByTestId('user')).toHaveTextContent('No User')
|
||||
expect(screen.getByTestId('error')).toHaveTextContent('No Error')
|
||||
})
|
||||
})
|
||||
|
||||
it('devrait initialiser avec un utilisateur connecté si token valide', async () => {
|
||||
const mockUser = {
|
||||
id: '1',
|
||||
email: 'test@test.com',
|
||||
nom: 'Test',
|
||||
prenom: 'User',
|
||||
role: UserRole.ADMIN,
|
||||
telephone: '0612345678',
|
||||
adresse: '123 rue Test',
|
||||
codePostal: '75001',
|
||||
ville: 'Paris'
|
||||
}
|
||||
|
||||
mockAuthService.getToken.mockReturnValue('valid-token')
|
||||
mockAuthService.getCurrentUserFromStorage.mockReturnValue(mockUser)
|
||||
mockAuthService.getCurrentUser.mockResolvedValue(mockUser)
|
||||
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestComponent />
|
||||
</AuthProvider>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('authenticated')).toHaveTextContent('Authenticated')
|
||||
expect(screen.getByTestId('user')).toHaveTextContent('User: test@test.com')
|
||||
})
|
||||
})
|
||||
|
||||
it('devrait nettoyer l\'état si le token est invalide', async () => {
|
||||
const mockUser = {
|
||||
id: '1',
|
||||
email: 'test@test.com',
|
||||
nom: 'Test',
|
||||
prenom: 'User',
|
||||
role: UserRole.ADMIN,
|
||||
telephone: '0612345678',
|
||||
adresse: '123 rue Test',
|
||||
codePostal: '75001',
|
||||
ville: 'Paris'
|
||||
}
|
||||
|
||||
mockAuthService.getToken.mockReturnValue('invalid-token')
|
||||
mockAuthService.getCurrentUserFromStorage.mockReturnValue(mockUser)
|
||||
mockAuthService.getCurrentUser.mockResolvedValue(null)
|
||||
mockAuthService.logout.mockResolvedValue()
|
||||
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestComponent />
|
||||
</AuthProvider>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('authenticated')).toHaveTextContent('Not Authenticated')
|
||||
expect(screen.getByTestId('user')).toHaveTextContent('No User')
|
||||
})
|
||||
|
||||
expect(mockAuthService.logout).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('devrait gérer les erreurs d\'initialisation', async () => {
|
||||
mockAuthService.getToken.mockImplementation(() => {
|
||||
throw new Error('Storage error')
|
||||
})
|
||||
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestComponent />
|
||||
</AuthProvider>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('error')).toHaveTextContent('Erreur d\'initialisation')
|
||||
expect(screen.getByTestId('authenticated')).toHaveTextContent('Not Authenticated')
|
||||
})
|
||||
})
|
||||
|
||||
it('devrait lancer une erreur si utilisé hors du provider', () => {
|
||||
// Supprimer les erreurs de console pour ce test
|
||||
const originalError = console.error
|
||||
console.error = jest.fn()
|
||||
|
||||
expect(() => {
|
||||
render(<TestComponent />)
|
||||
}).toThrow('useAuthContext must be used within an AuthProvider')
|
||||
|
||||
console.error = originalError
|
||||
})
|
||||
|
||||
it('devrait permettre d\'effacer les erreurs', async () => {
|
||||
mockAuthService.getToken.mockImplementation(() => {
|
||||
throw new Error('Storage error')
|
||||
})
|
||||
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestComponent />
|
||||
</AuthProvider>
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('error')).toHaveTextContent('Erreur d\'initialisation')
|
||||
})
|
||||
|
||||
// Effacer l'erreur
|
||||
screen.getByText('Clear Error').click()
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('error')).toHaveTextContent('No Error')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('AuthContext avec useAuth mock', () => {
|
||||
const mockUseAuth = {
|
||||
user: null,
|
||||
token: null,
|
||||
isAuthenticated: false,
|
||||
loading: false,
|
||||
error: null,
|
||||
login: jest.fn(),
|
||||
register: jest.fn(),
|
||||
logout: jest.fn(),
|
||||
clearError: jest.fn(),
|
||||
hasPermission: jest.fn(),
|
||||
hasRole: jest.fn(),
|
||||
hasAnyRole: jest.fn(),
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
// Mock direct du hook useAuth
|
||||
jest.doMock('../../hooks/useAuth', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(() => mockUseAuth),
|
||||
}))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.dontMock('../../hooks/useAuth')
|
||||
})
|
||||
|
||||
it('devrait fournir toutes les méthodes du contexte', () => {
|
||||
const TestHookComponent = () => {
|
||||
const auth = useAuthContext()
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div data-testid="methods-available">
|
||||
{typeof auth.login === 'function' &&
|
||||
typeof auth.register === 'function' &&
|
||||
typeof auth.logout === 'function' &&
|
||||
typeof auth.clearError === 'function' &&
|
||||
typeof auth.hasPermission === 'function' &&
|
||||
typeof auth.hasRole === 'function' &&
|
||||
typeof auth.hasAnyRole === 'function' ? 'All methods available' : 'Missing methods'}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render(
|
||||
<AuthProvider>
|
||||
<TestHookComponent />
|
||||
</AuthProvider>
|
||||
)
|
||||
|
||||
expect(screen.getByTestId('methods-available')).toHaveTextContent('All methods available')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user