Skip to content

Commit dc9e486

Browse files
bra-i-amdcoa
authored andcommitted
test: add unit tests for AssignNewRoleModal and AssignNewRoleTrigger components
1 parent a6c8176 commit dc9e486

2 files changed

Lines changed: 484 additions & 0 deletions

File tree

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
import { screen } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import { renderWrapper } from '@src/setupTest';
4+
import { Role } from 'types';
5+
import AssignNewRoleModal from './AssignNewRoleModal';
6+
7+
describe('AssignNewRoleModal', () => {
8+
const defaultProps = {
9+
isOpen: true,
10+
isLoading: false,
11+
roleOptions: [
12+
{
13+
role: 'instructor',
14+
name: 'Instructor',
15+
description: 'Can create and edit content',
16+
userCount: 5,
17+
permissions: ['view', 'edit'],
18+
},
19+
{
20+
role: 'admin',
21+
name: 'Administrator',
22+
description: 'Full access to the library',
23+
userCount: 2,
24+
permissions: ['view', 'edit', 'delete', 'manage'],
25+
},
26+
{
27+
role: 'viewer',
28+
name: 'Viewer',
29+
description: 'Can only view content',
30+
userCount: 10,
31+
permissions: ['view'],
32+
},
33+
] as Role[],
34+
selectedRole: '',
35+
close: jest.fn(),
36+
onSave: jest.fn(),
37+
handleChangeSelectedRole: jest.fn(),
38+
};
39+
40+
beforeEach(() => {
41+
jest.clearAllMocks();
42+
});
43+
44+
const renderComponent = (props = {}) => {
45+
const finalProps = { ...defaultProps, ...props };
46+
return renderWrapper(<AssignNewRoleModal {...finalProps} />);
47+
};
48+
49+
describe('Modal Visibility', () => {
50+
it('renders modal when isOpen is true', () => {
51+
renderComponent({ isOpen: true });
52+
53+
expect(screen.getByRole('dialog')).toBeInTheDocument();
54+
expect(screen.getByText('Add New Role')).toBeInTheDocument();
55+
});
56+
57+
it('does not render modal when isOpen is false', () => {
58+
renderComponent({ isOpen: false });
59+
60+
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
61+
expect(screen.queryByText('Add New Role')).not.toBeInTheDocument();
62+
});
63+
});
64+
65+
describe('Modal Structure', () => {
66+
it('renders modal header with correct title', () => {
67+
renderComponent({ isOpen: true });
68+
69+
expect(screen.getByText('Add New Role')).toBeInTheDocument();
70+
expect(screen.getByRole('dialog')).toBeInTheDocument();
71+
});
72+
73+
it('renders close button in header', () => {
74+
renderComponent();
75+
76+
expect(screen.getByRole('button', { name: /close/i })).toBeInTheDocument();
77+
});
78+
});
79+
80+
describe('Role Selection Form', () => {
81+
it('renders role selection form with correct label', () => {
82+
renderComponent();
83+
84+
expect(screen.getByText('Roles')).toBeInTheDocument();
85+
expect(screen.getByRole('combobox')).toBeInTheDocument();
86+
});
87+
88+
it('renders default option', () => {
89+
renderComponent();
90+
91+
expect(screen.getByText('Select a role')).toBeInTheDocument();
92+
expect(screen.getByRole('option', { name: 'Select a role' })).toBeDisabled();
93+
});
94+
95+
it('renders all role options', () => {
96+
renderComponent();
97+
98+
defaultProps.roleOptions.forEach((role) => {
99+
expect(screen.getByRole('option', { name: role.name })).toBeInTheDocument();
100+
});
101+
});
102+
103+
it('displays selected role correctly', () => {
104+
renderComponent({ selectedRole: 'instructor' });
105+
106+
const selectElement = screen.getByRole('combobox');
107+
expect(selectElement).toHaveValue('instructor');
108+
});
109+
110+
it('calls handleChangeSelectedRole when role selection changes', async () => {
111+
const user = userEvent.setup();
112+
renderComponent();
113+
114+
const selectElement = screen.getByRole('combobox');
115+
await user.selectOptions(selectElement, 'admin');
116+
117+
expect(defaultProps.handleChangeSelectedRole).toHaveBeenCalled();
118+
});
119+
});
120+
121+
describe('Action Buttons', () => {
122+
it('renders Cancel button with correct text', () => {
123+
renderComponent();
124+
125+
expect(screen.getByRole('button', { name: /cancel/i })).toBeInTheDocument();
126+
});
127+
128+
it('renders Save button with correct text when not loading', () => {
129+
renderComponent({ isLoading: false });
130+
131+
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();
132+
});
133+
134+
it('renders Save button with loading text when loading', () => {
135+
renderComponent({ isLoading: true });
136+
137+
expect(screen.getByRole('button', { name: /saving/i })).toBeInTheDocument();
138+
});
139+
140+
it('calls close when Cancel button is clicked', async () => {
141+
const user = userEvent.setup();
142+
renderComponent();
143+
144+
const cancelButton = screen.getByRole('button', { name: /cancel/i });
145+
await user.click(cancelButton);
146+
147+
expect(defaultProps.close).toHaveBeenCalledTimes(1);
148+
});
149+
150+
it('calls onSave when Save button is clicked', async () => {
151+
const user = userEvent.setup();
152+
renderComponent({ selectedRole: 'instructor' });
153+
154+
const saveButton = screen.getByRole('button', { name: /save/i });
155+
await user.click(saveButton);
156+
157+
expect(defaultProps.onSave).toHaveBeenCalledTimes(1);
158+
});
159+
});
160+
161+
describe('Button States', () => {
162+
it('disables Save button when no role is selected', () => {
163+
renderComponent({ selectedRole: '' });
164+
165+
const saveButton = screen.getByRole('button', { name: /save/i });
166+
expect(saveButton).toBeDisabled();
167+
});
168+
169+
it('enables Save button when role is selected and not loading', () => {
170+
renderComponent({ selectedRole: 'instructor', isLoading: false });
171+
172+
const saveButton = screen.getByRole('button', { name: /save/i });
173+
expect(saveButton).not.toBeDisabled();
174+
});
175+
176+
it('disables Save button when loading', () => {
177+
renderComponent({ selectedRole: 'instructor', isLoading: true });
178+
179+
const saveButton = screen.getByRole('button', { name: /saving/i });
180+
expect(saveButton).toBeDisabled();
181+
});
182+
183+
it('disables Cancel button when loading', () => {
184+
renderComponent({ isLoading: true });
185+
186+
const cancelButton = screen.getByRole('button', { name: /cancel/i });
187+
expect(cancelButton).toBeDisabled();
188+
});
189+
190+
it('enables Cancel button when not loading', () => {
191+
renderComponent({ isLoading: false });
192+
193+
const cancelButton = screen.getByRole('button', { name: /cancel/i });
194+
expect(cancelButton).not.toBeDisabled();
195+
});
196+
});
197+
198+
describe('Modal Close Behavior', () => {
199+
it('does not call close when modal header close is clicked during loading', async () => {
200+
const user = userEvent.setup();
201+
renderComponent({ isLoading: true });
202+
203+
const headerCloseButton = screen.getByRole('button', { name: /close/i });
204+
await user.click(headerCloseButton);
205+
206+
expect(defaultProps.close).not.toHaveBeenCalled();
207+
});
208+
209+
it('calls close when modal header close is clicked and not loading', async () => {
210+
const user = userEvent.setup();
211+
renderComponent({ isLoading: false });
212+
213+
const headerCloseButton = screen.getByRole('button', { name: /close/i });
214+
await user.click(headerCloseButton);
215+
216+
expect(defaultProps.close).toHaveBeenCalledTimes(1);
217+
});
218+
});
219+
});

0 commit comments

Comments
 (0)