Skip to content

Commit 82875eb

Browse files
committed
add gpt-generated tests for the group management panel
1 parent 6255f4a commit 82875eb

File tree

2 files changed

+345
-0
lines changed

2 files changed

+345
-0
lines changed
Lines changed: 344 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,344 @@
1+
import React from 'react';
2+
import { render, screen, act } from '@testing-library/react';
3+
import { MemoryRouter } from 'react-router-dom';
4+
import { vi } from 'vitest';
5+
import GroupMemberManagement from './GroupMemberManagement';
6+
import { MantineProvider } from '@mantine/core';
7+
import { notifications } from '@mantine/notifications';
8+
import userEvent from '@testing-library/user-event';
9+
10+
describe('Exec Group Management Panel read tests', () => {
11+
const renderComponent = async (
12+
fetchMembers: () => Promise<any[]>,
13+
updateMembers: () => Promise<any>
14+
) => {
15+
await act(async () => {
16+
render(
17+
<MemoryRouter>
18+
<MantineProvider withGlobalClasses withCssVariables forceColorScheme={'light'}>
19+
<GroupMemberManagement fetchMembers={fetchMembers} updateMembers={updateMembers} />
20+
</MantineProvider>
21+
</MemoryRouter>
22+
);
23+
});
24+
};
25+
26+
beforeEach(() => {
27+
vi.resetModules();
28+
});
29+
30+
it('renders with no members', async () => {
31+
const fetchMembers = async () => [];
32+
const updateMembers = async () => ({ success: [] });
33+
34+
await renderComponent(fetchMembers, updateMembers);
35+
36+
expect(screen.getByText('Current Members')).toBeInTheDocument();
37+
expect(screen.queryByText(/.*@illinois\.edu/)).not.toBeInTheDocument();
38+
});
39+
40+
it('renders with a single member', async () => {
41+
const fetchMembers = async () => [{ name: 'Doe, John', email: '[email protected]' }];
42+
const updateMembers = async () => ({
43+
success: [{ email: '[email protected]' }],
44+
});
45+
46+
await renderComponent(fetchMembers, updateMembers);
47+
expect(screen.getByText(/Doe, John \(jdoe@illinois\.edu\)/)).toBeInTheDocument();
48+
});
49+
50+
it('renders with multiple members', async () => {
51+
const fetchMembers = async () => [
52+
{ name: 'Doe, John', email: '[email protected]' },
53+
{ name: 'Smith, Jane', email: '[email protected]' },
54+
{ name: 'Brown, Bob', email: '[email protected]' },
55+
];
56+
const updateMembers = async () => ({
57+
success: [
58+
{ email: '[email protected]' },
59+
{ email: '[email protected]' },
60+
{ email: '[email protected]' },
61+
],
62+
});
63+
64+
await renderComponent(fetchMembers, updateMembers);
65+
expect(screen.getByText(/Doe, John \(jdoe@illinois\.edu\)/)).toBeInTheDocument();
66+
expect(screen.getByText(/Smith, Jane \(jsmith@illinois\.edu\)/)).toBeInTheDocument();
67+
expect(screen.getByText(/Brown, Bob \(bbrown@illinois\.edu\)/)).toBeInTheDocument();
68+
});
69+
70+
it('displays all required UI elements', async () => {
71+
const fetchMembers = async () => [];
72+
const updateMembers = async () => ({ success: [] });
73+
74+
await renderComponent(fetchMembers, updateMembers);
75+
76+
expect(screen.getByText('Exec Council Group Management')).toBeInTheDocument();
77+
expect(screen.getByText('Current Members')).toBeInTheDocument();
78+
expect(screen.getByLabelText('Add Member')).toBeInTheDocument();
79+
expect(screen.getByPlaceholderText('Enter email')).toBeInTheDocument();
80+
expect(screen.getByRole('button', { name: 'Add Member' })).toBeInTheDocument();
81+
expect(screen.getByRole('button', { name: 'Save Changes' })).toBeDisabled();
82+
});
83+
84+
it('adds a new member and saves changes', async () => {
85+
const notificationsMock = vi.spyOn(notifications, 'show');
86+
const user = userEvent.setup();
87+
const fetchMembers = async () => [];
88+
const updateMembers = vi.fn().mockResolvedValue({
89+
success: [{ email: '[email protected]' }],
90+
failure: [],
91+
});
92+
93+
await renderComponent(fetchMembers, updateMembers);
94+
95+
// Input the email
96+
const emailInput = screen.getByPlaceholderText('Enter email');
97+
await user.type(emailInput, '[email protected]');
98+
99+
// Click Add Member button
100+
const addButton = screen.getByRole('button', { name: 'Add Member' });
101+
await user.click(addButton);
102+
103+
// Verify member appears with "Queued for addition" badge
104+
expect(screen.getByText('[email protected]')).toBeInTheDocument();
105+
expect(screen.getByText('Queued for addition')).toBeInTheDocument();
106+
107+
// Click Save Changes which opens modal
108+
const saveButton = screen.getByRole('button', { name: 'Save Changes' });
109+
expect(saveButton).toBeEnabled();
110+
await user.click(saveButton);
111+
112+
// Wait for the modal to appear with title
113+
await screen.findByText('Confirm Changes');
114+
115+
// Find and click confirm button in modal
116+
const confirmButton = screen.getByRole('button', { name: 'Confirm and Save' });
117+
await user.click(confirmButton);
118+
119+
// Verify updateMembers was called with correct parameters
120+
expect(updateMembers).toHaveBeenCalledWith(['[email protected]'], []);
121+
122+
// Verify list is updated - "Queued for addition" badge should be gone
123+
expect(screen.getByText(/member \(member@illinois\.edu\)/)).toBeInTheDocument();
124+
expect(screen.queryByText('Queued for addition')).not.toBeInTheDocument();
125+
126+
// Verify notifications were shown
127+
expect(notificationsMock).toHaveBeenCalledWith(
128+
expect.objectContaining({
129+
message: 'All changes processed successfully!',
130+
color: 'green',
131+
})
132+
);
133+
134+
// Verify Save Changes button is disabled again after successful update
135+
expect(screen.getByRole('button', { name: 'Save Changes' })).toBeDisabled();
136+
137+
// Clean up
138+
notificationsMock.mockRestore();
139+
});
140+
it('handles failed member updates correctly', async () => {
141+
const notificationsMock = vi.spyOn(notifications, 'show');
142+
const user = userEvent.setup();
143+
const fetchMembers = async () => [];
144+
const updateMembers = vi.fn().mockResolvedValue({
145+
success: [],
146+
failure: [
147+
{
148+
149+
message: 'User does not exist in directory',
150+
},
151+
],
152+
});
153+
154+
await renderComponent(fetchMembers, updateMembers);
155+
156+
// Add a member that will fail
157+
const emailInput = screen.getByPlaceholderText('Enter email');
158+
await user.type(emailInput, '[email protected]');
159+
await user.click(screen.getByRole('button', { name: 'Add Member' }));
160+
161+
// Verify member shows in queue
162+
expect(screen.getByText('[email protected]')).toBeInTheDocument();
163+
expect(screen.getByText('Queued for addition')).toBeInTheDocument();
164+
165+
// Try to save changes
166+
await user.click(screen.getByRole('button', { name: 'Save Changes' }));
167+
await screen.findByText('Confirm Changes');
168+
await user.click(screen.getByRole('button', { name: 'Confirm and Save' }));
169+
170+
expect(notificationsMock).toHaveBeenCalledWith(
171+
expect.objectContaining({
172+
title: 'Error adding [email protected]',
173+
message: 'User does not exist in directory',
174+
color: 'red',
175+
})
176+
);
177+
178+
// Verify member is no longer shown as queued (since queues are cleared)
179+
expect(screen.queryByText('Queued for addition')).not.toBeInTheDocument();
180+
181+
// Verify Save Changes button is disabled since queues are cleared
182+
expect(screen.getByRole('button', { name: 'Save Changes' })).toBeDisabled();
183+
184+
notificationsMock.mockRestore();
185+
});
186+
187+
it('removes an existing member', async () => {
188+
const notificationsMock = vi.spyOn(notifications, 'show');
189+
const user = userEvent.setup();
190+
const fetchMembers = async () => [
191+
{
192+
name: 'Existing Member',
193+
194+
},
195+
];
196+
const updateMembers = vi.fn().mockResolvedValue({
197+
success: [{ email: '[email protected]' }],
198+
failure: [],
199+
});
200+
201+
await renderComponent(fetchMembers, updateMembers);
202+
203+
// Click remove button for the existing member using data-testid
204+
const removeButton = screen.getByTestId('[email protected]');
205+
await user.click(removeButton);
206+
207+
// Verify member shows removal badge
208+
expect(screen.getByText('Queued for removal')).toBeInTheDocument();
209+
210+
// Save changes
211+
const saveButton = screen.getByRole('button', { name: 'Save Changes' });
212+
expect(saveButton).toBeEnabled();
213+
await user.click(saveButton);
214+
215+
await screen.findByText('Confirm Changes');
216+
const confirmButton = screen.getByRole('button', { name: 'Confirm and Save' });
217+
await user.click(confirmButton);
218+
219+
// Verify updateMembers was called with correct parameters
220+
expect(updateMembers).toHaveBeenCalledWith(
221+
[], // toAdd
222+
['[email protected]'] // toRemove
223+
);
224+
225+
// Verify member is removed from the list
226+
expect(
227+
screen.queryByText(/Existing Member \(existing@illinois\.edu\)/)
228+
).not.toBeInTheDocument();
229+
230+
// Verify success notification
231+
expect(notificationsMock).toHaveBeenCalledWith(
232+
expect.objectContaining({
233+
message: 'All changes processed successfully!',
234+
color: 'green',
235+
})
236+
);
237+
238+
notificationsMock.mockRestore();
239+
});
240+
it('handles multiple member changes with mixed success/failure results', async () => {
241+
const notificationsMock = vi.spyOn(notifications, 'show');
242+
const user = userEvent.setup();
243+
244+
// Start with two existing members
245+
const fetchMembers = async () => [
246+
{ name: 'Stay Member', email: '[email protected]' },
247+
{ name: 'Remove Success', email: '[email protected]' },
248+
{ name: 'Remove Fail', email: '[email protected]' },
249+
];
250+
251+
// Mock mixed success/failure response
252+
const updateMembers = vi.fn().mockResolvedValue({
253+
success: [
254+
{ email: '[email protected]' }, // removal succeeded
255+
{ email: '[email protected]' }, // addition succeeded
256+
],
257+
failure: [
258+
{
259+
260+
message: 'Cannot remove admin user',
261+
},
262+
{
263+
264+
message: 'User not found in directory',
265+
},
266+
],
267+
});
268+
269+
await renderComponent(fetchMembers, updateMembers);
270+
271+
// Add two new members - one will succeed, one will fail
272+
const emailInput = screen.getByPlaceholderText('Enter email');
273+
274+
await user.type(emailInput, '[email protected]');
275+
await user.click(screen.getByRole('button', { name: 'Add Member' }));
276+
277+
await user.type(emailInput, '[email protected]');
278+
await user.click(screen.getByRole('button', { name: 'Add Member' }));
279+
280+
// Remove two existing members - one will succeed, one will fail
281+
await user.click(screen.getByTestId('[email protected]'));
282+
await user.click(screen.getByTestId('[email protected]'));
283+
284+
// Verify queued states before save
285+
expect(screen.getByText('[email protected]')).toBeInTheDocument();
286+
expect(screen.getByText('[email protected]')).toBeInTheDocument();
287+
expect(screen.getAllByText('Queued for addition')).toHaveLength(2);
288+
expect(screen.getAllByText('Queued for removal')).toHaveLength(2);
289+
290+
// Save changes
291+
const saveButton = screen.getByRole('button', { name: 'Save Changes' });
292+
expect(saveButton).toBeEnabled();
293+
await user.click(saveButton);
294+
295+
// Confirm in modal
296+
await screen.findByText('Confirm Changes');
297+
const confirmButton = screen.getByRole('button', { name: 'Confirm and Save' });
298+
await user.click(confirmButton);
299+
300+
// Verify updateMembers was called with all changes
301+
expect(updateMembers).toHaveBeenCalledWith(
302+
303+
304+
);
305+
306+
// Verify error notifications for failures
307+
expect(notificationsMock).toHaveBeenCalledWith(
308+
expect.objectContaining({
309+
title: 'Error adding [email protected]',
310+
message: 'User not found in directory',
311+
color: 'red',
312+
})
313+
);
314+
315+
expect(notificationsMock).toHaveBeenCalledWith(
316+
expect.objectContaining({
317+
title: 'Error removing [email protected]',
318+
message: 'Cannot remove admin user',
319+
color: 'red',
320+
})
321+
);
322+
323+
// Verify end state of member list
324+
// Success cases
325+
expect(screen.queryByText(/removesuccess@illinois\.edu/)).not.toBeInTheDocument(); // Successfully removed
326+
expect(screen.getByText(/addsuccess@illinois\.edu/)).toBeInTheDocument(); // Successfully added
327+
328+
// Failure cases
329+
expect(screen.getByText(/removefail@illinois\.edu/)).toBeInTheDocument(); // Failed to remove
330+
expect(screen.queryByText(/addfail@illinois\.edu/)).not.toBeInTheDocument(); // Failed to add
331+
332+
// Unchanged member
333+
expect(screen.getByText(/stay@illinois\.edu/)).toBeInTheDocument();
334+
335+
// Verify queued badges are cleared
336+
expect(screen.queryByText('Queued for addition')).not.toBeInTheDocument();
337+
expect(screen.queryByText('Queued for removal')).not.toBeInTheDocument();
338+
339+
// Verify Save Changes button is disabled after operation
340+
expect(screen.getByRole('button', { name: 'Save Changes' })).toBeDisabled();
341+
342+
notificationsMock.mockRestore();
343+
});
344+
});

src/ui/pages/iam/GroupMemberManagement.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ export const GroupMemberManagement: React.FC<GroupMemberManagementProps> = ({
163163
color="red"
164164
variant="light"
165165
onClick={() => handleRemoveMember(member.email)}
166+
data-testid={`remove-exec-member-${member.email}`}
166167
>
167168
<IconTrash size={16} />
168169
</ActionIcon>

0 commit comments

Comments
 (0)