diff --git a/src/authz-module/libraries-manager/ErrorPage/index.test.tsx b/src/authz-module/components/ErrorPage/index.test.tsx similarity index 73% rename from src/authz-module/libraries-manager/ErrorPage/index.test.tsx rename to src/authz-module/components/ErrorPage/index.test.tsx index 06ac6db1..93c42cb3 100644 --- a/src/authz-module/libraries-manager/ErrorPage/index.test.tsx +++ b/src/authz-module/components/ErrorPage/index.test.tsx @@ -2,68 +2,68 @@ import { screen } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ErrorBoundary } from 'react-error-boundary'; import { renderWrapper } from '@src/setupTest'; -import LibrariesErrorFallback from './index'; +import ErrorFallback from './index'; const ThrowError = ({ error }: { error:Error }) => { throw error; return null; }; -describe('LibrariesErrorFallback', () => { +describe('ErrorFallback', () => { it('renders Access Denied for 401', () => { const error = { name: '', message: 'NO_ACCESS', customAttributes: { httpErrorStatus: 401 } }; renderWrapper( - + , ); expect(screen.getByText(/Access Denied/i)).toBeInTheDocument(); - expect(screen.getByText(/Back to Libraries/i)).toBeInTheDocument(); + expect(screen.getByText(/Back to Studio/i)).toBeInTheDocument(); }); it('renders Not Found for 400 error', () => { const error = { name: '', message: 'Axios Error (Response): 400', customAttributes: { httpErrorStatus: 400 } }; renderWrapper( - + , ); expect(screen.getByText(/Page Not Found/i)).toBeInTheDocument(); - expect(screen.getByText(/Back to Libraries/i)).toBeInTheDocument(); + expect(screen.getByText(/Back to Studio/i)).toBeInTheDocument(); }); it('renders Not Found for 404', () => { const error = { name: '', message: 'NOT_FOUND', customAttributes: { httpErrorStatus: 404 } }; renderWrapper( - + , ); expect(screen.getByText(/Page Not Found/i)).toBeInTheDocument(); - expect(screen.getByText(/Back to Libraries/i)).toBeInTheDocument(); + expect(screen.getByText(/Back to Studio/i)).toBeInTheDocument(); }); it('renders Server Error for 500 and shows reload', async () => { const error = { name: '', message: 'SERVER_ERROR', customAttributes: { httpErrorStatus: 500 } }; renderWrapper( - + , ); expect(screen.getByText(/Something went wrong/i)).toBeInTheDocument(); expect(screen.getByText(/Reload Page/i)).toBeInTheDocument(); - expect(screen.getByText(/Back to Libraries/i)).toBeInTheDocument(); + expect(screen.getByText(/Back to Studio/i)).toBeInTheDocument(); }); it('renders generic error for other error error', () => { const error = { name: '', message: 'SOMETHING_ELSE', customAttributes: { httpErrorStatus: 418 } }; renderWrapper( - + , ); expect(screen.getByText(/Error/i)).toBeInTheDocument(); - expect(screen.getByText(/Back to Libraries/i)).toBeInTheDocument(); + expect(screen.getByText(/Back to Studio/i)).toBeInTheDocument(); }); it('calls reload action if present', async () => { @@ -73,7 +73,7 @@ describe('LibrariesErrorFallback', () => { name: '', message: 'SERVER_ERROR', customAttributes: { httpErrorStatus: 500 }, refetch, }; renderWrapper( - + , ); diff --git a/src/authz-module/libraries-manager/ErrorPage/index.tsx b/src/authz-module/components/ErrorPage/index.tsx similarity index 95% rename from src/authz-module/libraries-manager/ErrorPage/index.tsx rename to src/authz-module/components/ErrorPage/index.tsx index e93a7d7f..263865a7 100644 --- a/src/authz-module/libraries-manager/ErrorPage/index.tsx +++ b/src/authz-module/components/ErrorPage/index.tsx @@ -80,7 +80,7 @@ const ErrorPage = ({ error, resetErrorBoundary }: FallbackProps) => { {showBackButton && ( - )} - - ), -})); - -jest.mock('./components/AssignNewRoleModal', () => ({ - AssignNewRoleTrigger: () => , -})); - -describe('LibrariesUserManager', () => { - const mockMutate = jest.fn(); - const defaultMockData = { - libraryId: 'lib:123', - permissions: [{ key: 'view' }, { key: 'reuse' }], - roles: [ - { - role: 'admin', - name: 'Admin', - description: 'Administrator Role', - permissions: ['view', 'reuse'], - userCount: 5, - }, - { - role: 'instructor', - name: 'Instructor', - description: 'Instructor Role', - permissions: ['view'], - userCount: 10, - }, - ], - resources: [{ key: 'library', label: 'Library', description: '' }], - canManageTeam: true, - }; - - beforeEach(() => { - jest.clearAllMocks(); - - // Mock route params - (useParams as jest.Mock).mockReturnValue({ username: 'testuser' }); - - const { useNavigate, useLocation } = jest.requireMock('react-router-dom'); - useNavigate.mockReturnValue(mockNavigate); - useLocation.mockReturnValue({ pathname: '/authz/libraries/lib:123/testuser' }); - - // Mock library authz context - (useLibraryAuthZ as jest.Mock).mockReturnValue(defaultMockData); - - // Mock library data - (useLibrary as jest.Mock).mockReturnValue({ - data: { - title: 'Test Library', - org: 'Test Org', - }, - }); - - // Mock team members - (useTeamMembers as jest.Mock).mockReturnValue({ - data: { - results: [ - { - username: 'testuser', - email: 'testuser@example.com', - roles: ['admin', 'instructor'], - }, - ], - }, - isLoading: false, - isFetching: false, - }); - - // Mock revoke user roles - (useRevokeUserRoles as jest.Mock).mockReturnValue({ - mutate: mockMutate, - isPending: false, - }); - }); - - const renderComponent = () => { - renderWrapper( - - - , - ); - }; - - it('renders the user roles correctly', () => { - renderComponent(); - - // Breadcrumb check - expect(screen.getByText('Manage Access')).toBeInTheDocument(); - expect(screen.getByText('Library Team Management')).toBeInTheDocument(); - expect(screen.getByRole('listitem', { current: 'page' })).toHaveTextContent('testuser'); - // Page title and subtitle - expect(screen.getByRole('heading', { level: 2 })).toHaveTextContent('testuser'); - expect(screen.getByRole('heading', { level: 3 })).toHaveTextContent('testuser@example.com'); - - expect(screen.getByText('Admin')).toBeInTheDocument(); - expect(screen.getByText('Instructor')).toBeInTheDocument(); - - defaultMockData.roles.forEach((role) => { - expect(screen.getByText(role.name)).toBeInTheDocument(); - expect(screen.getByText(role.description)).toBeInTheDocument(); - }); - }); - - it('renders assign role trigger when user has canManageTeam permission', () => { - renderComponent(); - - expect(screen.getByText('Assign Role')).toBeInTheDocument(); - }); - - it('navigates to the wizard with the current user and return path when Assign Role is clicked', async () => { - const user = userEvent.setup(); - renderComponent(); - - await user.click(screen.getByRole('button', { name: /Assign Role/i })); - - expect(mockNavigate).toHaveBeenCalledWith( - buildWizardPath({ users: 'testuser', from: '/authz/libraries/lib:123/testuser' }), - ); - }); - - it('renders correct navigation link label and URL on breadcrumb', () => { - renderComponent(); - const navLinkManageAccess = screen.getByRole('link', { name: 'Manage Access' }); - expect(navLinkManageAccess).toBeInTheDocument(); - // TODO: Update expected URL when dedicated Manage Access page is created - expect(navLinkManageAccess).toHaveAttribute('href', '/authz/libraries/lib:123'); - const navLinkLibraryTeamManagement = screen.getByRole('link', { name: 'Library Team Management' }); - expect(navLinkLibraryTeamManagement).toBeInTheDocument(); - expect(navLinkLibraryTeamManagement).toHaveAttribute('href', '/authz/libraries/lib:123'); - }); - - describe('Navigation guards', () => { - it('redirects to team path when canManageTeam is false', () => { - (useLibraryAuthZ as jest.Mock).mockReturnValue({ - ...defaultMockData, - canManageTeam: false, - }); - - renderComponent(); - - expect(mockNavigate).toHaveBeenCalledWith('/authz/libraries/lib:123'); - }); - - it('redirects to team path when user is not found after loading completes', async () => { - (useTeamMembers as jest.Mock).mockReturnValue({ - data: { results: [] }, - isLoading: false, - isFetching: false, - }); - - renderComponent(); - - await waitFor(() => { - expect(mockNavigate).toHaveBeenCalledWith('/authz/libraries/lib:123'); - }); - }); - - it('does not redirect while member data is still fetching', () => { - (useTeamMembers as jest.Mock).mockReturnValue({ - data: undefined, - isLoading: true, - isFetching: true, - }); - - renderComponent(); - - // navigate should only be called for canManageTeam=true case (not at all here) - expect(mockNavigate).not.toHaveBeenCalled(); - }); - }); - - describe('Loading state', () => { - it('renders skeleton while loading team member data', () => { - (useTeamMembers as jest.Mock).mockReturnValue({ - data: undefined, - isLoading: true, - isFetching: true, - }); - - renderComponent(); - - // Just verify the component renders without crashing in loading state - expect(screen.queryByText('Admin')).not.toBeInTheDocument(); - }); - }); - - describe('Assign role button', () => { - it('navigates to assign-role wizard when Assign Role button is clicked', async () => { - const user = userEvent.setup(); - renderComponent(); - - // There are two "Assign Role" elements: the mocked AssignNewRoleTrigger and the real Button - // The real Button navigates; use getAllByText and click the one that is a - -