Skip to content

Commit cf1956a

Browse files
authored
Merge pull request #229 from Resgrid/develop
RU-T47 Fixing role assignments issue
2 parents 1bee450 + 1120f16 commit cf1956a

12 files changed

+988
-179
lines changed

app.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ export default ({ config }: ConfigContext): ExpoConfig => ({
8080
'android.permission.FOREGROUND_SERVICE_MICROPHONE',
8181
'android.permission.FOREGROUND_SERVICE_PHONE_CALL',
8282
'android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE',
83-
'android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK',
8483
'android.permission.READ_PHONE_STATE',
8584
'android.permission.READ_PHONE_NUMBERS',
8685
'android.permission.MANAGE_OWN_CALLS',
Lines changed: 153 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { render, screen } from '@testing-library/react-native';
1+
import { fireEvent, render, screen } from '@testing-library/react-native';
22
import React from 'react';
33

44
import { type PersonnelInfoResultData } from '@/models/v4/personnel/personnelInfoResultData';
@@ -9,57 +9,48 @@ import { RoleAssignmentItem } from '../role-assignment-item';
99
// Mock react-i18next
1010
jest.mock('react-i18next', () => ({
1111
useTranslation: () => ({
12-
t: (key: string, defaultValue?: string) => defaultValue || key,
12+
t: (key: string, defaultValue?: string | Record<string, string>) => {
13+
if (typeof defaultValue === 'string') return defaultValue;
14+
if (typeof defaultValue === 'object' && defaultValue.defaultValue) return defaultValue.defaultValue;
15+
return key;
16+
},
1317
}),
1418
}));
1519

16-
// Mock the Select components
17-
jest.mock('@/components/ui/select', () => ({
18-
Select: ({ children, selectedValue, onValueChange }: any) => {
19-
const { View } = require('react-native');
20+
// Mock nativewind
21+
jest.mock('nativewind', () => ({
22+
useColorScheme: () => ({ colorScheme: 'light' }),
23+
cssInterop: jest.fn(),
24+
}));
25+
26+
// Mock the RoleUserSelectionModal to simplify testing
27+
let mockModalOnSelectUser: ((userId?: string) => void) | undefined;
28+
jest.mock('../role-user-selection-modal', () => ({
29+
RoleUserSelectionModal: ({ isOpen, onClose, roleName, selectedUserId, users, onSelectUser }: any) => {
30+
const { View, Text, TouchableOpacity } = require('react-native');
31+
mockModalOnSelectUser = onSelectUser;
32+
if (!isOpen) return null;
2033
return (
21-
<View testID="select" onPress={() => onValueChange && onValueChange('user1')}>
22-
{children}
34+
<View testID="user-selection-modal">
35+
<Text testID="modal-role-name">{roleName}</Text>
36+
<TouchableOpacity testID="select-unassigned" onPress={() => { onSelectUser(undefined); onClose(); }}>
37+
<Text>Unassigned</Text>
38+
</TouchableOpacity>
39+
{users.map((user: any) => (
40+
<TouchableOpacity key={user.UserId} testID={`select-user-${user.UserId}`} onPress={() => { onSelectUser(user.UserId); onClose(); }}>
41+
<Text>{`${user.FirstName} ${user.LastName}`}</Text>
42+
</TouchableOpacity>
43+
))}
2344
</View>
2445
);
2546
},
26-
SelectTrigger: ({ children }: any) => {
27-
const { View } = require('react-native');
28-
return <View testID="select-trigger">{children}</View>;
29-
},
30-
SelectInput: ({ value, placeholder }: any) => {
31-
const { Text } = require('react-native');
32-
return <Text testID="select-input">{value || placeholder}</Text>;
33-
},
34-
SelectIcon: () => {
35-
const { View } = require('react-native');
36-
return <View testID="select-icon" />;
37-
},
38-
SelectPortal: ({ children }: any) => children,
39-
SelectBackdrop: () => {
40-
const { View } = require('react-native');
41-
return <View testID="select-backdrop" />;
42-
},
43-
SelectContent: ({ children }: any) => {
44-
const { View } = require('react-native');
45-
return <View testID="select-content">{children}</View>;
46-
},
47-
SelectDragIndicatorWrapper: ({ children }: any) => children,
48-
SelectDragIndicator: () => {
49-
const { View } = require('react-native');
50-
return <View testID="select-drag-indicator" />;
51-
},
52-
SelectItem: ({ label, value }: any) => {
53-
const { Text } = require('react-native');
54-
return <Text testID={`select-item-${value}`}>{label}</Text>;
55-
},
5647
}));
5748

58-
// Mock other UI components
49+
// Mock UI components
5950
jest.mock('@/components/ui/text', () => ({
60-
Text: ({ children, className }: any) => {
51+
Text: ({ children, className, testID }: any) => {
6152
const { Text } = require('react-native');
62-
return <Text testID="text">{children}</Text>;
53+
return <Text testID={testID || 'text'}>{children}</Text>;
6354
},
6455
}));
6556

@@ -70,6 +61,13 @@ jest.mock('@/components/ui/vstack', () => ({
7061
},
7162
}));
7263

64+
jest.mock('@/components/ui/hstack', () => ({
65+
HStack: ({ children }: any) => {
66+
const { View } = require('react-native');
67+
return <View testID="hstack">{children}</View>;
68+
},
69+
}));
70+
7371
describe('RoleAssignmentItem', () => {
7472
const mockOnAssignUser = jest.fn();
7573

@@ -88,19 +86,19 @@ describe('RoleAssignmentItem', () => {
8886
DepartmentId: 'dept1',
8987
IdentificationNumber: '',
9088
MobilePhone: '',
91-
GroupId: '',
92-
GroupName: '',
89+
GroupId: 'group1',
90+
GroupName: 'Engine 1',
9391
StatusId: '',
94-
Status: '',
95-
StatusColor: '',
92+
Status: 'Available',
93+
StatusColor: '#00ff00',
9694
StatusTimestamp: '',
9795
StatusDestinationId: '',
9896
StatusDestinationName: '',
9997
StaffingId: '',
100-
Staffing: '',
101-
StaffingColor: '',
98+
Staffing: 'On Shift',
99+
StaffingColor: '#0000ff',
102100
StaffingTimestamp: '',
103-
Roles: [],
101+
Roles: ['Firefighter'],
104102
},
105103
{
106104
UserId: 'user2',
@@ -128,6 +126,7 @@ describe('RoleAssignmentItem', () => {
128126

129127
beforeEach(() => {
130128
jest.clearAllMocks();
129+
mockModalOnSelectUser = undefined;
131130
});
132131

133132
it('renders the role name', () => {
@@ -143,20 +142,36 @@ describe('RoleAssignmentItem', () => {
143142
expect(screen.getByText('Captain')).toBeTruthy();
144143
});
145144

146-
it('displays placeholder when no user is assigned', () => {
145+
it('displays "Unassigned" when no user is assigned', () => {
146+
render(
147+
<RoleAssignmentItem
148+
role={mockRole}
149+
availableUsers={mockUsers}
150+
onAssignUser={mockOnAssignUser}
151+
currentAssignments={[]}
152+
/>
153+
);
154+
155+
expect(screen.getByText('Unassigned')).toBeTruthy();
156+
});
157+
158+
it('displays assigned user name inline when user is assigned', () => {
159+
const assignedUser = mockUsers[0];
160+
147161
render(
148162
<RoleAssignmentItem
149163
role={mockRole}
164+
assignedUser={assignedUser}
150165
availableUsers={mockUsers}
151166
onAssignUser={mockOnAssignUser}
152167
currentAssignments={[]}
153168
/>
154169
);
155170

156-
expect(screen.getByText('Select user')).toBeTruthy();
171+
expect(screen.getByText('John Doe')).toBeTruthy();
157172
});
158173

159-
it('displays assigned user name when user is assigned', () => {
174+
it('displays user details inline (group, status, staffing)', () => {
160175
const assignedUser = mockUsers[0];
161176

162177
render(
@@ -169,12 +184,35 @@ describe('RoleAssignmentItem', () => {
169184
/>
170185
);
171186

172-
expect(screen.getAllByText('John Doe')).toHaveLength(2); // One in input, one in options
187+
expect(screen.getByText('Engine 1')).toBeTruthy();
188+
expect(screen.getByText('Available')).toBeTruthy();
189+
expect(screen.getByText('On Shift')).toBeTruthy();
173190
});
174191

175-
it('filters out users assigned to other roles', () => {
192+
it('opens selection modal on tap', () => {
193+
render(
194+
<RoleAssignmentItem
195+
role={mockRole}
196+
availableUsers={mockUsers}
197+
onAssignUser={mockOnAssignUser}
198+
currentAssignments={[]}
199+
/>
200+
);
201+
202+
// Modal should not be visible initially
203+
expect(screen.queryByTestId('user-selection-modal')).toBeNull();
204+
205+
// Tap the item
206+
fireEvent.press(screen.getByTestId('role-assignment-role1'));
207+
208+
// Modal should now be visible
209+
expect(screen.getByTestId('user-selection-modal')).toBeTruthy();
210+
expect(screen.getByTestId('modal-role-name')).toBeTruthy();
211+
});
212+
213+
it('shows all users including those assigned to other roles in the modal', () => {
176214
const currentAssignments = [
177-
{ roleId: 'role2', userId: 'user2' }, // user2 is assigned to a different role
215+
{ roleId: 'role2', userId: 'user2', roleName: 'Engineer' }, // user2 is assigned to a different role
178216
];
179217

180218
render(
@@ -186,19 +224,21 @@ describe('RoleAssignmentItem', () => {
186224
/>
187225
);
188226

189-
// Should show unassigned option
190-
expect(screen.getByTestId('select-item-')).toBeTruthy();
191-
// Should show user1 (not assigned to other roles)
192-
expect(screen.getByTestId('select-item-user1')).toBeTruthy();
193-
// Should NOT show user2 (assigned to other role)
194-
expect(screen.queryByTestId('select-item-user2')).toBeNull();
227+
// Open modal
228+
fireEvent.press(screen.getByTestId('role-assignment-role1'));
229+
230+
// Should show both users (no filtering)
231+
expect(screen.getByTestId('select-user-user1')).toBeTruthy();
232+
expect(screen.getByTestId('select-user-user2')).toBeTruthy();
233+
// Unassigned option should always be present
234+
expect(screen.getByTestId('select-unassigned')).toBeTruthy();
195235
});
196236

197-
it('includes user assigned to the same role in available users', () => {
237+
it('shows all users in the modal including those assigned to same and other roles', () => {
198238
const assignedUser = mockUsers[0];
199239
const currentAssignments = [
200-
{ roleId: 'role1', userId: 'user1' }, // user1 is assigned to this role
201-
{ roleId: 'role2', userId: 'user2' }, // user2 is assigned to a different role
240+
{ roleId: 'role1', userId: 'user1', roleName: 'Captain' }, // user1 is assigned to this role
241+
{ roleId: 'role2', userId: 'user2', roleName: 'Engineer' }, // user2 is assigned to a different role
202242
];
203243

204244
render(
@@ -211,15 +251,15 @@ describe('RoleAssignmentItem', () => {
211251
/>
212252
);
213253

214-
// Should show assigned user name
215-
expect(screen.getAllByText('John Doe')).toHaveLength(2); // One in input, one in options
216-
// Should show user1 in options (assigned to this role)
217-
expect(screen.getByTestId('select-item-user1')).toBeTruthy();
218-
// Should NOT show user2 (assigned to other role)
219-
expect(screen.queryByTestId('select-item-user2')).toBeNull();
254+
// Open modal
255+
fireEvent.press(screen.getByTestId('role-assignment-role1'));
256+
257+
// Should show both users (no filtering, all users visible)
258+
expect(screen.getByTestId('select-user-user1')).toBeTruthy();
259+
expect(screen.getByTestId('select-user-user2')).toBeTruthy();
220260
});
221261

222-
it('shows unassigned option', () => {
262+
it('calls onAssignUser when selecting a user in the modal', () => {
223263
render(
224264
<RoleAssignmentItem
225265
role={mockRole}
@@ -229,7 +269,48 @@ describe('RoleAssignmentItem', () => {
229269
/>
230270
);
231271

232-
expect(screen.getByTestId('select-item-')).toBeTruthy();
233-
expect(screen.getByText('Unassigned')).toBeTruthy();
272+
// Open modal
273+
fireEvent.press(screen.getByTestId('role-assignment-role1'));
274+
275+
// Select user1
276+
fireEvent.press(screen.getByTestId('select-user-user1'));
277+
278+
expect(mockOnAssignUser).toHaveBeenCalledWith('user1');
279+
});
280+
281+
it('calls onAssignUser with undefined when selecting Unassigned', () => {
282+
const assignedUser = mockUsers[0];
283+
284+
render(
285+
<RoleAssignmentItem
286+
role={mockRole}
287+
assignedUser={assignedUser}
288+
availableUsers={mockUsers}
289+
onAssignUser={mockOnAssignUser}
290+
currentAssignments={[]}
291+
/>
292+
);
293+
294+
// Open modal
295+
fireEvent.press(screen.getByTestId('role-assignment-role1'));
296+
297+
// Select Unassigned
298+
fireEvent.press(screen.getByTestId('select-unassigned'));
299+
300+
expect(mockOnAssignUser).toHaveBeenCalledWith(undefined);
301+
});
302+
303+
it('has proper accessibility role on the pressable', () => {
304+
render(
305+
<RoleAssignmentItem
306+
role={mockRole}
307+
availableUsers={mockUsers}
308+
onAssignUser={mockOnAssignUser}
309+
currentAssignments={[]}
310+
/>
311+
);
312+
313+
const pressable = screen.getByTestId('role-assignment-role1');
314+
expect(pressable.props.accessibilityRole).toBe('button');
234315
});
235316
});

0 commit comments

Comments
 (0)