Skip to content

Commit f99b977

Browse files
test: for role deletion on roles table audit user page
1 parent a5c61b0 commit f99b977

5 files changed

Lines changed: 192 additions & 48 deletions

File tree

src/authz-module/audit-user/index.test.tsx

Lines changed: 135 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { render, screen, waitFor } from '@testing-library/react';
2+
import { AppContext } from '@edx/frontend-platform/react';
23
import userEvent from '@testing-library/user-event';
34
import { MemoryRouter, Route, Routes } from 'react-router-dom';
45
import { getAuthenticatedHttpClient } from '@edx/frontend-platform/auth';
56
import { IntlProvider } from '@edx/frontend-platform/i18n';
67
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
8+
import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext';
79
import AuditUserPage from './index';
810

911
jest.mock('@edx/frontend-platform/auth', () => ({
@@ -40,17 +42,32 @@ const renderWithRouter = (route = '/audit/johndoe') => {
4042
},
4143
});
4244

45+
const mockAppContext = {
46+
authenticatedUser: {
47+
username: 'testuser',
48+
49+
},
50+
config: {
51+
// @ts-ignore
52+
...process.env,
53+
},
54+
};
55+
4356
return render(
44-
<QueryClientProvider client={queryClient}>
45-
<IntlProvider locale="en">
46-
<MemoryRouter initialEntries={[route]}>
47-
<Routes>
48-
<Route path="/audit/:username" element={<AuditUserPage />} />
49-
<Route path="/authz" element={<div>Home Page</div>} />
50-
</Routes>
51-
</MemoryRouter>
52-
</IntlProvider>
53-
</QueryClientProvider>,
57+
<AppContext.Provider value={mockAppContext}>
58+
<QueryClientProvider client={queryClient}>
59+
<IntlProvider locale="en">
60+
<ToastManagerProvider>
61+
<MemoryRouter initialEntries={[route]}>
62+
<Routes>
63+
<Route path="/audit/:username" element={<AuditUserPage />} />
64+
<Route path="/authz" element={<div>Home Page</div>} />
65+
</Routes>
66+
</MemoryRouter>
67+
</ToastManagerProvider>
68+
</IntlProvider>
69+
</QueryClientProvider>
70+
</AppContext.Provider>,
5471
);
5572
};
5673

@@ -59,6 +76,11 @@ describe('AuditUserPage', () => {
5976
jest.clearAllMocks();
6077
});
6178

