@vtex/faststore-plugin-buyer-portal 1.1.39 → 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 +35 -0
- package/cypress/integration/addresses.test.ts +275 -0
- package/cypress/integration/users.test.ts +551 -0
- package/cypress/shared.ts +3 -0
- package/package.json +1 -1
- package/src/features/addresses/components/AddressDropdownMenu/AddressDropdownMenu.tsx +13 -14
- package/src/features/addresses/components/AddressForm/AddressForm.tsx +14 -5
- package/src/features/addresses/components/DeleteAddressDrawer/DeleteAddressDrawer.tsx +1 -0
- package/src/features/addresses/services/create-new-address.service.ts +14 -1
- package/src/features/addresses/services/search-address-by-name.service.ts +1 -1
- package/src/features/collections/types/index.ts +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/src/pages/collections.tsx +1 -1
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",
|
|
@@ -44,4 +69,14 @@ export const TEST_DATA = {
|
|
|
44
69
|
ACTION_TYPE: "Bypass all buying policies",
|
|
45
70
|
},
|
|
46
71
|
},
|
|
72
|
+
ADDRESS: {
|
|
73
|
+
EXISTING_ADDRESS: "Existing Test Address",
|
|
74
|
+
NEW_ADDRESS_NAME: "VTEX RJ",
|
|
75
|
+
NEW_ADDRESS_STREET: "Praia de Botafogo, 300",
|
|
76
|
+
EDIT_ADDRESS_STREET: "Praia de Botafogo, 400",
|
|
77
|
+
NEW_ADDRESS_COMPLEMENT: "Segundo Andar",
|
|
78
|
+
NEW_ADDRESS_CITY: "Rio de Janeiro",
|
|
79
|
+
NEW_ADDRESS_STATE: "RJ",
|
|
80
|
+
NEW_ADDRESS_POSTAL_CODE: "22250-905",
|
|
81
|
+
},
|
|
47
82
|
};
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import { TEST_CONFIG, TEST_DATA } from "../constants";
|
|
2
|
+
import { clickConfirmButton } from "../shared";
|
|
3
|
+
|
|
4
|
+
function navigateToAddressByName(addressName: string) {
|
|
5
|
+
cy.get("[data-fs-addresses-table]").within(() => {
|
|
6
|
+
cy.contains(addressName).should("be.visible").parent().parent().click();
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
cy.wait(8000);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function goToAddressPage() {
|
|
13
|
+
// Navigate to Addresses page
|
|
14
|
+
cy.contains("Addresses").click();
|
|
15
|
+
|
|
16
|
+
// Address Page is loading slowly, custom wait untill we get the request optimized
|
|
17
|
+
cy.wait(10000);
|
|
18
|
+
|
|
19
|
+
// Check to see if the address section loaded
|
|
20
|
+
cy.get("[data-fs-addresses-section]", { timeout: 100000 }).should("exist");
|
|
21
|
+
|
|
22
|
+
// Check to see if contract details loaded correctly
|
|
23
|
+
cy.get("[data-fs-bp-header-inside]").should("exist");
|
|
24
|
+
cy.contains(TEST_DATA.PROFILE.TEST_CONTRACT_NAME).should("be.visible");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function openCreateAddressDrawer() {
|
|
28
|
+
cy.get("[data-fs-bp-header-inside]").within(() => {
|
|
29
|
+
cy.get("button").eq(1).click(); // Dropdown triggers
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
cy.get("[data-fs-bp-create-address-drawer]").should("be.visible");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function openActionsDropdown(addressName: string) {
|
|
36
|
+
cy.get("[data-fs-addresses-table]")
|
|
37
|
+
.contains(addressName)
|
|
38
|
+
.parent()
|
|
39
|
+
.parent()
|
|
40
|
+
.find("[data-fs-bp-basic-dropdown-menu-trigger]")
|
|
41
|
+
.click();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe("Address Page", () => {
|
|
45
|
+
beforeEach(() => {
|
|
46
|
+
cy.login();
|
|
47
|
+
});
|
|
48
|
+
it("Should load current addresses, create a new one and be able to see it's details", () => {
|
|
49
|
+
// Visit the homepage
|
|
50
|
+
cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
|
|
51
|
+
|
|
52
|
+
goToAddressPage();
|
|
53
|
+
|
|
54
|
+
// Check Existing Address
|
|
55
|
+
cy.get("[data-fs-addresses-table]").within(() => {
|
|
56
|
+
cy.contains(TEST_DATA.ADDRESS.EXISTING_ADDRESS).should("exist");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
openCreateAddressDrawer();
|
|
60
|
+
|
|
61
|
+
// Fill The Address Form
|
|
62
|
+
// Open Country Selector
|
|
63
|
+
cy.get("[data-fs-bp-autocomplete-dropdown]")
|
|
64
|
+
.eq(0)
|
|
65
|
+
.click()
|
|
66
|
+
.within(() => {
|
|
67
|
+
cy.get("[data-fs-bp-autocomplete-dropdown-option]").eq(0).click();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Address Name
|
|
71
|
+
cy.get("[data-fs-bp-autocomplete-dropdown]")
|
|
72
|
+
.eq(1)
|
|
73
|
+
.type(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME);
|
|
74
|
+
|
|
75
|
+
// Street
|
|
76
|
+
cy.get("[class=street-address]").type(TEST_DATA.ADDRESS.NEW_ADDRESS_STREET);
|
|
77
|
+
|
|
78
|
+
// Complement
|
|
79
|
+
cy.get("[class=street-address2]").type(
|
|
80
|
+
TEST_DATA.ADDRESS.NEW_ADDRESS_COMPLEMENT
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// City
|
|
84
|
+
cy.get("[class=address-city]").type(TEST_DATA.ADDRESS.NEW_ADDRESS_CITY);
|
|
85
|
+
|
|
86
|
+
// Rio de Janeiro as State
|
|
87
|
+
cy.get("[data-fs-bp-autocomplete-dropdown]")
|
|
88
|
+
.eq(2)
|
|
89
|
+
.click()
|
|
90
|
+
.within(() => {
|
|
91
|
+
cy.get("[data-fs-bp-autocomplete-dropdown-option]").eq(18).click();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Postal Code
|
|
95
|
+
cy.get("[class=address-postal-code]")
|
|
96
|
+
.click()
|
|
97
|
+
.type(TEST_DATA.ADDRESS.NEW_ADDRESS_POSTAL_CODE);
|
|
98
|
+
|
|
99
|
+
// Mark as Shipping Address
|
|
100
|
+
cy.get("[data-fs-bp-autocomplete-dropdown]")
|
|
101
|
+
.eq(3)
|
|
102
|
+
.click()
|
|
103
|
+
.within(() => {
|
|
104
|
+
cy.get("[data-fs-bp-autocomplete-dropdown-option]").eq(0).click();
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
clickConfirmButton();
|
|
108
|
+
|
|
109
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
110
|
+
|
|
111
|
+
//Sometimes masterdata has cache, the reload helps with that
|
|
112
|
+
cy.reload();
|
|
113
|
+
|
|
114
|
+
// Check to see if address was created
|
|
115
|
+
cy.get("[data-fs-addresses-table]")
|
|
116
|
+
.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME)
|
|
117
|
+
.should("be.visible");
|
|
118
|
+
});
|
|
119
|
+
it("Should be able to edit street in specific Address via address details", () => {
|
|
120
|
+
// Visit the homepage
|
|
121
|
+
cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
|
|
122
|
+
|
|
123
|
+
// Navigate to Addresses page
|
|
124
|
+
goToAddressPage();
|
|
125
|
+
|
|
126
|
+
// Navigate to Address Details
|
|
127
|
+
navigateToAddressByName(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME);
|
|
128
|
+
|
|
129
|
+
// // Check if sections are rendered correctly
|
|
130
|
+
cy.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME).should("be.visible");
|
|
131
|
+
cy.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_STREET).should("be.visible");
|
|
132
|
+
cy.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_COMPLEMENT).should("be.visible");
|
|
133
|
+
cy.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_POSTAL_CODE).should("be.visible");
|
|
134
|
+
|
|
135
|
+
// Open Edit Drawer
|
|
136
|
+
cy.get("[data-fs-address-details-button]").click();
|
|
137
|
+
|
|
138
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
139
|
+
|
|
140
|
+
// Edit street
|
|
141
|
+
cy.get("[class=street-address]")
|
|
142
|
+
.clear()
|
|
143
|
+
.type(TEST_DATA.ADDRESS.EDIT_ADDRESS_STREET);
|
|
144
|
+
|
|
145
|
+
clickConfirmButton();
|
|
146
|
+
|
|
147
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
148
|
+
cy.reload();
|
|
149
|
+
|
|
150
|
+
// Check to see if information is edited
|
|
151
|
+
cy.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME).should("be.visible");
|
|
152
|
+
cy.contains(TEST_DATA.ADDRESS.EDIT_ADDRESS_STREET).should("be.visible");
|
|
153
|
+
cy.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_COMPLEMENT).should("be.visible");
|
|
154
|
+
cy.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_POSTAL_CODE).should("be.visible");
|
|
155
|
+
});
|
|
156
|
+
it("Should be able to edit the address from the address list page", () => {
|
|
157
|
+
// Visit the homepage
|
|
158
|
+
cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
|
|
159
|
+
|
|
160
|
+
// Navigate to Addresses page
|
|
161
|
+
goToAddressPage();
|
|
162
|
+
|
|
163
|
+
openActionsDropdown(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME);
|
|
164
|
+
|
|
165
|
+
cy.contains("Edit details").click();
|
|
166
|
+
|
|
167
|
+
// Edit street
|
|
168
|
+
cy.get("[class=street-address]")
|
|
169
|
+
.clear()
|
|
170
|
+
.type(TEST_DATA.ADDRESS.NEW_ADDRESS_STREET);
|
|
171
|
+
|
|
172
|
+
clickConfirmButton();
|
|
173
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("Should be able to set the address as default", () => {
|
|
177
|
+
// Visit the homepage
|
|
178
|
+
cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
|
|
179
|
+
|
|
180
|
+
// Navigate to Addresses page
|
|
181
|
+
goToAddressPage();
|
|
182
|
+
|
|
183
|
+
openActionsDropdown(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME);
|
|
184
|
+
|
|
185
|
+
cy.contains("Set as default").click();
|
|
186
|
+
|
|
187
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
188
|
+
|
|
189
|
+
// Check to see if is set as default
|
|
190
|
+
cy.get("[data-fs-addresses-table]")
|
|
191
|
+
.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME)
|
|
192
|
+
.parent()
|
|
193
|
+
.parent()
|
|
194
|
+
.get("[data-fs-bp-table-row-default-icon]")
|
|
195
|
+
.should("be.visible");
|
|
196
|
+
});
|
|
197
|
+
it("Should be able to Remove the address from the unit", () => {
|
|
198
|
+
// Visit the homepage
|
|
199
|
+
cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
|
|
200
|
+
|
|
201
|
+
// Navigate to Addresses page
|
|
202
|
+
goToAddressPage();
|
|
203
|
+
|
|
204
|
+
openActionsDropdown(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME);
|
|
205
|
+
|
|
206
|
+
// Inside the Action dropdown, remove from unit
|
|
207
|
+
cy.contains("Remove from Unit").click();
|
|
208
|
+
|
|
209
|
+
clickConfirmButton();
|
|
210
|
+
|
|
211
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
212
|
+
cy.reload();
|
|
213
|
+
|
|
214
|
+
// Check to see if the address has been removed
|
|
215
|
+
cy.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME).should("not.exist");
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("Should be able to Add existing Address", () => {
|
|
219
|
+
// Visit the homepage
|
|
220
|
+
cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
|
|
221
|
+
|
|
222
|
+
// Navigate to Addresses page
|
|
223
|
+
goToAddressPage();
|
|
224
|
+
|
|
225
|
+
// Check to see if address is really absent
|
|
226
|
+
cy.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME).should("not.exist");
|
|
227
|
+
|
|
228
|
+
openCreateAddressDrawer();
|
|
229
|
+
|
|
230
|
+
// Address Name with existing address
|
|
231
|
+
cy.get('[data-fs-bp-input-text="true"]')
|
|
232
|
+
.eq(1)
|
|
233
|
+
.should("be.visible")
|
|
234
|
+
.type(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME, { delay: 500 });
|
|
235
|
+
|
|
236
|
+
// Select existing address
|
|
237
|
+
cy.get('[data-fs-bp-autocomplete-dropdown-menu="true"]')
|
|
238
|
+
.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME)
|
|
239
|
+
.should("exist")
|
|
240
|
+
.click();
|
|
241
|
+
|
|
242
|
+
clickConfirmButton();
|
|
243
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
244
|
+
|
|
245
|
+
cy.reload();
|
|
246
|
+
|
|
247
|
+
// Check to see if address has been added
|
|
248
|
+
cy.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME).should("be.visible");
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it("Should be able to delete the address", () => {
|
|
252
|
+
// Visit the homepage
|
|
253
|
+
cy.visit(TEST_CONFIG.ROUTES.BUYER_PORTAL);
|
|
254
|
+
|
|
255
|
+
// Navigate to Addresses page
|
|
256
|
+
goToAddressPage();
|
|
257
|
+
|
|
258
|
+
openActionsDropdown(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME);
|
|
259
|
+
|
|
260
|
+
cy.contains("Delete").click();
|
|
261
|
+
|
|
262
|
+
// Fill check to allow deletion
|
|
263
|
+
cy.get("[class=address-name-confirmation]").type(
|
|
264
|
+
TEST_DATA.ADDRESS.NEW_ADDRESS_NAME
|
|
265
|
+
);
|
|
266
|
+
|
|
267
|
+
cy.get('[data-fs-bp-basic-drawer-button-variant="danger"]').click();
|
|
268
|
+
|
|
269
|
+
cy.wait(TEST_CONFIG.TIMEOUTS.PAGE_LOAD);
|
|
270
|
+
cy.reload();
|
|
271
|
+
|
|
272
|
+
// Check if address has been deleted
|
|
273
|
+
cy.contains(TEST_DATA.ADDRESS.NEW_ADDRESS_NAME).should("not.exist");
|
|
274
|
+
});
|
|
275
|
+
});
|
|
@@ -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
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
DeleteAddressDrawer,
|
|
11
11
|
} from "..";
|
|
12
12
|
import { BasicDropdownMenu, Icon } from "../../../shared/components";
|
|
13
|
-
import { useDrawerProps
|
|
13
|
+
import { useDrawerProps } from "../../../shared/hooks";
|
|
14
14
|
import { ADDRESS_MESSAGES } from "../../constants/messages";
|
|
15
15
|
import { useSetDefaultAddress } from "../../hooks/useSetDefaultAddress";
|
|
16
16
|
import { useUnsetDefaultAddress } from "../../hooks/useUnsetDefaultAddress";
|
|
@@ -28,7 +28,6 @@ export const AddressDropdownMenu = ({
|
|
|
28
28
|
currentAddress,
|
|
29
29
|
onUpdate,
|
|
30
30
|
}: AddressDropdownMenuProps) => {
|
|
31
|
-
const { currentOrgUnit } = useBuyerPortal();
|
|
32
31
|
const router = useRouter();
|
|
33
32
|
const { pushToast } = useUI();
|
|
34
33
|
|
|
@@ -107,9 +106,8 @@ export const AddressDropdownMenu = ({
|
|
|
107
106
|
});
|
|
108
107
|
};
|
|
109
108
|
|
|
110
|
-
const idsPath = currentOrgUnit?.path?.ids?.split("/") ?? [];
|
|
111
|
-
|
|
112
|
-
const isRootLevelOrgUnit = idsPath.length === 2;
|
|
109
|
+
// const idsPath = currentOrgUnit?.path?.ids?.split("/") ?? [];
|
|
110
|
+
// const isRootLevelOrgUnit = idsPath.length === 2;
|
|
113
111
|
|
|
114
112
|
const {
|
|
115
113
|
open: openEditDrawerProps,
|
|
@@ -175,15 +173,16 @@ export const AddressDropdownMenu = ({
|
|
|
175
173
|
<UIIcon name="MinusCircle" {...sizeProps} />
|
|
176
174
|
Remove from Unit
|
|
177
175
|
</DropdownItem>
|
|
178
|
-
{
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
</
|
|
186
|
-
|
|
176
|
+
{/* Remove validation while we reevaluate the rule */}
|
|
177
|
+
{/* {isRootLevelOrgUnit && ( */}
|
|
178
|
+
<DropdownItem
|
|
179
|
+
onClick={openDeleteAddressDrawer}
|
|
180
|
+
data-fs-bp-dropdown-menu-item-mode="danger"
|
|
181
|
+
>
|
|
182
|
+
<Icon name="Delete" {...sizeProps} data-fs-bp-delete-address />
|
|
183
|
+
<span data-fs-bp-delete-address>Delete</span>
|
|
184
|
+
</DropdownItem>
|
|
185
|
+
{/* )} */}
|
|
187
186
|
</BasicDropdownMenu>
|
|
188
187
|
{isEditDrawerOpen && (
|
|
189
188
|
<EditAddressDrawer
|
|
@@ -11,7 +11,7 @@ import { useBuyerPortal } from "../../../shared/hooks";
|
|
|
11
11
|
import { maskPostalCode } from "../../../shared/utils/postalCode";
|
|
12
12
|
import { CountryOptions } from "../../data/countries";
|
|
13
13
|
import { states as StateOptions } from "../../data/states";
|
|
14
|
-
import {
|
|
14
|
+
import { useDebouncedSearchAddress } from "../../hooks/useDebouncedSearchAddress";
|
|
15
15
|
import { AddressData, AddressInput } from "../../types";
|
|
16
16
|
|
|
17
17
|
export type AddressFormProps = {
|
|
@@ -45,10 +45,10 @@ export const AddressForm = ({
|
|
|
45
45
|
|
|
46
46
|
const [autoCompleteAddressName, setAutoCompleteAddressName] = useState("");
|
|
47
47
|
|
|
48
|
-
const { searchedAddresses } =
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
48
|
+
const { searchedAddresses } = useDebouncedSearchAddress(
|
|
49
|
+
autoCompleteAddressName,
|
|
50
|
+
currentOrgUnit?.id ?? ""
|
|
51
|
+
);
|
|
52
52
|
|
|
53
53
|
const cleanCompletedAddress = () => {
|
|
54
54
|
if (setCompletedAddress && setUseExistingAddress) {
|
|
@@ -75,6 +75,7 @@ export const AddressForm = ({
|
|
|
75
75
|
<AutocompleteDropdown
|
|
76
76
|
data-fs-bp-create-address-country-selector
|
|
77
77
|
label="Country"
|
|
78
|
+
className="address-country"
|
|
78
79
|
value={address.country}
|
|
79
80
|
options={CountryOptions}
|
|
80
81
|
onConfirmKeyPress={(option) =>
|
|
@@ -110,6 +111,7 @@ export const AddressForm = ({
|
|
|
110
111
|
<InputText
|
|
111
112
|
label="Address Name"
|
|
112
113
|
value={address.name}
|
|
114
|
+
className="address-name"
|
|
113
115
|
wrapperProps={{ style: { marginTop: 16 } }}
|
|
114
116
|
hasError={isTouched && !address.name?.trim()}
|
|
115
117
|
onChange={(event) =>
|
|
@@ -120,6 +122,7 @@ export const AddressForm = ({
|
|
|
120
122
|
<AutocompleteDropdown
|
|
121
123
|
label="Address Name"
|
|
122
124
|
value={autoCompleteAddressName}
|
|
125
|
+
className="address-name"
|
|
123
126
|
options={searchedAddresses}
|
|
124
127
|
onChange={(event) => {
|
|
125
128
|
setAutoCompleteAddressName(event.currentTarget.value);
|
|
@@ -157,6 +160,7 @@ export const AddressForm = ({
|
|
|
157
160
|
<InputText
|
|
158
161
|
label="Street Address"
|
|
159
162
|
value={address.streetAddress}
|
|
163
|
+
className="street-address"
|
|
160
164
|
wrapperProps={{ style: { marginTop: 16 } }}
|
|
161
165
|
hasError={isTouched && !address.streetAddress?.trim()}
|
|
162
166
|
onChange={(event) =>
|
|
@@ -171,6 +175,7 @@ export const AddressForm = ({
|
|
|
171
175
|
|
|
172
176
|
<InputText
|
|
173
177
|
label="Apt, Suite, Building (optional)"
|
|
178
|
+
className="street-address2"
|
|
174
179
|
value={address.streetAddress2}
|
|
175
180
|
wrapperProps={{ style: { marginTop: 16, marginBottom: 16 } }}
|
|
176
181
|
onChange={(event) =>
|
|
@@ -180,6 +185,7 @@ export const AddressForm = ({
|
|
|
180
185
|
|
|
181
186
|
<InputText
|
|
182
187
|
label="City"
|
|
188
|
+
className="address-city"
|
|
183
189
|
value={address.city}
|
|
184
190
|
wrapperProps={{ style: { marginTop: 16 } }}
|
|
185
191
|
hasError={isTouched && !address.city?.trim()}
|
|
@@ -191,6 +197,7 @@ export const AddressForm = ({
|
|
|
191
197
|
<AutocompleteDropdown
|
|
192
198
|
data-fs-bp-create-address-state-selector
|
|
193
199
|
label="State"
|
|
200
|
+
className="address-state"
|
|
194
201
|
value={address.state}
|
|
195
202
|
options={
|
|
196
203
|
address.countryCode ? StateOptions[address.countryCode] : []
|
|
@@ -221,6 +228,7 @@ export const AddressForm = ({
|
|
|
221
228
|
<InputText
|
|
222
229
|
label="Postal Code"
|
|
223
230
|
value={address.zip}
|
|
231
|
+
className="address-postal-code"
|
|
224
232
|
wrapperProps={{ style: { marginTop: 16, marginBottom: 16 } }}
|
|
225
233
|
hasError={isTouched && !address.zip?.trim()}
|
|
226
234
|
onChange={(event) => handleChangePostalCode(event.target.value)}
|
|
@@ -252,6 +260,7 @@ export const AddressForm = ({
|
|
|
252
260
|
<AutocompleteDropdown
|
|
253
261
|
label="Address Type"
|
|
254
262
|
value={address.types}
|
|
263
|
+
className="address-type"
|
|
255
264
|
options={addressTypeOptions}
|
|
256
265
|
onConfirmKeyPress={(option) =>
|
|
257
266
|
setAddress({ ...address, types: [option] })
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { scopesClient } from "../../shared/clients/ScopeClient";
|
|
1
2
|
import { addressesClient } from "../clients/AddressesClient";
|
|
2
3
|
|
|
3
4
|
import type { AddressInput } from "../types";
|
|
@@ -8,14 +9,26 @@ export type CreateNewAddressServiceProps = {
|
|
|
8
9
|
unitId: string;
|
|
9
10
|
} & AddressInput;
|
|
10
11
|
|
|
12
|
+
type NewAddress = {
|
|
13
|
+
newAddressId: string;
|
|
14
|
+
};
|
|
15
|
+
|
|
11
16
|
export const createNewAddressService = async ({
|
|
12
17
|
cookie,
|
|
13
18
|
...data
|
|
14
19
|
}: CreateNewAddressServiceProps) => {
|
|
15
|
-
|
|
20
|
+
const newAddress = (await addressesClient.createNewAddress(
|
|
16
21
|
data,
|
|
17
22
|
data.unitId,
|
|
18
23
|
data.customerId,
|
|
19
24
|
cookie
|
|
25
|
+
)) as NewAddress;
|
|
26
|
+
|
|
27
|
+
await scopesClient.addToScope(
|
|
28
|
+
data.unitId,
|
|
29
|
+
{ scopeName: "addresses", ids: [newAddress.newAddressId] },
|
|
30
|
+
cookie
|
|
20
31
|
);
|
|
32
|
+
|
|
33
|
+
return newAddress;
|
|
21
34
|
};
|
|
@@ -3,7 +3,7 @@ export type CollectionsSummary = {
|
|
|
3
3
|
name: string;
|
|
4
4
|
};
|
|
5
5
|
|
|
6
|
-
export type ContractCollection = CollectionsSummary & {
|
|
6
|
+
export type ContractCollection = CollectionsSummary & { isEnabled: boolean };
|
|
7
7
|
export type ScopeCollection = CollectionsSummary & { isEnabled: boolean };
|
|
8
8
|
|
|
9
9
|
export type GetCollectionsFromContractResponse = {
|
|
@@ -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
|
}
|
|
@@ -73,7 +73,7 @@ export async function loader(
|
|
|
73
73
|
}),
|
|
74
74
|
]);
|
|
75
75
|
|
|
76
|
-
const isContractEmpty = contractCollections.every((c) => !c.
|
|
76
|
+
const isContractEmpty = contractCollections.every((c) => !c.isEnabled);
|
|
77
77
|
const drawerCollections = allCollections.filter((col) => !col.isEnabled);
|
|
78
78
|
|
|
79
79
|
return {
|