Skip to content

Commit 28ae7e4

Browse files
committed
test: add unit tests for AddNewTeamMemberModal and update context mocks
1 parent 937dacb commit 28ae7e4

4 files changed

Lines changed: 285 additions & 1 deletion

File tree

src/authz-module/libraries-manager/LibrariesTeamManager.test.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ jest.mock('./components/TeamTable', () => ({
2424
default: () => <div data-testid="team-table">MockTeamTable</div>,
2525
}));
2626

27+
jest.mock('./components/AddNewTeamMemberTrigger', () => ({
28+
__esModule: true,
29+
default: () => <div data-testid="add-team-member-trigger">MockAddNewTeamMemberTrigger</div>,
30+
}));
31+
2732
describe('LibrariesTeamManager', () => {
2833
beforeEach(() => {
2934
initializeMockApp({
@@ -63,5 +68,8 @@ describe('LibrariesTeamManager', () => {
6368

6469
// TeamTable is rendered
6570
expect(screen.getByTestId('team-table')).toBeInTheDocument();
71+
72+
// AddNewTeamMemberTrigger is rendered
73+
expect(screen.getByTestId('add-team-member-trigger')).toBeInTheDocument();
6674
});
6775
});
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
import { screen } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import { renderWrapper } from '@src/setupTest';
4+
import AddNewTeamMemberModal from './AddNewTeamMemberModal';
5+
import { useLibraryAuthZ } from '../context';
6+
7+
// Mock the context module
8+
jest.mock('../context', () => {
9+
const actual = jest.requireActual('../context');
10+
return {
11+
...actual,
12+
useLibraryAuthZ: jest.fn(),
13+
};
14+
});
15+
const mockedUseLibraryAuthZ = useLibraryAuthZ as jest.Mock;
16+
17+
jest.mock('../../data/hooks', () => ({
18+
useTeamRoles: jest.fn(),
19+
}));
20+
21+
const defaultProps = {
22+
isOpen: true,
23+
isLoading: false,
24+
formValues: {
25+
users: '',
26+
role: '',
27+
},
28+
close: jest.fn(),
29+
onSave: jest.fn(),
30+
handleChangeForm: jest.fn(),
31+
};
32+
33+
const mockRoles = [
34+
{
35+
role: 'instructor',
36+
description: 'Can create and edit content',
37+
userCount: 3,
38+
objects: [
39+
{
40+
object: 'library',
41+
description: 'Library permissions',
42+
actions: ['view', 'edit', 'delete'],
43+
},
44+
],
45+
},
46+
{
47+
role: 'admin',
48+
description: 'Full access to the library',
49+
userCount: 1,
50+
objects: [
51+
{
52+
object: 'library',
53+
description: 'Library permissions',
54+
actions: ['view', 'edit', 'delete', 'manage'],
55+
},
56+
],
57+
},
58+
];
59+
60+
describe('AddNewTeamMemberModal', () => {
61+
beforeEach(() => {
62+
jest.clearAllMocks();
63+
mockedUseLibraryAuthZ.mockReturnValue({
64+
username: 'testuser',
65+
libraryId: 'lib123',
66+
roles: mockRoles,
67+
canManageTeam: true,
68+
});
69+
});
70+
71+
const renderModal = (props = {}) => {
72+
const finalProps = { ...defaultProps, ...props };
73+
return renderWrapper(
74+
<AddNewTeamMemberModal {...finalProps} />,
75+
);
76+
};
77+
78+
describe('Modal Rendering', () => {
79+
it('renders the modal when isOpen is true', () => {
80+
renderModal();
81+
82+
expect(screen.getByRole('dialog')).toBeInTheDocument();
83+
expect(screen.getByText('Add New Team Member')).toBeInTheDocument();
84+
});
85+
86+
it('does not render the modal when isOpen is false', () => {
87+
renderModal({ isOpen: false });
88+
89+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
90+
});
91+
});
92+
93+
describe('Form Elements', () => {
94+
it('renders the users textarea with correct label', () => {
95+
renderModal();
96+
97+
expect(screen.getByLabelText('Add users by username or email')).toBeInTheDocument();
98+
expect(screen.getByRole('textbox', { name: /add users by username or email/i })).toBeInTheDocument();
99+
});
100+
101+
it('renders the role select dropdown with correct label', () => {
102+
renderModal();
103+
104+
expect(screen.getByLabelText('Roles')).toBeInTheDocument();
105+
expect(screen.getByRole('combobox', { name: /roles/i })).toBeInTheDocument();
106+
});
107+
108+
it('renders role options correctly', () => {
109+
renderModal();
110+
111+
expect(screen.getByText('Select a role')).toBeInTheDocument();
112+
mockRoles.forEach((role) => {
113+
expect(screen.getByText(role.role)).toBeInTheDocument();
114+
});
115+
});
116+
117+
it('displays form values correctly', () => {
118+
renderModal({
119+
formValues: {
120+
121+
role: 'instructor',
122+
},
123+
});
124+
125+
expect(screen.getByDisplayValue('[email protected], [email protected]')).toBeInTheDocument();
126+
expect(screen.getByDisplayValue('instructor')).toBeInTheDocument();
127+
});
128+
});
129+
130+
describe('Form Interactions', () => {
131+
it('calls handleChangeForm when users textarea changes', async () => {
132+
const user = userEvent.setup();
133+
const handleChangeForm = jest.fn();
134+
renderModal({ handleChangeForm });
135+
136+
const usersTextarea = screen.getByRole('textbox', { name: /add users by username or email/i });
137+
await user.type(usersTextarea, '[email protected]');
138+
139+
expect(handleChangeForm).toHaveBeenCalled();
140+
});
141+
142+
it('calls handleChangeForm when role select changes', async () => {
143+
const user = userEvent.setup();
144+
const handleChangeForm = jest.fn();
145+
renderModal({ handleChangeForm });
146+
147+
const roleSelect = screen.getByRole('combobox', { name: /roles/i });
148+
await user.selectOptions(roleSelect, 'instructor');
149+
150+
expect(handleChangeForm).toHaveBeenCalled();
151+
});
152+
});
153+
154+
describe('Modal Actions', () => {
155+
it('renders Cancel and Save buttons', () => {
156+
renderModal();
157+
158+
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
159+
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
160+
});
161+
162+
it('calls close function when Cancel button is clicked', async () => {
163+
const user = userEvent.setup();
164+
const close = jest.fn();
165+
renderModal({ close });
166+
167+
await user.click(screen.getByRole('button', { name: /cancel/i }));
168+
169+
expect(close).toHaveBeenCalledTimes(1);
170+
});
171+
172+
it('calls onSave function when Save button is clicked', async () => {
173+
const user = userEvent.setup();
174+
const onSave = jest.fn();
175+
renderModal({
176+
onSave,
177+
formValues: {
178+
179+
role: 'instructor',
180+
},
181+
});
182+
183+
await user.click(screen.getByRole('button', { name: /save/i }));
184+
185+
expect(onSave).toHaveBeenCalledTimes(1);
186+
});
187+
188+
it('disables Save button when users field is empty', () => {
189+
renderModal({
190+
formValues: {
191+
users: '',
192+
role: 'instructor',
193+
},
194+
});
195+
196+
expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
197+
});
198+
199+
it('disables Save button when role field is empty', () => {
200+
renderModal({
201+
formValues: {
202+
203+
role: '',
204+
},
205+
});
206+
207+
expect(screen.getByRole('button', { name: /save/i })).toBeDisabled();
208+
});
209+
210+
it('enables Save button when both fields are filled', () => {
211+
renderModal({
212+
formValues: {
213+
214+
role: 'instructor',
215+
},
216+
});
217+
218+
expect(screen.getByRole('button', { name: /save/i })).not.toBeDisabled();
219+
});
220+
});
221+
222+
describe('Loading State', () => {
223+
it('disables Cancel button when loading', () => {
224+
renderModal({ isLoading: true });
225+
226+
expect(screen.getByRole('button', { name: /cancel/i })).toBeDisabled();
227+
});
228+
229+
it('disables Save button when loading', () => {
230+
renderModal({
231+
isLoading: true,
232+
formValues: {
233+
234+
role: 'instructor',
235+
},
236+
});
237+
238+
expect(screen.getByRole('button', { name: /saving/i })).toBeDisabled();
239+
});
240+
});
241+
});

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {
44
ActionRow, Button, Form, ModalDialog,
55
Stack,
66
} from '@openedx/paragon';
7-
import { useLibraryAuthZ } from 'authz-module/libraries-manager/context';
7+
import { useLibraryAuthZ } from '@src/authz-module/libraries-manager/context';
88
import messages from './messages';
99

1010
interface AddNewTeamMemberModalProps {

src/authz-module/libraries-manager/context.test.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ jest.mock('@src/data/hooks', () => ({
1313
useValidateUserPermissions: jest.fn(),
1414
}));
1515

16+
jest.mock('../data/hooks', () => ({
17+
useTeamRoles: jest.fn(),
18+
}));
19+
20+
// Get the mocked function
21+
const { useTeamRoles } = jest.requireMock('../data/hooks');
22+
1623
const TestComponent = () => {
1724
const context = useLibraryAuthZ();
1825
return (
@@ -28,6 +35,34 @@ describe('LibraryAuthZProvider', () => {
2835
beforeEach(() => {
2936
jest.clearAllMocks();
3037
(useParams as jest.Mock).mockReturnValue({ libraryId: 'lib123' });
38+
(useTeamRoles as jest.Mock).mockReturnValue({
39+
data: [
40+
{
41+
role: 'instructor',
42+
description: 'Can create and edit content',
43+
userCount: 3,
44+
objects: [
45+
{
46+
object: 'library',
47+
description: 'Library permissions',
48+
actions: ['view', 'edit', 'delete'],
49+
},
50+
],
51+
},
52+
{
53+
role: 'admin',
54+
description: 'Full access to the library',
55+
userCount: 1,
56+
objects: [
57+
{
58+
object: 'library',
59+
description: 'Library permissions',
60+
actions: ['view', 'edit', 'delete', 'manage'],
61+
},
62+
],
63+
},
64+
],
65+
});
3166
});
3267

3368
it('provides the correct context values to consumers', () => {

0 commit comments

Comments
 (0)