79+
beforeAll(() => {
80+
// @ts-ignore
81+
global.logError = jest.fn();
82+
});
83+
6284
it('renders user info and table when data is loaded', async () => {
6385
(getAuthenticatedHttpClient as jest.Mock).mockReturnValue({
6486
get: jest
@@ -185,4 +207,107 @@ describe('AuditUserPage', () => {
185207
expect(screen.getByText(mockUser.username, { selector: 'li[aria-current="page"]' })).toBeInTheDocument();
186208
});
187209
});
210+
211+
it('opens and closes the ConfirmDeletionModal when delete is clicked and cancel is pressed', async () => {
212+
(getAuthenticatedHttpClient as jest.Mock).mockReturnValue({
213+
get: jest
214+
.fn()
215+
.mockResolvedValueOnce({ data: mockUser })
216+
.mockResolvedValueOnce({ data: mockAssignments }),
217+
});
218+
219+
renderWithRouter();
220+
221+
await waitFor(() => {
222+
expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument();
223+
});
224+
225+
const user = userEvent.setup();
226+
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
227+
await user.click(deleteButton);
228+
229+
await waitFor(() => {
230+
expect(screen.getByRole('dialog')).toBeInTheDocument();
231+
expect(screen.getByText(/remove role\?/i)).toBeInTheDocument();
232+
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
233+
});
234+
235+
const cancelButton = screen.getByRole('button', { name: /cancel/i });
236+
await user.click(cancelButton);
237+
238+
await waitFor(() => {
239+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
240+
});
241+
});
242+
243+
it('calls onSave when confirming deletion in ConfirmDeletionModal', async () => {
244+
(getAuthenticatedHttpClient as jest.Mock).mockReturnValue({
245+
get: jest
246+
.fn()
247+
.mockResolvedValueOnce({ data: mockUser })
248+
.mockResolvedValueOnce({ data: mockAssignments }),
249+
delete: jest.fn().mockResolvedValue({ data: { errors: [] } }),
250+
});
251+
252+
renderWithRouter();
253+
254+
await waitFor(() => {
255+
expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument();
256+
});
257+
258+
const user = userEvent.setup();
259+
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
260+
await user.click(deleteButton);
261+
262+
await waitFor(() => {
263+
expect(screen.getByRole('dialog')).toBeInTheDocument();
264+
expect(screen.getByRole('button', { name: /remove/i })).toBeInTheDocument();
265+
});
266+
267+
const removeButton = screen.getByRole('button', { name: /remove/i });
268+
await user.click(removeButton);
269+
270+
await waitFor(() => {
271+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
272+
expect(screen.getByText(/role has been successfully removed/i)).toBeInTheDocument();
273+
});
274+
});
275+
276+
it('shows the extra warning when rolesCount is 1', async () => {
277+
(getAuthenticatedHttpClient as jest.Mock).mockReturnValue({
278+
get: jest
279+
.fn()
280+
.mockResolvedValueOnce({ data: mockUser })
281+
.mockResolvedValueOnce({
282+
data: {
283+
count: 1,
284+
results: [
285+
{
286+
id: '1',
287+
role: 'library_admin',
288+
org: 'Test Org',
289+
scope: 'lib:test',
290+
permissionCount: 5,
291+
},
292+
],
293+
next: null,
294+
previous: null,
295+
},
296+
}),
297+
});
298+
299+
renderWithRouter();
300+
301+
await waitFor(() => {
302+
expect(screen.getByRole('button', { name: /delete role action/i })).toBeInTheDocument();
303+
});
304+
305+
const user = userEvent.setup();
306+
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
307+
await user.click(deleteButton);
308+
309+
await waitFor(() => {
310+
expect(screen.getByText(/this is the user's only role/i)).toBeInTheDocument();
311+
});
312+
});
188313
});

src/authz-module/components/TableCells.test.tsx

Lines changed: 54 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ import {
99
OrgCell,
1010
ScopeCell,
1111
PermissionsCell,
12-
ActionsCell,
1312
ViewAllPermissionsCell,
13+
createActionsCell,
1414
} from './TableCells';
1515

1616
// TODO: remove console.log mocks and implement actual logic for these cells, then update tests accordingly
@@ -481,56 +481,75 @@ describe('TableCells Components', () => {
481481
});
482482
});
483483

