@controleonline/ui-common 1.2.80 → 1.2.81
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/AGENTS.md +8 -0
- package/package.json +9 -3
- package/src/api/fetch.js +7 -7
- package/src/react/components/AddImportModal.js +6 -2
- package/src/react/components/BackgroundRuntimeBridge.js +201 -215
- package/src/react/components/ConfirmModal.js +44 -9
- package/src/react/components/ConfirmModal.styles.js +55 -41
- package/src/react/components/CopyDeviceConfigModal.js +193 -0
- package/src/react/components/CopyDeviceConfigModal.styles.js +123 -0
- package/src/react/components/DefaultProvider.native.js +22 -0
- package/src/react/components/DefaultProvider.web.js +25 -1
- package/src/react/components/EntityLogBranch.js +482 -0
- package/src/react/components/EntityLogCards.js +369 -0
- package/src/react/components/EntityLogContent.js +113 -794
- package/src/react/components/LinkedOrderProductsTab.js +3 -3
- package/src/react/components/PrintService.js +130 -409
- package/src/react/components/RemoteCheckoutService.js +2 -2
- package/src/react/components/RuntimeBottomNavigationBar.js +141 -146
- package/src/react/components/RuntimeFooterMarqueeText.js +137 -0
- package/src/react/components/RuntimeInfoFooter.js +12 -15
- package/src/react/components/SystemErrorToast.js +49 -29
- package/src/react/components/SystemErrorToast.styles.js +43 -38
- package/src/react/components/UserAvatar.js +120 -28
- package/src/react/config/deviceConfigBootstrap.js +330 -304
- package/src/react/hooks/usePrintSpoolEffects.js +161 -0
- package/src/react/pages/DeviceDetailPage.js +6 -2882
- package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +233 -0
- package/src/react/pages/Devices/detail/DeviceDetailHeader.js +138 -0
- package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +167 -0
- package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +216 -0
- package/src/react/pages/Devices/detail/DeviceDetailPaymentSection.js +40 -0
- package/src/react/pages/Devices/detail/DeviceDetailPdvConfigSection.js +390 -0
- package/src/react/pages/Devices/detail/DeviceDetailRenderers.js +135 -0
- package/src/react/pages/Devices/detail/DeviceDetailScreen.js +493 -0
- package/src/react/pages/Devices/detail/OptionButtonChip.js +65 -0
- package/src/react/pages/Devices/detail/deviceDetailConstants.js +36 -0
- package/src/react/pages/Devices/detail/deviceDetailHelpers.js +70 -0
- package/src/react/pages/Devices/detail/useDeviceDetailActions.js +483 -0
- package/src/react/pages/Devices/detail/useDeviceDetailCopyConfig.js +87 -0
- package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +327 -0
- package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +429 -0
- package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +484 -0
- package/src/react/pages/Devices/detail/useDeviceDetailStateB.js +71 -0
- package/src/react/pages/Devices/deviceTypes/index.js +2 -0
- package/src/react/pages/Devices/deviceTypes/pdv.js +11 -0
- package/src/react/pages/Imports.js +3 -2
- package/src/react/pages/IntegrationConfigFields.js +126 -0
- package/src/react/pages/IntegrationConfigPage.js +122 -591
- package/src/react/pages/IntegrationConfigPage.utils.js +95 -0
- package/src/react/print/usePrintButtonController.js +9 -1
- package/src/react/services/Getnet/Checkout.js +81 -0
- package/src/react/services/Getnet/Getnet.js +77 -0
- package/src/react/services/paymentGatewayExecution.js +20 -0
- package/src/react/utils/bottomNavigationToolbar.js +161 -0
- package/src/react/utils/commercialDocumentOrders.js +18 -6
- package/src/react/utils/copyDeviceConfigs.js +198 -0
- package/src/react/utils/fileUrl.js +43 -0
- package/src/react/utils/importStatus.js +19 -10
- package/src/react/utils/logSettings.js +7 -7
- package/src/react/utils/maintenanceSettings.js +16 -0
- package/src/react/utils/paymentDevices.js +6 -0
- package/src/react/utils/printManagedPrinter.js +45 -0
- package/src/react/utils/printRouting.js +63 -0
- package/src/react/utils/printSpoolUtils.js +119 -0
- package/src/react/utils/printerDevices.js +368 -368
- package/src/react/utils/runtimeFooter.js +10 -19
- package/src/react/utils/shopFranchises.js +186 -65
- package/src/react/utils/systemErrorMessage.js +71 -67
- package/src/store/device_config/customActions.js +119 -30
- package/src/tests/browser/manager/bottom-navigation-role-toolbar.spec.js +353 -0
- package/src/tests/browser/manager/device-detail-alias-save.spec.js +345 -0
- package/src/tests/browser/manager/device-detail-copy-config.spec.js +365 -0
- package/src/tests/browser/manager/runtime-footer-marquee.spec.js +262 -0
- package/src/tests/browser/manager/theme-confirm-toast.spec.js +236 -0
- package/src/tests/react/components/RuntimeFooterMarqueeText.test.js +171 -0
- package/src/tests/react/components/UserAvatar.test.js +6 -2
- package/src/tests/react/components/themeAwareModals.test.mjs +60 -0
- package/src/tests/react/config/deviceConfigBootstrap.test.js +104 -66
- package/src/tests/react/pages/deviceDetailDelete.test.js +78 -0
- package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +48 -0
- package/src/tests/react/utils/bottomNavigationToolbar.test.js +222 -0
- package/src/tests/react/utils/copyDeviceConfigs.test.mjs +127 -0
- package/src/tests/react/utils/fileUrl.test.js +38 -0
- package/src/tests/react/utils/importStatus.test.js +26 -9
- package/src/tests/react/utils/passwordPolicy.test.js +27 -0
- package/src/tests/react/utils/printRouting.test.mjs +86 -0
- package/src/tests/react/utils/printSpoolUtils.test.mjs +52 -0
- package/src/tests/react/utils/runtimeFooter.test.js +7 -0
- package/src/tests/react/utils/shopFranchises.test.js +121 -115
- package/src/tests/react/utils/systemErrorMessage.test.js +32 -59
|
@@ -2,6 +2,9 @@ import {api} from '@controleonline/ui-common/src/api';
|
|
|
2
2
|
import * as types from '@controleonline/ui-default/src/store/default/mutation_types';
|
|
3
3
|
import {isWebRuntimeDevice} from '@controleonline/ui-common/src/react/utils/deviceRuntime';
|
|
4
4
|
|
|
5
|
+
/** Max age for company device_configs cache used by print routing (ms). */
|
|
6
|
+
export const COMPANY_DEVICE_CONFIGS_CACHE_TTL_MS = 30 * 1000;
|
|
7
|
+
|
|
5
8
|
const normalizePeopleKey = value =>
|
|
6
9
|
String(value || '')
|
|
7
10
|
.replace(/\D+/g, '')
|
|
@@ -34,16 +37,84 @@ const getStoredDevice = () => {
|
|
|
34
37
|
return {};
|
|
35
38
|
};
|
|
36
39
|
|
|
40
|
+
const resolveConfigId = config => {
|
|
41
|
+
if (!config) {
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
if (config.id != null && String(config.id).trim() !== '') {
|
|
45
|
+
return String(config.id).trim();
|
|
46
|
+
}
|
|
47
|
+
const iri = String(config['@id'] || '').trim();
|
|
48
|
+
if (!iri) {
|
|
49
|
+
return '';
|
|
50
|
+
}
|
|
51
|
+
const parts = iri.split('/');
|
|
52
|
+
return String(parts[parts.length - 1] || '').trim();
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Merge a saved device_config into the company list and clear loadedKey so the
|
|
57
|
+
* next ensureCompanyDeviceConfigsLoaded can refresh (print routing picks up
|
|
58
|
+
* manager/destination changes without app rebuild).
|
|
59
|
+
*/
|
|
60
|
+
const mergeSavedConfigIntoCompanyItems = (commit, getters, saved) => {
|
|
61
|
+
if (!saved) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const currentItems = Array.isArray(getters.items) ? getters.items : [];
|
|
66
|
+
const savedId = resolveConfigId(saved);
|
|
67
|
+
const savedDeviceId = String(
|
|
68
|
+
saved?.device?.device || saved?.device?.id || saved?.device || '',
|
|
69
|
+
).trim();
|
|
70
|
+
const savedType = String(saved?.type || '').trim().toUpperCase();
|
|
71
|
+
|
|
72
|
+
let replaced = false;
|
|
73
|
+
const nextItems = currentItems.map(item => {
|
|
74
|
+
const itemId = resolveConfigId(item);
|
|
75
|
+
if (savedId && itemId && savedId === itemId) {
|
|
76
|
+
replaced = true;
|
|
77
|
+
return {...item, ...saved};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const itemDeviceId = String(
|
|
81
|
+
item?.device?.device || item?.device?.id || item?.device || '',
|
|
82
|
+
).trim();
|
|
83
|
+
const itemType = String(item?.type || '').trim().toUpperCase();
|
|
84
|
+
if (
|
|
85
|
+
savedDeviceId &&
|
|
86
|
+
itemDeviceId &&
|
|
87
|
+
savedDeviceId === itemDeviceId &&
|
|
88
|
+
savedType &&
|
|
89
|
+
itemType === savedType
|
|
90
|
+
) {
|
|
91
|
+
replaced = true;
|
|
92
|
+
return {...item, ...saved};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return item;
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (!replaced && savedId) {
|
|
99
|
+
nextItems.push(saved);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
commit(types.SET_ITEMS, nextItems);
|
|
103
|
+
// Force next ensure to re-fetch when TTL allows (other runtimes still refresh via TTL).
|
|
104
|
+
commit(types.SET_LOADED_KEY, '');
|
|
105
|
+
commit(types.SET_LOADED_AT, 0);
|
|
106
|
+
};
|
|
107
|
+
|
|
37
108
|
export const addDeviceConfigs = ({commit, getters}, params) => {
|
|
38
109
|
let configsObj = {};
|
|
39
110
|
if (params.configs) {
|
|
40
111
|
if (typeof params.configs === 'string') {
|
|
41
112
|
try {
|
|
42
113
|
configsObj = JSON.parse(params.configs);
|
|
43
|
-
} catch (e) {
|
|
44
|
-
configsObj = {};
|
|
45
|
-
}
|
|
46
|
-
} else if (typeof params.configs === 'object') {
|
|
114
|
+
} catch (e) {
|
|
115
|
+
configsObj = {};
|
|
116
|
+
}
|
|
117
|
+
} else if (typeof params.configs === 'object') {
|
|
47
118
|
configsObj = params.configs;
|
|
48
119
|
}
|
|
49
120
|
}
|
|
@@ -82,30 +153,31 @@ export const addDeviceConfigs = ({commit, getters}, params) => {
|
|
|
82
153
|
if (isRuntimeWebDevice) {
|
|
83
154
|
commit(types.SET_ITEM, nextItem);
|
|
84
155
|
if (!canPersistRuntimeWebDevice) {
|
|
156
|
+
mergeSavedConfigIntoCompanyItems(commit, getters, nextItem);
|
|
85
157
|
return Promise.resolve(nextItem);
|
|
86
158
|
}
|
|
87
159
|
}
|
|
88
160
|
|
|
89
161
|
let options = {
|
|
90
162
|
method: 'POST',
|
|
91
|
-
body: updatedParams,
|
|
92
|
-
};
|
|
93
|
-
commit(types.SET_ISSAVING, true);
|
|
94
|
-
|
|
95
|
-
return api
|
|
163
|
+
body: updatedParams,
|
|
164
|
+
};
|
|
165
|
+
commit(types.SET_ISSAVING, true);
|
|
166
|
+
|
|
167
|
+
return api
|
|
96
168
|
.fetch(getters.resourceEndpoint + '/add-configs', options)
|
|
97
169
|
.then(data => {
|
|
98
170
|
let parsedConfigs = {};
|
|
99
|
-
if (data?.configs) {
|
|
100
|
-
if (typeof data.configs === 'string') {
|
|
101
|
-
try {
|
|
102
|
-
parsedConfigs = JSON.parse(data.configs);
|
|
103
|
-
} catch (e) {
|
|
104
|
-
parsedConfigs = {};
|
|
105
|
-
}
|
|
106
|
-
} else if (typeof data.configs === 'object') {
|
|
107
|
-
parsedConfigs = data.configs;
|
|
108
|
-
}
|
|
171
|
+
if (data?.configs) {
|
|
172
|
+
if (typeof data.configs === 'string') {
|
|
173
|
+
try {
|
|
174
|
+
parsedConfigs = JSON.parse(data.configs);
|
|
175
|
+
} catch (e) {
|
|
176
|
+
parsedConfigs = {};
|
|
177
|
+
}
|
|
178
|
+
} else if (typeof data.configs === 'object') {
|
|
179
|
+
parsedConfigs = data.configs;
|
|
180
|
+
}
|
|
109
181
|
}
|
|
110
182
|
const hasParsedConfigs =
|
|
111
183
|
parsedConfigs && Object.keys(parsedConfigs).length > 0;
|
|
@@ -118,15 +190,22 @@ export const addDeviceConfigs = ({commit, getters}, params) => {
|
|
|
118
190
|
: nextItem.configs || getters.item?.configs || {},
|
|
119
191
|
};
|
|
120
192
|
commit(types.SET_ITEM, d);
|
|
193
|
+
mergeSavedConfigIntoCompanyItems(commit, getters, d);
|
|
121
194
|
return d;
|
|
122
195
|
})
|
|
123
|
-
.catch(e => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
196
|
+
.catch(e => {
|
|
197
|
+
const status = Number(e?.status || e?.code || e?.response?.status || 0);
|
|
198
|
+
// 403 is expected for non-tenant-admin users during runtime bootstrap.
|
|
199
|
+
if (status === 403) {
|
|
200
|
+
console.debug('addDeviceConfigs skipped (no tenant admin authority):', e?.message || e);
|
|
201
|
+
} else {
|
|
202
|
+
console.error('addDeviceConfigs API error:', e);
|
|
203
|
+
}
|
|
204
|
+
commit(types.SET_ERROR, e.message);
|
|
205
|
+
throw e;
|
|
206
|
+
})
|
|
207
|
+
.finally(() => {
|
|
208
|
+
commit(types.SET_ISSAVING, false);
|
|
130
209
|
});
|
|
131
210
|
};
|
|
132
211
|
|
|
@@ -174,10 +253,16 @@ export const ensureCompanyDeviceConfigsLoaded = (context, params = {}) => {
|
|
|
174
253
|
return Promise.resolve([]);
|
|
175
254
|
}
|
|
176
255
|
|
|
177
|
-
|
|
256
|
+
const force = params?.force === true;
|
|
257
|
+
const loadedAt = Number(context.getters.loadedAt) || 0;
|
|
258
|
+
const cacheAgeMs = loadedAt > 0 ? Date.now() - loadedAt : Number.POSITIVE_INFINITY;
|
|
259
|
+
const cacheFresh =
|
|
260
|
+
!force &&
|
|
178
261
|
context.getters.loadedKey === peopleKey &&
|
|
179
|
-
Array.isArray(context.getters.items)
|
|
180
|
-
|
|
262
|
+
Array.isArray(context.getters.items) &&
|
|
263
|
+
cacheAgeMs < COMPANY_DEVICE_CONFIGS_CACHE_TTL_MS;
|
|
264
|
+
|
|
265
|
+
if (cacheFresh) {
|
|
181
266
|
return Promise.resolve(context.getters.items);
|
|
182
267
|
}
|
|
183
268
|
|
|
@@ -187,4 +272,8 @@ export const ensureCompanyDeviceConfigsLoaded = (context, params = {}) => {
|
|
|
187
272
|
|
|
188
273
|
return getCompanyDeviceConfigs(context, params);
|
|
189
274
|
};
|
|
190
|
-
|
|
275
|
+
|
|
276
|
+
export const invalidateCompanyDeviceConfigsCache = ({commit}) => {
|
|
277
|
+
commit(types.SET_LOADED_KEY, '');
|
|
278
|
+
commit(types.SET_LOADED_AT, 0);
|
|
279
|
+
};
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
const {expect, test} = require('playwright/test');
|
|
2
|
+
const packageJson = require('../../../../../../../package.json');
|
|
3
|
+
const {API_ORIGIN} = require('../../../../../../../src/tests/browser/apiOrigin');
|
|
4
|
+
|
|
5
|
+
const APP_VERSION = packageJson?.version || '1.0.0';
|
|
6
|
+
|
|
7
|
+
const CORS_HEADERS = {
|
|
8
|
+
'access-control-allow-origin': '*',
|
|
9
|
+
'access-control-allow-headers':
|
|
10
|
+
'API-TOKEN, APP-DOMAIN, DEVICE, ACCEPT, CONTENT-TYPE, X-Requested-With',
|
|
11
|
+
'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const jsonHeaders = () => ({
|
|
15
|
+
...CORS_HEADERS,
|
|
16
|
+
'content-type': 'application/ld+json; charset=utf-8',
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const collection = member => ({
|
|
20
|
+
member,
|
|
21
|
+
'hydra:member': member,
|
|
22
|
+
totalItems: member.length,
|
|
23
|
+
'hydra:totalItems': member.length,
|
|
24
|
+
summary: {},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const companyA = {
|
|
28
|
+
id: 3,
|
|
29
|
+
name: 'Controle Online',
|
|
30
|
+
alias: 'CONTROLE',
|
|
31
|
+
panel_enabled: true,
|
|
32
|
+
enabled: true,
|
|
33
|
+
commercial_enabled: true,
|
|
34
|
+
theme: {colors: {primary: '#0EA5E9', success: '#10b981'}},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const companyB = {
|
|
38
|
+
id: 9,
|
|
39
|
+
name: 'Gyros',
|
|
40
|
+
alias: 'GYROS',
|
|
41
|
+
panel_enabled: true,
|
|
42
|
+
enabled: true,
|
|
43
|
+
commercial_enabled: true,
|
|
44
|
+
theme: {colors: {primary: '#16A34A', success: '#10b981'}},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const fullToolbarMenus = {
|
|
48
|
+
modules: {
|
|
49
|
+
1: {
|
|
50
|
+
id: 1,
|
|
51
|
+
label: 'Toolbar',
|
|
52
|
+
icon: 'home',
|
|
53
|
+
sortOrder: 10,
|
|
54
|
+
menus: [
|
|
55
|
+
{
|
|
56
|
+
id: 11,
|
|
57
|
+
menuKey: 'home',
|
|
58
|
+
label: 'Início',
|
|
59
|
+
route: 'HomePage',
|
|
60
|
+
icon: 'home',
|
|
61
|
+
menuType: 'toolbar',
|
|
62
|
+
sortOrder: 10,
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: 12,
|
|
66
|
+
menuKey: 'opportunities',
|
|
67
|
+
label: 'Oportunidades',
|
|
68
|
+
route: 'CrmIndex',
|
|
69
|
+
icon: 'dollar-sign',
|
|
70
|
+
menuType: 'toolbar',
|
|
71
|
+
sortOrder: 20,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 13,
|
|
75
|
+
menuKey: 'customers',
|
|
76
|
+
label: 'Clientes',
|
|
77
|
+
route: 'ClientsIndex',
|
|
78
|
+
icon: 'users',
|
|
79
|
+
menuType: 'toolbar',
|
|
80
|
+
sortOrder: 30,
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 14,
|
|
84
|
+
menuKey: 'profile',
|
|
85
|
+
label: 'Perfil',
|
|
86
|
+
route: 'ProfilePage',
|
|
87
|
+
icon: 'user',
|
|
88
|
+
menuType: 'toolbar',
|
|
89
|
+
sortOrder: 40,
|
|
90
|
+
},
|
|
91
|
+
],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const limitedToolbarMenus = {
|
|
97
|
+
modules: {
|
|
98
|
+
1: {
|
|
99
|
+
id: 1,
|
|
100
|
+
label: 'Toolbar',
|
|
101
|
+
icon: 'home',
|
|
102
|
+
sortOrder: 10,
|
|
103
|
+
menus: [
|
|
104
|
+
{
|
|
105
|
+
id: 21,
|
|
106
|
+
menuKey: 'home',
|
|
107
|
+
label: 'Início',
|
|
108
|
+
route: 'HomePage',
|
|
109
|
+
icon: 'home',
|
|
110
|
+
menuType: 'toolbar',
|
|
111
|
+
sortOrder: 10,
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 24,
|
|
115
|
+
menuKey: 'profile',
|
|
116
|
+
label: 'Perfil',
|
|
117
|
+
route: 'ProfilePage',
|
|
118
|
+
icon: 'user',
|
|
119
|
+
menuType: 'toolbar',
|
|
120
|
+
sortOrder: 40,
|
|
121
|
+
},
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const installSession = async (page, {companyId = 3} = {}) => {
|
|
128
|
+
await page.addInitScript(
|
|
129
|
+
({appVersion, companyId}) => {
|
|
130
|
+
const set = (k, v) => {
|
|
131
|
+
try {
|
|
132
|
+
localStorage.setItem(k, v);
|
|
133
|
+
} catch {}
|
|
134
|
+
};
|
|
135
|
+
set(
|
|
136
|
+
'session',
|
|
137
|
+
JSON.stringify({
|
|
138
|
+
id: 7,
|
|
139
|
+
people: '/people/7',
|
|
140
|
+
api_key: 'test-api-key',
|
|
141
|
+
active: 1,
|
|
142
|
+
mycompany: companyId,
|
|
143
|
+
roles: ['ROLE_ADMIN'],
|
|
144
|
+
}),
|
|
145
|
+
);
|
|
146
|
+
set('config', JSON.stringify({language: 'pt-br'}));
|
|
147
|
+
set('app-type', 'MANAGER');
|
|
148
|
+
set(
|
|
149
|
+
'device',
|
|
150
|
+
JSON.stringify({
|
|
151
|
+
id: 'web-manager',
|
|
152
|
+
device: 'web-manager',
|
|
153
|
+
type: 'WEB',
|
|
154
|
+
appName: 'Browser Manager',
|
|
155
|
+
appVersion,
|
|
156
|
+
buildNumber: appVersion,
|
|
157
|
+
systemName: 'web',
|
|
158
|
+
systemVersion: 'web',
|
|
159
|
+
deviceType: 'web',
|
|
160
|
+
metadata: {},
|
|
161
|
+
}),
|
|
162
|
+
);
|
|
163
|
+
},
|
|
164
|
+
{appVersion: APP_VERSION, companyId},
|
|
165
|
+
);
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const mockManagerApi = async (page, {toolbarByCompany}) => {
|
|
169
|
+
await page.route(`${API_ORIGIN}/**`, async route => {
|
|
170
|
+
const request = route.request();
|
|
171
|
+
const url = new URL(request.url());
|
|
172
|
+
const pathname = url.pathname.replace(/^\/+/, '');
|
|
173
|
+
const method = request.method().toUpperCase();
|
|
174
|
+
|
|
175
|
+
if (method === 'OPTIONS') {
|
|
176
|
+
return route.fulfill({status: 204, headers: CORS_HEADERS, body: ''});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (pathname === 'menus-people') {
|
|
180
|
+
const companyId = String(url.searchParams.get('myCompany') || '');
|
|
181
|
+
const menuType = String(url.searchParams.get('menuType') || '');
|
|
182
|
+
const payload =
|
|
183
|
+
menuType === 'toolbar'
|
|
184
|
+
? toolbarByCompany[companyId] || {modules: {}}
|
|
185
|
+
: {modules: {}};
|
|
186
|
+
return route.fulfill({
|
|
187
|
+
status: 200,
|
|
188
|
+
headers: jsonHeaders(),
|
|
189
|
+
body: JSON.stringify({
|
|
190
|
+
response: {
|
|
191
|
+
data: payload,
|
|
192
|
+
count: 1,
|
|
193
|
+
error: '',
|
|
194
|
+
success: true,
|
|
195
|
+
},
|
|
196
|
+
}),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (
|
|
201
|
+
pathname === 'people/companies/my' ||
|
|
202
|
+
pathname === 'people/company/default'
|
|
203
|
+
) {
|
|
204
|
+
const list = [companyA, companyB];
|
|
205
|
+
return route.fulfill({
|
|
206
|
+
status: 200,
|
|
207
|
+
headers: jsonHeaders(),
|
|
208
|
+
body: JSON.stringify(
|
|
209
|
+
pathname === 'people/companies/my' ? collection(list) : companyA,
|
|
210
|
+
),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (pathname.startsWith('people/') || pathname === 'companies') {
|
|
215
|
+
return route.fulfill({
|
|
216
|
+
status: 200,
|
|
217
|
+
headers: jsonHeaders(),
|
|
218
|
+
body: JSON.stringify(companyA),
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (pathname === 'configs/discovery-configs') {
|
|
223
|
+
return route.fulfill({
|
|
224
|
+
status: 200,
|
|
225
|
+
headers: jsonHeaders(),
|
|
226
|
+
body: JSON.stringify({configs: {}}),
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (pathname === 'devices' || pathname.startsWith('device')) {
|
|
231
|
+
return route.fulfill({
|
|
232
|
+
status: 200,
|
|
233
|
+
headers: jsonHeaders(),
|
|
234
|
+
body: JSON.stringify(collection([])),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return route.fulfill({
|
|
239
|
+
status: 200,
|
|
240
|
+
headers: jsonHeaders(),
|
|
241
|
+
body: JSON.stringify(collection([])),
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
test.describe('MANAGER bottom navigation role toolbar', () => {
|
|
247
|
+
test.use({viewport: {width: 390, height: 844}});
|
|
248
|
+
|
|
249
|
+
test('full role sees authorized toolbar routes including Clientes', async ({
|
|
250
|
+
page,
|
|
251
|
+
}) => {
|
|
252
|
+
await mockManagerApi(page, {
|
|
253
|
+
toolbarByCompany: {
|
|
254
|
+
'3': fullToolbarMenus,
|
|
255
|
+
'9': limitedToolbarMenus,
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
await installSession(page, {companyId: 3});
|
|
259
|
+
await page.goto('/');
|
|
260
|
+
|
|
261
|
+
const nav = page.getByTestId('bottom-navigation');
|
|
262
|
+
await expect(nav).toBeVisible({timeout: 20000});
|
|
263
|
+
await expect(nav.getByText('Início')).toBeVisible();
|
|
264
|
+
await expect(nav.getByText('Oportunidades')).toBeVisible();
|
|
265
|
+
await expect(nav.getByText('Clientes')).toBeVisible();
|
|
266
|
+
await expect(nav.getByText('Perfil')).toBeVisible();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
test('limited role does not expose business routes; keeps Início and Perfil', async ({
|
|
270
|
+
page,
|
|
271
|
+
}) => {
|
|
272
|
+
await mockManagerApi(page, {
|
|
273
|
+
toolbarByCompany: {
|
|
274
|
+
'3': limitedToolbarMenus,
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
await installSession(page, {companyId: 3});
|
|
278
|
+
await page.goto('/');
|
|
279
|
+
|
|
280
|
+
const nav = page.getByTestId('bottom-navigation');
|
|
281
|
+
await expect(nav).toBeVisible({timeout: 20000});
|
|
282
|
+
await expect(nav.getByText('Início')).toBeVisible();
|
|
283
|
+
await expect(nav.getByText('Perfil')).toBeVisible();
|
|
284
|
+
await expect(nav.getByText('Clientes')).toHaveCount(0);
|
|
285
|
+
await expect(nav.getByText('Oportunidades')).toHaveCount(0);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
test('API failure keeps safe fallback without business routes', async ({
|
|
289
|
+
page,
|
|
290
|
+
}) => {
|
|
291
|
+
await page.route(`${API_ORIGIN}/**`, async route => {
|
|
292
|
+
const request = route.request();
|
|
293
|
+
const url = new URL(request.url());
|
|
294
|
+
const pathname = url.pathname.replace(/^\/+/, '');
|
|
295
|
+
const method = request.method().toUpperCase();
|
|
296
|
+
|
|
297
|
+
if (method === 'OPTIONS') {
|
|
298
|
+
return route.fulfill({status: 204, headers: CORS_HEADERS, body: ''});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (pathname === 'menus-people') {
|
|
302
|
+
return route.fulfill({
|
|
303
|
+
status: 500,
|
|
304
|
+
headers: jsonHeaders(),
|
|
305
|
+
body: JSON.stringify({
|
|
306
|
+
response: {data: [], count: 0, error: 'fail', success: false},
|
|
307
|
+
}),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (
|
|
312
|
+
pathname === 'people/companies/my' ||
|
|
313
|
+
pathname === 'people/company/default' ||
|
|
314
|
+
pathname.startsWith('people/') ||
|
|
315
|
+
pathname === 'companies'
|
|
316
|
+
) {
|
|
317
|
+
return route.fulfill({
|
|
318
|
+
status: 200,
|
|
319
|
+
headers: jsonHeaders(),
|
|
320
|
+
body: JSON.stringify(
|
|
321
|
+
pathname === 'people/companies/my'
|
|
322
|
+
? collection([companyA])
|
|
323
|
+
: companyA,
|
|
324
|
+
),
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (pathname === 'configs/discovery-configs') {
|
|
329
|
+
return route.fulfill({
|
|
330
|
+
status: 200,
|
|
331
|
+
headers: jsonHeaders(),
|
|
332
|
+
body: JSON.stringify({configs: {}}),
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return route.fulfill({
|
|
337
|
+
status: 200,
|
|
338
|
+
headers: jsonHeaders(),
|
|
339
|
+
body: JSON.stringify(collection([])),
|
|
340
|
+
});
|
|
341
|
+
});
|
|
342
|
+
|
|
343
|
+
await installSession(page, {companyId: 3});
|
|
344
|
+
await page.goto('/');
|
|
345
|
+
|
|
346
|
+
const nav = page.getByTestId('bottom-navigation');
|
|
347
|
+
await expect(nav).toBeVisible({timeout: 20000});
|
|
348
|
+
await expect(nav.getByText('Início')).toBeVisible();
|
|
349
|
+
await expect(nav.getByText('Perfil')).toBeVisible();
|
|
350
|
+
await expect(nav.getByText('Clientes')).toHaveCount(0);
|
|
351
|
+
await expect(nav.getByText('Oportunidades')).toHaveCount(0);
|
|
352
|
+
});
|
|
353
|
+
});
|