@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.
Files changed (90) hide show
  1. package/AGENTS.md +8 -0
  2. package/package.json +9 -3
  3. package/src/api/fetch.js +7 -7
  4. package/src/react/components/AddImportModal.js +6 -2
  5. package/src/react/components/BackgroundRuntimeBridge.js +201 -215
  6. package/src/react/components/ConfirmModal.js +44 -9
  7. package/src/react/components/ConfirmModal.styles.js +55 -41
  8. package/src/react/components/CopyDeviceConfigModal.js +193 -0
  9. package/src/react/components/CopyDeviceConfigModal.styles.js +123 -0
  10. package/src/react/components/DefaultProvider.native.js +22 -0
  11. package/src/react/components/DefaultProvider.web.js +25 -1
  12. package/src/react/components/EntityLogBranch.js +482 -0
  13. package/src/react/components/EntityLogCards.js +369 -0
  14. package/src/react/components/EntityLogContent.js +113 -794
  15. package/src/react/components/LinkedOrderProductsTab.js +3 -3
  16. package/src/react/components/PrintService.js +130 -409
  17. package/src/react/components/RemoteCheckoutService.js +2 -2
  18. package/src/react/components/RuntimeBottomNavigationBar.js +141 -146
  19. package/src/react/components/RuntimeFooterMarqueeText.js +137 -0
  20. package/src/react/components/RuntimeInfoFooter.js +12 -15
  21. package/src/react/components/SystemErrorToast.js +49 -29
  22. package/src/react/components/SystemErrorToast.styles.js +43 -38
  23. package/src/react/components/UserAvatar.js +120 -28
  24. package/src/react/config/deviceConfigBootstrap.js +330 -304
  25. package/src/react/hooks/usePrintSpoolEffects.js +161 -0
  26. package/src/react/pages/DeviceDetailPage.js +6 -2882
  27. package/src/react/pages/Devices/detail/DeviceDetailAlertsCommandsSection.js +233 -0
  28. package/src/react/pages/Devices/detail/DeviceDetailHeader.js +138 -0
  29. package/src/react/pages/Devices/detail/DeviceDetailMovementSections.js +167 -0
  30. package/src/react/pages/Devices/detail/DeviceDetailOrdersPrintSection.js +216 -0
  31. package/src/react/pages/Devices/detail/DeviceDetailPaymentSection.js +40 -0
  32. package/src/react/pages/Devices/detail/DeviceDetailPdvConfigSection.js +390 -0
  33. package/src/react/pages/Devices/detail/DeviceDetailRenderers.js +135 -0
  34. package/src/react/pages/Devices/detail/DeviceDetailScreen.js +493 -0
  35. package/src/react/pages/Devices/detail/OptionButtonChip.js +65 -0
  36. package/src/react/pages/Devices/detail/deviceDetailConstants.js +36 -0
  37. package/src/react/pages/Devices/detail/deviceDetailHelpers.js +70 -0
  38. package/src/react/pages/Devices/detail/useDeviceDetailActions.js +483 -0
  39. package/src/react/pages/Devices/detail/useDeviceDetailCopyConfig.js +87 -0
  40. package/src/react/pages/Devices/detail/useDeviceDetailLoaders.js +327 -0
  41. package/src/react/pages/Devices/detail/useDeviceDetailSaves.js +429 -0
  42. package/src/react/pages/Devices/detail/useDeviceDetailStateA.js +484 -0
  43. package/src/react/pages/Devices/detail/useDeviceDetailStateB.js +71 -0
  44. package/src/react/pages/Devices/deviceTypes/index.js +2 -0
  45. package/src/react/pages/Devices/deviceTypes/pdv.js +11 -0
  46. package/src/react/pages/Imports.js +3 -2
  47. package/src/react/pages/IntegrationConfigFields.js +126 -0
  48. package/src/react/pages/IntegrationConfigPage.js +122 -591
  49. package/src/react/pages/IntegrationConfigPage.utils.js +95 -0
  50. package/src/react/print/usePrintButtonController.js +9 -1
  51. package/src/react/services/Getnet/Checkout.js +81 -0
  52. package/src/react/services/Getnet/Getnet.js +77 -0
  53. package/src/react/services/paymentGatewayExecution.js +20 -0
  54. package/src/react/utils/bottomNavigationToolbar.js +161 -0
  55. package/src/react/utils/commercialDocumentOrders.js +18 -6
  56. package/src/react/utils/copyDeviceConfigs.js +198 -0
  57. package/src/react/utils/fileUrl.js +43 -0
  58. package/src/react/utils/importStatus.js +19 -10
  59. package/src/react/utils/logSettings.js +7 -7
  60. package/src/react/utils/maintenanceSettings.js +16 -0
  61. package/src/react/utils/paymentDevices.js +6 -0
  62. package/src/react/utils/printManagedPrinter.js +45 -0
  63. package/src/react/utils/printRouting.js +63 -0
  64. package/src/react/utils/printSpoolUtils.js +119 -0
  65. package/src/react/utils/printerDevices.js +368 -368
  66. package/src/react/utils/runtimeFooter.js +10 -19
  67. package/src/react/utils/shopFranchises.js +186 -65
  68. package/src/react/utils/systemErrorMessage.js +71 -67
  69. package/src/store/device_config/customActions.js +119 -30
  70. package/src/tests/browser/manager/bottom-navigation-role-toolbar.spec.js +353 -0
  71. package/src/tests/browser/manager/device-detail-alias-save.spec.js +345 -0
  72. package/src/tests/browser/manager/device-detail-copy-config.spec.js +365 -0
  73. package/src/tests/browser/manager/runtime-footer-marquee.spec.js +262 -0
  74. package/src/tests/browser/manager/theme-confirm-toast.spec.js +236 -0
  75. package/src/tests/react/components/RuntimeFooterMarqueeText.test.js +171 -0
  76. package/src/tests/react/components/UserAvatar.test.js +6 -2
  77. package/src/tests/react/components/themeAwareModals.test.mjs +60 -0
  78. package/src/tests/react/config/deviceConfigBootstrap.test.js +104 -66
  79. package/src/tests/react/pages/deviceDetailDelete.test.js +78 -0
  80. package/src/tests/react/pages/receitaFederalIntegrationContract.test.mjs +48 -0
  81. package/src/tests/react/utils/bottomNavigationToolbar.test.js +222 -0
  82. package/src/tests/react/utils/copyDeviceConfigs.test.mjs +127 -0
  83. package/src/tests/react/utils/fileUrl.test.js +38 -0
  84. package/src/tests/react/utils/importStatus.test.js +26 -9
  85. package/src/tests/react/utils/passwordPolicy.test.js +27 -0
  86. package/src/tests/react/utils/printRouting.test.mjs +86 -0
  87. package/src/tests/react/utils/printSpoolUtils.test.mjs +52 -0
  88. package/src/tests/react/utils/runtimeFooter.test.js +7 -0
  89. package/src/tests/react/utils/shopFranchises.test.js +121 -115
  90. 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
- console.error('addDeviceConfigs API error:', e);
125
- commit(types.SET_ERROR, e.message);
126
- throw e;
127
- })
128
- .finally(() => {
129
- commit(types.SET_ISSAVING, false);
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
- if (
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
+ });