484-
describe('ActionsCell', () => {
485-
const mockRow = {
484+
describe('createActionsCell', () => {
485+
const mockOnClickDeleteButton = jest.fn();
486+
const baseRow = {
486487
original: {
487-
role: 'library_admin', id: '123', org: 'Test Org', scope: 'Test Scope', permissionCount: 1,
488+
role: 'library_admin',
489+
org: 'Test Org',
490+
scope: 'Test Scope',
491+
permissionCount: 1,
488492
},
489493
};
490494

491-
it('renders a delete button', () => {
492-
const props = {
493-
row: mockRow,
494-
column: { id: 'actions' },
495-
};
495+
beforeEach(() => {
496+
jest.clearAllMocks();
497+
});
496498

497-
renderWrapper(<ActionsCell {...props} />);
499+
it('renders a delete button and calls onClickDeleteButton when clicked', async () => {
500+
const user = userEvent.setup();
501+
const CustomActionsCell = createActionsCell({
502+
onClickDeleteButton: mockOnClickDeleteButton,
503+
isUserAuthenticatedPage: false,
504+
});
505+
renderWrapper(<CustomActionsCell row={baseRow} column={{ id: 'actions' }} />);
498506

499507
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
500508
expect(deleteButton).toBeInTheDocument();
509+
510+
await user.click(deleteButton);
511+
expect(mockOnClickDeleteButton).toHaveBeenCalledWith({ role: 'library_admin', scope: 'Test Scope' });
501512
});
502513

503-
it('calls handleDelete when delete button is clicked', async () => {
504-
const user = userEvent.setup();
505-
const props = {
506-
row: mockRow,
507-
column: { id: 'actions' },
514+
it('renders a disabled button for admin roles when isUserAuthenticatedPage is true', () => {
515+
const adminRow = {
516+
original: {
517+
role: 'course_admin',
518+
org: 'Test Org',
519+
scope: 'Test Scope',
520+
permissionCount: 1,
521+
},
508522
};
523+
const CustomActionsCell = createActionsCell({
524+
onClickDeleteButton: mockOnClickDeleteButton,
525+
isUserAuthenticatedPage: true,
526+
});
527+
renderWrapper(<CustomActionsCell row={adminRow} column={{ id: 'actions' }} />);
509528

510-
renderWrapper(<ActionsCell {...props} />);
511-
512-
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
513-
await user.click(deleteButton);
514-
// TODO: replace console.log with actual delete logic and update this test accordingly
515-
// eslint-disable-next-line no-console
516-
expect(console.log).toHaveBeenCalledWith('Delete clicked for row:', mockRow);
529+
const button = screen.getByRole('button', { name: /delete role action/i });
530+
expect(button).toBeDisabled();
517531
});
518532

519-
it('handles keyboard interaction for delete button', async () => {
520-
const user = userEvent.setup();
521-
const props = {
522-
row: mockRow,
523-
column: { id: 'actions' },
533+
it('renders info icon with tooltip for Django managed roles', async () => {
534+
const djangoRow = {
535+
original: {
536+
role: 'django.superuser',
537+
org: 'Test Org',
538+
scope: 'Test Scope',
539+
permissionCount: 1,
540+
},
524541
};
542+
const user = userEvent.setup();
543+
const CustomActionsCell = createActionsCell({
544+
onClickDeleteButton: mockOnClickDeleteButton,
545+
isUserAuthenticatedPage: true,
546+
});
547+
renderWrapper(<CustomActionsCell row={djangoRow} column={{ id: 'actions' }} />);
525548

526-
renderWrapper(<ActionsCell {...props} />);
527-
528-
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
529-
deleteButton.focus();
530-
await user.keyboard('{Enter}');
531-
// TODO: replace console.log with actual delete logic and update this test accordingly
532-
// eslint-disable-next-line no-console
533-
expect(console.log).toHaveBeenCalledWith('Delete clicked for row:', mockRow);
549+
const infoIcon = screen.getByRole('img', { hidden: true });
550+
expect(infoIcon).toBeInTheDocument();
551+
await user.hover(infoIcon);
552+
expect(screen.getByText(/Please go to Django Admin to manage it/i)).toBeInTheDocument();
534553
});
535554
});
536555

src/authz-module/libraries-manager/components/AddNewTeamMemberModal/AddNewTeamMemberTrigger.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { screen, waitFor } from '@testing-library/react';
33
import userEvent from '@testing-library/user-event';
44
import { renderWrapper } from '@src/setupTest';
55
import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks';
6-
import { ToastManagerProvider } from 'authz-module/data/context/ToastManagerContext';
6+
import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext';
77
import AddNewTeamMemberTrigger from './AddNewTeamMemberTrigger';
88

99
jest.mock('@edx/frontend-platform/logging');

src/authz-module/libraries-manager/components/AssignNewRoleModal/AssignNewRoleTrigger.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event';
33
import { renderWrapper } from '@src/setupTest';
44
import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context';
55
import { useAssignTeamMembersRole } from '@src/authz-module/data/hooks';
6-
import { ToastManagerProvider } from 'authz-module/data/context/ToastManagerContext';
6+
import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext';
77
import AssignNewRoleTrigger from './AssignNewRoleTrigger';
88

99
jest.mock('@edx/frontend-platform/logging');

src/authz-module/libraries-manager/components/TeamTable/index.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import userEvent from '@testing-library/user-event';
33
import { renderWrapper } from '@src/setupTest';
44
import { useTeamMembers } from '@src/authz-module/data/hooks';
55
import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context';
6-
import { ToastManagerProvider } from 'authz-module/data/context/ToastManagerContext';
6+
import { ToastManagerProvider } from '@src/authz-module/data/context/ToastManagerContext';
77
import { CONTENT_LIBRARY_PERMISSIONS } from '@src/authz-module/constants';
88
import TeamTable from './index';
99

0 commit comments

Comments
 (0)