Skip to content

Commit be1e23b

Browse files
test: for role deletion on roles table audit user page
1 parent 84b25cb commit be1e23b

5 files changed

Lines changed: 194 additions & 50 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: 56 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {
77
OrgCell,
88
ScopeCell,
99
PermissionsCell,
10-
ActionsCell,
1110
ViewAllPermissionsCell,
11+
createActionsCell,
1212
} from './TableCells';
1313

1414
// TODO: remove console.log mocks and implement actual logic for these cells, then update tests accordingly
@@ -253,56 +253,75 @@ describe('TableCells Components', () => {
253253
});
254254
});
255255

256-
describe('ActionsCell', () => {
257-
const mockRow = {
256+
describe('createActionsCell', () => {
257+
const mockOnClickDeleteButton = jest.fn();
258+
const baseRow = {
258259
original: {
259-
role: 'library_admin', id: '123', org: 'Test Org', scope: 'Test Scope', permissionCount: 1,
260+
role: 'library_admin',
261+
org: 'Test Org',
262+
scope: 'Test Scope',
263+
permissionCount: 1,
260264
},
261265
};
262266

263-
it('renders a delete button', () => {
264-
const props = {
265-
row: mockRow,
266-
column: { id: 'actions' },
267-
};
268-
269-
renderWrapper(<ActionsCell {...props} />);
270-
271-
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
272-
expect(deleteButton).toBeInTheDocument();
267+
beforeEach(() => {
268+
jest.clearAllMocks();
273269
});
274270

275-
it('calls handleDelete when delete button is clicked', async () => {
271+
it('renders a delete button and calls onClickDeleteButton when clicked', async () => {
276272
const user = userEvent.setup();
277-
const props = {
278-
row: mockRow,
279-
column: { id: 'actions' },
280-
};
281-
282-
renderWrapper(<ActionsCell {...props} />);
273+
const CustomActionsCell = createActionsCell({
274+
onClickDeleteButton: mockOnClickDeleteButton,
275+
isUserAuthenticatedPage: false,
276+
});
277+
renderWrapper(<CustomActionsCell row={baseRow} column={{ id: 'actions' }} />);
283278

284279
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
280+
expect(deleteButton).toBeInTheDocument();
281+
285282
await user.click(deleteButton);
286-
// TODO: replace console.log with actual delete logic and update this test accordingly
287-
// eslint-disable-next-line no-console
288-
expect(console.log).toHaveBeenCalledWith('Delete clicked for row:', mockRow);
283+
expect(mockOnClickDeleteButton).toHaveBeenCalledWith({ role: 'library_admin', scope: 'Test Scope' });
289284
});
290285

291-
it('handles keyboard interaction for delete button', async () => {
292-
const user = userEvent.setup();
293-
const props = {
294-
row: mockRow,
295-
column: { id: 'actions' },
286+
it('renders a disabled button for admin roles when isUserAuthenticatedPage is true', () => {
287+
const adminRow = {
288+
original: {
289+
role: 'course_admin',
290+
org: 'Test Org',
291+
scope: 'Test Scope',
292+
permissionCount: 1,
293+
},
296294
};
295+
const CustomActionsCell = createActionsCell({
296+
onClickDeleteButton: mockOnClickDeleteButton,
297+
isUserAuthenticatedPage: true,
298+
});
299+
renderWrapper(<CustomActionsCell row={adminRow} column={{ id: 'actions' }} />);
300+
301+
const button = screen.getByRole('button', { name: /delete role action/i });
302+
expect(button).toBeDisabled();
303+
});
297304

298-
renderWrapper(<ActionsCell {...props} />);
299-
300-
const deleteButton = screen.getByRole('button', { name: /delete role action/i });
301-
deleteButton.focus();
302-
await user.keyboard('{Enter}');
303-
// TODO: replace console.log with actual delete logic and update this test accordingly
304-
// eslint-disable-next-line no-console
305-
expect(console.log).toHaveBeenCalledWith('Delete clicked for row:', mockRow);
305+
it('renders info icon with tooltip for Django managed roles', async () => {
306+
const djangoRow = {
307+
original: {
308+
role: 'django.superuser',
309+
org: 'Test Org',
310+
scope: 'Test Scope',
311+
permissionCount: 1,
312+
},
313+
};
314+
const user = userEvent.setup();
315+
const CustomActionsCell = createActionsCell({
316+
onClickDeleteButton: mockOnClickDeleteButton,
317+
isUserAuthenticatedPage: true,
318+
});
319+
renderWrapper(<CustomActionsCell row={djangoRow} column={{ id: 'actions' }} />);
320+
321+
const infoIcon = screen.getByRole('img', { hidden: true });
322+
expect(infoIcon).toBeInTheDocument();
323+
await user.hover(infoIcon);
324+
expect(screen.getByText(/Please go to Django Admin to manage it/i)).toBeInTheDocument();
306325
});
307326
});
308327

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)