@vtex/faststore-plugin-buyer-portal 1.1.40 → 1.1.41
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cypress/constants.ts +25 -0
- package/cypress/integration/users.test.ts +551 -0
- package/package.json +1 -1
- package/src/features/users/components/DeleteUserDrawer/DeleteUserDrawer.tsx +14 -11
- package/src/features/users/components/UserDropdownMenu/UserDropdownMenu.tsx +7 -0
- package/src/features/users/layouts/UserDetailsLayout/UserDetailsLayout.tsx +4 -1
- package/src/features/users/layouts/UserDetailsLayout/user-details-layout.scss +7 -0
- package/src/features/users/layouts/UsersLayout/UsersLayout.tsx +5 -3
- package/src/features/users/layouts/UsersLayout/users-layout.scss +12 -0
package/cypress/constants.ts
CHANGED
|
@@ -36,6 +36,31 @@ export const TEST_DATA = {
|
|
|
36
36
|
TEST_CONTRACT_NAME: "teste_fields",
|
|
37
37
|
TEST_CONTRACT_ID: "27e05218-d807-11ef-b37f-8dc7bb2ee7fa",
|
|
38
38
|
},
|
|
39
|
+
USERS: {
|
|
40
|
+
USER_ORG_UNIT_ADMIN: {
|
|
41
|
+
NAME: "User Org Unit Admin",
|
|
42
|
+
ROLE: "Organizational Unit Admin",
|
|
43
|
+
},
|
|
44
|
+
USER_ORG_UNIT_BUYER: {
|
|
45
|
+
NAME: "User Org Unit Buyer",
|
|
46
|
+
ROLE: "Buyer",
|
|
47
|
+
},
|
|
48
|
+
USER_NAME_FOR_ROLES: "User for Test Roles",
|
|
49
|
+
NEW_USER: {
|
|
50
|
+
NAME: `New User ${Date.now().toString(36)}-${Math.random()
|
|
51
|
+
.toString(36)
|
|
52
|
+
.substring(2, 5)}`,
|
|
53
|
+
EMAIL: `new_user+e2e_test${Date.now().toString(36)}-${Math.random()
|
|
54
|
+
.toString(36)
|
|
55
|
+
.substring(2, 5)}@vtex.com`,
|
|
56
|
+
ROLE: "Buyer",
|
|
57
|
+
},
|
|
58
|
+
USER_FROM_OTHER_ORG_UNIT: {
|
|
59
|
+
NAME: "User from other unit",
|
|
60
|
+
EMAIL: "rodrigo.tavares+devb2b@vtex.com",
|
|
61
|
+
ROLE: "Order Approver",
|
|
62
|
+
},
|
|
63
|
+
},
|
|
39
64
|
BUYING_POLICIES: {
|
|
40
65
|
NEW_POLICY: {
|
|
41
66
|
NAME: "Test Buying Policy E2E",
|
|
@@ -0,0 +1,551 @@
|
|
|
1
|
+
import { TEST_CONFIG, TEST_DATA } from "../constants";
|
|
2
|
+
|
|
3
|
+
const navigateToUsers = () => {
|
|
4
|
+
cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
|
|
5
|
+
cy.contains("Users").click();
|
|
6
|
+
|
|
7
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.RETRY_DELAY);
|
|
8
|
+
cy.get("[data-fs-users-section]").should("exist");
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const verifyUserState = (
|
|
12
|
+
userName: string,
|
|
13
|
+
shouldExist: boolean,
|
|
14
|
+
maxRetries = TEST_CONFIG.RETRY.DEFAULT_MAX_ATTEMPTS
|
|
15
|
+
) => {
|
|
16
|
+
const verifyAndRetry = (currentRetry = 0) => {
|
|
17
|
+
cy.get("[data-fs-users-section]").should("exist");
|
|
18
|
+
cy.get("[data-fs-bp-base-tabs-layout-content]").scrollTo("bottom");
|
|
19
|
+
|
|
20
|
+
cy.get("body").then(($body) => {
|
|
21
|
+
const exists = $body.text().includes(userName);
|
|
22
|
+
if (exists !== shouldExist && currentRetry < maxRetries) {
|
|
23
|
+
cy.log(`Retry attempt ${currentRetry + 1} of ${maxRetries}`);
|
|
24
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.RETRY_DELAY);
|
|
25
|
+
cy.reload();
|
|
26
|
+
verifyAndRetry(currentRetry + 1);
|
|
27
|
+
} else if (exists !== shouldExist) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
`Failed to verify user state after ${maxRetries} retries`
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
verifyAndRetry();
|
|
36
|
+
|
|
37
|
+
if (shouldExist) {
|
|
38
|
+
cy.contains(userName).should("be.visible");
|
|
39
|
+
} else {
|
|
40
|
+
cy.contains(userName).should("not.exist");
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const fillUserForm = (
|
|
45
|
+
userData: typeof TEST_DATA.USERS.NEW_USER,
|
|
46
|
+
shouldUpdateEmailAndRole = true
|
|
47
|
+
) => {
|
|
48
|
+
// Fill the user name
|
|
49
|
+
cy.get("label")
|
|
50
|
+
.contains("Full Name")
|
|
51
|
+
.parent()
|
|
52
|
+
.find("[data-fs-bp-input-text-input]")
|
|
53
|
+
.clear()
|
|
54
|
+
.type(userData.NAME);
|
|
55
|
+
|
|
56
|
+
if (shouldUpdateEmailAndRole) {
|
|
57
|
+
// Fill the user email
|
|
58
|
+
cy.get("label")
|
|
59
|
+
.contains("Email")
|
|
60
|
+
.parent()
|
|
61
|
+
.find("[data-fs-bp-input-text-input]")
|
|
62
|
+
.clear()
|
|
63
|
+
.type(userData.EMAIL);
|
|
64
|
+
|
|
65
|
+
cy.wait(500);
|
|
66
|
+
// Fill the user role
|
|
67
|
+
cy.get('[data-fs-bp-create-user-roles="true"]')
|
|
68
|
+
.contains(userData.ROLE)
|
|
69
|
+
.parents('[data-fs-bp-create-user-role-wrapper="true"]')
|
|
70
|
+
.within(() => {
|
|
71
|
+
cy.get('[type="checkbox"]').check();
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const openUserDropdown = (userName: string) => {
|
|
77
|
+
cy.get("[data-fs-bp-table-row]")
|
|
78
|
+
.contains(userName)
|
|
79
|
+
.parents("[data-fs-bp-table-row]")
|
|
80
|
+
.within(() => {
|
|
81
|
+
cy.get("[data-fs-bp-basic-dropdown-menu-trigger]").click();
|
|
82
|
+
});
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const confirmDeletion = (userName: string) => {
|
|
86
|
+
const tryDelete = (attempt = 0) => {
|
|
87
|
+
cy.contains("Delete user").should("be.visible");
|
|
88
|
+
cy.contains("Confirm by typing the user name below:").should("be.visible");
|
|
89
|
+
|
|
90
|
+
cy.get("label")
|
|
91
|
+
.contains("User name")
|
|
92
|
+
.parent()
|
|
93
|
+
.find("[data-fs-bp-input-text-input]")
|
|
94
|
+
.clear()
|
|
95
|
+
.type(userName);
|
|
96
|
+
|
|
97
|
+
cy.contains("button", "Delete").click();
|
|
98
|
+
|
|
99
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
100
|
+
|
|
101
|
+
cy.get("body").then(($body) => {
|
|
102
|
+
const errorOccurred = $body
|
|
103
|
+
.text()
|
|
104
|
+
.includes("An error occurred while deleting the user");
|
|
105
|
+
if (errorOccurred && attempt < TEST_CONFIG.RETRY.DEFAULT_MAX_ATTEMPTS) {
|
|
106
|
+
cy.log(`Retrying delete attempt ${attempt + 1}`);
|
|
107
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.RETRY_DELAY);
|
|
108
|
+
tryDelete(attempt + 1);
|
|
109
|
+
} else if (errorOccurred) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Failed to delete user after ${TEST_CONFIG.RETRY.DEFAULT_MAX_ATTEMPTS} retries due to error`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
tryDelete();
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
describe("Users", () => {
|
|
121
|
+
beforeEach(() => {
|
|
122
|
+
cy.login();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it("Should load and display users information correctly (User List)", () => {
|
|
126
|
+
navigateToUsers();
|
|
127
|
+
|
|
128
|
+
// Check that the table columns are present
|
|
129
|
+
cy.contains("Name").should("be.visible");
|
|
130
|
+
cy.contains("Role").should("be.visible");
|
|
131
|
+
|
|
132
|
+
// Check that the expected users are present in the table
|
|
133
|
+
cy.contains(TEST_DATA.USERS.USER_ORG_UNIT_ADMIN.NAME)
|
|
134
|
+
.scrollIntoView()
|
|
135
|
+
.should("be.visible")
|
|
136
|
+
.parent()
|
|
137
|
+
.parent()
|
|
138
|
+
.within(() => {
|
|
139
|
+
cy.contains(TEST_DATA.USERS.USER_ORG_UNIT_ADMIN.ROLE)
|
|
140
|
+
.scrollIntoView()
|
|
141
|
+
.should("be.visible");
|
|
142
|
+
cy.get("[data-fs-bp-basic-dropdown-menu-trigger]")
|
|
143
|
+
.should("be.visible")
|
|
144
|
+
.click();
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Check if the menu options are visible
|
|
148
|
+
cy.contains("Open").should("be.visible");
|
|
149
|
+
cy.contains("Edit details").should("be.visible");
|
|
150
|
+
cy.contains("Change organizational unit").should("be.visible");
|
|
151
|
+
cy.contains("Delete").should("be.visible");
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("Should load user details page successfully", () => {
|
|
155
|
+
navigateToUsers();
|
|
156
|
+
|
|
157
|
+
// Click on User Test 1 to go to details page
|
|
158
|
+
cy.contains(TEST_DATA.USERS.USER_ORG_UNIT_ADMIN.NAME)
|
|
159
|
+
.scrollIntoView()
|
|
160
|
+
.should("be.visible")
|
|
161
|
+
.parents("[data-fs-bp-table-row]")
|
|
162
|
+
.click();
|
|
163
|
+
|
|
164
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
165
|
+
|
|
166
|
+
// Check that the user details section is visible
|
|
167
|
+
cy.get("[data-fs-user-details-section]").should("exist");
|
|
168
|
+
|
|
169
|
+
// Check that the user's name is displayed in the details
|
|
170
|
+
cy.get("[data-fs-user-details-row-value]")
|
|
171
|
+
.contains(TEST_DATA.USERS.USER_ORG_UNIT_ADMIN.NAME)
|
|
172
|
+
.should("be.visible");
|
|
173
|
+
|
|
174
|
+
cy.get("[data-fs-user-details-row-value]")
|
|
175
|
+
.contains(TEST_DATA.USERS.USER_ORG_UNIT_ADMIN.ROLE)
|
|
176
|
+
.should("be.visible");
|
|
177
|
+
|
|
178
|
+
// Verify labels and buttons are present
|
|
179
|
+
cy.contains("Name").should("be.visible");
|
|
180
|
+
cy.contains("Email").should("be.visible");
|
|
181
|
+
cy.contains("Role").should("be.visible");
|
|
182
|
+
cy.contains("Organizational Unit").should("be.visible");
|
|
183
|
+
cy.contains("Edit").should("be.visible");
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it("Should add a new user", () => {
|
|
187
|
+
navigateToUsers();
|
|
188
|
+
|
|
189
|
+
// === CREATE USER ===
|
|
190
|
+
cy.log("=== Creating new user ===");
|
|
191
|
+
|
|
192
|
+
// Click to add a new user
|
|
193
|
+
cy.get("[data-fs-header-inside-button]").should("be.visible").click();
|
|
194
|
+
|
|
195
|
+
// Fill user form
|
|
196
|
+
fillUserForm(TEST_DATA.USERS.NEW_USER);
|
|
197
|
+
|
|
198
|
+
// Submit form
|
|
199
|
+
cy.contains("button", "Add").click();
|
|
200
|
+
|
|
201
|
+
// Verify success message
|
|
202
|
+
cy.contains("User successfully added").should("be.visible");
|
|
203
|
+
|
|
204
|
+
// Reload and verify user was created
|
|
205
|
+
verifyUserState(TEST_DATA.USERS.NEW_USER.NAME, true);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
it("Should view and edit the newly created user", () => {
|
|
209
|
+
navigateToUsers();
|
|
210
|
+
|
|
211
|
+
// === VIEW USER DETAILS ===
|
|
212
|
+
cy.log("=== Viewing user details ===");
|
|
213
|
+
cy.get("[data-fs-bp-base-tabs-layout-content]").scrollTo("bottom");
|
|
214
|
+
|
|
215
|
+
cy.contains(TEST_DATA.USERS.NEW_USER.NAME)
|
|
216
|
+
.should("be.visible")
|
|
217
|
+
.parents("[data-fs-bp-table-row]")
|
|
218
|
+
.click();
|
|
219
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
220
|
+
|
|
221
|
+
// Verify user details page
|
|
222
|
+
cy.get("[data-fs-user-details-section]").should("exist");
|
|
223
|
+
cy.get("[data-fs-user-details-row-value]")
|
|
224
|
+
.contains(TEST_DATA.USERS.NEW_USER.ROLE)
|
|
225
|
+
.should("be.visible");
|
|
226
|
+
|
|
227
|
+
// Go back to users list
|
|
228
|
+
navigateToUsers();
|
|
229
|
+
|
|
230
|
+
// === EDIT USER ===
|
|
231
|
+
cy.log("=== Editing user ===");
|
|
232
|
+
|
|
233
|
+
openUserDropdown(TEST_DATA.USERS.NEW_USER.NAME);
|
|
234
|
+
cy.contains("Edit details").click();
|
|
235
|
+
|
|
236
|
+
// Verify edit drawer is open
|
|
237
|
+
cy.contains("Edit user details").should("be.visible");
|
|
238
|
+
|
|
239
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
240
|
+
|
|
241
|
+
// Update user information
|
|
242
|
+
const updatedUserData = {
|
|
243
|
+
NAME: `${TEST_DATA.USERS.NEW_USER.NAME} Updated`,
|
|
244
|
+
EMAIL: TEST_DATA.USERS.NEW_USER.EMAIL,
|
|
245
|
+
ROLE: TEST_DATA.USERS.NEW_USER.ROLE,
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
fillUserForm(updatedUserData, false);
|
|
249
|
+
|
|
250
|
+
// Save changes
|
|
251
|
+
cy.contains("button", "Save").click();
|
|
252
|
+
|
|
253
|
+
// Verify update
|
|
254
|
+
verifyUserState(updatedUserData.NAME, true);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("Should delete the updated user", () => {
|
|
258
|
+
navigateToUsers();
|
|
259
|
+
|
|
260
|
+
// === DELETE USER ===
|
|
261
|
+
cy.log("=== Deleting user ===");
|
|
262
|
+
cy.get("[data-fs-bp-base-tabs-layout-content]").scrollTo("bottom");
|
|
263
|
+
|
|
264
|
+
const updatedUserData = {
|
|
265
|
+
NAME: `${TEST_DATA.USERS.NEW_USER.NAME} Updated`,
|
|
266
|
+
EMAIL: TEST_DATA.USERS.NEW_USER.EMAIL,
|
|
267
|
+
ROLE: TEST_DATA.USERS.NEW_USER.ROLE,
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
openUserDropdown(updatedUserData.NAME);
|
|
271
|
+
cy.contains("Delete").click();
|
|
272
|
+
|
|
273
|
+
cy.get(`a:contains("${updatedUserData.NAME}")`)
|
|
274
|
+
.invoke("text")
|
|
275
|
+
.then((userNameText) => {
|
|
276
|
+
confirmDeletion(userNameText);
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Verify if the user was deleted
|
|
280
|
+
verifyUserState(updatedUserData.NAME, false);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it("Should not allow adding a user already in another org unit", () => {
|
|
284
|
+
navigateToUsers();
|
|
285
|
+
|
|
286
|
+
// Click to add a new user
|
|
287
|
+
cy.get("[data-fs-header-inside-button]").should("be.visible").click();
|
|
288
|
+
|
|
289
|
+
// Fill form with existing user data - Org Unit for E2E Tests Child 3
|
|
290
|
+
fillUserForm(TEST_DATA.USERS.USER_FROM_OTHER_ORG_UNIT);
|
|
291
|
+
|
|
292
|
+
// Submit form
|
|
293
|
+
cy.contains("button", "Add").click();
|
|
294
|
+
|
|
295
|
+
// Assert error message is shown
|
|
296
|
+
cy.contains("User cannot be added").should("be.visible");
|
|
297
|
+
cy.contains("This user is assigned to another organizational unit.").should(
|
|
298
|
+
"be.visible"
|
|
299
|
+
);
|
|
300
|
+
cy.contains(
|
|
301
|
+
"Users can only belong to one organizational unit at a time."
|
|
302
|
+
).should("be.visible");
|
|
303
|
+
|
|
304
|
+
// Close error dialog
|
|
305
|
+
cy.contains("Ok").click();
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("Should create a new user add, edit and remove roles", () => {
|
|
309
|
+
// Visit the buyer portal home page
|
|
310
|
+
cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
|
|
311
|
+
|
|
312
|
+
// Click on the "Users" link in the sidebar menu
|
|
313
|
+
cy.get("a[data-fs-vertical-nav-menu-link]").contains("Users").click();
|
|
314
|
+
|
|
315
|
+
// Check that the Users page is rendered
|
|
316
|
+
cy.get('h1[data-fs-bp-header-inside-title="true"]').should(
|
|
317
|
+
"contain",
|
|
318
|
+
"Users"
|
|
319
|
+
);
|
|
320
|
+
|
|
321
|
+
// Check that at least one user row exists
|
|
322
|
+
cy.get('a[data-fs-bp-table-row-link="true"]').should("exist");
|
|
323
|
+
|
|
324
|
+
// Click the add user button at the top
|
|
325
|
+
cy.get('button[data-fs-header-inside-button="true"]').click();
|
|
326
|
+
|
|
327
|
+
// Check that the user creation modal is open
|
|
328
|
+
cy.get(
|
|
329
|
+
'div[data-fs-modal-content="true"][data-fs-bp-create-user-drawer="true"]'
|
|
330
|
+
).should("be.visible");
|
|
331
|
+
cy.get('h2[data-fs-bp-basic-drawer-heading-title="true"]').should(
|
|
332
|
+
"contain",
|
|
333
|
+
"Add User"
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
// Fill out the user creation form
|
|
337
|
+
cy.get('input[data-fs-bp-input-text-input="true"]')
|
|
338
|
+
.first()
|
|
339
|
+
.type(TEST_DATA.USERS.USER_NAME_FOR_ROLES); // Full Name
|
|
340
|
+
cy.get('input[data-fs-bp-input-text-input="true"]')
|
|
341
|
+
.eq(1)
|
|
342
|
+
.type("userfortestroles@vtex.com"); // Email
|
|
343
|
+
cy.get('input[type="checkbox"][value="Buyer"]').check(); // Assign Buyer role
|
|
344
|
+
|
|
345
|
+
// Click the "Add" button to submit the form
|
|
346
|
+
cy.get(
|
|
347
|
+
'button[data-fs-bp-basic-drawer-button="true"][data-fs-bp-basic-drawer-button-variant="confirm"]'
|
|
348
|
+
)
|
|
349
|
+
.should("not.be.disabled")
|
|
350
|
+
.click();
|
|
351
|
+
|
|
352
|
+
cy.wait(3000);
|
|
353
|
+
|
|
354
|
+
// Retry logic to check if user appears in the list
|
|
355
|
+
const checkUserExists = (attempt = 1) => {
|
|
356
|
+
cy.reload();
|
|
357
|
+
cy.get("body").then(($body) => {
|
|
358
|
+
if ($body.text().includes(TEST_DATA.USERS.USER_NAME_FOR_ROLES)) {
|
|
359
|
+
// User found, verify it exists
|
|
360
|
+
cy.contains("td", TEST_DATA.USERS.USER_NAME_FOR_ROLES).should(
|
|
361
|
+
"exist"
|
|
362
|
+
);
|
|
363
|
+
} else if (attempt < 3) {
|
|
364
|
+
cy.log(`User not found, retry attempt ${attempt + 1} of 3`);
|
|
365
|
+
cy.wait(2000);
|
|
366
|
+
checkUserExists(attempt + 1);
|
|
367
|
+
} else {
|
|
368
|
+
throw new Error(
|
|
369
|
+
`User ${TEST_DATA.USERS.USER_NAME_FOR_ROLES} not found after 3 attempts`
|
|
370
|
+
);
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
checkUserExists();
|
|
376
|
+
|
|
377
|
+
// Check that the 'Buyer' badge is present in the same row
|
|
378
|
+
cy.contains(
|
|
379
|
+
'tr[data-fs-bp-table-row="true"]',
|
|
380
|
+
TEST_DATA.USERS.USER_NAME_FOR_ROLES
|
|
381
|
+
)
|
|
382
|
+
.find('span[data-fs-tag="true"]')
|
|
383
|
+
.should("contain.text", "Buyer");
|
|
384
|
+
|
|
385
|
+
// --- Edit user roles: add Organizational Unit Admin ---
|
|
386
|
+
cy.contains(
|
|
387
|
+
'tr[data-fs-bp-table-row="true"]',
|
|
388
|
+
TEST_DATA.USERS.USER_NAME_FOR_ROLES
|
|
389
|
+
)
|
|
390
|
+
.find('button[data-fs-bp-basic-dropdown-menu-trigger="true"]')
|
|
391
|
+
.click();
|
|
392
|
+
cy.get(
|
|
393
|
+
'div[data-fs-dropdown-menu="true"] button[data-fs-dropdown-item="true"]'
|
|
394
|
+
)
|
|
395
|
+
.contains("Edit details")
|
|
396
|
+
.click();
|
|
397
|
+
cy.get(
|
|
398
|
+
'div[data-fs-modal-content="true"][data-fs-bp-update-user-drawer="true"]'
|
|
399
|
+
).should("be.visible");
|
|
400
|
+
cy.get('h2[data-fs-bp-basic-drawer-heading-title="true"]').should(
|
|
401
|
+
"contain",
|
|
402
|
+
"Edit user details"
|
|
403
|
+
);
|
|
404
|
+
cy.get("input#role-organizational-unit\\ admin").check({ force: true });
|
|
405
|
+
cy.get(
|
|
406
|
+
'button[data-fs-bp-basic-drawer-button="true"][data-fs-bp-basic-drawer-button-variant="confirm"]'
|
|
407
|
+
)
|
|
408
|
+
.should("not.be.disabled")
|
|
409
|
+
.click();
|
|
410
|
+
|
|
411
|
+
cy.wait(1000);
|
|
412
|
+
cy.reload();
|
|
413
|
+
cy.contains(
|
|
414
|
+
'tr[data-fs-bp-table-row="true"]',
|
|
415
|
+
TEST_DATA.USERS.USER_NAME_FOR_ROLES
|
|
416
|
+
)
|
|
417
|
+
.find('span[data-fs-tag="true"]')
|
|
418
|
+
.should("contain.text", "Organizational Unit Admin");
|
|
419
|
+
|
|
420
|
+
// --- Edit user roles: remove Organizational Unit Admin ---
|
|
421
|
+
cy.contains(
|
|
422
|
+
'tr[data-fs-bp-table-row="true"]',
|
|
423
|
+
TEST_DATA.USERS.USER_NAME_FOR_ROLES
|
|
424
|
+
)
|
|
425
|
+
.find('button[data-fs-bp-basic-dropdown-menu-trigger="true"]')
|
|
426
|
+
.click();
|
|
427
|
+
cy.get(
|
|
428
|
+
'div[data-fs-dropdown-menu="true"] button[data-fs-dropdown-item="true"]'
|
|
429
|
+
)
|
|
430
|
+
.contains("Edit details")
|
|
431
|
+
.click();
|
|
432
|
+
cy.get(
|
|
433
|
+
'div[data-fs-modal-content="true"][data-fs-bp-update-user-drawer="true"]'
|
|
434
|
+
).should("be.visible");
|
|
435
|
+
cy.get("input#role-organizational-unit\\ admin").uncheck({ force: true });
|
|
436
|
+
cy.get(
|
|
437
|
+
'button[data-fs-bp-basic-drawer-button="true"][data-fs-bp-basic-drawer-button-variant="confirm"]'
|
|
438
|
+
)
|
|
439
|
+
.should("not.be.disabled")
|
|
440
|
+
.click();
|
|
441
|
+
|
|
442
|
+
cy.wait(1000);
|
|
443
|
+
cy.reload();
|
|
444
|
+
cy.contains(
|
|
445
|
+
'tr[data-fs-bp-table-row="true"]',
|
|
446
|
+
TEST_DATA.USERS.USER_NAME_FOR_ROLES
|
|
447
|
+
)
|
|
448
|
+
.find('span[data-fs-tag="true"]')
|
|
449
|
+
.should("not.contain.text", "Organizational Unit Admin");
|
|
450
|
+
|
|
451
|
+
// Open the dropdown menu (three dots) for the created user
|
|
452
|
+
cy.contains(
|
|
453
|
+
'tr[data-fs-bp-table-row="true"]',
|
|
454
|
+
TEST_DATA.USERS.USER_NAME_FOR_ROLES
|
|
455
|
+
)
|
|
456
|
+
.find('button[data-fs-bp-basic-dropdown-menu-trigger="true"]')
|
|
457
|
+
.click();
|
|
458
|
+
|
|
459
|
+
// Click the "Delete" option in the dropdown menu
|
|
460
|
+
cy.get(
|
|
461
|
+
'div[data-fs-dropdown-menu="true"] button[data-fs-dropdown-item="true"]'
|
|
462
|
+
)
|
|
463
|
+
.contains("Delete")
|
|
464
|
+
.click();
|
|
465
|
+
|
|
466
|
+
// Check that the delete user modal is open
|
|
467
|
+
cy.get(
|
|
468
|
+
'div[data-fs-modal-content="true"][data-fs-bp-delete-user-drawer="true"]'
|
|
469
|
+
).should("be.visible");
|
|
470
|
+
cy.get('h2[data-fs-bp-basic-drawer-heading-title="true"]').should(
|
|
471
|
+
"contain",
|
|
472
|
+
"Delete user"
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
// Type the user name to confirm deletion
|
|
476
|
+
cy.get('input[data-fs-bp-input-text-input="true"]').type(
|
|
477
|
+
TEST_DATA.USERS.USER_NAME_FOR_ROLES
|
|
478
|
+
);
|
|
479
|
+
|
|
480
|
+
cy.wait(1000);
|
|
481
|
+
|
|
482
|
+
// Click the "Delete" button to confirm
|
|
483
|
+
cy.get(
|
|
484
|
+
'button[data-fs-bp-basic-drawer-button="true"][data-fs-bp-basic-drawer-button-variant="danger"]'
|
|
485
|
+
)
|
|
486
|
+
.should("not.be.disabled")
|
|
487
|
+
.click();
|
|
488
|
+
|
|
489
|
+
cy.wait(1000);
|
|
490
|
+
// Reload the users page
|
|
491
|
+
cy.reload();
|
|
492
|
+
|
|
493
|
+
// Check that the user no longer exists in the list
|
|
494
|
+
cy.contains("td", TEST_DATA.USERS.USER_NAME_FOR_ROLES).should("not.exist");
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it("Should allow changing the organizational unit from the list dropdown menu", () => {
|
|
498
|
+
navigateToUsers();
|
|
499
|
+
|
|
500
|
+
// Open dropdown for the test user
|
|
501
|
+
cy.get("[data-fs-header-inside-button]").should("be.visible").click();
|
|
502
|
+
fillUserForm(TEST_DATA.USERS.NEW_USER);
|
|
503
|
+
|
|
504
|
+
cy.contains("button", "Add").click();
|
|
505
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
506
|
+
|
|
507
|
+
verifyUserState(TEST_DATA.USERS.NEW_USER.NAME, true);
|
|
508
|
+
|
|
509
|
+
openUserDropdown(TEST_DATA.USERS.NEW_USER.NAME);
|
|
510
|
+
cy.contains("Change organizational unit").click();
|
|
511
|
+
|
|
512
|
+
// Verify change org unit modal/drawer is open
|
|
513
|
+
cy.contains("Change user organizational unit").should("be.visible");
|
|
514
|
+
cy.contains(
|
|
515
|
+
`Users can belong to only one Organizational Unit. Search and select a new unit for ${TEST_DATA.USERS.NEW_USER.NAME}`
|
|
516
|
+
).should("be.visible");
|
|
517
|
+
|
|
518
|
+
cy.get('[data-fs-bp-input-text="true"]')
|
|
519
|
+
.should("be.visible")
|
|
520
|
+
.type(TEST_DATA.ORG_UNITS.ROOT, { delay: 1000 });
|
|
521
|
+
|
|
522
|
+
cy.wait(0);
|
|
523
|
+
|
|
524
|
+
// Wait for and click on the option in the dropdown
|
|
525
|
+
cy.get('[data-fs-bp-autocomplete-dropdown-menu="true"]')
|
|
526
|
+
.contains(TEST_DATA.ORG_UNITS.CHILD_2, { timeout: 10000 })
|
|
527
|
+
.should("exist")
|
|
528
|
+
.click();
|
|
529
|
+
|
|
530
|
+
// Confirm change
|
|
531
|
+
cy.contains("button", "Save").click();
|
|
532
|
+
|
|
533
|
+
// Reload and verify user is now in the new org unit
|
|
534
|
+
cy.reload();
|
|
535
|
+
|
|
536
|
+
// Check if the user is not on the same org unit
|
|
537
|
+
verifyUserState(TEST_DATA.USERS.NEW_USER.NAME, false);
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it.skip("Should not allow deleting the last admin user", () => {
|
|
541
|
+
// TODO: Implement this test
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
it.skip("Should not allow moving the last admin user", () => {
|
|
545
|
+
// TODO: Implement this test
|
|
546
|
+
});
|
|
547
|
+
|
|
548
|
+
it.skip("Should not allow removing the role from the last admin user", () => {
|
|
549
|
+
// TODO: Implement this test
|
|
550
|
+
});
|
|
551
|
+
});
|
package/package.json
CHANGED
|
@@ -31,7 +31,7 @@ export const DeleteUserDrawer = ({
|
|
|
31
31
|
|
|
32
32
|
const handleRemoveSuccess = () => {
|
|
33
33
|
pushToast({
|
|
34
|
-
message: "User successfully
|
|
34
|
+
message: "User successfully deleted",
|
|
35
35
|
status: "INFO",
|
|
36
36
|
});
|
|
37
37
|
onDelete?.();
|
|
@@ -49,15 +49,16 @@ export const DeleteUserDrawer = ({
|
|
|
49
49
|
});
|
|
50
50
|
};
|
|
51
51
|
|
|
52
|
-
const { removeUserFromOrgUnit } =
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
52
|
+
const { removeUserFromOrgUnit, isRemoveUserFromOrgUnitLoading } =
|
|
53
|
+
useRemoveUserFromOrgUnit({
|
|
54
|
+
onSuccess: handleRemoveSuccess,
|
|
55
|
+
onError: () => {
|
|
56
|
+
pushToast({
|
|
57
|
+
message: "An error occurred while deleting the user",
|
|
58
|
+
status: "ERROR",
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
});
|
|
61
62
|
|
|
62
63
|
const isDeleteButtonEnabled = userNameConfirmation === user.name;
|
|
63
64
|
|
|
@@ -72,6 +73,7 @@ export const DeleteUserDrawer = ({
|
|
|
72
73
|
orgUnitId: orgUnit?.id || "",
|
|
73
74
|
userId: user.id,
|
|
74
75
|
})}
|
|
76
|
+
data-fs-bp-delete-user-drawer-user-name
|
|
75
77
|
>
|
|
76
78
|
{user.name}
|
|
77
79
|
</a>
|
|
@@ -100,7 +102,8 @@ export const DeleteUserDrawer = ({
|
|
|
100
102
|
Cancel
|
|
101
103
|
</BasicDrawer.Button>
|
|
102
104
|
<BasicDrawer.Button
|
|
103
|
-
variant="
|
|
105
|
+
variant="danger"
|
|
106
|
+
isLoading={isRemoveUserFromOrgUnitLoading}
|
|
104
107
|
onClick={handleDeleteClick}
|
|
105
108
|
disabled={!isDeleteButtonEnabled}
|
|
106
109
|
>
|
|
@@ -43,6 +43,10 @@ export const UserDropdownMenu = ({ user }: UserDropdownMenuProps) => {
|
|
|
43
43
|
...updateUserDrawerProps
|
|
44
44
|
} = useDrawerProps();
|
|
45
45
|
|
|
46
|
+
const handleUpdate = () => {
|
|
47
|
+
route.reload();
|
|
48
|
+
};
|
|
49
|
+
|
|
46
50
|
return (
|
|
47
51
|
<>
|
|
48
52
|
<BasicDropdownMenu>
|
|
@@ -80,6 +84,7 @@ export const UserDropdownMenu = ({ user }: UserDropdownMenuProps) => {
|
|
|
80
84
|
<DeleteUserDrawer
|
|
81
85
|
user={user}
|
|
82
86
|
isOpen={isDeleteUserDrawerOpen}
|
|
87
|
+
onDelete={handleUpdate}
|
|
83
88
|
{...deleteUserDrawerProps}
|
|
84
89
|
/>
|
|
85
90
|
)}
|
|
@@ -87,6 +92,7 @@ export const UserDropdownMenu = ({ user }: UserDropdownMenuProps) => {
|
|
|
87
92
|
<ReassignOrgUnitDrawer
|
|
88
93
|
user={user}
|
|
89
94
|
isOpen={isReassignOrgUnitDrawerOpen}
|
|
95
|
+
onReassing={handleUpdate}
|
|
90
96
|
{...reassignOrgUnitDrawerProps}
|
|
91
97
|
/>
|
|
92
98
|
)}
|
|
@@ -96,6 +102,7 @@ export const UserDropdownMenu = ({ user }: UserDropdownMenuProps) => {
|
|
|
96
102
|
userId={user.id}
|
|
97
103
|
orgUnitId={currentOrgUnit?.id ?? ""}
|
|
98
104
|
isOpen={isUpdateUserDrawerOpen}
|
|
105
|
+
onCreate={handleUpdate}
|
|
99
106
|
{...updateUserDrawerProps}
|
|
100
107
|
/>
|
|
101
108
|
)}
|
|
@@ -89,7 +89,10 @@ export const UserDetailsLayout = ({
|
|
|
89
89
|
|
|
90
90
|
<div data-fs-user-details-row>
|
|
91
91
|
<span data-fs-user-details-row-label>Role</span>
|
|
92
|
-
<span
|
|
92
|
+
<span
|
|
93
|
+
data-fs-user-details-row-value
|
|
94
|
+
data-fs-user-details-row-value-roles
|
|
95
|
+
>
|
|
93
96
|
{user?.roles?.map((role) => (
|
|
94
97
|
<Tag key={role} data-fs-user-details-row-value-tag>
|
|
95
98
|
{role}
|
|
@@ -130,6 +130,7 @@
|
|
|
130
130
|
color: #707070;
|
|
131
131
|
font-size: var(--fs-text-size-1);
|
|
132
132
|
font-weight: 500;
|
|
133
|
+
min-width: 12.5rem;
|
|
133
134
|
}
|
|
134
135
|
|
|
135
136
|
[data-fs-user-details-row-value] {
|
|
@@ -138,6 +139,12 @@
|
|
|
138
139
|
font-weight: 500;
|
|
139
140
|
}
|
|
140
141
|
|
|
142
|
+
[data-fs-user-details-row-value-roles] {
|
|
143
|
+
display: flex;
|
|
144
|
+
flex-wrap: wrap;
|
|
145
|
+
gap: var(--fs-spacing-1);
|
|
146
|
+
}
|
|
147
|
+
|
|
141
148
|
[data-fs-user-details-change] {
|
|
142
149
|
margin-left: auto;
|
|
143
150
|
font-weight: 600;
|
|
@@ -103,9 +103,11 @@ export const UsersLayout = ({ data: { users, search } }: UsersLayoutProps) => {
|
|
|
103
103
|
dropdownMenu={<UserDropdownMenu user={user} />}
|
|
104
104
|
>
|
|
105
105
|
<Table.Cell>
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
<div data-fs-bp-table-user-roles>
|
|
107
|
+
{user.roles?.map((role) => (
|
|
108
|
+
<Tag key={role}>{role}</Tag>
|
|
109
|
+
))}
|
|
110
|
+
</div>
|
|
109
111
|
</Table.Cell>
|
|
110
112
|
</Table.Row>
|
|
111
113
|
))}
|
|
@@ -158,4 +158,16 @@
|
|
|
158
158
|
}
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
|
+
|
|
162
|
+
[data-fs-bp-table] {
|
|
163
|
+
margin-bottom: var(--fs-spacing-8);
|
|
164
|
+
|
|
165
|
+
[data-fs-bp-table-row] {
|
|
166
|
+
[data-fs-bp-table-user-roles] {
|
|
167
|
+
display: flex;
|
|
168
|
+
flex-wrap: wrap;
|
|
169
|
+
gap: var(--fs-spacing-1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
161
173
|
}
|