@controleonline/ui-customers 1.0.81 → 1.0.82
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/.github/workflows/integration-source-gate.yml +68 -0
- package/.github/workflows/publish-npm.yml +91 -0
- package/AGENTS.md +12 -6
- package/docs/technical/Client-Details-Criar-Usuario.md +113 -0
- package/package.json +5 -4
- package/src/react/components/tabs/AddressesTab.js +240 -335
- package/src/react/components/tabs/DocumentAttachments.js +270 -0
- package/src/react/components/tabs/DocumentsTab.js +355 -463
- package/src/react/components/tabs/DocumentsTab.styles.js +123 -0
- package/src/react/components/tabs/EmployeeCreateFormFields.js +203 -0
- package/src/react/components/tabs/EmployeesTab.js +17 -171
- package/src/react/components/tabs/EmployeesTab.styles.js +220 -172
- package/src/react/components/tabs/GeneralTab.js +84 -46
- package/src/react/components/tabs/GeneralTab.styles.js +81 -77
- package/src/react/components/tabs/MediaTab.js +50 -5
- package/src/react/components/tabs/PeopleCategoriesPanel.js +161 -78
- package/src/react/components/tabs/SalesmanTab.js +1 -1
- package/src/react/components/tabs/UserFormModal.js +26 -2
- package/src/react/components/tabs/UsersTab.js +30 -19
- package/src/react/components/tabs/UsersTab.styles.js +257 -257
- package/src/react/components/tabs/UsersTabApiKeyModal.js +115 -0
- package/src/react/components/tabs/UsersTabUserModal.js +222 -0
- package/src/react/components/tabs/addressesTabHelpers.js +190 -0
- package/src/react/components/tabs/documentsTabHelpers.js +142 -0
- package/src/react/components/tabs/employeeContacts.js +3 -23
- package/src/react/components/tabs/employeeUnlink.js +7 -4
- package/src/react/components/tabs/employeesTabHelpers.js +33 -9
- package/src/react/components/tabs/generalTabHelpers.js +30 -8
- package/src/react/components/tabs/useGeneralTabPeopleLink.js +68 -0
- package/src/react/components/tabs/usersTabHelpers.js +24 -23
- package/src/react/pages/details.page.styles.js +114 -100
- package/src/react/utils/humanCompanyLinkTypes.js +28 -0
- package/src/tests/browser/manager/client-details-add-user-modal.spec.js +281 -0
- package/src/tests/browser/manager/client-details-address-create-coords.spec.js +414 -0
- package/src/tests/browser/manager/client-details-address-edit.spec.js +331 -0
- package/src/tests/browser/manager/client-details-contact-edit.spec.js +198 -0
- package/src/tests/browser/manager/client-details-create-contact.spec.js +306 -0
- package/src/tests/browser/manager/client-details-documents.spec.js +346 -0
- package/src/tests/browser/manager/client-details-identity-case.spec.js +227 -0
- package/src/tests/browser/manager/client-details-people-type.manifest.md +18 -0
- package/src/tests/browser/manager/client-details-people-type.spec.js +345 -0
- package/src/tests/browser/manager/client-details-supplier-product-create.spec.js +334 -0
- package/src/tests/browser/manager/client-details-users-tab-load.spec.js +194 -0
- package/src/tests/browser/manager/employees-salesman-after-sales.spec.js +441 -0
- package/src/tests/react/components/SalesmanTab.company.test.js +40 -0
- package/src/tests/react/components/tabs/UsersTab.styles.test.js +39 -0
- package/src/tests/react/components/tabs/addressesTabHelpers.test.js +78 -0
- package/src/tests/react/components/tabs/documentsTabContract.test.mjs +37 -0
- package/src/tests/react/components/tabs/documentsTabHelpers.test.mjs +93 -0
- package/src/tests/react/components/tabs/employeeContacts.test.js +46 -4
- package/src/tests/react/components/tabs/employeeLinkTypes.contract.test.mjs +52 -0
- package/src/tests/react/components/tabs/employeeUnlink.test.js +11 -11
- package/src/tests/react/components/tabs/employeesTabHelpers.media.test.js +2 -23
- package/src/tests/react/components/tabs/employeesTabHelpers.test.js +59 -0
- package/src/tests/react/components/tabs/generalTabHelpers.test.js +35 -0
- package/src/tests/react/components/tabs/peopleMediaTabs.test.js +217 -217
- package/src/tests/react/components/tabs/peopleType.contract.test.mjs +47 -0
- package/src/tests/react/components/tabs/resolveSeededLinkType.test.mjs +20 -0
- package/src/tests/react/components/tabs/salesmanTabMedia.no-redeclare.test.mjs +33 -0
- package/src/tests/react/components/tabs/usersTabHelpers.test.js +20 -0
- package/src/tests/react/components/tabs/usersTabHelpers.test.mjs +63 -0
- package/src/tests/react/utils/humanCompanyLinkTypes.test.js +50 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke: client-details contacts/employee create (app-community#331)
|
|
3
|
+
* Covers open modal → fill → save → no opaque "Request failed" → contact listed.
|
|
4
|
+
*/
|
|
5
|
+
const {expect, test} = require('playwright/test');
|
|
6
|
+
const {API_ORIGIN} = require('../../../../../../../src/tests/browser/apiOrigin');
|
|
7
|
+
const {version: appVersion} = require('../../../../../../../package.json');
|
|
8
|
+
|
|
9
|
+
const CORS_HEADERS = {
|
|
10
|
+
'access-control-allow-origin': '*',
|
|
11
|
+
'access-control-allow-headers':
|
|
12
|
+
'API-TOKEN, APP-DOMAIN, DEVICE, ACCEPT, CONTENT-TYPE, X-Requested-With',
|
|
13
|
+
'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const jsonHeaders = () => ({
|
|
17
|
+
...CORS_HEADERS,
|
|
18
|
+
'content-type': 'application/ld+json; charset=utf-8',
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const textHeaders = () => ({
|
|
22
|
+
...CORS_HEADERS,
|
|
23
|
+
'content-type': 'text/css; charset=utf-8',
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const collection = (member = []) => ({
|
|
27
|
+
member,
|
|
28
|
+
'hydra:member': member,
|
|
29
|
+
totalItems: member.length,
|
|
30
|
+
'hydra:totalItems': member.length,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const company = {
|
|
34
|
+
'@id': '/people/2',
|
|
35
|
+
id: 2,
|
|
36
|
+
name: 'CONTROLE ONLINE',
|
|
37
|
+
alias: 'CONTROLE ONLINE',
|
|
38
|
+
panel_enabled: true,
|
|
39
|
+
enabled: true,
|
|
40
|
+
commercial_enabled: true,
|
|
41
|
+
theme: {
|
|
42
|
+
colors: {
|
|
43
|
+
primary: '#0EA5E9',
|
|
44
|
+
secondary: '#F97316',
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
configs: {},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const client = {
|
|
51
|
+
'@id': '/people/31487',
|
|
52
|
+
id: 31487,
|
|
53
|
+
name: 'CLIENTE TESTE LTDA',
|
|
54
|
+
alias: 'CLIENTE TESTE',
|
|
55
|
+
peopleType: 'J',
|
|
56
|
+
enable: true,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const mockCreateContactApi = async page => {
|
|
60
|
+
const peoplePosts = [];
|
|
61
|
+
const peopleLinkPosts = [];
|
|
62
|
+
let contacts = [];
|
|
63
|
+
|
|
64
|
+
await page.route(`${API_ORIGIN}/**`, async route => {
|
|
65
|
+
const request = route.request();
|
|
66
|
+
const url = new URL(request.url());
|
|
67
|
+
const pathname = url.pathname.replace(/^\/+/, '');
|
|
68
|
+
const method = request.method().toUpperCase();
|
|
69
|
+
|
|
70
|
+
if (method === 'OPTIONS') {
|
|
71
|
+
return route.fulfill({status: 204, headers: CORS_HEADERS, body: ''});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (pathname === 'themes-colors.css') {
|
|
75
|
+
return route.fulfill({
|
|
76
|
+
status: 200,
|
|
77
|
+
headers: textHeaders(),
|
|
78
|
+
body: ':root { --primary: #0ea5e9; --secondary: #f97316; }',
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (pathname === 'runtime/ip') {
|
|
83
|
+
return route.fulfill({
|
|
84
|
+
status: 200,
|
|
85
|
+
headers: jsonHeaders(),
|
|
86
|
+
body: JSON.stringify({ip: '127.0.0.1'}),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (pathname === 'people/company/default') {
|
|
91
|
+
return route.fulfill({
|
|
92
|
+
status: 200,
|
|
93
|
+
headers: jsonHeaders(),
|
|
94
|
+
body: JSON.stringify(company),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (pathname === 'people/companies/my') {
|
|
99
|
+
return route.fulfill({
|
|
100
|
+
status: 200,
|
|
101
|
+
headers: jsonHeaders(),
|
|
102
|
+
body: JSON.stringify(collection([company])),
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (pathname === 'people/31487' && method === 'GET') {
|
|
107
|
+
return route.fulfill({
|
|
108
|
+
status: 200,
|
|
109
|
+
headers: jsonHeaders(),
|
|
110
|
+
body: JSON.stringify(client),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (pathname === 'people' && method === 'POST') {
|
|
115
|
+
let body = {};
|
|
116
|
+
try {
|
|
117
|
+
body = request.postDataJSON() || {};
|
|
118
|
+
} catch {
|
|
119
|
+
body = {};
|
|
120
|
+
}
|
|
121
|
+
peoplePosts.push(body);
|
|
122
|
+
const newPerson = {
|
|
123
|
+
'@id': '/people/90001',
|
|
124
|
+
id: 90001,
|
|
125
|
+
name: body.name || 'NOVO CONTATO',
|
|
126
|
+
alias: body.alias || 'NOVO',
|
|
127
|
+
peopleType: body.peopleType || 'F',
|
|
128
|
+
};
|
|
129
|
+
return route.fulfill({
|
|
130
|
+
status: 201,
|
|
131
|
+
headers: jsonHeaders(),
|
|
132
|
+
body: JSON.stringify(newPerson),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (
|
|
137
|
+
(pathname === 'people_links' || pathname === 'people-links') &&
|
|
138
|
+
method === 'POST'
|
|
139
|
+
) {
|
|
140
|
+
let body = {};
|
|
141
|
+
try {
|
|
142
|
+
body = request.postDataJSON() || {};
|
|
143
|
+
} catch {
|
|
144
|
+
body = {};
|
|
145
|
+
}
|
|
146
|
+
peopleLinkPosts.push(body);
|
|
147
|
+
const link = {
|
|
148
|
+
'@id': '/people_links/1',
|
|
149
|
+
id: 1,
|
|
150
|
+
company: body.company || '/people/31487',
|
|
151
|
+
people: body.people || '/people/90001',
|
|
152
|
+
linkType: body.linkType || 'employee',
|
|
153
|
+
};
|
|
154
|
+
contacts = [
|
|
155
|
+
{
|
|
156
|
+
'@id': '/people_links/1',
|
|
157
|
+
id: 1,
|
|
158
|
+
linkType: link.linkType,
|
|
159
|
+
people: {
|
|
160
|
+
'@id': '/people/90001',
|
|
161
|
+
id: 90001,
|
|
162
|
+
name: peoplePosts[0]?.name || 'NOVO CONTATO',
|
|
163
|
+
alias: peoplePosts[0]?.alias || 'NOVO',
|
|
164
|
+
},
|
|
165
|
+
company: {id: 31487},
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
return route.fulfill({
|
|
169
|
+
status: 201,
|
|
170
|
+
headers: jsonHeaders(),
|
|
171
|
+
body: JSON.stringify(link),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (
|
|
176
|
+
(pathname === 'people_links' || pathname === 'people-links') &&
|
|
177
|
+
method === 'GET'
|
|
178
|
+
) {
|
|
179
|
+
return route.fulfill({
|
|
180
|
+
status: 200,
|
|
181
|
+
headers: jsonHeaders(),
|
|
182
|
+
body: JSON.stringify(collection(contacts)),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (pathname.startsWith('people/') && method === 'GET') {
|
|
187
|
+
return route.fulfill({
|
|
188
|
+
status: 200,
|
|
189
|
+
headers: jsonHeaders(),
|
|
190
|
+
body: JSON.stringify({id: Number(pathname.split('/')[1]) || 0, name: 'X'}),
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (method === 'GET') {
|
|
195
|
+
return route.fulfill({
|
|
196
|
+
status: 200,
|
|
197
|
+
headers: jsonHeaders(),
|
|
198
|
+
body: JSON.stringify(collection([])),
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return route.fulfill({
|
|
203
|
+
status: 200,
|
|
204
|
+
headers: jsonHeaders(),
|
|
205
|
+
body: JSON.stringify({}),
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
await page.addInitScript(
|
|
210
|
+
({version}) => {
|
|
211
|
+
localStorage.setItem(
|
|
212
|
+
'session',
|
|
213
|
+
JSON.stringify({
|
|
214
|
+
id: 19,
|
|
215
|
+
people: '/people/19',
|
|
216
|
+
api_key: 'test-api-key',
|
|
217
|
+
active: 1,
|
|
218
|
+
mycompany: 2,
|
|
219
|
+
roles: ['ROLE_SUPER'],
|
|
220
|
+
}),
|
|
221
|
+
);
|
|
222
|
+
localStorage.setItem('app-type', 'MANAGER');
|
|
223
|
+
localStorage.setItem('config', JSON.stringify({language: 'pt-br'}));
|
|
224
|
+
localStorage.setItem(
|
|
225
|
+
'device',
|
|
226
|
+
JSON.stringify({
|
|
227
|
+
id: 'web-manager',
|
|
228
|
+
device: 'web-manager',
|
|
229
|
+
type: 'WEB',
|
|
230
|
+
appName: 'Browser Manager',
|
|
231
|
+
appVersion: version,
|
|
232
|
+
buildNumber: version,
|
|
233
|
+
systemName: 'web',
|
|
234
|
+
systemVersion: 'web',
|
|
235
|
+
deviceType: 'web',
|
|
236
|
+
metadata: {},
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
239
|
+
},
|
|
240
|
+
{version: appVersion},
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
return {peoplePosts, peopleLinkPosts};
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
test.describe('client-details create contact (#331)', () => {
|
|
247
|
+
test('creates employee contact without opaque Request failed', async ({page}) => {
|
|
248
|
+
const {peoplePosts} = await mockCreateContactApi(page);
|
|
249
|
+
|
|
250
|
+
await page.goto(
|
|
251
|
+
'/client-details?clientId=31487&contextKey=contacts&initialTab=contacts&parentCompanyId=8&linkType=employee',
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
await expect(
|
|
255
|
+
page.getByText('CLIENTE TESTE', {exact: false}).first(),
|
|
256
|
+
).toBeVisible({timeout: 15000});
|
|
257
|
+
|
|
258
|
+
const addSelectors = [
|
|
259
|
+
page.getByRole('button', {name: /novo|adicionar|add|criar/i}).first(),
|
|
260
|
+
page.locator('[data-testid="add-contact"], [aria-label*="add" i]').first(),
|
|
261
|
+
page.getByText(/novo contato|adicionar contato|novo funcion/i).first(),
|
|
262
|
+
];
|
|
263
|
+
|
|
264
|
+
for (const locator of addSelectors) {
|
|
265
|
+
try {
|
|
266
|
+
if (await locator.isVisible({timeout: 2000})) {
|
|
267
|
+
await locator.click();
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
} catch {
|
|
271
|
+
// try next
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const nameField = page
|
|
276
|
+
.locator('input[name="name"], input[placeholder*="nome" i], input[aria-label*="nome" i]')
|
|
277
|
+
.first();
|
|
278
|
+
const aliasField = page
|
|
279
|
+
.locator(
|
|
280
|
+
'input[name="alias"], input[placeholder*="apelido" i], input[aria-label*="apelido" i]',
|
|
281
|
+
)
|
|
282
|
+
.first();
|
|
283
|
+
|
|
284
|
+
if (await nameField.isVisible({timeout: 3000}).catch(() => false)) {
|
|
285
|
+
await nameField.fill('CONTATO SMOKE 331');
|
|
286
|
+
if (await aliasField.isVisible().catch(() => false)) {
|
|
287
|
+
await aliasField.fill('SMOKE331');
|
|
288
|
+
}
|
|
289
|
+
const saveBtn = page
|
|
290
|
+
.getByRole('button', {name: /salvar|save|gravar|confirmar/i})
|
|
291
|
+
.first();
|
|
292
|
+
if (await saveBtn.isVisible().catch(() => false)) {
|
|
293
|
+
await saveBtn.click();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
await expect(page.getByText('Request failed', {exact: false})).toHaveCount(0, {
|
|
298
|
+
timeout: 5000,
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
if (peoplePosts.length > 0) {
|
|
302
|
+
expect(peoplePosts[0].peopleType).toBe('F');
|
|
303
|
+
expect(String(peoplePosts[0].name || '')).toMatch(/CONTATO|SMOKE/i);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
});
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
const { expect, test } = require('playwright/test');
|
|
2
|
+
const { API_ORIGIN } = require('../../../../../../../src/tests/browser/apiOrigin');
|
|
3
|
+
const { version: appVersion } = require('../../../../../../../package.json');
|
|
4
|
+
|
|
5
|
+
const CORS_HEADERS = {
|
|
6
|
+
'access-control-allow-origin': '*',
|
|
7
|
+
'access-control-allow-headers':
|
|
8
|
+
'API-TOKEN, APP-DOMAIN, DEVICE, ACCEPT, CONTENT-TYPE, X-Requested-With',
|
|
9
|
+
'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const collection = (member = [], summary = {}) => ({
|
|
13
|
+
member,
|
|
14
|
+
'hydra:member': member,
|
|
15
|
+
totalItems: member.length,
|
|
16
|
+
'hydra:totalItems': member.length,
|
|
17
|
+
summary,
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const jsonHeaders = () => ({
|
|
21
|
+
...CORS_HEADERS,
|
|
22
|
+
'content-type': 'application/ld+json; charset=utf-8',
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const textHeaders = () => ({
|
|
26
|
+
...CORS_HEADERS,
|
|
27
|
+
'content-type': 'text/css; charset=utf-8',
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const createCompany = () => ({
|
|
31
|
+
id: 3,
|
|
32
|
+
name: 'Controle Online',
|
|
33
|
+
alias: 'CONTROLE ONLINE',
|
|
34
|
+
panel_enabled: true,
|
|
35
|
+
enabled: true,
|
|
36
|
+
commercial_enabled: true,
|
|
37
|
+
theme: { colors: { primary: '#0EA5E9', secondary: '#F97316' } },
|
|
38
|
+
configs: {},
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
const documentType = {
|
|
42
|
+
'@id': '/document_types/1',
|
|
43
|
+
id: 1,
|
|
44
|
+
documentType: 'CNPJ',
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const documentFile = {
|
|
48
|
+
'@id': '/document_files/5',
|
|
49
|
+
id: 5,
|
|
50
|
+
document: '/documents/8',
|
|
51
|
+
file: {
|
|
52
|
+
'@id': '/files/50',
|
|
53
|
+
id: 50,
|
|
54
|
+
name: 'cartao-cnpj.png',
|
|
55
|
+
extension: 'png',
|
|
56
|
+
url: 'https://cdn.example/cartao-cnpj.png',
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const person = {
|
|
61
|
+
'@id': '/people/40',
|
|
62
|
+
id: 40,
|
|
63
|
+
name: 'EMPRESA DOCUMENTOS LTDA',
|
|
64
|
+
alias: 'DOCS',
|
|
65
|
+
peopleType: 'J',
|
|
66
|
+
enable: true,
|
|
67
|
+
document: [
|
|
68
|
+
{
|
|
69
|
+
'@id': '/documents/8',
|
|
70
|
+
id: 8,
|
|
71
|
+
document: '12345678000199',
|
|
72
|
+
documentType,
|
|
73
|
+
documentFiles: [documentFile],
|
|
74
|
+
file: documentFile.file,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const mockDocumentsApi = async page => {
|
|
80
|
+
const company = createCompany();
|
|
81
|
+
|
|
82
|
+
await page.route(`${API_ORIGIN}/**`, async route => {
|
|
83
|
+
const request = route.request();
|
|
84
|
+
const url = new URL(request.url());
|
|
85
|
+
const pathname = url.pathname.replace(/^\/+/, '');
|
|
86
|
+
const method = request.method().toUpperCase();
|
|
87
|
+
|
|
88
|
+
if (method === 'OPTIONS') {
|
|
89
|
+
return route.fulfill({ status: 204, headers: CORS_HEADERS, body: '' });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (pathname === 'themes-colors.css') {
|
|
93
|
+
return route.fulfill({
|
|
94
|
+
status: 200,
|
|
95
|
+
headers: textHeaders(),
|
|
96
|
+
body: ':root { --primary: #0ea5e9; --secondary: #f97316; }',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (pathname === 'runtime/ip') {
|
|
101
|
+
return route.fulfill({
|
|
102
|
+
status: 200,
|
|
103
|
+
headers: jsonHeaders(),
|
|
104
|
+
body: JSON.stringify({ ip: '127.0.0.1' }),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (pathname === 'people/company/default') {
|
|
109
|
+
return route.fulfill({
|
|
110
|
+
status: 200,
|
|
111
|
+
headers: jsonHeaders(),
|
|
112
|
+
body: JSON.stringify(company),
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (pathname === 'people/companies/my') {
|
|
117
|
+
return route.fulfill({
|
|
118
|
+
status: 200,
|
|
119
|
+
headers: jsonHeaders(),
|
|
120
|
+
body: JSON.stringify(collection([company])),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (pathname === 'people/40' || pathname === 'people/30') {
|
|
125
|
+
return route.fulfill({
|
|
126
|
+
status: 200,
|
|
127
|
+
headers: jsonHeaders(),
|
|
128
|
+
body: JSON.stringify(person),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (pathname === 'people' || pathname.startsWith('people?')) {
|
|
133
|
+
return route.fulfill({
|
|
134
|
+
status: 200,
|
|
135
|
+
headers: jsonHeaders(),
|
|
136
|
+
body: JSON.stringify(collection([person])),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (pathname === 'document_types' || pathname.startsWith('document_types')) {
|
|
141
|
+
return route.fulfill({
|
|
142
|
+
status: 200,
|
|
143
|
+
headers: jsonHeaders(),
|
|
144
|
+
body: JSON.stringify(collection([documentType])),
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (pathname === 'document_files' || pathname.startsWith('document_files')) {
|
|
149
|
+
return route.fulfill({
|
|
150
|
+
status: 200,
|
|
151
|
+
headers: jsonHeaders(),
|
|
152
|
+
body: JSON.stringify(collection([documentFile])),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (pathname === 'documents' || pathname.startsWith('documents')) {
|
|
157
|
+
return route.fulfill({
|
|
158
|
+
status: 200,
|
|
159
|
+
headers: jsonHeaders(),
|
|
160
|
+
body: JSON.stringify(collection(person.document)),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (pathname === 'files' || pathname.startsWith('files')) {
|
|
165
|
+
return route.fulfill({
|
|
166
|
+
status: 200,
|
|
167
|
+
headers: jsonHeaders(),
|
|
168
|
+
body: JSON.stringify(collection([documentFile.file])),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (pathname === 'people_links' || pathname.startsWith('people_links')) {
|
|
173
|
+
return route.fulfill({
|
|
174
|
+
status: 200,
|
|
175
|
+
headers: jsonHeaders(),
|
|
176
|
+
body: JSON.stringify(collection([])),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (pathname === 'status' || pathname.startsWith('status')) {
|
|
181
|
+
return route.fulfill({
|
|
182
|
+
status: 200,
|
|
183
|
+
headers: jsonHeaders(),
|
|
184
|
+
body: JSON.stringify(collection([])),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (pathname === 'menus' || pathname === 'menus-people') {
|
|
189
|
+
return route.fulfill({
|
|
190
|
+
status: 200,
|
|
191
|
+
headers: jsonHeaders(),
|
|
192
|
+
body: JSON.stringify({ modules: {} }),
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (pathname === 'configs/discovery-configs') {
|
|
197
|
+
return route.fulfill({
|
|
198
|
+
status: 200,
|
|
199
|
+
headers: jsonHeaders(),
|
|
200
|
+
body: JSON.stringify({ configs: {} }),
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return route.fulfill({
|
|
205
|
+
status: 200,
|
|
206
|
+
headers: jsonHeaders(),
|
|
207
|
+
body: JSON.stringify(collection([])),
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
await page.addInitScript(
|
|
212
|
+
({ appVersionValue, companyData }) => {
|
|
213
|
+
const setLocalStorageItem = (key, value) => {
|
|
214
|
+
try {
|
|
215
|
+
localStorage.setItem(key, value);
|
|
216
|
+
} catch {
|
|
217
|
+
/* ignore */
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
setLocalStorageItem(
|
|
222
|
+
'session',
|
|
223
|
+
JSON.stringify({
|
|
224
|
+
id: 7,
|
|
225
|
+
people: '/people/7',
|
|
226
|
+
api_key: 'test-api-key',
|
|
227
|
+
active: 1,
|
|
228
|
+
mycompany: companyData.id,
|
|
229
|
+
roles: ['ROLE_SUPER'],
|
|
230
|
+
}),
|
|
231
|
+
);
|
|
232
|
+
setLocalStorageItem('config', JSON.stringify({ language: 'pt-br' }));
|
|
233
|
+
setLocalStorageItem('app-type', 'MANAGER');
|
|
234
|
+
setLocalStorageItem('currentCompany', JSON.stringify(companyData));
|
|
235
|
+
setLocalStorageItem(
|
|
236
|
+
'device',
|
|
237
|
+
JSON.stringify({
|
|
238
|
+
id: 'web-manager',
|
|
239
|
+
device: 'web-manager',
|
|
240
|
+
type: 'WEB',
|
|
241
|
+
appName: 'Browser Manager',
|
|
242
|
+
appVersion: appVersionValue,
|
|
243
|
+
buildNumber: appVersionValue,
|
|
244
|
+
systemName: 'web',
|
|
245
|
+
systemVersion: 'web',
|
|
246
|
+
deviceType: 'web',
|
|
247
|
+
metadata: {},
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
250
|
+
window.__APP_VERSION__ = appVersionValue;
|
|
251
|
+
},
|
|
252
|
+
{ appVersionValue: appVersion, companyData: company },
|
|
253
|
+
);
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
test.describe('Client details documents attachments', () => {
|
|
257
|
+
test('opens Documents tab and shows attachment actions (Ver/Abrir/Compartilhar/Imprimir)', async ({
|
|
258
|
+
page,
|
|
259
|
+
}) => {
|
|
260
|
+
await mockDocumentsApi(page);
|
|
261
|
+
|
|
262
|
+
const consoleErrors = [];
|
|
263
|
+
page.on('console', msg => {
|
|
264
|
+
if (msg.type() === 'error') {
|
|
265
|
+
consoleErrors.push(msg.text());
|
|
266
|
+
}
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
await page.goto('/client-details?clientId=40&contextKey=contacts&parentCompanyId=3');
|
|
270
|
+
|
|
271
|
+
// Shell / header of client-details must load
|
|
272
|
+
await expect(
|
|
273
|
+
page.getByText(/EMPRESA DOCUMENTOS|DOCS|Dados Cadastrais/i).first(),
|
|
274
|
+
).toBeVisible({ timeout: 20000 });
|
|
275
|
+
|
|
276
|
+
// Navigate to Documents tab (label varies by i18n fallback)
|
|
277
|
+
const docsTab = page
|
|
278
|
+
.getByText(/^Documentos$/i)
|
|
279
|
+
.or(page.getByRole('tab', { name: /Documentos/i }))
|
|
280
|
+
.or(page.getByText(/Documentos/i))
|
|
281
|
+
.first();
|
|
282
|
+
await docsTab.click({ timeout: 15000 }).catch(async () => {
|
|
283
|
+
await page.getByRole('button', { name: /Documentos/i }).first().click({ timeout: 10000 });
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Document number / type visible
|
|
287
|
+
await expect(page.getByText(/CNPJ/i).first()).toBeVisible({ timeout: 15000 });
|
|
288
|
+
await expect(
|
|
289
|
+
page.getByText(/12\.345\.678\/0001-99|12345678000199|12\.345\.678/i).first(),
|
|
290
|
+
).toBeVisible({ timeout: 10000 }).catch(() => {});
|
|
291
|
+
|
|
292
|
+
// Attachment section + file name
|
|
293
|
+
await expect(page.getByText(/Anexos \(JPG, PNG, PDF\)|Anexos/i).first()).toBeVisible({
|
|
294
|
+
timeout: 10000,
|
|
295
|
+
});
|
|
296
|
+
await expect(page.getByText(/cartao-cnpj\.png/i).first()).toBeVisible({ timeout: 10000 });
|
|
297
|
+
|
|
298
|
+
// Action buttons required by acceptance criteria
|
|
299
|
+
await expect(page.getByText(/^Ver$/i).first()).toBeVisible({ timeout: 10000 });
|
|
300
|
+
await expect(page.getByText(/^Abrir$/i).first()).toBeVisible({ timeout: 10000 });
|
|
301
|
+
await expect(page.getByText(/^Compartilhar$/i).first()).toBeVisible({ timeout: 10000 });
|
|
302
|
+
await expect(page.getByText(/^Imprimir$/i).first()).toBeVisible({ timeout: 10000 });
|
|
303
|
+
|
|
304
|
+
// Preview (Ver) opens modal with file name
|
|
305
|
+
await page.getByText(/^Ver$/i).first().click();
|
|
306
|
+
await expect(page.getByText(/cartao-cnpj\.png/i).first()).toBeVisible({ timeout: 5000 });
|
|
307
|
+
|
|
308
|
+
// Close preview if close control exists
|
|
309
|
+
const closeBtn = page.getByRole('button', { name: /close|fechar/i }).or(
|
|
310
|
+
page.locator('[aria-label="close"]'),
|
|
311
|
+
);
|
|
312
|
+
if (await closeBtn.first().isVisible().catch(() => false)) {
|
|
313
|
+
await closeBtn.first().click().catch(() => {});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Critical console errors must not appear for this flow
|
|
317
|
+
const critical = consoleErrors.filter(
|
|
318
|
+
text =>
|
|
319
|
+
!/favicon|Download the React DevTools|Warning:|gravatar|404/i.test(text) &&
|
|
320
|
+
/TypeError|ReferenceError|Cannot read|is not a function|Unhandled/i.test(text),
|
|
321
|
+
);
|
|
322
|
+
expect(critical, `Console errors: ${critical.join(' | ')}`).toEqual([]);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('API contract: document_types and document_files respond for attachments', async ({
|
|
326
|
+
page,
|
|
327
|
+
}) => {
|
|
328
|
+
await mockDocumentsApi(page);
|
|
329
|
+
await page.goto('/client-details?clientId=40&contextKey=contacts&parentCompanyId=3');
|
|
330
|
+
|
|
331
|
+
const typesPayload = await page.evaluate(async origin => {
|
|
332
|
+
const response = await fetch(`${origin}/document_types`);
|
|
333
|
+
return response.json();
|
|
334
|
+
}, API_ORIGIN);
|
|
335
|
+
expect(
|
|
336
|
+
typesPayload.member?.[0]?.documentType || typesPayload['hydra:member']?.[0]?.documentType,
|
|
337
|
+
).toBe('CNPJ');
|
|
338
|
+
|
|
339
|
+
const filesPayload = await page.evaluate(async origin => {
|
|
340
|
+
const response = await fetch(`${origin}/document_files`);
|
|
341
|
+
return response.json();
|
|
342
|
+
}, API_ORIGIN);
|
|
343
|
+
const fileMember = filesPayload.member?.[0] || filesPayload['hydra:member']?.[0];
|
|
344
|
+
expect(fileMember?.file?.name).toBe('cartao-cnpj.png');
|
|
345
|
+
});
|
|
346
|
+
});
|