@controleonline/ui-common 1.2.79 → 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/passwordPolicy.js +73 -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 +6 -1
- 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 +27 -10
- 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 +10 -8
- package/src/tests/react/utils/shopFranchises.test.js +121 -115
- package/src/tests/react/utils/systemErrorMessage.test.js +32 -59
|
@@ -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
|
+
});
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smoke browser: Manager /device-detail alias save updates header without refresh.
|
|
3
|
+
* fluxo: manager-devices
|
|
4
|
+
* Refs: app-community#382
|
|
5
|
+
*
|
|
6
|
+
* Criteria:
|
|
7
|
+
* - Edit device name → save → displayed alias in header becomes the new value without page reload.
|
|
8
|
+
* - Modules under Devices/detail remain ≤ 500 lines (absolute limit).
|
|
9
|
+
*/
|
|
10
|
+
const { expect, test } = require('playwright/test');
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const packageJson = require('../../../../../../../package.json');
|
|
14
|
+
const { API_ORIGIN } = require('../../../../../../../src/tests/browser/apiOrigin');
|
|
15
|
+
|
|
16
|
+
const APP_VERSION = packageJson?.version || '1.0.0';
|
|
17
|
+
const CURRENT_DEVICE_ID = 'web-7';
|
|
18
|
+
const DEVICE_ENTITY_ID = 396;
|
|
19
|
+
const INITIAL_ALIAS = 'Caixa atual';
|
|
20
|
+
const NEXT_ALIAS = 'Caixa Renomeado #382';
|
|
21
|
+
|
|
22
|
+
const CORS_HEADERS = {
|
|
23
|
+
'access-control-allow-origin': '*',
|
|
24
|
+
'access-control-allow-headers':
|
|
25
|
+
'API-TOKEN, APP-DOMAIN, DEVICE, ACCEPT, CONTENT-TYPE, X-Requested-With',
|
|
26
|
+
'access-control-allow-methods': 'GET,POST,PUT,PATCH,DELETE,OPTIONS',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const jsonHeaders = () => ({
|
|
30
|
+
...CORS_HEADERS,
|
|
31
|
+
'content-type': 'application/ld+json; charset=utf-8',
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const collection = (member) => ({
|
|
35
|
+
member,
|
|
36
|
+
'hydra:member': member,
|
|
37
|
+
totalItems: member.length,
|
|
38
|
+
'hydra:totalItems': member.length,
|
|
39
|
+
summary: {},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const currentDevice = {
|
|
43
|
+
'@id': `/devices/${DEVICE_ENTITY_ID}`,
|
|
44
|
+
'@type': 'Device',
|
|
45
|
+
id: DEVICE_ENTITY_ID,
|
|
46
|
+
device: CURRENT_DEVICE_ID,
|
|
47
|
+
alias: INITIAL_ALIAS,
|
|
48
|
+
metadata: {
|
|
49
|
+
runtime: 'web',
|
|
50
|
+
network: { publicIp: '127.0.0.1' },
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const createDeviceConfig = ({ id, type, device = currentDevice }) => ({
|
|
55
|
+
'@id': `/device_configs/${id}`,
|
|
56
|
+
'@type': 'DeviceConfig',
|
|
57
|
+
id,
|
|
58
|
+
type,
|
|
59
|
+
people: '/people/3',
|
|
60
|
+
device,
|
|
61
|
+
configs: JSON.stringify({
|
|
62
|
+
'config-version': APP_VERSION,
|
|
63
|
+
'pos-gateway': 'infinite-pay',
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const detailDir = path.join(__dirname, '../../../react/pages/Devices/detail');
|
|
68
|
+
|
|
69
|
+
const MODULES_MAX_500 = [
|
|
70
|
+
'DeviceDetailScreen.js',
|
|
71
|
+
'DeviceDetailHeader.js',
|
|
72
|
+
'DeviceDetailRenderers.js',
|
|
73
|
+
'DeviceDetailPdvConfigSection.js',
|
|
74
|
+
'DeviceDetailOrdersPrintSection.js',
|
|
75
|
+
'DeviceDetailAlertsCommandsSection.js',
|
|
76
|
+
'DeviceDetailMovementSections.js',
|
|
77
|
+
'DeviceDetailPaymentSection.js',
|
|
78
|
+
'deviceDetailConstants.js',
|
|
79
|
+
'deviceDetailHelpers.js',
|
|
80
|
+
'OptionButtonChip.js',
|
|
81
|
+
'useDeviceDetailStateA.js',
|
|
82
|
+
'useDeviceDetailStateB.js',
|
|
83
|
+
'useDeviceDetailLoaders.js',
|
|
84
|
+
'useDeviceDetailActions.js',
|
|
85
|
+
'useDeviceDetailSaves.js',
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
const mockDevicesApi = async (page) => {
|
|
89
|
+
const company = {
|
|
90
|
+
id: 3,
|
|
91
|
+
name: 'Teste',
|
|
92
|
+
alias: 'TESTE',
|
|
93
|
+
panel_enabled: true,
|
|
94
|
+
enabled: true,
|
|
95
|
+
commercial_enabled: true,
|
|
96
|
+
theme: {
|
|
97
|
+
colors: {
|
|
98
|
+
primary: '#0EA5E9',
|
|
99
|
+
cardBackground: '#FFFFFF',
|
|
100
|
+
cardBorder: '#D8E0EA',
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const managerConfig = createDeviceConfig({ id: 487, type: 'MANAGER' });
|
|
106
|
+
const deviceConfigs = [managerConfig];
|
|
107
|
+
/** @type {Array<{id:number,alias:string}>} */
|
|
108
|
+
const savedAliasPuts = [];
|
|
109
|
+
|
|
110
|
+
// Live mutable alias for GET after PUT
|
|
111
|
+
let liveAlias = INITIAL_ALIAS;
|
|
112
|
+
|
|
113
|
+
await page.route(`${API_ORIGIN}/**`, async (route) => {
|
|
114
|
+
const request = route.request();
|
|
115
|
+
const url = new URL(request.url());
|
|
116
|
+
const pathname = url.pathname.replace(/^\/+/, '');
|
|
117
|
+
const method = request.method().toUpperCase();
|
|
118
|
+
|
|
119
|
+
if (method === 'OPTIONS') {
|
|
120
|
+
return route.fulfill({ status: 204, headers: CORS_HEADERS, body: '' });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (pathname === 'themes-colors.css') {
|
|
124
|
+
return route.fulfill({
|
|
125
|
+
status: 200,
|
|
126
|
+
headers: { ...CORS_HEADERS, 'content-type': 'text/css; charset=utf-8' },
|
|
127
|
+
body: ':root { --primary: #0ea5e9; }',
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (pathname === 'runtime/ip') {
|
|
132
|
+
return route.fulfill({
|
|
133
|
+
status: 200,
|
|
134
|
+
headers: jsonHeaders(),
|
|
135
|
+
body: JSON.stringify({ ip: '127.0.0.1' }),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (pathname === 'menus-people') {
|
|
140
|
+
return route.fulfill({
|
|
141
|
+
status: 200,
|
|
142
|
+
headers: jsonHeaders(),
|
|
143
|
+
body: JSON.stringify({ modules: {} }),
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (pathname === 'people/companies/my') {
|
|
148
|
+
return route.fulfill({
|
|
149
|
+
status: 200,
|
|
150
|
+
headers: jsonHeaders(),
|
|
151
|
+
body: JSON.stringify(collection([company])),
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (pathname === 'people/company/default') {
|
|
156
|
+
return route.fulfill({
|
|
157
|
+
status: 200,
|
|
158
|
+
headers: jsonHeaders(),
|
|
159
|
+
body: JSON.stringify(company),
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (pathname === 'configs/discovery-configs') {
|
|
164
|
+
return route.fulfill({
|
|
165
|
+
status: 200,
|
|
166
|
+
headers: jsonHeaders(),
|
|
167
|
+
body: JSON.stringify({ configs: {} }),
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (pathname === 'devices' && method === 'GET') {
|
|
172
|
+
return route.fulfill({
|
|
173
|
+
status: 200,
|
|
174
|
+
headers: jsonHeaders(),
|
|
175
|
+
body: JSON.stringify(
|
|
176
|
+
collection([{ ...currentDevice, alias: liveAlias }]),
|
|
177
|
+
),
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (
|
|
182
|
+
(pathname === `devices/${DEVICE_ENTITY_ID}` ||
|
|
183
|
+
pathname === `devices/${CURRENT_DEVICE_ID}`) &&
|
|
184
|
+
method === 'GET'
|
|
185
|
+
) {
|
|
186
|
+
return route.fulfill({
|
|
187
|
+
status: 200,
|
|
188
|
+
headers: jsonHeaders(),
|
|
189
|
+
body: JSON.stringify({ ...currentDevice, alias: liveAlias }),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Alias save: PUT /devices/{id}
|
|
194
|
+
if (
|
|
195
|
+
(pathname === `devices/${DEVICE_ENTITY_ID}` ||
|
|
196
|
+
pathname === `devices/${CURRENT_DEVICE_ID}`) &&
|
|
197
|
+
(method === 'PUT' || method === 'PATCH')
|
|
198
|
+
) {
|
|
199
|
+
let body = {};
|
|
200
|
+
try {
|
|
201
|
+
body = request.postDataJSON() || {};
|
|
202
|
+
} catch {
|
|
203
|
+
body = {};
|
|
204
|
+
}
|
|
205
|
+
const next = String(body.alias || body.name || '').trim() || liveAlias;
|
|
206
|
+
liveAlias = next;
|
|
207
|
+
savedAliasPuts.push({ id: DEVICE_ENTITY_ID, alias: next });
|
|
208
|
+
return route.fulfill({
|
|
209
|
+
status: 200,
|
|
210
|
+
headers: jsonHeaders(),
|
|
211
|
+
body: JSON.stringify({ ...currentDevice, alias: next }),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (pathname === 'devices' && method === 'POST') {
|
|
216
|
+
return route.fulfill({
|
|
217
|
+
status: 200,
|
|
218
|
+
headers: jsonHeaders(),
|
|
219
|
+
body: JSON.stringify({ ...currentDevice, alias: liveAlias }),
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (pathname === 'device_configs' && method === 'GET') {
|
|
224
|
+
return route.fulfill({
|
|
225
|
+
status: 200,
|
|
226
|
+
headers: jsonHeaders(),
|
|
227
|
+
body: JSON.stringify(collection(deviceConfigs)),
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (pathname.startsWith('device_configs/') && method === 'GET') {
|
|
232
|
+
const id = Number(pathname.split('/')[1]);
|
|
233
|
+
const found = deviceConfigs.find((c) => c.id === id) || managerConfig;
|
|
234
|
+
return route.fulfill({
|
|
235
|
+
status: 200,
|
|
236
|
+
headers: jsonHeaders(),
|
|
237
|
+
body: JSON.stringify({
|
|
238
|
+
...found,
|
|
239
|
+
device: { ...found.device, alias: liveAlias },
|
|
240
|
+
}),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Fallback: empty collection / 200
|
|
245
|
+
return route.fulfill({
|
|
246
|
+
status: 200,
|
|
247
|
+
headers: jsonHeaders(),
|
|
248
|
+
body: JSON.stringify(collection([])),
|
|
249
|
+
});
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
await page.addInitScript(
|
|
253
|
+
({ appVersion }) => {
|
|
254
|
+
localStorage.setItem('token', 'smoke-token-382');
|
|
255
|
+
localStorage.setItem('config', JSON.stringify({ language: 'pt-br' }));
|
|
256
|
+
localStorage.setItem(
|
|
257
|
+
'device',
|
|
258
|
+
JSON.stringify({
|
|
259
|
+
id: 'web-7',
|
|
260
|
+
device: 'web-7',
|
|
261
|
+
type: 'MANAGER',
|
|
262
|
+
appName: 'Browser Manager',
|
|
263
|
+
appVersion,
|
|
264
|
+
buildNumber: appVersion,
|
|
265
|
+
systemName: 'web',
|
|
266
|
+
systemVersion: 'web',
|
|
267
|
+
deviceType: 'web',
|
|
268
|
+
metadata: { runtime: 'web' },
|
|
269
|
+
}),
|
|
270
|
+
);
|
|
271
|
+
},
|
|
272
|
+
{ appVersion: APP_VERSION },
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
return { savedAliasPuts, getLiveAlias: () => liveAlias };
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
test.describe('device-detail alias save (browser smoke #382)', () => {
|
|
279
|
+
test('all detail modules respect absolute 500-line limit', async () => {
|
|
280
|
+
for (const name of MODULES_MAX_500) {
|
|
281
|
+
const f = path.join(detailDir, name);
|
|
282
|
+
expect(fs.existsSync(f), `missing ${name}`).toBe(true);
|
|
283
|
+
const lines = fs.readFileSync(f, 'utf8').split(/\r?\n/).length;
|
|
284
|
+
expect(lines, `${name} has ${lines} lines (max 500)`).toBeLessThanOrEqual(
|
|
285
|
+
500,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
test('deviceAliasSync helper exists for save path', async () => {
|
|
291
|
+
const helper = path.join(
|
|
292
|
+
__dirname,
|
|
293
|
+
'../../../react/utils/deviceAliasSync.js',
|
|
294
|
+
);
|
|
295
|
+
expect(fs.existsSync(helper)).toBe(true);
|
|
296
|
+
expect(fs.readFileSync(helper, 'utf8')).toMatch(/buildDeviceAliasStoreUpdates/);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
test('edit alias → save → header shows new value without refresh', async ({
|
|
300
|
+
page,
|
|
301
|
+
}) => {
|
|
302
|
+
const api = await mockDevicesApi(page);
|
|
303
|
+
|
|
304
|
+
await page.goto('/devices-index?store=device_config');
|
|
305
|
+
|
|
306
|
+
// Open device detail (MANAGER profile of current device)
|
|
307
|
+
await page.getByTestId('device-config-487').click();
|
|
308
|
+
await expect(page).toHaveURL(/device-detail/);
|
|
309
|
+
|
|
310
|
+
// Header must show initial alias
|
|
311
|
+
await expect(page.getByText(INITIAL_ALIAS, { exact: true })).toBeVisible({
|
|
312
|
+
timeout: 15000,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Header shows initial alias via testID
|
|
316
|
+
await expect(page.getByTestId('device-alias-text')).toHaveText(INITIAL_ALIAS, {
|
|
317
|
+
timeout: 15000,
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Start edit via testID
|
|
321
|
+
await page.getByTestId('device-alias-edit').click();
|
|
322
|
+
|
|
323
|
+
// Fill input (testID on RN TextInput maps to data-testid on web)
|
|
324
|
+
const aliasInput = page.getByTestId('device-alias-input');
|
|
325
|
+
await expect(aliasInput).toBeVisible({ timeout: 10000 });
|
|
326
|
+
await aliasInput.fill(NEXT_ALIAS);
|
|
327
|
+
|
|
328
|
+
// Save via testID
|
|
329
|
+
await page.getByTestId('device-alias-save').click();
|
|
330
|
+
|
|
331
|
+
// Wait for PUT
|
|
332
|
+
await expect
|
|
333
|
+
.poll(() => api.savedAliasPuts.length, { timeout: 15000 })
|
|
334
|
+
.toBeGreaterThanOrEqual(1);
|
|
335
|
+
expect(api.savedAliasPuts[api.savedAliasPuts.length - 1].alias).toBe(
|
|
336
|
+
NEXT_ALIAS,
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
// CRITICAL acceptance: header shows NEW alias without page reload
|
|
340
|
+
await expect(page.getByText(NEXT_ALIAS, { exact: true })).toBeVisible({
|
|
341
|
+
timeout: 10000,
|
|
342
|
+
});
|
|
343
|
+
await expect(page).toHaveURL(/device-detail/);
|
|
344
|
+
});
|
|
345
|
+
});
|